JSON
NX-API CLI
  • Introduction
  • Development Environment
  • NX-API Overview
  • NX-API Python
  • Ansible NXOS
  • pyATS
  • NetDevOps
  • Bonus: Postman
  • Bonus: YANG

Python Requests using NX-API CLI JSON

This section uses NX-API CLI JSON and Jinja templating to dynamically generate the payloads to configure Underlay Ethernet interfaces (description, IP address, etc) via a Python script. You will define the script to handle two inputs, a data file and a Jinja template file. This is where you will use the data file defined in the previous section. After the code, you will define the Jinja template file that will be used to generate the interfaces configuration for each switch in your fabric. In this manner, your code will be reusable for Jinja templates that you create in the future as supported by your data file.




Step 1 - Create a New Python File in VSCode

Open a new file for creating a Python NX-API CLI JSON script:

    
        code -r /home/cisco/Documents/nxapilab/scripts/nxapi_cli_json.py
    

Step 2 - Python Import Statements

In your VSCode, a new, unsaved file should now be open called nxapi_cli_json.py.

In your new Python file, you need to import a few packages. You will make use of some packages natively available in Python to create a Python script that handles command line arguments; these are the import argparse, import pathlib, and import sys packages. You then need to import jinja2 to be able to use Jinja templating and import yaml to be able to import (or read) YAML files.

In the previous lab sections you have mainly been working with JSON for your requests. JSON directly maps to the Python dictionary data structure, but to be able to perform the proper encoding and decoding import json. You need to be able to perform similar requests that you have been doing from the NX-API Sandbox. To do so, another package that is required is to import requests.

Either copy or type the import statements below and place them at the top of your Python file.

    
        import argparse
        import pathlib
        import sys
        import jinja2
        import yaml
        import json
        import requests


        requests.packages.urllib3.disable_warnings()


        

With the proper packages imported, you are going to write Python functions that can be used to programmatically generate the NXAPI CLI JSON payloads and perform the POST requests to the switch(s) NXAPI CLI JSON REST interface.

It is worth noting that the NX-API Sandbox will convert the request format into a very generic Python script for both NX-API CLI and NX-API REST. However, you are going to be more advanced and construct your Python to be efficient and reusable. The functions you will build below can be evolved into more advanced Python concepts such as Classes and Methods.


Step 3 - Python Function Definition to Build NX-API CLI JSON Payloads

The function definition below is used to dynamically generate your JSON configuration payload and adheres to the NXAPI CLI JSON interface's payload structure. The function definition, nxapi_payload_builder, takes a single argument, commands, which is the raw, line for line cli commands that you know today and breaks them apart line by line to generate a semi-colon seperated string of commands that is used as the input in the payload. The cli commands are generated from a Jinja template file that you will define later in this section.

Either copy or type the code below and place it next into your Python file.

    
        def nxapi_payload_builder(commands):
            cmds = [command.strip() for command in commands.splitlines()]

            if isinstance(cmds, list):
                cmds = " ; ".join(cmds)

            payload = {
                "ins_api": {
                    "version": "1.0",
                    "type": "cli_conf",
                    "chunk": "0",
                    "sid": "1",
                    "input": cmds,
                    "output_format": "json",
                    "rollback": "rollback-on-error"
                }
            }

            return payload


        


Step 4 - Python Function Definition to Perform the POST Operation to the NX-API Interface

After nxapi_payload_builder, the function definition, nxapi_post, will be used to perform the actual POST request to the switch(s) REST interface. This function exclusively relies on the Request library that you imported earlier. Notice below the code syntax, requests.post(). The Request library supports all the CRUD (Create, Update, Delete) operations, but here you only require POST. Whenever you make a call using requests, you are creating a Request object which sends a request/query off to the switch(s).

Requests can natively have a Python dictionary passed to it and in turn will handle the processing to encode the payload to JSON using the json= argument. In your case that looks like json=payload where payload is the dictionary returned from your previously defined functions. Also, a Response object is generated when the Request object gets a reply. The response is stored in the response variable and can be used for various checks. One such check is using the response.ok attribute to check for a successful request operation such as in the code below; if response.ok, where anything less than a 400 error code is a successful HTML code.

