Wednesday, 29 June 2016

Python for Network Engineers - Part 3 - Using Cisco Nexus NX-API

In this blog article we’re going to look further into using the Python requests module to interface with REST APIs.  In particular we’ll look at Cisco Nexus NX-API.

Blog Series

Python for Network Engineers - Part 1 - Introduction
Python for Network Engineers - Part 2 - Making REST calls
Python for Network Engineers - Part 3 - Using Cisco Nexus NX-API
Python for Network Engineers - Part 4 - Using Arista EOS eAPI
Python for Network Engineers - Part 5 - Using Junos NETCONF interface
Python for Network Engineers - Part 6 - Using Cisco Nexus NETCONF interface
Python for Network Engineers - Part 7 - Using Palo Alto Networks XML API


Cisco NX-API

For this example I’m using a Cisco Nexus 7000 with NX-OS 7.2 and Ubuntu 16.04 Linux with Python.  Please see the previous blog article (part2) for details of how to install the requests module

Cisco Setup
On the Nexus switch then all we have to do is the following from the configuration prompt:
feature nxapi

Python Bit
Next let’s open an interactive Python session and create a REST connection to the Nexus switch.  In the first instance we’ll just run a show command.  The “s” object is the session to the Nexus switch and the “r” object is the response data.
import requests, json
uname = upass = 'admin'
uri = 'http://10.1.1.1/ins/'
s = requests.session()
s.auth = (uname,upass)
s.verify = False
s.headers.update({'Content-Type' : 'application/json'})
data = {'ins_api': {'chunk': '0', 'version': '1.2', 'sid': '1', 'input': 'show version', 'type': 'cli_show', 'output_format': 'json'}}
r = s.request('post', uri, data=json.dumps(data))

As described in the previous blog we can now do some checks against the session and response data and see if it ran as expected:
s.cookies
r.status_code
r.text
r.headers

One thing to note is that in the response headers set a cookie and the requests library has automatically added it to the session data for future requests.  Next we’ll look at the response data payload:
print json.dumps(r.json(), indent=2)
print json.dumps(r.json()['ins_api']['outputs'], indent=2)

Example 1
For the next example I’ll just make a small change to the previous JSON payload and run the REST call again.  This time I’ve also shown the output:
data['input'] = 'show vlan brief'
r = s.request('post', uri, data=json.dumps(data))
print json.dumps(r.json()['ins_api']['outputs'], indent=2)
{
  "output": {
    "msg": "Success",
    "input": "show vlan brief",
    "code": "200",
    "body": {
      "TABLE_vlanbriefxbrief": {
        "ROW_vlanbriefxbrief": {
          "vlanshowbr-vlanid": 16777216,
          "vlanshowbr-vlanid-utf": 1,
          "vlanshowbr-vlanname": "default",
          "vlanshowbr-vlanstate": "active",
          "vlanshowbr-shutstate": "noshutdown"
        }
      }
    }
  }
}
As you can see on my switch there is only the default vlan.

Example 2
For the next example we’ll add a new vlan.  As this is a configuration command then we need to update the JSON to reflect this
data['type'] = 'cli_conf'
data['input'] = 'vlan 99'
r = s.request('post', uri, data=json.dumps(data))
print json.dumps(r.json()['ins_api']['outputs'], indent=2)
{
  "output": {
    "msg": "Success",
    "body": {},
    "code": "200"
  }
}

Now if we run the show command again (changing the JSON back to a show command) we can see the new VLAN has been created:
data['type'] = 'cli_show'
data['input'] = 'show vlan brief'
r = s.request('post', uri, data=json.dumps(data))
print json.dumps(r.json()['ins_api']['outputs'], indent=2)
{
  "output": {
    "msg": "Success",
    "input": "show vlan brief",
    "code": "200",
    "body": {
      "TABLE_vlanbriefxbrief": {
        "ROW_vlanbriefxbrief": [
          {
            "vlanshowbr-vlanid": 16777216,
            "vlanshowbr-vlanid-utf": 1,
            "vlanshowbr-vlanname": "default",
            "vlanshowbr-vlanstate": "active",
            "vlanshowbr-shutstate": "noshutdown"
          },
          {
            "vlanshowbr-vlanid": 1660944384,
            "vlanshowbr-vlanid-utf": 99,
            "vlanshowbr-vlanname": "VLAN0099",
            "vlanshowbr-vlanstate": "active",
            "vlanshowbr-shutstate": "noshutdown"
          }
        ]
      }
    }
  }
}

Example 3
In the last example we ran a configuration command under the global context.  However what if we want to run a command under a specific interface or other sub-prompt?  In this example we’ll show how to run these commands using a semi-colon delimiter.  To be clear we need to separate commands with a space, semi-colon and another space as shown in the example below:
data['type'] = 'cli_conf'
data['input'] = 'vlan 199 ; name REST_API_TEST'
r = s.request('post', uri, data=json.dumps(data))
print json.dumps(r.json()['ins_api']['outputs'], indent=2)
{
  "output": [
    {
      "msg": "Success",
      "body": {},
      "code": "200"
    },
    {
      "msg": "Success",
      "body": {},
      "code": "200"
    }
  ]
}

Now if we run the show command again (changing the JSON back to a show command) we can see the new VLAN has been created with the correct name:
data['type'] = 'cli_show'
data['input'] = 'show vlan brief'
r = s.request('post', uri, data=json.dumps(data))
print json.dumps(r.json()['ins_api']['outputs'], indent=2)
{
  "output": {
    "msg": "Success",
    "input": "show vlan brief",
    "code": "200",
    "body": {
      "TABLE_vlanbriefxbrief": {
        "ROW_vlanbriefxbrief": [
          {
            "vlanshowbr-vlanid": 16777216,
            "vlanshowbr-vlanid-utf": 1,
            "vlanshowbr-vlanname": "default",
            "vlanshowbr-vlanstate": "active",
            "vlanshowbr-shutstate": "noshutdown"
          },
          {
            "vlanshowbr-vlanid": 1660944384,
            "vlanshowbr-vlanid-utf": 99,
            "vlanshowbr-vlanname": "VLAN0099",
            "vlanshowbr-vlanstate": "active",
            "vlanshowbr-shutstate": "noshutdown"
          },
          {
            "vlanshowbr-vlanid": 3338665984,
            "vlanshowbr-vlanid-utf": 199,
            "vlanshowbr-vlanname": "REST_API_TEST",
            "vlanshowbr-vlanstate": "active",
            "vlanshowbr-shutstate": "noshutdown"
          }
        ]
      }
    }
  }
}

Recap
To conclude the basic JSON data structure for NX-OS is:
data = {'ins_api': {'chunk': '0', 'version': '1.2', 'sid': '1', 'output_format': 'json'}}

Then to run a show or a configuration command, add either of the following keys:
data['type'] = 'cli_show'
data['type'] = 'cli_conf'

And then add a key with the command or commands you wish to run:
data['input'] = 'show vlan brief'
data['input'] = 'vlan 199 ; name REST_API_TEST'

So as long as you know what you want to do on the Cisco cli its very straight forward to run this from a script, get a response code to say if it ran well and capture the output in a structured way to pull out individual parameters.

Resources



No comments:

Post a Comment