19 min read

In this article by Eric Chou, author of the book Mastering Python Networking we will look at following topics:

  • Treating Infrastructure as code and data modeling
  • Cisco NX-API and Application centric infrastruucture

(For more resources related to this topic, see here.)

Infrastructure as Python code

In a perfect world, network engineers and people who design and manage networks should focus on what they want the network to achieve instead of the device-level interactions. In my first job as an Intern for a local ISP, wide-eyed and excited, I received my first assignment to install a router on customer site to turn up their fractional frame relay link (remember those?). How would I do that? I asked, I was handed a standard operating procedure for turning up frame relay links. I went to the customer site, blindly type in the commands and looked at the green lights flash, happily packed my bag and pad myself on the back for a job well done. As exciting as that first assignment was, I did not fully understand what I was doing. I was simply following instructions without thinking about the implication of the commands I was typing in. How would I troubleshoot something if the light was red instead of green? I think I would have called back to the office.

Of course network engineering is not about typing in commands onto a device, it is about building a way that allow services to be delivered from one point to another with as little friction as possible. The commands we have to use and the output we have to interpret are merely a mean to an end. I would like to hereby argue that we should focus as much on the Intent of the network as possible for an Intent-Driven Networking and abstract ourselves from the device-level interaction on an as-needed basis.

In using API, it is my opinion that it gets us closer to a state of Intent-Driven Networking. In short, because we abstract the layer of specific command executed on destination device, we focus on our intent instead of the specific command given to the device. For example, if our intend is to deny an IP from entering our network, we might use ‘access-list and access-group’ on a Cisco and ‘filter-list’ on a Juniper. However, in using API, our program can start asking the executor for their Intent while masking what kind of physical device it is they are talking to.

Screen Scraping vs. API Structured Output

Imagine a common scenario where we need to log in to the device and make sure all the interfaces on the devices are in an up/up state (both status and protocol are showing as up). For the human network engineers getting to a Cisco NX-OS device, it is simple enough to issue the show IP interface brief command and easily tell from the output which interface is up:

nx-osv-2# show ip int brief
IP Interface Status for VRF "default"(1)
Interface IP Address Interface Status
Lo0 192.168.0.2 protocol-up/link-up/admin-up
Eth2/1 10.0.0.6 protocol-up/link-up/admin-up
nx-osv-2#

The line break, white spaces, and the first line of column title are easily distinguished from the human eye. In fact, they are there to help us line up, say, the IP addresses of each interface from line 1 to line 2 and 3. If we were to put ourselves into the computer’s eye, all these spaces and line breaks are only taking away from the real important output, which is ‘which interfaces are in the up/up state’? To illustrate this point, we can look at the Paramiko output again:

>>> new_connection.send('sh ip int briefn')
16
>>> output = new_connection.recv(5000)
>>> print(output)
b'sh ip int briefrrnIP Interface Status for VRF
"default"(1)rnInterface IP Address Interface
StatusrnLo0 192.168.0.2 protocol-up/link-up/admin-up
rnEth2/1 10.0.0.6 protocol-up/link-up/admin-up rnrnxosv-
2# '
>>>

If we were to parse out that data, of course there are many ways to do it, but here is what I would do in a pseudo code fashion:

  1. Split each line via line break.
  2. I may or may not need the first line that contain the executed command, for now, I don’t think I need it.
  3. Take out everything on the second line up until the VRF and save that in a variable as we want to know which VRF the output is showing of.
  4. For the rest of the lines, because we do not know how many interfaces there are, we will do a regular expression to search if the line starts with possible interfaces, such as lo for loopback and ‘Eth’.
  5. We will then split this line into three sections via space, each consist of name of interface, IP address, then the interface status.
  6. The interface status will then be split further using the forward slash (/) to give us the protocol, link, and admin status.

