Sunday, January 3, 2021

Cisco SD-WAN CLI application developed using the vManage REST API

 

Introduction

In this Learning Lab you will build your first Python application, using the vManage REST API and everything else you've learned in this module:

  • SD-WAN and the components of the SD-WAN solution from Cisco
  • Interacting with the vManage REST API via the Swagger documentation and Postman.
  • Building Python the efunctions login()get_request() and post_request() and the Pythonc class rest_api_lib.

You will build a custom CLI application that will perform the following tasks:

  • Interact with the vManage REST API
  • GET data about devices in the fabric, configuration templates, and which devices are associated with which templates.
  • Attach and detach configuration templates to and from devices.

Objectives

When you have completed this Learning Lab, you will be able to:

  • Build a Python-based CLI application that leverages the vManage REST API to get data, parse data, extract pertinent information, and display it to the user.

Prerequisites

To complete this lab you need:

Step 1: Useful Python libraries

In the previous Learning Lab, you built the class that performs the authentication and GET and POST API requests. In this step you will explore the Python libraries that you will use throughout the rest of this Learning Lab.

Requests

You used the requests library in the previous Learning Lab. requests was developed by Kenneth Reitz and you can find more details about it and the documentation at this link. There are several similar libraries that are available for making REST API calls, and if you prefer to use them instead, you may.

Command Line Interface Creation Kit

The next library that you will use extensively in this Learning Lab is tthe Command Line Interface Creation Kit, known as click. The library is described on its website as "a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. ... It aims to make the process of writing command line tools quick and fun while also preventing any frustration caused by the inability to implement an intended CLI API."

Tabulate

The tabulate library is used to "pretty-print" tabular data in Python. When you want to easily print small tables or have a readable presentation of mixed textual and numeric data, then you can use tabulate in your Python code. In this Learning Lab, you will use tabulate in your code to display to the user data that you gather from the vManage REST API. You can find more details about it at the following link.

Step 2: Import libraries and environment variables

In this step you will start building the CLI Python application. You will need an integrated development environment (IDE), such as Visual Studio Code.

Note: You can also open the sdwan.py file in the Getting Started GitHub repository if you want to review the completed code.

To build the application from the beginning, create a new Python file called sdwan.py in your IDE. You can copy and paste the snippets of code in the following steps and by the end of the Learning Lab your code should be similar to the sdwan.py file in the Getting Started GitHub repository.

We start by importing all the libraries that you will use in your Python code. Next, you define three environment variables called: SDWAN_IPSDWAN_USERNAME and SDWAN_PASSWORD.

There are several other ways to input data into your code, from passing arguments at the time you start the application to reading values from a file to connecting and extracting values from a database. The three environment variables are imported and stored in the Python code in variables with the same names. You will use them later to connect to the vManage instance represented with the SDWAN_IP IP address with the username SDWAN_USERNAME and password SDWAN_PASSWORD.

The application will then verify that importing the environment variables was successful. If any of the three values is missing, the user receives a message showing how to set the environment variables.

The code so far is similar to the following:

#! /usr/bin/env python

import requests
import sys
import json
import click
import os
import tabulate
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

SDWAN_IP = os.environ.get("SDWAN_IP")
SDWAN_USERNAME = os.environ.get("SDWAN_USERNAME")
SDWAN_PASSWORD = os.environ.get("SDWAN_PASSWORD")

if SDWAN_IP is None or SDWAN_USERNAME is None or SDWAN_PASSWORD is None:
    print("CISCO SDWAN details must be set via environment variables before running.")
    print("   export SDWAN_IP=10.10.20.90")
    print("   export SDWAN_USERNAME=admin")
    print("   export SDWAN_PASSWORD=C1sco12345")
    print("")
    exit("1")


Step 3: An instance of the rest_api_lib class

In the next part of the app code, you will include an instance of the rest_api_lib class and call the instance sdwanp.

