Archive for August, 2014

08/24/2014

Executing Arbitrary Junos ‘Show’ Commands with PyEZ and ncclient

by Jeff Loughridge

Still screen scraping routers with Expect scripts? It’s time to move to NETCONF, the industry standard for communicating with network infrastructure. The protocol was heavily influenced by Juniper’s XML API. Not surprisingly, Junos routers have solid NETCONF support. In this post, I’ll detail how an operator can use ncclient and PyEZ to execute arbitrary ‘show’ commands to obtain operational state. By “arbitrary”, I mean that the ‘show’ commands can be any valid Junos operational command that is not known at the time the script was written. The input could take the form of a file.

Let’s start with PyEZ, as this framework is much more friendly to network engineers who dabble in Python. To execute a ‘show’ command, the documentation recommends using the associated RPC for the command. You can map ‘show’ commands to RPCs by appending ‘| display xml rpc’. Here’s example output.

jeffl@SRX_R1> show version | display xml rpc
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.2I0/junos">
    <rpc>
        <get-software-information>
        </get-software-information>
    </rpc>
    <cli>
        <banner></banner>
    </cli>
</rpc-reply>

jeffl@SRX_R1>

The RPC in this example is get-software-information. This process is covered in more depth on the PyEZ wiki.

If you wanted to create a script that executed user-specified ‘show’ commands in a text file, do you see the problem? You’d need some hack to obtain the RPC for the ‘show’ command. Fortunately, PyEZ makes use of the <command> tag shortcut that is also employed by slax scripts. The method is Device.cli() (my thanks go to Kurt Bales and Nitin Kumar on the PyEZ mailing list for pointing this out to me).

The documentation rightly warns against the use of Device.cli(). The purpose of PyEZ is to get away from screen scraping and programmatically handling the router’s response. Fortunately, Device.cli() acepts an format=’xml’ parameter. This returns the RPC reply in XML for easy parsing in the language of your choosing. An example of this is op = jdev.cli(command, format=’xml’), where jdev is an instance of the Device class.

The PyEZ module is great for many automation tasks. The module is not needed for this use case, however. We can use the lower-level ncclient to achieve the same result. This requires a higher comfort level with Python and the lxml module.

The following code borrows from the Juniper examples in the ncclient github repo.

 

#!/usr/bin/env python
# Demonstrates the use of the 'command' tag to execute arbritrary 'show' commands.
# This code was inspired by Ebben Aries's command-jnpr.py at
# https://github.com/leopoul/ncclient/blob/master/examples/juniper/command-jnpr.py
#
# usage: python ncclient_demo.py <show command> <xpath expression>
# python ncclient_demo.py 'show route 2600::/64' '//rt-entry/nh'
# 
# Jeff Loughridge
# August 2014

import sys

from lxml import etree as etree
from ncclient import manager
from ncclient.xml_ import *

def connect(host, port, user, password, source):

    try:
        show_command = sys.argv[1]
    except IndexError:
        print "please specify show command as first argument."
        sys.exit(1)

    try:
        xpath_expr = sys.argv[2]
    except IndexError:
        xpath_expr=''

    conn = manager.connect(host=host,
            port=port,
            username=user,
            password=password,
            timeout=3,
            device_params = {'name':'junos'},
            hostkey_verify=False)

    try:
        result = conn.command(command=show_command, format='xml')
    except Exception, e:
        print "ncclient_demo.py: Encountered critical error"
        print e
        sys.exit(1)

    tree = etree.XML(result.tostring)

    if xpath_expr:
        filtered_tree_list = tree.xpath(xpath_expr)
        for element in filtered_tree_list:
            print etree.tostring(element)
    else:
        print etree.tostring(tree)

if __name__ == '__main__':
    connect('ROUTER', 830, 'USER', 'PASSWORD', 'candidate')

I posted this a gist.

To use this example, there are several prerequisites.

  1. You must have a Junos router configured with ‘set system services netconf’.
  2. The ROUTER, USER, and PASSWORD placeholders in the script must be filled in with a valid router IP/FQDN, user, and password.
  3. The lxml and ncclient modules must be installed. These are in PyPi and can be installed with pip.

The script supports the optional use of XPath expressions to parse the output. XPath is complex topic that is covered elsewhere. The parsing can be performed with lxml’s ElementPath or countless other python modules used to parse XML.

I hope readers find this example useful.

 

Advertisements