Tuesday, July 30, 2013

Python remote class for TAHI's v6eval engine.

The TAHI v6eval Perl module for IPv6 compliance testing at one point included a bunch of 'remote' scripts to automate tasks on the target device under test (the NUT, or node under test).

These scripts were always more like examples than set in stone automation and always required adjustment for the target device.   The scripts used a assumed serial port connection via null modem to the target and Perl expect to check for the prompt, issue commands, etc..

At work to automate the compliance testing, I wanted to use the out-of-band management port via ssh to issue commands, and to avoid long waits on reboot with just quick restarts of  services etc, so I made my own remote interface that used this:

It uses some other utils we've coded for keyswapping with the management host,  a ssh remote wrapper, etc, that I won't provide here, but you can see how it works below.

It uses v6eval's own configuration of tn.def and nut.def to provide all the connectivity,
In tn.def:


  • the 'RemoteDevice' option is re-used to provide an ssh ip address. 
  • 'RemoteMethod' is also defined to 'ssh' and although not parsed here could be easily expanded to switch different access methods based on that variable.

In nut.def:

  • the 'User' option is used to provide username for ssh access.
  • the 'Password' option is used to provide the password for ssh access.
Of course 'System' is used by the engine to determine which scripts to execute in /usr/local/v6eval/bin/[system]

All options passed to each of the '.rmt' scripts are available when using the remote class ropt dictionary for easy consumption.




#!/usr/bin/env python
"""
reboot.rmt
reboots the NUT (via a quick reload)

example invocation from the TAHI v6eval remote suite

./reboot.rmt -u root -p default -d 192.168.1.1
"""

import remote
import sys
import time

def reboot(argv):
    nut = remote.remote(argv)

    cmd = 'reload'

    rc = nut.runcmd(cmd)
    # maximum time needed for config reload
    time.sleep(90)

    if rc != 0:
        print "cmd remote command %s returned %d" % (cmd, rc)
        return nut.exitFail

    return nut.exitPass

if __name__ == '__main__':
    sys.exit(reboot(sys.argv))




# remote.py
# python v6eval remote interface class

import os
import logging
import tcutils.commandutils as cu

from tcutils import logger
from paramikoutils import keyswap

DEBUG = bool(os.environ.get('DEBUG'))
log = logger.stdout_logger('remote',
    level=logging.DEBUG if DEBUG else logging.INFO)

class remote:
    """v6eval remote interface class"""
    def __init__(self, argv):
        """init remote class"""

        # options passed by v6eval
        #my $cmd  = searchPath($rpath, $fname);
        #   $cmd .= " -t $NutDef{System}"       if $NutDef{System};
        #   $cmd .= " -u $NutDef{User}"         if $NutDef{User};
        #   $cmd .= " -p $NutDef{Password}"     if $NutDef{Password};
        #   $cmd .= " -T $TnDef{RemoteCuPath}"  if $TnDef{RemoteCuPath};
        #   $cmd .= " -d $TnDef{RemoteDevice}"  if $TnDef{RemoteDevice};
        #   $cmd .= " -v $TnDef{RemoteDebug}"   if $TnDef{RemoteDebug};
        #   $cmd .= " -i $TnDef{RemoteIntDebug}"    if $TnDef{RemoteIntDebug};
        #   $cmd .= " -o $TnDef{RemoteLog}"     if $TnDef{RemoteLog};
        #   $cmd .= " -s $TnDef{RemoteSpeed}"   if $TnDef{RemoteSpeed};
        #   $cmd .= " -l $TnDef{RemoteLogout}"  if $TnDef{RemoteLogout};
        #   $cmd .= " $RemoteOption $opts @args";

        self.exitPass=0            # PASS
        self.exitIgnore=1          # Ignore (ex. initializeation script)
        self.exitNS=2              # Not yet supported
        self.exitWarn=3            # WARN
        self.exitHostOnly=4        # Host Only
        self.exitRouterOnly=5      # Router Only
        self.exitSpecialOnly=6     # Special Only
        self.exitExceptHost=7      # Except Host
        self.exitExceptRouter=8    # Except Router
        self.exitExceptSpecial=9   # Except Special
        self.exitSkip=10           # Skip
        self.exitTypeMismatch=11   # Type Mismatch
        #
        self.exitFail=32           # FAIL
        self.exitInitFail=33       # Initialization Fail
        self.exitCleanupFail=34    # Cleanup Fail
        #                          # 35 - 63: reserved for future use
        #
        self.exitFatal=64          # FATAL (terminate series of related tests)

        self.lastrc = 0
        self.laststdout = ""
        self.laststderr = ""

        self.ropt = { }
        self.ropt = self.parseargs(argv)

        # root user
        if '-u' in argv:
            self.user = argv[argv.index('-u') + 1]
        # root password
        if '-p' in argv:
            self.passwd = argv[argv.index('-p') + 1]
        # management ip
        if '-d' in argv:
            self.mgmt_ip = argv[argv.index('-d') + 1]

        # save the original options
        self.argv = argv
        keyswap.keyswap(self.mgmt_ip, self.user, self.passwd)

    def parseargs(self, args):
        """find the foo=bar variables"""
        arg_dict = { }
        for arg in args:
            try:
                nom, val = arg.split('=')
                arg_dict[nom] = val
            except ValueError:
                pass

        return arg_dict

    def runcmd(self, cmd):
        """run the command on the NUT"""
        (self.lastrc, self.laststdout, self.laststderr) = cu.runcmd_ssh(self.mgmt_ip, cmd)
        if self.lastrc > 0:
           log.error('command: %s FAILED %s' % (cmd, self.laststderr))
           return 1
        return 0