The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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