Getting Busy With the Junos EZ Library

Juniper Logo

At the end of my last post, I had completed the installation of Jeremy Schulman’s EZ Library on an Ubuntu server and had run a test script that showed basic information about a Junos router.

I started thinking about how I could use this Python library to do some automated monitoring of my devices, and decided that a good task to start me off might be something useful for my home network. I have a Juniper EX3200 switch, and it would be nice to have a web page that could show a simple port status table so that anybody in the family can easily check status when there’s a problem.

Let’s get cracking then!

Junos EZ Library

To get interface status in our script using the EZ library, we will need to use the ethport module. We’ll include pretty print (pprint) to make the output look nicer, and we’ll need to import Device as usual:

from pprint import pprint
from jnpr.junos import Device
from jnpr.junos.op.ethport import EthPortTable

Opening the device is the same as before – configure a Device object with a hostname, username and password, then call the open() method:

dev = Device("juniper", user="script", password="script123")
dev.open()

It’s worth noting that your user must have the ability to issue netconf commands which in my test environment means being a super-user; lower privileges like operator won’t cut it, and the script will fail.

Now we should be connected to the device, we need to find a way to get information about the Ethernet interfaces. Jeremy’s approach with this library is to try and keep information access as simple as possible, so information is returned in a table format. In this case we can grab all the interferace information we want with just two commands. The first creates a new Ethernet port table object called “eths” associated with our Junos Device object (dev). We need to populate that object, which we do by calling the get() method on eths:

eths = EthPortTable(dev)
eths.get()

We now have, hopefully, a bunch of information about the Ethernet ports in the eths object. Let’s use pprint to easily check:

pprint(eths.keys())

Finally, because we’re neat people, let’s close our session to the Junos device:

dev.close()

Our completed script should look like this:

from pprint import pprint
from jnpr.junos import Device
from jnpr.junos.op.ethport import EthPortTable

dev = Device("juniper", user="script", password="script123")
dev.open()

eths = EthPortTable(dev)
eths.get()

# Show the interface keys we found
pprint(eths.keys())

dev.close()

We can now run the script and see if it works:

john@dns1:~/jnpr$ python showport.py
['ge-0/0/0',
 'ge-0/0/1',
 'ge-0/0/2',
 'ge-0/0/3',
 'ge-0/0/4',
 'ge-0/0/5',
 'ge-0/0/6',
 'ge-0/0/7',
 'ge-0/0/8',
 'ge-0/0/9',
 'ge-0/0/10',
 'ge-0/0/11',
 'ge-0/0/12',
 'ge-0/0/13',
 'ge-0/0/14',
 'ge-0/0/15',
 'ge-0/0/16',
 'ge-0/0/17',
 'ge-0/0/18',
 'ge-0/0/19',
 'ge-0/0/20',
 'ge-0/0/21',
 'ge-0/0/22',
 'ge-0/0/23']
john@dns1:~/jnpr$

Alright! The script has pulled out a list of Ethernet interfaces, which is nice, but I need more information than this if I’m going to provide status on a web page. Right now the documentation is not as complete as it will ultimately be (after all, this is a very early release, so we cannot expect completing yet!), so to check what properties I might be able to pull, I’m going to look at the underlying ethport module code. I should add that there are easier ways to find this out, but seeing the code will be useful for us shortly. Let’s see how scary it is.

ethport.py

On my system, the Junos EZ modules are installed in:

/usr/local/lib/python2.7/dist-packages/jnpr/junos

This is where you find device.py, the core module used to connect to a Junos device. Since we import the ethport module using the line from jnpr.junos.op.ethport, it’s fair to assume that we should look inside the op/ directory, and that’s where we find ethport.py:

john@dns1:/usr/local/lib/python2.7/dist-packages/jnpr/junos/op$ ls -al ethport.py
-rw-r--r-- 1 root staff 1632 Nov 26 13:32 ethport.py

As it turns out, unlike the complexity of the device.py module, this one is really quite simple. I’ll paste a subset of the content here (I removed a few lines for brevity):

EthPortView = RSM.View(RSM.Fields()
  .str('oper', 'oper-status')
  .str('admin','admin-status')
  .str('link_mode','link-mode')
  .str('speed')
  .int('rx_bytes', 'input-bytes', group='mac_stats')
  .int('tx_bytes', 'output-bytes', group='mac_stats')
  .end,
  groups = {
    'mac_stats':'ethernet-mac-statistics'
  }
)

