REST API with Cisco SDWAN

This post is going to be a quick overview of the REST API using Cisco SDWAN (viptela) to show some examples. I will first give a brief overview of REST before getting into a couple examples of actually using it. To keep this post from going on too long, I will stick to the basics of REST and any information that seems necessary to understand the examples.

For a REST interaction to work, you must have a URL and a Method. Depending on the Method you may also need Headers and a Body. The URL points to a device and one of its resources, a Method tells it what you want to do with the resource, the Header tells the device what kind of information we are going to send (such as a JSON object), and then the Body contains the information we want to send.

REST has 4 methods by which you can interact with a device, GET, POST, PUT, and DELETE.

  • GET: Used to get information from a device. You would use this thing to retrieve a list endpoints, status of endpoints, policies, templates etc.
  • POST: Used to send something to a device. This could be used to send authentication information or send new configuration to a device.
  • PUT: Used to update an existing object on a device. If the object doesn’t exist, it will also create it. You might use this to add or change a parameter, such as updating existing device template with new configuration
  • DELETE: Used to delete something on a device, such as removing an interface, policy or template.

When you send an API request you should receive a response back. This is in the form of a numerical code and sometimes a Body with further information. The codes are things like 200 OK, 404 Not Found, 500 Internal Server Error, etc. Check out this page for a full list and more detailed information on the error codes.

To find out everything you can do with a REST API you will need to check the documentation provided for the device. For Cisco SDWAN, you can check the documentation on their webpage. You can access a list of all the API URLs directly from the vManage server its self, though you will need help from the previously mentioned documentation page to get the full picture. You can get to this list by going to https://vmanage-IP:PORT/apidocs

For your application to use the vManage API you must first log in. You need to do this first thing each time your application runs, or if you close your python interpreter and reopen it you will need to re-authenticate. To do this, you make a call to the authentication API URL of vManage and send it your credentials. See the below example for a python script to log into your vManage server.

import requests

login_url = 'https://10.10.20.90:8443/j_security_check'
login_credentials = {'j_username':'admin', 'j_password':'admin'}

session = requests.session()

response = session.post(url=login_url, data=login_credentials, verify=False)

if b'<html>' in response.content:
    print('Login Failed')
else:
    print('Login Success')

The module we will use to send calls to the API is called requests. You can install this with “pip install requests”. We set up “login_url” to contain the API URL that vManage uses for authentication and “login_credentials” to hold our username and password. vManage is expecting to receive a dictionary with the keys “j_username” and “j_password” to login. We set up an object “session” to create an instance of the session() class which was imported from requests. Now to perform the actual login, we use “session.post()” and pass in our API URL and the data we want to send which is our credentials. In this example we also set “verify” to False, this is to ignore an ssl certificate error. Since this is just a lab, we don’t really care about the certificate error, however in production you would want to make sure your certifications are set up properly and set this to True.

You can see wether this worked or not by looking at “response.content”. If it worked, it will simply contain “<Response [200]>”. If it did not work, you will get a much longer output but you can just look for b’<html’> to indicate failure. You need the lowercase b there, as response.content is a byte type object.

Now let’s look at requesting some information. Lets get a list of devices the vManage server knows about:

import json

device_list_url = 'https://10.10.20.90:8443/dataservice/device'

response = session.get(url=device_list_url, verify=False)
response = json.loads(response.content)

for device in response['data']:
    print(device['host-name'], device['local-system-ip'], device['uuid'])

This time we will also need to import json. And like before, we create “device_list_url” to hold the API URL and since we already have an instance of requests.session() with the name “session” set up before we will use it again to send our request. This time, we are using the GET method so we need to use session.get() and pass in our url. This time, we aren’t sending any data so we don’t need to include the data variable. Then we use json.loads() to properly read the data we received in “response.content”. At this point you have a python dictionary stored in the “response” object. There is a lot of information stored in it, however for this example I wrote a basic for loop to just pull out a brief list of every device vManage knows about its IP address, and ID:

vmanage-01 4.4.4.90 e85cd6d5-4dea-46a1-affb-38fd4cd8debd
vsmart-01 4.4.4.70 50cb5f09-3b74-49b7-aa5a-c17db012206c
vbond-01 4.4.4.80 8163a199-96b2-4886-9b20-bcb9fce4bf88
vedge-001 4.4.4.60 100faff9-8b36-4312-bf97-743b26bd0211
vedge-002 4.4.4.61 f3d4159b-4172-462c-9c8d-8db76c31521d
vedge-003 4.4.4.64 0435c3e8-d3a0-4c6b-af0a-f95d41974d69
vedge-004 4.4.4.65 46c18a49-f6f3-4588-a49a-0b1cc387f179

Alright, so now we have demonstrated how to retrieve data, let’s look at how to make a change. Heres some code to attach a device to a template:

