Network Programmability with NX-OS & Python – First Steps

*Note: Some basic introductory knowledge of Python will be assumed for these blogposts. If you’re new to Python, I suggest having a look at Al Sweigart’s Automate The Boring Stuff online book for a beginner’s guide. Also, I’ll come back and update this post with real working code samples that you can try it yourself – for now, I’m using loose Python-like syntax to demonstrate the behavior rather than the individual lines of code.

Preface

Over the last year, I’ve had a shift in focus (mostly thanks to $DAYJOB) in my daily responsibilities to focus in the datacentre space of networking. The largest part of these new responsibilities has been improving our deployments of network gear, reducing time-to-market and eliminating as many common errors caused by these rollouts as possible. Without too many details, these implementations are accomplished via manual work of logging into devices, applying a configuration and verifying the change was completed successfully. This involves all copy-pasta of CLI commands and has caused our department myriads of headaches (and late night phones calls).

Hardware & Software

We needed to deploy a lot of new gear. This was brought up by the common enterprise IT culprits for putting in new kit, such as end-of-life concerns  and adding capacity to the current network. I’m sure everyone has been in the same planning meetings, where you needed to replace lots of old, aging and creaky equipment as well as addressing new business requirements.

The platform of our choice has been the Cisco Nexus 9300 line of top-of-rack switches. After rolling the standard 3-tier network architecture for many years, we decided to switch gears and build a modern leaf-spine Clos fabric using Cisco’s VXLAN EVPN technology. This has given us great capacity for 10G server access and scalability using 40G & 100G in the spine & core layers.

The best benefit of rolling with this technology & newer hardware has been the programmatic interfaces that Cisco has included. This comes to us in the form of NX-API.

NX-API

Cisco’s NX-API is an interface that allows an operator to interact with a Nexus device via standard HTTP calls. While this by itself doesn’t seem to be all that useful (and admittedly for most network engineers, adds complexity to their workflow) , the real benefits comes in with the way in which NX-API interacts with the device’s state.

Take this simple example of a manual CLI configuration:

switch1# conf t
 switch1(config)# vlan 100
 switch1(config-vlan)# name MyVlan100
 switch1(config)# interface Vlan100
 switch1(config-if)# ip address 10.0.100.1/24
 switch1(config-if)# no ip redirects
 switch1(config-if)# ip arp timeout 300
 switch1(config-if)# int Ethernet1/4
 switch1(config-if)# switchport mode trunk
 switch1(config-if)# switchport trunk allowed vlan 100

How would you typically verify this config was applied?

show vlan id 100
show interface Vlan100
show interface Ethernet1/4

All good so far. Now, imagine you had to verify this configuration was active on 5 devices? What about 50 devices? You can see how this starts to get unruly extremely quickly. Unfortunately, this has been the reality for most network engineers for decades as our vendors have not exposed interfaces for us to interact with that return this in a structured manner.

Let’s say you had 36 Cisco Nexus 9K’s that you wanted to verify this exact configuration. With NX-API, you use these same commands and get something back like this:

{ 'result': 
 [
  { 'body': 
   [
    { 'TABLE_VLAN':
     [
      { 'ROW_VLAN':
       [ 
         { 'vlan_id': 100,
           'vlan_name': 'MyVlan100',
           'vlan_state': 'active' } ],
            ... 
          }
       ]
      }
    ]
   }
 ]
}

This particular example shows that same “show vlan id 100” command but formatted in JSON. NX-API takes most common CLI commands and puts wrappers on individual components of the data that is typically shown in the CLI output. What this allows you to do is, using a scripting language, to drill down to exactly the data you want and present it in a deterministic fashion. For example, the ‘vlan_id’ will always return an integer of 1-4095, ‘vlan_name’ will return a string of characters representing the name of the VLAN, etc. This means that you perform basic operations such as checking that the VLAN ID is greater than or less than a given number, that the VLAN name conforms to some pattern that you set for your naming conventions or that it’s even active at all. Here’s how you might use it in a script:

import requests
import json

url='http://YOURIP/ins'
switchuser='USERID'
switchpassword='PASSWORD'

myheaders={'content-type':'application/json-rpc'}
payload=[
  {
    "jsonrpc": "2.0",
    "method": "cli",
    "params": {
      "cmd": "show interface",
      "version": 1
    },
    "id": 1
  }
]
response = requests.post(url,data=json.dumps(payload), headers=myheaders,
                         auth=(switchuser,switchpassword)).json()

The above code snippet is directly taken from NX-API Sandbox.

Another nice feature of this API is the Cisco NX-API Sandbox. So long as you’ve added “feature nxapi” to the device configuration, you can open up your web browser and log straight into this sandbox (by simply opening http://switch_ip_address/). The Sandbox will also give you example code snippets for building the proper payload and sending this HTTP request in Python so you can put in your scripts. Check out Cisco’s Programmability Guide for a quick guide on using the Sandbox.

Once you have a few working scripts that are using the API, you can even send the configuration commands using similar HTTP calls. I’ll cover this more on a later post.

Consistent Data

With an API such as this available, you can combine your CLI-fu with some basic knowledge of Python data types to interact with a device in interesting ways. For example, if you pulled the list of BGP peers via “show ip bgp summary” and the returned JSON data, you can loop through what is essentially a list of dictionaries to pull out the data you’re looking to extract. That is what you see in the for loop in the code sample above.

for each list item in result['key1']['key2']['key3']['list_of_peers']:   
   # Whenever we loop over this particular list of dictionaries ('list_of_peers'), 
   # the dictionary I get back will almost always have the same set of keys that I can access
   print(item['bgp_peer_ip']) 
   print(item['bgp_asn'])
   print(item['bgp_prefixes_received'])

Every time you send that command to any switch, you’re always going to receive the same nested list of key-value pairs, to which you can loop over quickly in Python and pull out only the data you care about. No longer do you need to SSH to a device (either manually or via an SSH client library), authenticate to a device, execute the desired command and scrape through the output manually (again, either manually in a terminal window or parsing it as a giant string in your interpreter).

Conclusion

If you get overwhelmed from the code above and all the fancy brackets, don’t worry. A good starting point would be to go through a beginner Python course such as LPTHW and familiarize yourself with variable types (such as integers, strings, lists, dictionaries, etc) and how to interact with them. This also can be done in any language of choice. All modern programming languages and interpreters have the necessary libraries (typically in their standard libraries) for sending HTTP1.1 requests to an endpoint and parsing well-known notations such as JSON or XML.

When you combine that with what you already know about using network devices everyday, you can start automating your repetitive, boring and simple day-to-day tasks and/or troubleshooting. It can be extended from everything to gathering routing & interface statistics to parsing MAC address tables to verifying SNMP community strings. The possibilities truly are endless.

In my next post, I’ll focus on taking another step in using tools such as Jinja2 combined with NX-API to generate plain-text configuration files, which can help build your configs in an error-free & consistent way.