Whew, that is a lot of work just for something that a human being can tell in a glance! You might be able to optimize the code and the number of lines, but in general this is what we need to do when we need to ‘screen scrap’ something that is somewhat unstructured. There are many downsides to this method, the few bigger problems that I see are:

  • Scalability: We spent so much time in painstakingly details for each output, it is hard to imagine we can do this for the hundreds of commands that we typically run.
  • Predicability: There is really no guarantee that the output stays the same. If the output is changed ever so slightly, it might just enter with our hard earned battle of information gathering.
  • Vendor and software lock-in: Perhaps the biggest problem is that once we spent all these time parsing the output for this particular vendor and software version, in this case Cisco NX-OS, we need to repeat this process for the next vendor that we pick. I don’t know about you, but if I were to evaluate a new vendor, the new vendor is at a severe on-boarding disadvantage if I had to re-write all the screen scrap code again.

Let us compare that with an output from an NX-API call for the same ‘show IP interface brief’ command. We will go over the specifics of getting this output from the device later in this article, but what is important here is to compare the follow following output to the previous screen scraping steps:

{
     "ins_api":{
     "outputs":{
     "output":{
     "body":{
     "TABLE_intf":[
       {
       "ROW_intf":{
       "admin-state":"up",
       "intf-name":"Lo0",
       "iod":84,
       "ip-disabled":"FALSE",
       "link-state":"up",
       "prefix":"192.168.0.2",
       "proto-state":"up"
       }
       },
     {
     "ROW_intf":{
     "admin-state":"up",
     "intf-name":"Eth2/1",
     "iod":36,
     "ip-disabled":"FALSE",
     "link-state":"up",
     "prefix":"10.0.0.6",
     "proto-state":"up"
     }
     }
     ],
      "TABLE_vrf":[
      {
     "ROW_vrf":{
     "vrf-name-out":"default"
     }
     },
     {
     "ROW_vrf":{
     "vrf-name-out":"default"
     }
     }
     ]
     },
     "code":"200",
     "input":"show ip int brief",
     "msg":"Success"
     }
     },
     "sid":"eoc",
     "type":"cli_show",
     "version":"1.2"
     }
    }

NX-API can return output in XML or JSON, this is obviously the JSON output that we are looking at. Right away you can see the answered are structured and can be mapped directly to Python dictionary data structure. There is no parsing required, simply pick the key you want and retrieve the value associated with that key. There is also an added benefit of a code to indicate command success or failure, with a message telling the sender reasons behind the success or failure. You no longer need to keep track of the command issued, because it is already returned to you in the ‘input’ field. There are also other meta data such as the version of the NX-API.

This type of exchange makes life easier for both vendors and operators. On the vendor side, they can easily transfer configuration and state information, as well as add and expose extra fields when the need rises. On the operator side, they can easily ingest the information and build their infrastructure around it. It is generally agreed on that automation is much needed and a good thing, the questions usually centered around which format and structure the automation should take place. As you can see later in this article, there are many competing technologies under the umbrella of API, on the transport side alone, we have REST API, NETCONF, RESTCONF, amongst others. Ultimately the overall market will decide, but in the mean time, we should all take a step back and decide which technology best suits our need.

Data modeling for infrastructure as code

According to Wikipedia,

“A data model is an abstract model that organizes elements of data and standardizes how they relate to one another and to properties of the real world entities. For instance, a data model may specify that the data element representing a car be composed of a number of other elements which, in turn, represent the color and size of the car and define its owner.”

