NAME
ExecCmds.pm - execute a set of commands against devices defined in perl arrays.
SYNOPSIS
use ExecCmds;
my %config = (
.
. # See CONFIG below for details
.
);
my @devs = ( x.x.x.x, y.y.y.y, .... );
my $cmds = ExecCmds->new(%config);
$cmds->run(@devices);
DESCRIPTION
Execute a number of commands against Cisco Routers and Switches for lots and lots of them.
We fork a number of processes to handle the bulk of the work. Default is 5 children runing at a time but that can be changed easily.
A number of Pre/Post hard and soft conditions can be defined and executed and changes to the device aborted if conditions fail. These conditions can be simple commands or complete Perl proggies executed over various information retrieved from the device in question.
Basic actions performed (eventually):
- Check usage options
- Collect device information
- Do pre-condition tests and fail if appropriately
- Update Router\Switch Config
- Check Router\Switch config is correct using defined
post-conditions.
- Log all changes
CONFIG
The format for the arguments to new() and configure() are as follows.
They can be handed in via a pre-built hash or straight. A bit like
this:
ExecCmds-_new( number=>n, debug=>1, rcmds=>[], ... );
or by building up a config hash and handing that in. I recommend
building the hash and then passing it in thus:
my %config = ();
$config{'number'} = 5;
$config{'debug'} = 1;
$config{'rcmds'} = [ .... ];
.
.
@devs = ('a','b',...'c');
my $cmd = ExecCmds->new(%config);
later
$cmd->configure('verbose'=>1);
$cmd->run(@devs);
Here is a list of the config options:
number => n, is the number of children to fork to get some parrallelism
debug => 1, -d Turns on debugging dont do anything to the devices
care => 0, don't care about failures
verbose => 0, Be verbose about what is happening
pretty => 1, Add lots of useful fluff in the logs
log => 'file', where loggin should go. Into file if specified
otherwise it goes to routername.log. If you want
loggin to STDOUT use '-' as the file name. if
you want no logging use '/dev/null'
# Our router commands to execute. These commands will be fed directly
# to the router for consumption once Questions, Preconditions and
# macro processing side effects have taken place.
rcmds => [
'show ip interface brief',
'show proc cpu',
'show proc mem',
];
# An optional User/Pass/Enable combination. Each combination
# is tried in the order show. If this arrayref is empty then
# standard TACACS passwords are tried. If you want to try
# TACACS first then fall back just make the first user TACACS
# as shown below
pass => [
{'user' => 'TACACS', 'pass' => '', 'enable' => ''},
{'user' => '', 'pass' => 'b00k1ngs', 'enable' => '0r1g1n'},
{'user' => 'tzpj07', 'pass' => '0rico001', 'enable' => 'foresite'},
];
# Our router Questions. Note: this is not a Pre/Post condition, Just
# a question over the config. We print the truth or otherwise of the
# question.
r_q_regex => [
'm/access-list 55 permit/s',
];
# Our router Pre conditions. Note: Pre conditions must execute and
# return true otherwise we halt processing
r_pre_regex => [
'm/access-list 55 permit/s'
];
# Our router Post conditions. These don't effect the final execution
# but they do define if we will say everything executed OK.
r_post_regex => [
'm/access-list 55 permit/s'
];
# Our Switch commands. A distinction is made between switches and
# routers. Really IOS and CATOS devices
scmds => [
'show proc',
'show ver',
'show loggining buffer',
'show port status'
];
# Our Switch Questions. Note: this is not a Pre/Post condition
s_q_regex => [
'm/set ip permit/s',
];
# Our Switch Pre conditions
s_post_regex => [
'm/set ip permit/s',
];
# Our Switch Post conditions
s_pre_regex => [
'm/set ip permit/s',
];
DEVICES
Devices are handed to ExecCmds::run via an array reference. It
can come in two forms.
Form1 (simple) is just an array of devices to execute
against. It is the simplest way to apply the config over a
number ofdevcies.
Form2 (complex) allows name to ipaddress mapings that don't match DNS
to be used. Also allows for macro processing, copy/paste and the
like.
Here is how we normally pick up all the devices.
# Pick up all the devices names from the DB. This could be specified
# explicitly if we liked.
use IPSM::Devices;
$db = IPSM::Devices->new() || die "Can't get Device DB connection";
@devices = $db->list_devices();
But you probably dont do this. You probably use a file or DNS or your
own DB :-)
Form1
-----
%config = (....);
@devices = ( dev1, dev2, dev3, .....) ;
my $exec = ::ExecCmds->new(%config);
$exec->run(@devices);
In this case rcmds and scmds are executed against each device just
as they were defined.
Form2
-----
@devices = (
[
{'CN' => 'ddarwa01'}, # cn is the name
{'IP' => '10.145.224.249'}, # ip is the IP address to connect to
# Some commands to execute in this order
{'$snarf0' => 'cmd("sh ip interface brief")'},
{'$loop' => '$snarf0 =~ /^(Loopback\d+)\s+10.87.71.254/m'},
],
# next device
[
{'CN' => 'nbanka01'}, # cn is the name
{'IP' => '10.49.225.232'}, # ip is the IP address to connect to
# Some commands to execute in this order
{'$snarf0' => 'cmd("sh ip interface brief")'},
{'$loop' => '$snarf0 =~ /^(Loopback\d+)\s+10.69.15.254/m'},
],
.
.
.
);
$rcmds = [
'conf t',
'int %%loop%%',
'no shut',
'exit',
'wr',
'quit',
];
%config = (..,rcmds=>$rcmds ,..);
my $exec = ::ExecCmds->new(%config);
$exec->run(@devices);
In this case the name (CN) and the IP address (IP) must be defined.
CN is used to name the output messages for logging. It will be
converted to upper case. IP is used to specify the address to connect
to (just in case CN doesn't resolve).
You can also define a number of variables that will be expanded into
your $rcmds/$scmds array refs. Above we define the variable %%loop%%
The $rcmds/$scmds commands will have %%loop%% expanded on a per device
basis before commands, queries and pre/post conditions are executed.
In the above case %%loop%% is set to contain what the $loop Perl
variable has been defined as in the devices array.
$loop contains the results of the Perl code:
$snarf0 =~ /^(Loopback\d+)\s+10.87.71.254/m';
$snarf was filled with the data from running a command against the
device. The command run was "sh ip interface brief" in this case.
Each command will be executed seperately and tested to make sure it
completed properly. Any failure will fail the run on this device.
Example 1
Turn on service password encryption for a set of devices. We already have a copy of the configs for these devices in /home/configs/current
# Setup the config hash
%config = (
# Note we use TACACS first then xyppy2, kz2ykc, and finally try without
# any user name at all.
pass => [
{'user' => 'TACACS', 'pass' => '', 'enable' => ''},
{'user' => 'xyppy2', 'pass' => 'x2dvm413', 'enable' => 'p4f2bcuw'},
{'user' => 'kz2ykc', 'pass' => 'cHarChaR', 'enable' => 'please'},
{'user' => '', 'pass' => 'smarty', 'enable' => 'dumb'},
],
# Our router commands to execute
rcmds => [
'conf t',
'service password-encryption',
'', # This is a real control Z
'wr',
],
# Our router Pre conditions say we don't want to find service
# password-encryption turned on in the running config
r_pre_regex => [
'!m/^service password-encryption/m',
],
# Our router Post conditions say we want to see that it is turned on
# after the commands have been run
r_post_regex => [
'm/^service password-encryption/m',
],
# Our Switch commands - none
scmds => [ ],
# Our Switch Pre conditions - none
s_pre_regex => [ ],
# Our Switch Post conditions - none
s_post_regex => [ ],
);
# Collect some machines
my $cmd='find /home/configs/current -type f | xargs grep -l "no service password-encryption" | perl -pe "s{/home/configs/current/}{}"';
my @devs = `$cmd`;
# Some machines 1070 actually
@devices = map { s/\.txt\n//; $_; } @devs;
my $cmds = ExecCmds->new();
$cmds->configure(%config);
$cmds->run(@devices);
new
Usage:
my $exec = ExecCmds->new();
my $exec = ExecCmds->new(%config);
Inputs:
Nothing or a hash defining current configuration data as describe
under CONFIG above
Returns:
A reference to an ExecCmds object that can later be used to
access the API
configure
Usage:
$exec->configure(%config);
my %config = $exec->configure();
Inputs:
A hash defining current configuration data as describe
under CONFIG above
Returns:
A hash of the current configuration
run
Usage:
$exec->run(@devices);
Args:
An array of devices to execute commands against. You must have
configured the object first before this will do anything useful.
Returns:
Nothing usually unless only one device is handed in. In which
case it will return an array describing the run.
Comments:
Output is usually logged to whereever you have specified the
'log' config variable. If you didn't set 'log' then a comentary
of the run is placed in DEVICENAME.log. Where DEVICENAME is the
name of the device from @devices above.
To get a more complete output of what was done, including the
output from the device interaction make sure the 'verbose' config
variable is set to 1.
To have ONLY the interaction with the device passed back in an
array do this;
$exec->configure('pretty'=>0, 'verbose'=>1, 'log'=>'/dev/null');
foreach (@devices)
{
my @interaction = $exec->run($_);
}
That is, set 'pretty' off, 'verbose' on and call run() with only
one device specified. Passing more than one device allows run()
to fork as many processes as defined in the 'number' setting.
More comments:
The run() method will organise a number of sub processes to
execute to get the job done. To specify how many sub process you
want run you set the 'number' config variable. Default is 5.
Setting 'number' to 1 stops forking and causes only the first
device in @devices to be accessed with the results returned in an
array.
Because we use sub processes it is easier to have each process
log its interaction to a per-process file. If you defined a log
file then it will get over written unless you set the 'log'
config variable properly thus:
$exec->configure('log'=>'/tmp/mylog-%%name%%.log');
$exec->run(@devices);
Here %%name%% will be expanded to be the device name as passed
into run().
exec_dev_cmds
Usage:
This is an internal function. Don't use it unless you know what
your doing.
ExecCmds::exec_dev_cmds(....);
Args:
$dev the device to mess with
$pass a ref to an array of hashrefs containing details of
user/pass/enable to try
$router_cmds a ref to an array of commands to add to router
if it is indeed a router
$r_q_regex a ref to an array of regex's to test for. But we
dont fail the run if these fail. Just print out if is
true or false
$r_pre_regex and $r_post_regex are refs to arrays of regex's
to run over the config. These must all succeed before/after
commands. Maybe undef
$switch_cmds a ref to an array of commands to add to swtch
if it is indeed a switch
$s_q_regex a ref to an array of regex's to test for. But we
dont fail the run if these fail. Just print out if is
true or false
$s_pre_regex and $s_post_regex are refs to arrays of regex's
to run over the config.
$txt is a string that will be modified in place with
details of the run.
Returns:
A string of details of the run.
Comments:
You can ignore this method it is used internally. The run()
method uses exec_dev_cmds() to get its job done.
exec_dev_cmds adds a new setting to a router/switch config whilst testing pre and/or post conditions over the config. Tests can also be performed to see if something is true or not about the router/switch.
In the case of routers we check the 'start-config' in the post-condition regular expression and the 'running config' in the pre-condition. The pre/post conditions can be any Perl expression that evaluates to True. Examples are shown below.
This example shows an access-list being dropped and rebuilt and tested for. We are checking to make sure the access-list is already there and failing if it isn't $r_pre_regex defines this. Once the commands are executed we do a post check $r_post_regex. The same is done for a switch device. If we had used $r_q_regex instead the success or failure of the test would not stop the commands from being executed.
The routine does a check on the device to work out wether it is a switch or router or whatever and runs the appropriate commands.
$dev = 'RN-48MA-05-2610-99';
Note: conf t, exit and write should be in all $rcmds array-refs.
$rcmds = [
'conf t',
'no access-list 55',
'access-list 55 permit 10.25.159.44',
'access-list 55 permit 10.25.155.24',
'exit',
'write',
];
$pass = []; # Just default to using our DB of info
$r_q_regex = []; # No looking and testing
$r_pre_regex = ['m/access-list 55 permit/s'];
$r_post_regexp = ['m/access-list 55 permit/s'];
# switch commands here
$scmds = ['set ip permit 10.25.155.24 telnet'];
$s_q_regex = []; # No simple tests
$s_post_regex = ['m/set ip permit/s'];
$s_pre_regex = ['m/set ip permit/s'];
Here is a call to the subroutine given the above definitions. Note well
how $ret is passed in. $ret will be populated with a run down of how the
interaction went. It will contain any success\failure strings returned
from Cisco::Utility. If no \$ret is passed in then it is ignored. Pre
and Post conditions are also ignored if not defined. $s/r_q_regex arrays
are also ignored if they are empty
my $ret = '';
if (ExecCmds::exec_dev_cmds(
$dev,$pass,$rcmds,$r_q_regex, $r_pre_regex,$r_post_regexp,
$scmds,$s_q_regex,$s_pre_regex,$s_post_regex,\$ret))
{
Great it worked
}
else
{
bummer!
}
print $ret;
AUTHOR
Mark Pfeiffer <markpf@mlp-consulting.com.au>
Jeremy Nelson <jem@apposite.com.au> provided Utility functions
COPYRIGHT
Copyright (c) 2002 Mark Pfeiffer and Jeremy Nelson. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Cisco is a registered trade mark of Cisco Systems, Inc.
This code is in no way associated with Cisco Systems, Inc.
All other trademarks mentioned in this document are the property of
their respective owners.
DISCLAIMER
We make no warranties, implied or otherwise, about the suitability of this software. We shall not in any case be liable for special, incidental, consequential, indirect or other similar damages arising from the transfer, storage, or use of this code.
This code is offered in good faith and in the hope that it may be of use.
cheers
markp
Mon May 20 13:14:39 EST 2002 or there abouts