The three variables for SDWAN_IPSDWAN_USERNAME, and SDWAN_PASSWORD are passed as inputs into the rest_api_lib class. An instance of this class is created and a requests session is created with the vManage instance. In this session, you can use the get_request() and post_request() methods to GET and POST data to the vManage REST API.

Note: The following code sample may be missing some underscore characters. Refer to the code in the Getting Started with Cisco SD-WAN GitHub repository.

class rest_api_lib:
    def __init__(self, vmanage_ip, username, password):
        self.vmanage_ip = vmanage_ip
        self.session = {}
        self.login(self.vmanage_ip, username, password)

    def login(self, vmanage_ip, username, password):
        """Login to vmanage"""
        base_url_str = 'https://%s:8443/'%vmanage_ip

        login_action = '/j_security_check'

        #Format data for loginForm
        login_data = {'j_username' : username, 'j_password' : password}

        #Url for posting login data
        login_url = base_url_str + login_action

        sess = requests.session()
        #If the vmanage has a certificate signed by a trusted authority change verify to True
        login_response = sess.post(url=login_url, data=login_data, verify=False)


        if b'<html>' in login_response.content:
            print ("Login Failed")
            sys.exit(0)

        self.session[vmanage_ip] = sess

                token_url = 'https://%s/'%vmanage_ip

        token_action = '/dataservice/client/token'

        token = sess.get(url=token_url)
        # print(token)
        headers = {'X-XSRF-TOKEN':token}

        if token.status_code != 200:
            if b'<html>' in token_url.content:
                print(token_url)
                print ("Login Token Failed")
                exit(0)
        else:
            print("Token Success")

        token = token.text


    def get_request(self, mount_point):
        """GET request"""
        url = "https://%s:8443/dataservice/%s"%(self.vmanage_ip, mount_point)
        #print url
        response = self.session[self.vmanage_ip].get(url, verify=False)
        data = response.content
        return data

    def post_request(self, mount_point, payload, headers={'Content-Type': 'application/json'}):
        """POST request"""
        url = "https://%s:8443/dataservice/%s"%(self.vmanage_ip, mount_point)
        payload = json.dumps(payload)
        print (payload)

        response = self.session[self.vmanage_ip].post(url=url, data=payload, headers=headers, verify=False)
        data = response.json()
        return data

sdwanp = rest_api_lib(SDWAN_IP, SDWAN_USERNAME, SDWAN_PASSWORD)

Step 4: CLI with click

You will now use click to create the CLI component of the application. In click, CLI commands can be attached to other commands of type Group so that arbitrary nesting of scripts and functions is possible. You group all the commands that the CLI will implement in the application under one group as shown in the following example:

@click.group()
def cli():
    """Command line tool for deploying templates to CISCO SDWAN.
    """
    pass

@click.command()
def device_list():
    pass

@click.command()
def template_list():
    pass

@click.command()
def attached_devices():
    pass

@click.command()
def attach():
    pass

@click.command()
def detach():
    pass

cli.add_command(attach)
cli.add_command(detach)
cli.add_command(device_list)
cli.add_command(attached_devices)
cli.add_command(template_list)

if __name__ == "__main__":
    cli()

Five CLI commands are grouped under the cli Groupdevice-listtemplate-listattached-devicesattach and detach. The commands correspond to what you want the application to do from the beginning:

  • Get a list of all the devices in the SD-WAN fabric (device-list).
  • Get a list of all the configuration templates on the vManage instance (template-list).
  • Get a list of all the devices attached to a specific template (attached-devices).
  • Attach a configuration template to a device (attach).
  • Detach a device from a configuration template (detach).

In the snippet of code above, the functions don't have any code or functionality behind them at this point. You can see that each one of them is just using pass. In the next part of this Learning Lab you will start to populate each function with functional code to accomplish what you have planned.

Step 5: List of devices and templates

You will now populate the device_list and template_list functions with working code. You should remember that the REST API endpoint and resource that you accessed to get information about all the devices in the fabric was https://{{vmanage}}:{{port}}/dataservice/device. You already have a session established with the vManage server in the instance of the rest_api_lib class that you called sdwanp. You will use the get_request method of this object to get a list of all the devices in the fabric and store the JSON data that is returned by the API in the response variable.