The data modeling process can be illustrated in the following graph:

 Data Modeling Process (source:  https://en.wikipedia.org/wiki/Data_model)

When applied to networking, we can applied this concept as an abstract model that describe our network, let it be datacenter, campus, or global Wide Area Network. If we take a closer look at a physical datacenter, a layer 2 Ethernet switch can be think of as a device containing a table of Mac addresses mapped to each ports. Our switch data model described how the Mac address should be kept in a table, which are the keys, additional characteristics (think of VLAN and private VLAN), and such. Similarly, we can move beyond devices and map the datacenter in a model. We can start with the number of devices are in each of the access, distribution, core layer, how they are connected, and how they should behave in a production environment. For example, if we have a Fat-Tree network, how many links should each of the spine router should have, how many routes they should contain, and how many next-hop should each of the prefixes have. These characteristics can be mapped out in a format that can be referenced against as the ideal state that we should always checked against.

One of the relatively new network data modeling language that is gaining traction is YANG. Yet Another Next Generation (YANG) (Despite common belief, some of the IETF workgroup do have a sense of humor). It was first published in RFC 6020 in 2010, and has since gain traction among vendors and operators. At the time of writing, the support for YANG varies greatly from vendors to platforms, the adaptation rate in production is therefore relatively low. However, it is a technology worth keeping an eye out for.

Cisco API and ACI

Cisco Systems, as the 800 pound gorilla in the networking space, has not missed on the trend of network automation. The problem has always been the confusion surrounding Cisco’s various product lines and level of technology support. With product lines spans from routers, switches, firewall, servers (unified computing), wireless, collaboration software and hardware, analytic software, to name a few, it is hard to know where to start.

Since this book focuses on Python and networking, we will scope the section to the main networking products. In particular we will cover the following:

  • Nexus product automation with NX-API
  • Cisco NETCONF and YANG examples
  • Cisco application centric infrastructure for datacenter
  • Cisco application centric infrastructure for enterprise

For the NX-API and NETCONF examples here, we can either use the Cisco DevNet always-on lab devices or locally run Cisco VIRL. Since ACI is a separated produce and license on top of the physical switches, for the following ACI examples, I would recommend using the DevNet labs to get an understanding of the tools. Unless, of course, that you are one of the lucky ones who have a private ACI lab that you can use.

Cisco NX-API

Nexus is Cisco’s product line of datacenter switches. NX-API (http://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus9000/sw/6-x/programmability/guide/b_Cisco_Nexus_9000_Series_NX-OS_Programmability_Guide/b_Cisco_Nexus_9000_Series_NX-OS_Programmability_Guide_chapter_011.html) allows the engineer to interact with the switch outside of the device via a variety of transports, including SSH, HTTP, and HTTPS.

Installation and preparation

Here are the Ubuntu Packages that we will install, you may already have some of the packages such as Python development, pip, and Git:

$ sudo apt-get install -y python3-dev libxml2-dev libxslt1-dev libffi-dev
libssl-dev zlib1g-dev python3-pip git python3-requests

If you are using Python 2:

sudo apt-get install -y python-dev
libxml2-dev libxslt1-dev libffi-dev libssl-dev zlib1g-dev
python-pip git python-requests

The ncclient (https://github.com/ncclient/ncclient) library is a Python library for NETCONF clients, we will install from the GitHub repository to install the latest version:

$ git clone https://github.com/ncclient/ncclient
$ cd ncclient/
$ sudo python3 setup.py install
$ sudo python setup.py install

NX-API on Nexus devices is off by default, we will need to turn it on. We can either use the user already created or create a new user for the NETCONF procedures.

feature nxapi
username cisco password 5 $1$Nk7ZkwH0$fyiRmMMfIheqE3BqvcL0C1 role networkopera
tor
username cisco role network-admin
username cisco passphrase lifetime 99999 warntime 14 gracetime 3

For our lab, we will turn on both HTTP and sandbox configuration, they should be turned off in production.

nx-osv-2(config)# nxapi http port 80
nx-osv-2(config)# nxapi sandbox
We are now ready to look at our first NX-API example.

NX-API examples

Since we have turned on sandbox, we can launch a web browser and take a look at the various message format, requests, and response based on the CLI command that we are already familiar with.

In the following example, I selected JSON-RPC and CLI command type for the command show version:

 

The sandbox comes in handy if you are unsure about the supportability of message format or if you have questions about the field key for which the value you want to retrieve in your code.

In our first example, we are just going to connect to the Nexus device and print out the capabilities exchanged when the connection was first made:

#!/usr/bin/env python3
from ncclient import manager
conn = manager.connect(
host='172.16.1.90',
port=22,
username='cisco',
password='cisco',
hostkey_verify=False,
device_params={'name': 'nexus'},
look_for_keys=False)
for value in conn.server_capabilities:
print(value)
conn.close_session()

The connection parameters of host, port, username, and password are pretty self-explanatory. The device parameter specifies the kind of device the client is connecting to, as we will also see a differentiation in the Juniper NETCONF sections. The hostkey_verify bypass the known_host requirement for SSH while the look_for_keys option disables key authentication but use username and password for authentication.

The output will show that the XML and NETCONF supported feature by this version of NX-OS:

$ python3 cisco_nxapi_1.py
urn:ietf:params:xml:ns:netconf:base:1.0
urn:ietf:params:netconf:base:1.0

Using ncclient and NETCONF over SSH is great because it gets us closer to the native implementation and syntax. We will use the library more later on. For NX-API, I personally feel that it is easier to deal with HTTPS and JSON-RPC. In the earlier screenshot of NX-API Developer Sandbox, if you noticed in the Request box, there is a box labeled Python. If you click on it, you would be able to get an automatically converted Python script based on the requests library. 

Requests is a very popular, self-proclaimed HTTP for humans library used by companies like Amazon, Google, NSA, amongst others. You can find more information about it on the official site (http://docs.python-requests.org/en/master/).

For the show version example, the following Python script is automatically generated for you. I am pasting in the output without any modification:

"""
NX-API-BOT
"""
import requests
import json
"""
Modify these please
"""
url='http://YOURIP/ins'
switchuser='USERID'
switchpassword='PASSWORD'
myheaders={'content-type':'application/json-rpc'}
payload=[
{
"jsonrpc": "2.0",
"method": "cli",
"params": {
"cmd": "show version",
"version": 1.2
},
"id": 1
}
]
response = requests.post(url,data=json.dumps(payload),
headers=myheaders,auth=(switchuser,switchpassword)).json()

In cisco_nxapi_2.py file, you will see that I have only modified the URL, username and password of the preceding file, and parse the output to only include the software version. Here is the output:

$ python3 cisco_nxapi_2.py
7.2(0)D1(1) [build 7.2(0)ZD(0.120)]

The best part about using this method is that the same syntax works with both configuration command as well as show commands. This is illustrated in cisco_nxapi_3.py file. For multi-line configuration, you can use the id field to specify the order of operations. In cisco_nxapi_4.py, the following payload was listed for changing the description of interface Ethernet 2/12 in the interface configuration mode.

{
        "jsonrpc": "2.0",
        "method": "cli",
        "params": {
          "cmd": "interface ethernet 2/12",
          "version": 1.2
        },
        "id": 1
      },
      {
        "jsonrpc": "2.0",
        "method": "cli",
        "params": {
          "cmd": "description foo-bar",
          "version": 1.2
        },
        "id": 2
      },
      {
        "jsonrpc": "2.0",
        "method": "cli",
        "params": {
          "cmd": "end",
          "version": 1.2
        },
        "id": 3
      },
      {
        "jsonrpc": "2.0",
        "method": "cli",
        "params": {
          "cmd": "copy run start",
          "version": 1.2
        },
        "id": 4
      }
    ]

In the next section, we will look at examples for Cisco NETCONF and YANG model.

Cisco and YANG model

Earlier in the article, we looked at the possibility of expressing the network using data modeling language YANG. Let us look into it a little bit.

First off, we should know that YANG only defines the type of data sent over NETCONF protocol and NETCONF exists as a standalone protocol as we saw in the NX-API section. YANG being relatively new, the supportability is spotty across vendors and product lines. For example, if we run the same capability exchange script we saw preceding to a Cisco 1000v running  IOS-XE, this is what we would see:

urn:cisco:params:xml:ns:yang:cisco-virtual-service?module=ciscovirtual-
service&revision=2015-04-09
http://tail-f.com/ns/mibs/SNMP-NOTIFICATION-MIB/200210140000Z?
module=SNMP-NOTIFICATION-MIB&revision=2002-10-14
urn:ietf:params:xml:ns:yang:iana-crypt-hash?module=iana-crypthash&
revision=2014-04-04&features=crypt-hash-sha-512,crypt-hashsha-
256,crypt-hash-md5
urn:ietf:params:xml:ns:yang:smiv2:TUNNEL-MIB?module=TUNNELMIB&
revision=2005-05-16
urn:ietf:params:xml:ns:yang:smiv2:CISCO-IP-URPF-MIB?module=CISCOIP-
URPF-MIB&revision=2011-12-29
urn:ietf:params:xml:ns:yang:smiv2:ENTITY-STATE-MIB?module=ENTITYSTATE-
MIB&revision=2005-11-22
urn:ietf:params:xml:ns:yang:smiv2:IANAifType-MIB?module=IANAifType-
MIB&revision=2006-03-31
<omitted>

Compare that to the output that we saw, clearly IOS-XE understand more YANG model than NX-OS. Industry wide network data modeling for networking is clearly something that is beneficial to network automation. However, given the uneven support across vendors and products, it is not something that is mature enough to be used across your production network, in my opinion. For the book I have included a script called cisco_yang_1.py that showed how to parse out NETCONF XML output with YANG filters urn:ietf:params:xml:ns:yang:ietf-interfaces as a starting point to see the existing tag overlay.

You can check the latest vendor support on the YANG Github project page (https://github.com/YangModels/yang/tree/master/vendor).

Cisco ACI

Cisco Application Centric Infrastructure (ACI) is meant to provide a centralized approach to all of the network components. In the datacenter context, it means the centralized controller is aware of and manages the spine, leaf, top of rack switches as well as all the network service functions. This can be done thru GUI, CLI, or API. Some might argue that the ACI is Cisco’s answer to the broader defined software defined networking.

One of the somewhat confusing point for ACI, is the difference between ACI and ACI-EM. In short, ACI focuses on datacenter operations while ACI-EM focuses on enterprise modules. Both offers a centralized view and control of the network components, but each has it own focus and share of tools. For example, it is rare to see any major datacenter deploy customer facing wireless infrastructure but wireless network is a crucial part of enterprises today. Another example would be the different approaches to network security. While security is important in any network, in the datacenter environment lots of security policy is pushed to the edge node on the server for scalability, in enterprises security policy is somewhat shared between the network devices and servers.

Unlike NETCONF RPC, ACI API follows the REST model to use the HTTP verb (GET, POST, PUT, DELETE) to specify the operation intend.

We can look at the cisco_apic_em_1.py file, which is a modified version of the Cisco sample code on lab2-1-get-network-device-list.py (https://github.com/CiscoDevNet/apicem-1.3-LL-sample-codes/blob/master/basic-labs/lab2-1-get-network-device-list.py).

The abbreviated section without comments and spaces are listed here.

The first function getTicket() uses HTTPS POST on the controller with path /api/vi/ticket with username and password embedded in the header. Then parse the returned response for a ticket with limited valid time.

def getTicket():
url = "https://" + controller + "/api/v1/ticket"
payload = {"username":"usernae","password":"password"}
header = {"content-type": "application/json"}
response= requests.post(url,data=json.dumps(payload), headers=header,
verify=False)
r_json=response.json()
ticket = r_json["response"]["serviceTicket"]
return ticket

The second function then calls another path /api/v1/network-devices with the newly acquired ticket embedded in the header, then parse the results.

url = "https://" + controller + "/api/v1/network-device"
header = {"content-type": "application/json", "X-Auth-Token":ticket}

The output displays both the raw JSON response output as well as a parsed table. A partial output when executed against a DevNet lab controller is shown here:

Network Devices =
    {
     "version": "1.0",
     "response": [
     {
     "reachabilityStatus": "Unreachable",
     "id": "8dbd8068-1091-4cde-8cf5-d1b58dc5c9c7",
     "platformId": "WS-C2960C-8PC-L",
    &lt;omitted&gt;
     "lineCardId": null,
     "family": "Wireless Controller",
     "interfaceCount": "12",
     "upTime": "497 days, 2:27:52.95"
     }
    ]
    }
    8dbd8068-1091-4cde-8cf5-d1b58dc5c9c7 Cisco Catalyst 2960-C Series
     Switches
    cd6d9b24-839b-4d58-adfe-3fdf781e1782 Cisco 3500I Series Unified
    Access Points
    &lt;omitted&gt;
    55450140-de19-47b5-ae80-bfd741b23fd9 Cisco 4400 Series Integrated 
    Services Routers
    ae19cd21-1b26-4f58-8ccd-d265deabb6c3 Cisco 5500 Series Wireless LAN 
    Controllers

As one can see, we only query a single controller device, but we are able to get a high level view of all the network devices that the controller is aware of. The downside is, of course, the ACI controller only supports Cisco devices at this time.

Summary

In this article, we looked at various ways to communicate and manage network devices from Cisco.

Resources for Article:


Further resources on this subject:

LEAVE A REPLY

Please enter your comment!
Please enter your name here