attach_url = 'https://10.10.20.90:8443/dataservice/template/device/config/attachcli'
attach_info = {
      "deviceTemplateList":[
      {
        "templateId":"e8a0f34c-93c4-47e4-841d-e679f89e0252",       
        "device":[ 
        {
          "csv-status":"complete",
          "csv-deviceId":"46c18a49-f6f3-4588-a49a-0b1cc387f179",
          "csv-deviceIP":"4.4.4.65",
          "csv-host-name":"vedge-004",
          "//system/host-name":"vedge-004",
          "//system/system-ip":"4.4.4.65",
          "//system/site-id":"400",
          "csv-templateId":"e8a0f34c-93c4-47e4-841d-e679f89e0252",
          "selected":"true"
        }
        ],
        "isEdited":"false", 
        "isMasterEdited":"false"
      }
      ]
    }
attach_info = json.dumps(attach_info)
response = session.post(url=attach_url, data=attach_info, headers={'Content-Type': 'application/json'}, verify=False)
print(response)
print(response.content)

Again, we set up our API URL as “attach_url”. Next, we need some data to tell vManage which device we want to attach and the template we want to attach it to. We will store that in “attach_info”. You are probably wondering how exactly I know how this data object needs to be set up. As I mentioned before, that information is found in the API documentation. For this particular operation you can find the information here. Something different in this code is json.dumps(). We use this to convert our python dictionary “attach_info” into a json formatted object that vManage can accept. And finally, we use session.post() to send the information. This time you will notice we include “headers”. We need to let vManage know what “Content-Type” we are about to send it, which in this case is “application/json”. We can also print “response” and “response.content” to confirm it works. You can see below that we received a 200 OK response, along with some information about the interaction:

<Response [200]>
b'{"id":"push_file_template_configuration-2e107fbf-1cc9-44c1-803f-e8742faf9e2a"}'

Heres the full code for everything I’ve shown here:

import requests,json

login_url = 'https://10.10.20.90:8443/j_security_check'
login_credentials = {'j_username':'admin', 'j_password':'admin'}

session = requests.session()

response = session.post(url=login_url, data=login_credentials, verify=False)

if b'<html>' in response.content:
    print('Login Failed')
else:
    print('Login Success')

device_list_url = 'https://10.10.20.90:8443/dataservice/device'

response = session.get(url=device_list_url, verify=False)
response = json.loads(response.content)

for device in response['data']:
    print(device['host-name'], device['local-system-ip'], device['uuid'])

attach_url = 'https://10.10.20.90:8443/dataservice/template/device/config/attachcli'
attach_info = {
      "deviceTemplateList":[
      {
        "templateId":"e8a0f34c-93c4-47e4-841d-e679f89e0252",       
        "device":[ 
        {
          "csv-status":"complete",
          "csv-deviceId":"46c18a49-f6f3-4588-a49a-0b1cc387f179",
          "csv-deviceIP":"4.4.4.65",
          "csv-host-name":"vedge-004",
          "//system/host-name":"vedge-004",
          "//system/system-ip":"4.4.4.65",
          "//system/site-id":"400",
          "csv-templateId":"e8a0f34c-93c4-47e4-841d-e679f89e0252",
          "selected":"true"
        }
        ],
        "isEdited":"false", 
        "isMasterEdited":"false"
      }
      ]
    }
attach_info = json.dumps(attach_info)
response = session.post(url=attach_url, data=attach_info, 
           headers={'Content-Type': 'application/json'}, verify=False)

Ok, so that was a pretty long post. Hopefully you made it through to the end and have a better understanding of what REST is and how you might use it. For these examples, I tested with an SDWAN lab running on cisco devnet. If you want to spin up your own lab to play around with, you can do that here.

I also want to bring attention to a git repository with some more robust code you can use to get started playing with REST API and Cisco SDWAN. What I’ve shown above is same basic explanations, so check out this repository to dig in further.


UPDATE 1/11/20: In vManage version 19.2+ a token is required to be sent in the headers with every request. I have added an update to this post at the bottom to demonstrate this.

In vManage 19.2 a change has been made that requires the use of a token in the headers for any API interaction to work. It’s simple to use, after running the request to log in you just need to request the token through a GET request and add an additional field in your header to be sent with each subsequent request. See the code below:

#First, authenticate as usual.
login_url = 'https://10.10.20.90:8443/j_security_check'
login_credentials = {'j_username':'admin', 'j_password':'admin'}

session = requests.session()

response = session.post(url=login_url, data=login_credentials, verify=False)

#make sure the response gives a 200 OK message, then request the token
token_url = 'https://10.10.20.90:8443/dataservice/client/token'

token = session.get(url=token_url)
token = token.text
#Create or add the token to your headers dictionary
headers = {'X-XSRF-TOKEN':token}

Pretty simple. If you are writing a script that could be used with version 19.2+ and older versions, you can write something to attempt to retrieve the token but just move on if it fails. Trying a GET request on the URL token will return a 404 on older versions of vManage, so you can just have you code move on if you get a 404. One other thing to note, it seems the format of some data returned has changed somewhere in 19.x. For example, if you are pulling policies or templates from an 18.4 vManage and then pushing them into a 19.x vManage you will get errors due to dictionary key changes between the versions. Either upgrade your source vManage to 19.1 before pulling templates, or temporarily downgrade the destination vmanage to the same version as the source. vManage automatically converts its existing configuration when upgrading versions.

Leave a Reply