Next you extract just the [data] portion of the JSON and store it in a variable called items. The items variable at this point contains all the devices in the fabric and a lot of additional data about each of them as you've seen in previous Learning Labs. You can see the contents of the items variable when you use the Sandbox vManage instance below:


Click to HERE to expand contents of the items variable

You will now iterate over each item in the items with a for loop and extract data for the hostnamedevice-typeuuidsystem-ipsite-idversion and device-model of each device. After this you use tabulate to display the data you extracted to the user.

The Python code for the device-list() function and CLI option with the same name should be similar to the following:

@click.command()
def device_list():
    """Retrieve and return network devices list.
        Returns information about each device that is part of the fabric.
        Example command:
            ./sdwan.py device_list
    """
    click.secho("Retrieving the devices.")

    url = base_url + "/device"

    response = requests.get(url=url, headers=header,verify=False)
    if response.status_code == 200:
        items = response.json()['data']
    else:
        print("Failed to get list of devices " + str(response.text))
        exit()

    headers = ["Host-Name", "Device Type", "Device ID", "System IP", "Site ID", "Version", "Device Model"]
    table = list()

    for item in items:
        tr = [item['host-name'], item['device-type'], item['uuid'], item['system-ip'], item['site-id'], item['version'], item['device-model']]
        table.append(tr)
    try:
        click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
    except UnicodeEncodeError:
        click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))

Whether you have been building sdwan.py from scratch throught this lab or you are viewing the file from the GitHub repository, you can now run the application and try to get a list of all the devices in the SD-WAN fabric.

Preparing to run the application

Before you run the application, in your terminal, add the environment variables for SDWAN_IPSDWAN_USERNAME, and SDWAN_PASSWORD:

  export vManage_IP=10.10.20.90
  export vManage_PORT=8443
  export vManage_USERNAME=admin
  export vManage_PASSWORD=C1sco12345

Retrieving a device list

Run the application and pass in the device_list parameter:

./sdwan.py device-list

You should get a list of all the devices in the DevNet SD-WAN always-on sandbox:

(venv) root$ python sdwan.py device_list
Retrieving the devices.
╒═══════════════╤═══════════════╤══════════════════════════════════════════╤═════════════╤═══════════╤════════════════╤═════════════════╕
│ Host-Name     │ Device Type   │ Device ID                                │ System IP   │   Site ID │ Version        │ Device Model    │
╞═══════════════╪═══════════════╪══════════════════════════════════════════╪═════════════╪═══════════╪════════════════╪═════════════════╡
│ vmanage       │ vmanage       │ 81ac6722-a226-4411-9d5d-45c0ca7d567b     │ 10.10.1.110119.2.2         │ vmanage         │
├───────────────┼───────────────┼──────────────────────────────────────────┼─────────────┼───────────┼────────────────┼─────────────────┤
│ vsmart        │ vsmart        │ f7b49da3-383e-4cd5-abc1-c8e97d345a9f     │ 10.10.1.510119.2.2         │ vsmart          │
├───────────────┼───────────────┼──────────────────────────────────────────┼─────────────┼───────────┼────────────────┼─────────────────┤
│ vbond         │ vbond         │ ed0863cb-83e7-496c-b118-068e2371b13c     │ 10.10.1.310119.2.2         │ vedge-cloud     │
├───────────────┼───────────────┼──────────────────────────────────────────┼─────────────┼───────────┼────────────────┼─────────────────┤
│ dc-cedge01    │ vedge         │ CSR-61CD2335-4775-650F-8538-4EC7BDFFD04C │ 10.10.1.1110016.12.3.0.3752 │ vedge-CSR-1000v │
├───────────────┼───────────────┼──────────────────────────────────────────┼─────────────┼───────────┼────────────────┼─────────────────┤
│ site1-cedge01 │ vedge         │ CSR-807E37A3-537A-07BA-BD71-8FB76DE9DC38 │ 10.10.1.13100116.12.3.0.3752 │ vedge-CSR-1000v │
├───────────────┼───────────────┼──────────────────────────────────────────┼─────────────┼───────────┼────────────────┼─────────────────┤
│ site2-cedge01 │ vedge         │ CSR-DE6DAB19-BA1A-E543-959C-FD117F4A6205 │ 10.10.1.15100216.12.3.0.3752 │ vedge-CSR-1000v │
├───────────────┼───────────────┼──────────────────────────────────────────┼─────────────┼───────────┼────────────────┼─────────────────┤
│ site3-vedge01 │ vedge         │ 0140a336-5fd5-9829-10d2-f6ba0b177efd     │ 10.10.1.17100319.2.2         │ vedge-cloud     │
╘═══════════════╧═══════════════╧══════════════════════════════════════════╧═════════════╧═══════════╧════════════════╧═════════════════╛

