HTTP calls using the Python requests library

Within this post, I’ll dive deeper into the python code example from the Cisco NX-API on Nexus 5500 post. We will have a closer look at the HTTP/HTTPs calls using the python requests library and on the interaction with HTTP based interfaces.

This post is based on my first post about the Cisco NX-API on Nexus 5500 and I recommend reading this post first to get a full understanding of this topic.

Requests: HTTP for Humans

I already explained the basics of HTTP within the context of REST APIs in my short practical introduction to REST APIs. Later on, we saw how to interact with Cisco Nexus Switches using the NX-API, which is also HTTP/HTTPs based. This post will explain the use of the requests library in more detail, based on the interface description cleaner, which was the code example for the post about the Cisco NX-API.

The requests library was already part of the dependencies listed in Ansible playbook that is used by Vagrant to create a complete isolated environment. If you like to install the requests library on your local system, you can simply do this using python pip:

$ pip install requests

You can find the full documentation for this library at the official homepage at Requests: HTTP for Humans. That all what we need, now lets have a closer look at the “interface description cleaner” script.

Interact with the Cisco NX-API using the requests library

Within the code example, I wrote some functions that are used to interact with the Cisco NX-OS Switch using the Cisco NX-API. As a short reminder: The Cisco NX-API is simply a HTTP/HTTPs interface to execute commands on the device and to get parsed data back.

The function, that we use to interact with the Cisco NX-API starts with the following two lines of code:

def nxapi_call(hostname, payload, username, password, content_type="json"):
    headers={'content-type':'application/%s' % content_type}
    response = requests.post("https://%s:%s/ins" % (hostname, HTTPS_SERVER_PORT),
                             auth=(username, password),
                             headers=headers,
                             data=json.dumps(payload),
                             verify=False,
                             timeout=4)
    # [...]

On line 85-90 you see how to do a HTTP POST operation to a certain URL using requests. Within the example, we use the URL that is specified by the Cisco NX-API (https://hostname:port/ins). This function call has the following parameters:

  • auth – The authentication information for the HTTP operation, for the Cisco NX-API we use basic HTTP authentication with a username and password
  • headers – A python dictionary with the HTTP header values that should be in the request, for the Cisco NX-API we use this to specify the content-type, which is set to JavaScript Object Notation (JSON) by default
  • data – The payload of the POST request as JSON formatted string, I’ll explain the required format for the Cisco NX-API in more detail below
  • verify (optional) – The parameter is set to False to disable the verification of the HTTPs certificates (required when using HTTPs with self-signed certificates)
  • timeout – timeout value in seconds

This function provides the foundation for the Cisco NX-API calls within our example. As you can see, we heavily use the python dictionaries along with the JSON data format in this case. For a more detailed explanation of this part, just have a look on my python dictionaries and JSON crash course post.

The payload parameter is more difficult to describe, because the format depends on the type of request (cli show etc.). For this reason, I defined two additional functions that use the baisc nxapi_call(): the function nxapi_cli_show(), which provides the ability to execute a show command using the NX-API and the nxapi_cli_conf() function to execute some configuration commands. These functions build the required payload dictionary and then call the base nxapi_call() function.

The nxapi_cli_show() function looks similar to the following code example:

def nxapi_cli_show(show_command, hostname, username, password):
    payload = [
        {
            "jsonrpc": "2.0",
            "method": "cli",
            "params": {
                "cmd": show_command,
                "version": 1.2
            },
            "id": 1
        }
    ]
    return nxapi_call(hostname, payload, username, password, "json-rpc")

This dictionary specifies the show command that should be executed on the device. In this case, we use the JSON-RPC (remote procedure call) content-type to execute it. The JSON-RPC is a very simple specification which describes how to execute a method on a remote server using JSON.

The nxapi_cli_conf() function looks very similar to the show function, but we use just a JSON content-type of the request, as the following code example describes.

def nxapi_cli_conf(commands, hostname, username, password):
    # convert the given configuration commands to a format which can be used within the Cisco NX-API and verify
    # that the configuration script does not end with the termination sign (lead to an error in the last command)
    commands = commands.replace("\n"," ; ")
    if commands.endswith(" ; "):
        commands = commands[:-3]

    payload = {
        "ins_api": {
            "version": "1.2",
            "type": "cli_conf",
            "chunk": "0",               # do not chunk results
            "sid": "1",
            "input": commands,
            "output_format": "json"
        }
    }
    return nxapi_call(hostname, payload, username, password, "json")

You need to convert the string with the CLI commands to a single line, separated by a semicolon, to get it working. The Cisco NX-API allows also a chunking of the response, when executing large commands, but for the use case from my example, this was not necessary.

That’s all for the payload option within the requests function call. After the HTTP POST operation, we perform some error checking operations as you can see in the full python code of the example:

def nxapi_call(hostname, payload, username, password, content_type="json"):
    headers={'content-type':'application/%s' % content_type}
    response = requests.post("https://%s:%s/ins" % (hostname, HTTPS_SERVER_PORT),
                             auth=(username, password),
                             headers=headers,
                             data=json.dumps(payload),
                             verify=False,
                             timeout=4)
    if response.status_code == 200:
        # verify result if a cli_conf operation was performed
        if "ins_api" in payload:
            if "type" in payload['ins_api'].keys():
                if "cli_conf" in payload['ins_api']['type']:
                    for result in response.json()['ins_api']['outputs']['output']:
                        if result['code'] != "200":
                            print("--> partial configuration failed, please verify your configuration!")
                            break
        return response.json()
    else:
        msg = "call to %s failed, status code %d (%s)" % (target_host,
                                                          response.status_code,
                                                          response.content.decode("utf-8"))
        print(msg)
        raise Exception(msg)

First, we need to** check the status code of the HTTP response**. The result of the requests.post() function helps us in this case, because it has an attribute status_code. If it is set to 200 (HTTP OK), anything was successful with our post request from a HTTP protocol perspective. If not, then there is something wrong with our format of the call or the server, therefore we simply raise an exception.

After we verified the HTTP status code and the original request was a configuration operation, we need to verify the result of the command execution. When performing configuration operations using the Cisco NX-API, you get back a success or failed message for every command that is executed. Within our interface description cleaner, we simply check if there is an output string which is not code = 200. If this is the case, we print a warning message.

The result of the function is a python dictionary that is created directly from the response object using the response.json() method.

You can also do GET, DELETE and PATCH HTTP operations using the requests library, but they were not allowed when working with the Cisco NX-API. These structure of the functions is quite similar to the POST HTTP operations, therefore I recommend to take a look at the official documentation of the request library at this point: Requests: HTTP for Humans.

HTTPs and self-signed certificates

During the creation of the code example for the Cisco NX-API, I ran into an issue (more cosmetical) when using HTTPs with self-signed certificates that are considered untrusted by default. If you start a HTTP operation using the verify=False attribute, you will also get a warning message, that these are unsecured calls. To deactivate this message, you need to add the following line of code to your python script:

requests.packages.urllib3.disable_warnings()

Using the requests library with a REST API

When working with a REST API interface, the use of the request library is quite similar. Within the product database project on my GitHub page, you can find some python code to work with the REST API in the test directory which were used within the test cases.

Conclusion

The use of the request library is again a fundamental topic when programming with python. It is not very complicated to do the HTTP calls and the use of the library is also straightforward, as you can see in the example code from this post. One of the more complicated parts is the error handling and the wok with the data that comes back, e.g. when parsing HTML data using beautifulsoup4, but this topic is part for another post.

That’s it for this week. I hope it helps you and if you have any questions or feedback to this post, please leave a comment below. Thanks for reading.