tl;dr I think I'm making this more complicated than it needs to be. I'd like to get some tips on using Trigger because I'm really unsure how to use it the way it was intended to be used. Read on for details. My apologies for my verbosity.
I've got Trigger installed and configured with some test devices in netdevices.xml. I've read the docs and tried a few things and I'm still unsure how to proceed. I'm still a beginner to python and want to write some scripts for our group at work. I began by writing some stuff using telnetlib but realized quickly that I was doing it the hard way and should have used pexpect instead. While searching for some examples of using pexpect, I ran across a recommendation for Trigger, which is how I got here. It seems like a really fantastic tool but I have to admit that I don't yet know how to make the best use of it.
My goal was to do expect-style scripting without the hassle. Trigger's asynchronous utilities seem to take care of that, but at a cost. I'm very new to Twisted, as well, and I'm just now getting caught up with how chains work. I'm not sure how to use them to do what I want to do. Here is a real life example (copied from my explanation in IRC):
We have run across a bug on Cisco 7600/6500s that occasionally stops inbound service policies from working. We use ingress policies for DSCP marking. If the marking breaks, all sorts of other stuff breaks since we use DSCP markings for security, as well. So, I need to build a script that can go out to all of the 7600s and 6500s my group manages and check for broken service policies. My idea is to run a query on NetDevices to get a list of actual devices. The script would interate through the list and check each one. On each device, I need to run a command, like some variation of "show policy-map" or maybe even just a "show run" and parse it to get a list of interface with ingress service policies. Then I need to iterate through that list and run "show mls qos ip <interface>" on each interface to verify that ingress packet marking counters are non-zero. If any are zero, that means we've hit the bug, so I need to log the device and interface for review later.
I suppose I could use Commando to iterate through a list of devices and run all possible commands that I might need and then save the output to a file, then later separately pull those files in and parse them to get the data I need, but that wasn't my original idea.
Here is a very simple example of what I came up with using async callbacks:
from trigger.netdevices import NetDevices
from twisted.internet import reactor
nd = NetDevices()
dev = nd.find('labrouter')
def print_version(data):
print(data[0])
def print_siib(data):
print(data[0])
def stop_reactor(data):
print 'Stopping reactor'
if reactor.running:
reactor.stop()
async = dev.execute(['show version'])
async.addCallback(print_version)
async = dev.execute(['show ip interface brief'])
async.addCallback(print_siib)
async.addBoth(stop_reactor)
reactor.run()
That works, but I can only run reactor once, so I can only talk to one device. That's just not going to work for me. Incidentally, someone on /r/learnpython suggested I learn to use inline callbacks. I guess I see what they're doing, but it seriously increased the complexity of the code. Here is an example of that (most of this code came from the redditor helping me):
import sys
from trigger.netdevices import NetDevices
from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.python import log
import re
@inlineCallbacks
def getInterfaces(dev):
interfaces = yield dev.execute(['show ip int brief'])
# Insert code here to parse `stuff`.
returnValue(interfaces)
@inlineCallbacks
def main():
# log.startLogging(sys.stdout)
nd = NetDevices()
dev = nd.find('labrouter')
version = yield dev.execute(['show version'])
#log.msg('Got version: {}'.format(version))
m = re.search(r'Version (.*),', version[0], re.M|re.I)
if not m:
raise ValueError('Version not found!')
else:
print 'Version is ', m.group(1)
interfaces = yield getInterfaces(dev)
for interface in interfaces:
#log.msg('Doing stuff with {}'.format(interface))
if not interface.startswith('Interface'):
print interface
# Assuming doStuffWithInterface returns a Deferred:
# yield doStuffWithInterface(dev, interface)
if __name__ == '__main__':
main().addBoth(lambda _: reactor.stop())
reactor.run()
I decided to just try my hand at pexpect since I had never used it before. Here is a short example of that, which works but then I don't get the benefits of Trigger:
import pexpect
import sys
HOST = ['192.168.1.107']
USERNAME = 'lab'
PASS = 'cisco'
ENABLE = 'cisco'
def ios_initial_cmds():
child.expect(['>', '#'])
child.sendline('term len 0')
command_list = ['show version', 'show ip int brief', 'show ip route']
child = pexpect.spawn('telnet', HOST)
#child.logfile = sys.stdout
child.expect ('Username:')
child.sendline(USERNAME)
child.expect('Password:')
child.sendline(PASS)
ios_initial_cmds()
child.expect(['>', '#'])
p = child.before.split('\n')[1]
UNPRIV_PROMPT = p + ">"
ENABLE_PROMPT = p + "#"
PROMPTS = [UNPRIV_PROMPT, ENABLE_PROMPT]
for command in command_list:
child.sendline(command)
child.expect(PROMPTS)
out = child.before
print out
child.sendline('exit')
So, what approach would you recommend? I'm leaning toward the Commando approach with separate log parsing modules to get the data I ultimately really need instead of parsing the device output as I get it. What do you think?
Thanks!