Retrieving a template list

The template_list() function is very similar to device_list(). The main difference is the REST API endpoint that you are accessing, which is https://{{vmanage}}:{{port}}/dataservice/template/device. The data you extract from the returned JSON is about the Template NameDevice TypeTemplate IDAttached devices and Template version. You use tabulate again to display the data to the user. The code for template_list() is as follows:

@click.command()
def template_list():
    """Retrieve and return templates list.
        Returns the templates available on the vManage instance.
        Example command:
            ./sdwan.py template_list
    """
    click.secho("Retrieving the templates available.")

    url = base_url + "/template/device"

    response = requests.get(url=url, headers=header,verify=False)
    if response.status_code == 200:
        items = response.json()['data']
    else:
        print("Failed to get list of templates")
        exit()

    headers = ["Template Name", "Device Type", "Template ID", "Attached devices", "Template version"]
    table = list()

    for item in items:
        tr = [item['templateName'], item['deviceType'], item['templateId'], item['devicesAttached'], item['templateAttached']]
        table.append(tr)
    try:
        click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
    except UnicodeEncodeError:
        click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))

If you run the application after adding the template-list code and pass in the template-list parameter:

./sdwan.py template-list

The output should be similar to the following:

(venv) root$ python sdwan.py template_list
Retrieving the templates available.
╒═════════════════════════════╤═════════════════╤══════════════════════════════════════╤════════════════════╤════════════════════╕
│ Template Name               │ Device Type     │ Template ID                          │   Attached devices │   Template version │
╞═════════════════════════════╪═════════════════╪══════════════════════════════════════╪════════════════════╪════════════════════╡
│ vSmart_Template             │ vsmart          │ 90f26d2d-8136-4414-84de-4e8df52374e6 │                  19 │
├─────────────────────────────┼─────────────────┼──────────────────────────────────────┼────────────────────┼────────────────────┤
│ Site_1_and_2_cEdge_Template │ vedge-CSR-1000v │ c566d38e-2219-4764-a714-4abeeab607dc │                  213 │
├─────────────────────────────┼─────────────────┼──────────────────────────────────────┼────────────────────┼────────────────────┤
│ Site_3_vEdge_Template       │ vedge-cloud     │ db4c997a-7212-4ec1-906e-ed2b86c3f42f │                  113 │
├─────────────────────────────┼─────────────────┼──────────────────────────────────────┼────────────────────┼────────────────────┤
│ DC_cEdge_Template           │ vedge-CSR-1000v │ 24d4be69-8038-48a3-b546-c6df199b6e29 │                  114 │
╘═════════════════════════════╧═════════════════╧══════════════════════════════════════╧════════════════════╧════════════════════╛

In the next step you will have a look at the attached_devices() function and how to pass in options to the CLI application you are building.

Step 6: Which devices are attached to which templates?

In this step, you will get a list of all devices attached to a configuration template. Unlike the device_list and template_list functions, which did not take any parameters, the attached_devices function needs as input the template ID in order to return the list of devices attached to it. In order to pass in parameters to the function, the click library provides @click.option(). You can specify as many options as you want and also provide a description for what each option means.