Finally, all this is wrapped in a try/except code block. What this simply does is say, try to perform this operation, i.e. the POST operation, but if there is a problem with the code, then raise an exception by terminating the program and providing the exception. Error handling is a vast topic and this is only a simple example.

Either copy or type the code below and place it next into your Python file.

    
        def nxapi_post(url, headers, payload, username, password):
            try:
                print(f"Payload sent to {url}:")
                print(payload)
                response = requests.post(url, json=payload, headers=headers, auth=(username, password), verify=False)
                print(f"Response from {url}:")
                if response.ok:
                    print(json.loads(response.text))
                else:
                    print("Error! " + str(response.status_code))
                print()
            except requests.exceptions.RequestException as e:
                raise SystemExit(e)


        


Step 5 - Python Main Function

After you have successfully created your function definitions, it's time to create the main method, that is the entry point for your Python script. It will perform the function calls you just defined based on your command line arguments. The first part of the code below will use argparse to define these command line arguments with subsequent if condition checks to ensure the proper file types are being used. If the proper file types are used, the code will then open the data file and load the contents into a Python dictionary using the yaml.safe_load() function. Additionally, the code will open the Jinja template file, which is to be defined in the next step, and load the contents into a variable called template.

The authentication credential variables for the switches are also defined by referencing the dictionary keys and values from the data file. The REST header is also defined as a dictionary with the key/value pair of {'content-type': 'application/json'} since you are working with the NXAPI CLI JSON interface.

The last section of the code, is how you iterate over the devices defined in your data file that make up your staging fabric. This is done by again referencing the dictionary keys and values from the data file down to the devices key. Then using the .keys() dictionary method to iterate over the dictionary keys, which are the switch names. Using the data for each switch device, the code will then render the Jinja template using the template.render() function. This renders the raw CLI that you know today using the Jinja template that is to be defined in the next step. The NXAPI CLI JSON payload is then generated using the nxapi_payload_builder function you defined earlier; again this function will take the raw CLI and generate the semi-colon seperated string of commands that is used as the input in the payload. Lastly, the switch(s) REST interface is called using the nxapi_post function you also defined earlier, where this function takes the URL, header, payload, and authentication credentials as arguments to configure the switch via the NXAPI CLI JSON interface.

Either copy or type the code below and place it next into your Python file.

    
        def main():
            parser = argparse.ArgumentParser()

            parser.add_argument("-d", "--data", type=pathlib.Path)
            parser.add_argument("-t", "--template", type=pathlib.Path)
            parser.add_argument("--version", action="version", version="%(prog)s 0.1.0")

            args = parser.parse_args()

            if args.data.suffix in (".yaml", ".yml"):
                with open(args.data) as f:
                    data = yaml.safe_load(f)
            else:
                sys.exit(f"{args.data.suffix} is not a supported file format.")

            if args.template.suffix != ".j2":
                sys.exit(f"{args.template.suffix} is not a supported template format.")

            with open(args.template) as f:
                template = jinja2.Template(f.read())

            username = data['fabric']['defaults']['credentials']['username']
            password = data['fabric']['defaults']['credentials']['password']
            headers = {'content-type': 'application/json'}

            for device in data['fabric']['devices'].keys():
                commands = template.render(data['fabric']['devices'][device])
                payload = nxapi_payload_builder(commands)
                url = f"https://{data['fabric']['devices'][device]['mgmt']}/ins"
                nxapi_post(url, headers, payload, username, password)


        if __name__ == '__main__':
            main()
        


Step 6 - Interface Jinja Template

With your Python script defined, it's time to create the Jinja template file that will be used to generate the interfaces configuration for each switch in your fabric. The Jinja template will be driven by the data file you defined in the first section.