This ‘view’ will extract items from the interface data pulled from the Junos device and make them accessible to me using convenient names. For example, it will create a string property called admin which will match the admin-status of the interface. Similarly, rx_bytes will map to the input-bytes interface statistic, which in turn is part of a group called mac_stats – and mac_stats in turn is defined lower down as representing ethernet-mac-statistics. You may be wondering what this is all about and where these names come from. Here’s a clue:

john@noisy> show interfaces extensive | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.3R3/junos">
    <interface-information xmlns="http://xml.juniper.net/junos/12.3R3/junos-interface" junos:style="normal">
        <physical-interface>
            <name>ge-0/0/0</name>
            <admin-status junos:format="Enabled">up</admin-status>
            <oper-status>up</oper-status>
            <description>server1.mydomain.home</description>
            <link-level-type>Ethernet</link-level-type>
            <speed>Auto</speed>
            <duplex>Auto</duplex>
    [...]
        <ethernet-mac-statistics junos:style="verbose">
            <input-bytes>19604488</input-bytes>
            <output-bytes>3201086862</output-bytes>
            <input-packets>221634</input-packets>
            <output-packets>25254790</output-packets>
    [...]

These element names should look familiar from the ethport.py file. And where some entries referenced the mac-stats group (which was translated as ethernet-mac-statistics) , it indicates that this information is within another element. Note that we didn’t have to say that the first group were within the physical-interface group, and we’ll see why that is in a moment.

In actual fact, the best place to check for elements returned for interface queries (if your head can bear it) is the Juniper DTD for Interfaces Response Tags. This is the definitive reference for available elements, and is something that could be used in the same way as an SNMP MIB, to build a dynamic front end based on available fields.

So, something is clearly missing here – we’ve defined a number of properties but not said how we’re going to get them yet. That’s the next part of the file ethport.py:

EthPortTable = RSM.GetTable('get-interface-information',
  args =  {'media': True, 'interface_name': '[fgx]e*' },
  args_key = 'interface_name',
  item = 'physical-interface',
  view = EthPortView
)

This definition defines a table object (EthPortTable) that will use the RPC call get-interface-information to retrieve the necessary data. More specifically we’re limiting to interfaces whose name matches [fgx]e – in other words, Fast, Gigabit and TenGigabit Ethernet interfaces (fe/ge/xe). Note that the value “item” is set to “physical-interface” – and that’s where the default values are being read from, and is why it didn’t have to be called out in a group in the view definition. Finally, the table is associated with a view – in this case the one we defined above.

One interface element that’s not in the default view is the description, which is something I need for my interface status web page. Thankfully, the ethport.py file includes an example of how to extend the existing definitions without having to duplicate them all. In this case we will create a new view that adds to what’s in the existing EthPortView view:

EthPortView2 = RSM.View( extends=EthPortView,
  fields = RSM.Fields()
    .str('description', 'description')
    .end
)

I’ve added a new string property called ‘description’ which maps to the ‘description’ element in the physical-interface XML. I’ll now create a new Table that references this view:

EthPortTable2 = RSM.GetTable('get-interface-information',
  args =  {'media': True, 'interface_name': '[fgx]e*' },
  args_key = 'interface_name',
  item = 'physical-interface',
  view = EthPortView2
)

So here’s my final (very simple!) test script to generate interface status information:

from jnpr.junos import Device
from jnpr.junos.op.ethport import EthPortTable2

dev = Device("juniper", user="script", password="script123")
dev.open()

eths = EthPortTable2(dev)
eths.get()

for key in eths:
    print key.name, key.admin, key.oper, '(' + key.speed + ')  ', key.description

dev.close()

The only marginally clever bit is that we know that eths will contain 24 interfaces, and we need to pull out the information for each individual interface on its own line. I added a simple for loop that runs through the entries in eths (the “keys”) which allows me to grab properties for each key. Let’s see how it works:

john@dns1:~/jnpr$ python portinfo.py
ge-0/0/0 up up (Auto)   server1.mydomin.home
ge-0/0/1 up up (Auto)   AP-Mainfloor - Infrastructure Wiring
ge-0/0/2 up down (Auto)   Ruckus AP7982 AP - ** POE ENABLED **
ge-0/0/3 up down (Auto)   Kitchen Workstation - Infrastructure Wiring
ge-0/0/4 up down (Auto)   userv2
[...]

