#!/usr/local/bin/perl # LG (Looking Glass) # by Chris Josephes # # A framework for accessing information on routers # remotely # # Package Definition # package Router::LG; # # Includes # # # Global Variables # # Software version $VERSION=0.98; # Table of Submodules of LG %Loaded=(); # Default TTL for cached data $DefaultTTL=600; # # Constructor Method # # # new # Construct a new LG object sub new { my ($object)={}; bless ($object); return($object); } # # version # Return version data of the LG class sub version { return($VERSION); } # # Data Methods # # # router # Set/Get the router stucture that we will be accessing sub router { my ($self,$router)=@_; if ($router) { $self->{"router"}=$router; $self->_loadDriver($router->{"-class"}); unless ($self->{"router"}->{"-driver"}) { return(undef); } } return($self->{"router"}); } # # parameters # Set/Get the parameters to be parsed into the command string # passing "-remove" clears the array sub parameters { my ($self,@list)=@_; #print("Params Called\n"); if (@list) { if ($list[0] eq "-clear") { $self->{"parameters"}=[]; } else { while (@list) { push(@{$self->{"parameters"}},shift(@list)); } } } return(@{$self->{"parameters"}}); } # # cache # Sets/Gets caching setup # arguements: -directory (cache directory) -ttl (time to live in secs) sub cache { my ($self,%args)=@_; if (%args) { if ($args{"-directory"}) { # determine if directory is safe if (-d $args{"-directory"}) { $self->{"cache"}->{"-directory"}=$args{"-directory"}; } else { $self->error("Cache directory does not exist."); } } $self->{"cache"}->{"-ttl"}=$args{"-ttl"} || $DefaultTTL; } return(%{$self->{"cache"}}); } # # commands # Returns a list of commands the router driver understands sub commands { my ($self)=@_; my ($driver); $driver=$self->_driver(); if ($driver) { return($driver->commands()); } else { $self->error("Driver class not yet defined"); return(undef); } } sub command { my ($self,$command,@rest)=@_; my ($driver); $driver=$self->_driver(); if ($driver) { if (@rest) { $driver->command($command,@rest); } return($driver->command($command)); } else { $self->error("Driver class not yet defined"); return(undef); } } sub cmdlabels { my ($self)=@_; my ($cmd,@list,%lhash,%th); (@list)=$self->commands(); print("LIST: ",@list,"\n"); while (@list) { $cmd=pop(@list); (%th)=$self->command($cmd); $lhash{$cmd}=$th{"-label"}; } return(%lhash); } # # Cache Control Methods # # # _cacheOK # Determine if cached data from a command is valid by checking the following: # - If parameters were passed with the command # - If the command can be cached # - If the previously cached data is still valid sub _cacheOK { my ($self,$command)=@_; my ($file,$update,$age); return (undef) if ($self->parameters()); $file=$self->_cacheFile($command); if (-e $file) { (undef,undef,undef,undef,undef,undef,undef,undef,undef,$update)= stat($file); $age=time()-$update; return($age < $self->{"cache"}->{"-ttl"} ? 1 : 0); } else { return(undef); } } # # _readCache # Read cached output from an earlier execution sub _readCache { my ($self,$command)=@_; my ($file,$x); $file=$self->_cacheFile($command); open (F, $file) || return(undef); while ($x=) { push(@output,$x); } close(F); return(@output); } # # _writeCache # Copy data from a router to a cachefile sub _writeCache { my ($self,$command,@output)=@_; my ($file); $file=$self->_cacheFile($command); open (F, ">$file") || return(undef); print F (@output); close(F); } # # Execution Methods # # # parse # Return the command string the driver would send to the router sub parse { my ($self,$command)=@_; my ($driver,$string); $driver=$self->_driver(); $string=$driver->parse($command); return($string); } # # execute # Have the driver send the command and parameters to the router sub execute { my ($self,$command)=@_; my ($driver,$cs,@output); $driver=$self->_driver(); #print("REF2:",ref($driver),"\n"); #print("Determining cache\n"); if ($driver->cachable($command)) { if ($self->_cacheOK($command)) { #print("Cached data\n"); (@output)=$self->_readCache($command); $cs=1; } else { #print("Active data\n"); # (@output)=&{$class."::exec"}($self,$command); (@output)=$driver->exec($command); $cs=0; # Now send this data to the cache! $self->_writeCache($command,@output); } } else { #print("Active data\n"); #(@output)=&{$class."::exec"}($self,$command); (@output)=$driver->exec($command); $cs=0; } return($cs,@output); } # # error # Set or return an error message sub error { my ($self,@msgs)=@_; if (@msgs) { if ($msgs[0] eq "-clear") { $self->{ec}=[]; } else { while (@msgs) { my ($err,$pkg); $err=shift(@msgs); $pkg=caller(); $err="$pkg => $err\n"; # print("E: $err"); push(@{$self->{ec}},$err); } } } if ($self->{ec}) { return(@{$self->{ec}}) } else { return("LG => No Errors\n"); } } # # Hidden Methods # # # _loadDriver # Load a LG::* class to access a specific router sub _loadDriver { my ($self,$driver)=@_; my ($class); $driver=lc($driver); $driver=ucfirst($driver); $class="Router::LG::".$driver; #print("Attempting to load $class\n"); unless ($Loaded{$driver}) { if (eval "require ($class)") { $Loaded{$driver}=1; } else { $self->error("Loading of ($class) failed"); return(undef); } } $self->{"router"}->{"-driver"}=&{$class."::new"}($self); return($self->{"router"}->{"-driver"}); } # # _driver # Return the driver object sub _driver { my ($self)=@_; return($self->{"router"}->{"-driver"}); } # # _cacheFile # Return a generated cache filename sub _cacheFile { my ($self,$command)=@_; my ($file); $file=$self->{"cache"}->{"-directory"}."/".$command."--". $self->{"router"}->{"-hostname"}; return($file); } # # Exit Block # 1; __END__ # # POD Block # =head1 NAME Router::LG - Looking Glass =head1 SYNOPSIS use Router::LG; $glass=Router::LG::new(); =head1 DESCRIPTION The LG class is based on the lg.pl program originally written by Ed Kern of Digex. The original lg.pl program was used as a web-based front end to obtain information from routers via RSH. All of the original features of lg.pl have been incorporated into LG.pm. - Multiple router vendor support - Multiple parameter support - Custom command definition =head1 HOW IT WORKS The LG module has a set of driver modules for accessing routers made by different vendors, like Cisco or Juniper. Each driver contains code and data structures regarding what commands it can send and how it can communicate with the router. The driver module is dynamically loaded when a router is defined. =head1 CONSTRUCTOR METHOD =over 4 =item $glass=Router::LG->new(); =back =head1 OBJECT METHODS =over 4 =item version(); Returns version data on the Router::LG class. =back =over 4 =item router($ROUTER); Sets or returns the router data structure. The router data structure defines the host address of the router, the vendor class, the remote access method, and any arguements are required to connect. The following code defines a router data structure and tells the Looking Glass object to use it. =over 4 #!/usr/local/bin/perl $router={ -hostname => "core.pop.isp.node", -class => "Cisco", -method => "rsh", -args => { -luser => "local_user", -ruser => "remote_user", }, }; $glass->router($router); =back See the POD documentation for a particular Router::LG class, (ex, Router::LG::Cisco, or Router::LG::Driver) =back =over 4 =item parameters([-clear|@LIST]); Sets or returns an array of parameters that will be inserted into the command sent to the router. Setting the first arguement of the list to "-clear" erases the contents of the array. =back =over 4 =item cache( -directory => $CACHE, -ttl => $TTL); Sets of returns a hash defining the optional cache setup. Defining a cache takes two parameters, a directory, and a default time to live (in seconds). If no TTL is defined, a default value of 10 minutes will be used. =back =over 4 =item commands(); Returns a list of commands the driver supports =back =over 4 =item command($COMMAND,%PARAMETERS); Sets or returns data regarding a command structure. See the POD documentation for the particular vendor class for more information. =back =over 4 =item execute($COMMAND); Executes the command on the router. If the command is successful, the method returns a cache state variable, followed by the lines of output the router sent back. The cache state variable can be a 1 indicating that LG returned cached data instead of accessing the router, or a 0 indicating that the router was accessed to obtain the data. If the command failed, undef will be returned. Example: ($cache,@output)=$glass->execute("env"); =back =over 4 =item $glass->parse($COMMAND); Works like execute, however it returns the command it would send to the router, but doesn't actually send it. =back =over 4 =item $glass->error([-clear|@LIST]); Sets or returns one or more error messages. If the first arguement in the list is "-clear", it will erase the list of errors. =back =head1 HIDDEN METHODS The following methods are part of the LG class, but implementors are not expected to use them. =over 4 =item _loadDriver() Loads the driver defined in the router data structure =back =over 4 =item _driver() Returns the object pointer to the router driver =back =over 4 =item _cacheFile Returns a parsed filename for the cachefile, which is the name of the command followed by two dashes and the hostname of the remote router =back =over 4 =item _cacheOK Returns a boolean flag indicating whether or not we could use cached data instead of accessing the remote router =back =over 4 =item _readCache Reads a cached output file =back =over 4 =item _writeCache Writes output to a cache file =back