You will need to build the URL that specifies the resource in the vManage REST API that will return the devices attached to the template. The REST API documentation shows that the resource for the GET call to is https://{{vmanage}}:{{port}}/dataservice/template/device/config/attached/templateId. Using the vManage session that you opened in the sdwanp instance of the rest_api_lib class, you get_request on this resource and store the returned data in a variable called response.

From the data that is retrieved by the function, you will extract only information pertaining to the hostnamedevice IPsite IDhost ID and host type and use tabulate to display the data to the user. The code for the attached_devices function should look similar to the one below:

@click.command()
@click.option("--template", help="Name of the template you wish to retrieve information for")
def attached_devices(template):
    """Retrieve and return devices associated to a template.
        Example command:
            ./sdwan.py attached_devices --template db4c997a-7212-4ec1-906e-ed2b86c3f42f
    """

    url = base_url + "/template/device/config/attached/{0}".format(template)

    response = requests.get(url=url, headers=header,verify=False)
    if response.status_code == 200:
        items = response.json()['data']
    else:
        print("Failed to get template details")
        exit()

    headers = ["Host Name", "Device IP", "Site ID", "Host ID", "Host Type"]
    table = list()

    for item in items:
        tr = [item['host-name'], item['deviceIP'], item['site-id'], item['uuid'], item['personality']]
        table.append(tr)
    try:
        click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
    except UnicodeEncodeError:
        click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))