The way that this interfaces Jinja template is defined, is that it will use a for loop to iterate over the interfaces defined in the data file under each switch device. Within the iteration, the interface decription is defined, followed by an if condition check to determine the port type. For this lab, our data file defines and uses only one of the port types defined in the if condition check, routed. As you can see in the template, if the port type is routed, then the interface be configured as a routed port with an IP address. The other port types defined in the data file are trunk and access and are available for future use cases, thus making the Jinja template reusable. Lastly, regardless of the port type, the interface is configured to be no shutdown.

    
        touch /home/cisco/Documents/nxapilab/scripts/templates/interfaces.j2
        cat <<EOF >> /home/cisco/Documents/nxapilab/scripts/templates/interfaces.j2
        {% for interface in interfaces -%}
        interface {{ interface['name'] }}
        description {{ interface['description'] }}
        {% if interface['port_type'] == "routed" -%}
        no switchport
        ip address {{ interface['ip_address'] }}/{{ interface['mask'] }}
        {% elif interface['port_type'] == "trunk" -%}
        switchport mode trunk
        switchport mode trunk allowed vlan {{ interface['allowed_vlan'] }}
        {% else -%}
        switchport mode access
        switchport access vlan {{ interface['vlan'] }}
        {% endif -%}
        no shutdown
        {% endfor %}
        EOF
        


Step 7 - Execute Python Script

Upon successfully constructing the Python code above, save your nxapi_cli_json.py file using Ctrl+s on the Windows keyboard or by clicking File then Save.

Warning

Be sure to save your file! Not saving will result in your code not executing.


You will see a white circle to the right of your file name if any unsaved code is found.

Return to the VSCode Terminal command line to execute your code using:

    
        python nxapi_cli_json.py --data=data.yaml --template=templates/interfaces.j2
    

Printed below is the code execution on the switches in your staging fabric; you can see the JSON payload sent to the switch(s) and the response from the switch(s):

Make note of the input key in the payload and the semi-colon seperated string of commands that were generated from the Jinja template.

    Payload sent to https://10.15.1.11/ins:
    {'ins_api': {'version': '1.0', 'type': 'cli_conf', 'chunk': '0', 'sid': '1', 'input': 'interface Ethernet1/1 ; description To Leaf1 Eth1/10 ; no switchport ; ip address 10.1.1.0/31 ; no shutdown ; interface Ethernet1/2 ; description To Leaf2 Eth1/10 ; no switchport ; ip address 10.1.1.2/31 ; no shutdown', 'output_format': 'json', 'rollback': 'rollback-on-error'}}
    Response from https://10.15.1.11/ins:
    {'ins_api': {'version': '1.0', 'sid': 'eoc', 'type': 'cli_conf', 'outputs': {'output': [{'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}]}}}
    
    Payload sent to https://10.15.1.12/ins:
    {'ins_api': {'version': '1.0', 'type': 'cli_conf', 'chunk': '0', 'sid': '1', 'input': 'interface Ethernet1/10 ; description To Spine1 Eth1/1 ; no switchport ; ip address 10.1.1.1/31 ; no shutdown', 'output_format': 'json', 'rollback': 'rollback-on-error'}}
    Response from https://10.15.1.12/ins:
    {'ins_api': {'version': '1.0', 'sid': 'eoc', 'type': 'cli_conf', 'outputs': {'output': [{'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}]}}}
    
    Payload sent to https://10.15.1.13/ins:
    {'ins_api': {'version': '1.0', 'type': 'cli_conf', 'chunk': '0', 'sid': '1', 'input': 'interface Ethernet1/10 ; description To Spine1 Eth1/2 ; no switchport ; ip address 10.1.1.3/31 ; no shutdown', 'output_format': 'json', 'rollback': 'rollback-on-error'}}
    Response from https://10.15.1.13/ins:
    {'ins_api': {'version': '1.0', 'sid': 'eoc', 'type': 'cli_conf', 'outputs': {'output': [{'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}, {'code': '200', 'msg': 'Success', 'body': {}}]}}}

Continue to next section for using NX-API CLI JSON-RPC in a similar Python script to perform OSPF configuration based on your data file for switches in your staging fabric.