requests
library to make RESTCONF API requestsRESTCONF really is a REST API over HTTP(S) transport using either JSON or XML data encoding, where the URL and encoded data specifications are defined by the YANG data models.
Model-driven API would not be possible without YANG data models provided by IETF, ITU, OpenConfig, etc., and also provided by networking vendors, like Cisco, for device-specific features and functionality.
These data models define the structure of the RESTCONF API within the RESTful framework. How the YANG models are defined and utilized by the API and developers is a larger subject we will not be covering here. What we will cover is how to get data from a network device through some exploration, then parse it and display the results.
REST stands for REpresentational State Transfer, also referred to as RESTful.
OK, but what does that mean?
To break it down into simple terms, it is a stateless application programming interface (API) that conforms to and utilizes the HTTP transport. Utilizing the same transport as modern web browsers, you already have some experience with RESTful APIs just by using the internet. A lot of websites now use APIs to dynamically load, update, and display information within the web browser.
Every request is stateless, meaning that other than potentially authentication, there is no concept of one request being aware of another request. Basically, no assumptions are made in the requests to the API provider; you need to send all the information needed on every request, usually even authentication.
REST does utilize what are called HTTP methods or verbs. You already have experience with some of the verbs from just using a modern web browser. The following HTTP verbs are sometimes called methods, and their general purposes are:
For the purpose of the exercises, we will be focused on read operations, which utilize the GET HTTP method. This is the same as when you type in a website in the address bar and hit Enter. The browser uses the URL to figure out the host to connect and then sends the rest of the URL to the server, setting the HTTP method to GET.
HTTP status codes or error codes (depending on the number) are
important to understand. A typical GET request, when successful, will have a
status code of 200
. Other status codes you may already be familiar with are
404
(page not found) or 500
(server error). Generally, you will need to at
least know the status code categories.
Status Code | Category Description |
---|---|
200 | Success (200) or informational |
300 | Redirection to another resource |
400 | Client error like not found (404), unauthorized (401), or forbidden (403) |
500 | Server-side issues |
What RESTCONF brings beyond REST and HTTP(S) is the utilization of YANG data models defining the structure of the encoded data and even the URL to access the network device. We will be focused on the GET method operations of RESTCONF here to keep it simple.
At this point, we are going to use your local Python 3.6+. If you have not installed Python 3.6+, please refer to the linked setup instructions in the Overview.
We will be discussing the Python script in sections. As each section is discussed, copy and paste each code block in order to your script so that when we get to the end, you will have a fully functioning script.
Time to construct our Python script to get the hostname and version of the network device.
First, we need to import
the needed Python standard libraries.
# Include python modules
import requests
from requests.auth import HTTPBasicAuth
import json
import pprint
These are the libraries we will be using in this script.
The requests
library allows us to make an HTTP request, which we
will use to make the RESTCONF request to the network device.
The from requests.auth import HTTPBasicAuth
exposes the HTTPBasicAuth
method from the request.auth
library to be used later.
The json
library assists in working with JSON data encoding.
The pprint
library—also known as PrettyPrinter—prints Python data structures
in a more human-friendly or readable way.
# Disable SSL Verification Warning because of Private SSL Certificate
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
By default, the requests
library will issue a warning when private SSL
certificates are encountered, so we disable the certificate verification warning.
The above code dives into another library that requests
uses to disable the
warning.
# Request information
username = "developer"
password = "C1sco12345"
hostname = "sandbox-iosxe-latest-1.cisco.com"
port = "443"
uri = "/restconf/data/Cisco-IOS-XE-native:native/"
payload = ""
Here, we are setting some RESTCONF connection-related variables.
Note: For the purpose of this exercise, we are connecting the DevNet Always-On CSR 1000V Sandbox for our RESTCONF request. If you having problems connecting, please make sure the Sandbox is available.
# Construct URL
url = f'https://{hostname}:{port}/{uri}'
Using Python f-strings introduced in Python 3.6, we can use our connection
variables with in the f-string template. The variables surrounded
by { }
in the f-string are replaced, resulting in our url
string.
# Setup HTTP headers
http_auth = HTTPBasicAuth(username, password)
http_headers = {
'Accept': 'application/yang-data+json',
'Content-Type': 'application/yang-data+json'
}
Here, we are using the HTTPBasicAuth
method from the requests.auth
library
to create the needed basic authentication string from the username
and
password
variables.
Next, we set the appropriate HTTP headers in a Python dictionary data construct.
RESTCONF expects that we set the Content-Type
header stating the format of
the payload
and the Accept
header that defines which format we expect to
receive back. Both headers can be either for JSON or XML; in this case,
we are requesting JSON for both.
# Make request to network device
try:
response = requests.get(url, headers=http_headers, auth=http_auth, data=payload, verify=False)
except requests.exceptions.Timeout: # If timeout occurs, print message
print('Connection to device timed out')
except requests.exceptions.ConnectionError: # If there is a connection error, print message
print('Connection Error')
Now we use the requests
library to make the RESTCONF request, using all the
variables we have previously set up.
Also, we use what is called a try
block with exception handling. So if there
is a problem—the connection timing out or a connection error—those exceptions
will be “caught” and the appropriate error will be printed.
# Parse JSON Payload from network device
device_json = json.loads(response.text);
pprint.pprint(device_json, indent=2, compact=False)
At this point, to use the JSON response payload, we have to parse it. Otherwise, we cannot work with the values.
Let’s try running the script to see the payload from the device.
workstation $ python3 get_iosxe_version.py
{ 'Cisco-IOS-XE-native:native': { 'Cisco-IOS-XE-diagnostics:diagnostic': { 'bootup': { 'level': 'minimal'}},
'boot-end-marker': [ None],
'boot-start-marker': [ None],
'call-home': { 'Cisco-IOS-XE-call-home:contact-email-addr': 'sch-smart-licensing@cisco.com',
'Cisco-IOS-XE-call-home:tac-profile': { 'profile': { 'CiscoTAC-1': { 'active': True,
'destination': { 'transport-method': 'http'}}}}},
'cdp': { 'Cisco-IOS-XE-cdp:run-enable': True},
'control-plane': { },
'crypto': { 'Cisco-IOS-XE-crypto:pki': { 'certificate': { 'chain': [ { 'certificate': [ { 'certtype': 'ca',
'serial': '01'}],
'name': 'SLA-TrustPoint'},
{ 'certificate': [ { 'certtype': 'self-signed',
'serial': '01'}],
'name': 'TP-self-signed-807034967'}]},
'trustpoint': [ { 'enrollment': { 'pkcs12': [ None]},
'id': 'SLA-TrustPoint',
'revocation-check': [ 'crl']},
{ 'enrollment': { 'selfsigned': [ None]},
'id': 'TP-self-signed-807034967',
'revocation-check': [ 'none'],
'subject-name': 'cn=IOS-Self-Signed-Certificate-807034967'}]}},
'enable': { 'secret': { 'secret': '$9$GNcSrWbM1PyCqU$9.BCza34ClqbgyABGzRV1v5hjCWhaoN9K.gqxCtcCvE',
'type': '9'}},
'hostname': 'csr1000v-1',
'interface': { 'GigabitEthernet': [ { 'Cisco-IOS-XE-ethernet:negotiation': { 'auto': True},
<< OUTPUT TRUCATED >>
So why did we print the parse JSON? It is one way to figure out which data is available from the device.
It is important to note that YANG data models do define this structure. Using tools like Cisco YANG Suite, you can load the YANG models to explore the structure of the API.
In this case, we know we want version
and hostname
values from the
payload, and if you look, you can find them.
Because we have seen the payload, we can comment out the pprint
so that it will not
normally run when we execute the script.
#pprint.pprint(device_json, indent=2, width=1, compact=False)
# Get version and hostname from payload
device_hostname = device_json['Cisco-IOS-XE-native:native']['hostname']
device_version = device_json['Cisco-IOS-XE-native:native']['version']
Now that we have seen the JSON payload, we have an idea of how to get the
version
and hostname
for the device. Here, you can see we use the
Cisco-IOS-XE-native:native
key to access that dictionary value. From
there, we can access the hostname
and version
keys to get those values.
# Print device hostname and version
print(f'Hostname: {device_hostname}')
print(f'Version: {device_version}')
Finally, we print the hostname and version of the network device, using f-string templates.
workstation $ python3 get_iosxe_version.py
Hostname: csr1000v-1
Version: 17.3
Awesome—it worked!
While this is a simple script utilizing the RESTCONF API on Cisco IOS XE to get the hostname and version, it still shows the basics of how to use Python and the standard libraries to utilize the RESTCONF API. There are a few things that can be done to extend the Python script beyond the basic demonstration in this exercise to make it more functional, but that is for a later time.
I hope this exercise was helpful and inspires you to learn more!