Let's run the application and see the output of the new function. Pass in the attached_devices parameter together with the template ID that you've obtained in the previous step:

 python sdwan.py attached-devices --template b7ccd31e-0e59-417d-8f52-72fc9106060a`

The output should be similar to the following if there is at least one device attached to the template:

(venv) root$ python sdwan.py attached_devices --template db4c997a-7212-4ec1-906e-ed2b86c3f42f
╒═══════════════╤═════════════╤═══════════╤══════════════════════════════════════╤═════════════╕
│ Host Name     │ Device IP   │   Site ID │ Host ID                              │ Host Type   │
╞═══════════════╪═════════════╪═══════════╪══════════════════════════════════════╪═════════════╡
│ site3-vedge01 │ 10.10.1.1710030140a336-5fd5-9829-10d2-f6ba0b177efd │ vedge       │
╘═══════════════╧═════════════╧═══════════╧══════════════════════════════════════╧═════════════╛

If your output doesn't have any devices, it just means there are no devices attached to this template. In the next step you'll see how you can attach a template to a specific device.

Step 7: Attaching devices to a configuration template

In this step, you will learn how to attach a configuration template that is available in vManage to devices in the SD-WAN fabric.

Login into the DevNet Always on sandbox vManage GUI at this link with username admin and password C1sco12345. On the left hand side in the expandable menu navigate to Configuration -> Templates and have a look at the Site_3_vEdge_Template and try to apply it to one of the vEdge devices. In this part of the Learning Lab you will do the exact same steps but using the vManage REST API and Python code for the attach() function.

The Site_3_vEdge_Template has several parameters that can be changed and applied at the time of template attachment. These parameters are for the hostname, the IPv4 address on the loopback interface, the IPv4 address on the Gigabit0/0 interface, the system IP and the site ID. All these parameters will be passed into the attach function as @click.options. The template ID as well as the target device ID that the template will be attached to will also be passed in asoptions.

Since you are making configuration changes with this function, the REST API call will be a POST and the endpoint that the call will be made to is https://{{vmanage}}:{{port}}/dataservice/template/device/config/attachfeature. For information about finding the endpoint for attaching templates to devices, refer to the vManage REST API documentation.

In the documentation you can also see that the payload is mandatory and should be similar to the following:

{
  "deviceTemplateList":[
  {
    "templateId":"41f6a440-c5cc-4cc6-9ca1-af18e332a781",
    "device":[
    {
      "csv-status":"complete",
      "csv-deviceId":"5e5f45e7-3062-44b2-b6f6-40c682149e05",
      "csv-deviceIP":"172.16.255.11",
      "csv-host-name":"vm1",
      "//system/host-name":"vm1",
      "//system/system-ip":"172.16.255.11",
      "//system/site-id":"100",
      "csv-templateId":"41f6a440-c5cc-4cc6-9ca1-af18e332a781",
      "selected":"true"
    }
    ],
    "isEdited":false,
    "isMasterEdited":false
  }
  ]
}

Based on the VEDGE_BASIC_TEMPLATE configuration template from the DevNet Sandbox vManage instance, edit the payload with the parameter values you take as input from the user via the @click.option. Once the payload is built, thepost_requestmethod of thesdwanpinstance is invoked and the response of this call is displayed to the user. The Python code for theattach function should look like the following example:

@click.command()
@click.option("--template", help="Name of the template to deploy")
@click.option("--variables", help="Device Template variable values yaml file")
#@click.argument("parameters", nargs=-1)
def attach(template, variables):
    """Attach a template with Cisco SDWAN.
        Provide all template parameters and their values as arguments.
        Example command:
          ./sdwan.py attach --template template-id --variables Site-3-vEdge-Variables.yaml
    """
    click.secho("Attempting to attach template.")

    with open(variables) as f:
        config = yaml.safe_load(f.read())

    system_ip = config.get("system_ip")
    host_name = config.get("host_name")
    template_id = template

    template_variables = {
                            "csv-status":"complete",
                            "csv-deviceId": config.get("device_id"),
                            "csv-deviceIP": system_ip,
                            "csv-host-name": host_name,
                            "//system/host-name": host_name,
                            "//system/system-ip": system_ip,
                            "//system/site-id": config.get("site_id"),
                            "/1/vpn_1_if_name/interface/if-name": config.get("vpn_1_if_name"),
                            "/1/vpn_1_if_name/interface/ip/address": config.get("vpn_1_if_ipv4_address"),
                            "/512/vpn-instance/ip/route/0.0.0.0/0/next-hop/vpn_512_next_hop_ip_address/address": config.get("vpn_512_next_hop_ip_address"),
                            "/512/vpn_512_if_name/interface/if-name": config.get("vpn_512_if_name"),
                            "/512/vpn_512_if_name/interface/ip/address": config.get("vpn_512_if_ipv4_address"),
                            "/0/vpn-instance/ip/route/0.0.0.0/0/next-hop/mpls_next_hop/address": config.get("mpls_next_hop"),
                            "/0/vpn-instance/ip/route/0.0.0.0/0/next-hop/public_internet_next_hop/address": config.get("public_internet_next_hop"),
                            "/0/vpn_public_internet_interface/interface/if-name": config.get("vpn_public_internet_interface"),
                            "/0/vpn_public_internet_interface/interface/ip/address": config.get("vpn_public_interface_if_ipv4_address"),
                            "/0/vpn_mpls_interface/interface/if-name": config.get("vpn_mpls_interface"),
                            "/0/vpn_mpls_interface/interface/ip/address": config.get("vpn_mpls_if_ipv4_address"),
                            "//system/gps-location/latitude": config.get("latitude"),
                            "//system/gps-location/longitude": config.get("longitude")
                         }

You can now try out the function just defined and run the application with the attach option. In this case you will need to pass in all the required parameters, with a command similar to the following:

`python sdwan.py attach --template db4c997a-7212-4ec1-906e-ed2b86c3f42f --variables Site-3-vEdge-Variables.yaml


The output should look similar to the following:

```JSON
Attempting to attach template.
Attached Site 3 vEdge Template

You can verify that the template was successfully applied by running the attached_devices option once more and passing in the template ID.

Step 8: Detaching template from a device

You've seen how to attach a configuration template to a device. In this step you will build the function to detach a configuration template from a device. Similar to the attach function, the detach function will perform configuration changes to a specific device so the REST API call will be a POST and the payload for it can be found in the vManage REST API documentation in the Detach Device Templates section.

The detach payload requires only two parameters: the device Id and the system IP of the device from which the template will be detached. The detach function takes the two parameters as input from the user and passes them into the code via the @click.option. Once the payload is built, thepost_requestmethod is invoked on thehttps://{{vmanage}}:{{port}}/dataservice/template/config/device/mode/cli resource. The response of the REST API call is then displayed to the user.

You can also replicate detaching a configuration template in the vManage GUI, in a method similar to attaching a template. The Python code that will accomplish the exact same thing should be similar to the following:

@click.command()
@click.option("--target", help="ID of the device to detach")
@click.option("--sysip", help="System IP of the system to detach")
def detach(target, sysip):
    """Detach a template with Cisco SDWAN.
        Provide all template parameters and their values as arguments.
        Example command:
          ./sdwan.py detach --target 0140a336-5fd5-9829-10d2-f6ba0b177efd --sysip 10.10.1.17
    """
    click.secho("Attempting to detach template.")

    payload = {
        "deviceType":"vedge",
        "devices":[  
            {
                "deviceId":str(target),
                "deviceIP":str(sysip)
            }
        ]
    }

    url = base_url + "/template/config/device/mode/cli"

    response = requests.post(url=url, data=json.dumps(payload), headers=header, verify=False)
    if response.status_code == 200:
        id = response.json()["id"]
        url = base_url + "/device/action/status/" + str(id)
        while(1):
            status_res = requests.get(url,headers=header,verify=False)
            if status_res.status_code == 200:
                push_status = status_res.json()
                if push_status['summary']['status'] == "done":
                    if 'Success' in push_status['summary']['count']:
                        print("Changed configuration mode to CLI")
                    elif 'Failure' in push_status['summary']['count']:
                        print("Failed to change configuration mode to CLI")
                        exit()
                    break
    else:
        print("Failed to detach template with error " + response.text)
        exit()

Let's verify that the application is working correctly and try to detach the template you've associated to vEdge03 in the previous step. In order to detach the template you need to pass in the target ID which in this case is the device ID of vEdge03 and the system IP of vEdge03. The application should be run like so: python sdwan.py detach --target 0140a336-5fd5-9829-10d2-f6ba0b177efd --sysip 10.10.1.17 and the output should be similar to the one below:

(venv) root$ python sdwan.py detach --target 0140a336-5fd5-9829-10d2-f6ba0b177efd --sysip 10.10.1.17
Attempting to detach template.
Changed configuration mode to CLI

You can once again check that the template was successfully detached by running the application with the attached_devices option.

Step 9: The final version of the application

You will now combine all the components that you've built so far into one file and one application.

You've come a long way in this module, from the first Learning Lab in which you learned about what SD-WAN is, what are the components of the Cisco SD-WAN solution and how they interact together, how does the vManage REST API look and how you can interact with it via the swagger page to the second Learning Lab in which you've had a look at POSTMAN and how to make REST API calls with it.

You've used git to clone the repository with all the functions for logging into vManage and sending GET and POST REST API calls and you built the rest_api_lib class.

And you've taken the Python class and put it to great use in building your first CLI application for Cisco SD-WAN. You've had a look at the requests, click and tabulate Python libraries and then you built the functions to get a list of all the devices in the fabric, all the templates, all the devices attached to a specific template and attaching and detaching templates to devices.

It is now finally time to bring all the components together and wrap up the application. If you have followed along and built the snippets of code in your own IDE or you have the sdwan.py file from the github.com repo opened, your final Python application code should be similar to the following:

Note: The following code sample may be missing some underscore characters. Refer to the code in the Getting Started with Cisco SD-WAN GitHub repository.


Click HERE to expand the sdwan.py file

You can follow the instructions in the README.md file in the GitHub repository and run this application in a Python3 virtual environment with all the Python libraries mentioned in the requirements.txt installed and use the DevNet Sandbox always-on vManage instance. you should be able to run the application and interact with the REST API and run through all the functions you've created.