Wrap a tiny bit of HTML around this printed output and my web page is ready to roll. I think that’s a pretty good return on four lines of code plus a bunch of standard headers/imports, don’t you? It surely beats the heck out of screen scraping.

Error Checking

It’s early days, and right now error handling in the Junos EZ library isn’t quite where I suspect it will ultimately be. For example, if you try to connect to a device with a user account that has insufficient privileges (in this case, “operator”), you’ll get errors like this:

john@dns1:~/jnpr$ python ps3.py
Traceback (most recent call last):
  File "ps3.py", line 5, in <module>
    dev.open()
  File "/usr/local/lib/python2.7/dist-packages/jnpr/junos/device.py", line 215, in open
    self.facts_refresh()
  File "/usr/local/lib/python2.7/dist-packages/jnpr/junos/device.py", line 379, in facts_refresh
    gather(self, self._facts)
  File "/usr/local/lib/python2.7/dist-packages/jnpr/junos/facts/chassis.py", line 36, in chassis
    JXML.INHERIT
  File "/usr/local/lib/python2.7/dist-packages/jnpr/junos/rpcmeta.py", line 35, in get_config
    return self._junos.execute( rpc )
  File "/usr/local/lib/python2.7/dist-packages/jnpr/junos/device.py", line 257, in execute
    raise RpcError(cmd=rpc_cmd_e, rsp=rsp)
jnpr.junos.exception.RpcError

This doesn’t immediately scream “You don’t have rights”, and suspect this kind of thing will be one of the larger challenges going forward – to trap errors appropriately and figure them out. I don’t have an issue with this right now, as this is, again, early code, so it goes with the territory.

Early Conclusions

So far, I’m really enjoying playing with this library. I’ve got a lot more to explore and I suspect that my lack of Python experience will come and bite me badly, but in concept I’ve managed to achieve a useful task with only minimal skills.

Jeremy mentioned last week that he is looking at making it even easier to customize the tables (e.g. ethport.py) by using YAML rather than having to hack Python; that’s a new item on the wish list, and I’ll watch with interest, as the easier this can be, the better it meets the aim of offering programmability to everybody from the likes of me through full time DevOps geeks.

4 Comments on Getting Busy With the Junos EZ Library

  1. Hi John,

    Excellent post, thank you so much!

    I have actually added the YAML support since we last spoke. So if you install the 0.0.2 release from PyPi or look at the master branch on the github repo, you can see the update. The ethport.py code is now very short and simply imports the YAML definition:

    https://github.com/jeremyschulman/py-junos-eznc/blob/master/lib/jnpr/junos/op/ethport.yml

    I have also added support for composite keys, and a good example of that is the LACP table.

    I’ll also look at making the error messages more use-friendly. You are always welcome to open an “issue” on github so I track these accordingly 🙂

    Thank you again!

  2. Can you try running this and letting me know what I am not doing correctly:

    ###This shows
    from pprint import pprint
    from jnpr.junos import Device
    from jnpr.junos.op.ethport import EthPortTable

    dev = Device(host=’sag24.beaverton.or.bverton.comcast.net’, user=’showlogs’, password=’mafia1′)
    dev.open()
    EthPortView = RSM.View(RSM.Fields()
    .str(‘oper’, ‘oper-status’)
    .str(‘admin’,’admin-status’)
    .str(‘link_mode’,’link-mode’)
    .str(‘speed’)
    .int(‘rx_bytes’, ‘input-bytes’, group=’mac_stats’)
    .int(‘tx_bytes’, ‘output-bytes’, group=’mac_stats’)
    .end,
    groups = {
    ‘mac_stats’:’ethernet-mac-statistics’
    }
    )

    EthPortTable = RSM.GetTable(‘get-interface-information’,
    args = {‘media’: True, ‘interface_name’: ‘[fgx]e*’ },
    args_key = ‘interface_name’,
    item = ‘physical-interface’,
    view = EthPortView
    )

    eths = EthPortTable(dev)
    eths.get()

    # Show the interface keys we found
    #pprint(eths.keys())

    dev.close()

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.