Table of Contents
restconf request with python request:
Starting in this section, we will receive and send configuration to and from network devices using the restconf protocol implemented with Python scripts.
In this section we will use a simple Python “request” library to communicate with network devices, but from the next section we will use the inventory management and multi-threading capability of the Python Nornir plugin.
restconf with python request library
Requests library is the most famous python library used to send http request through python script.
Here we use the Python requests library to get the configuration of network devices. However, we need to provide the IP address of network devices and credentials inside the python script because the python request doesn’t have the features like inventory management.
This is a sample python code snippet to send http request through requests library.
import requests url = "https://192.168.2.91:443/restconf/data/ietf-interfaces:interfaces" payload={} header1 = { 'Content-Type': 'application/yang-data+json', 'Accept': 'application/yang-data+json', } response = requests.request("GET", url, headers=header1, data=payload, auth=("rayka", "rayka-co.com"), verify=False)
how to find url in restconf request
In the first line we import the “requests” library.
In the url variable we specify the URL of the restconf protocol in the network device where we get or set the configuration.
This is the most important part of the code snippet. I have already explained in the netconf protocol, in lesson 6 and lesson 7, how to get the xpath to reach each part of the configuration.
The first part of the URL is always fixed. The IP address and port to connect to the network device and then “/restconf/data/“. But the second part of the URL is what you need to know to get or set the configuration.
https://{{host}}:{{port}}/restconf/data/
The second part of the url is the xpath to reach the each part of the configuration.
For network devices, netconf is still the best way to find the xpath, as we discussed in Lesson 6 and Lesson 7.
However, for more modern network solutions such as network SDN solutions, there is detailed restconf documentation showing the restconf url to reach each section of the configuration.
The other method that can be sometimes helpful is public postman collections.
For example, this link on the Postman website has public Postman collections for most Cisco networking solutions.
You can use existing API request as reference or as a sample to be used also in your python scripts.
python request command structure
Payload and headers are the other section of python requests library to get or set the configuration of network devices.
We use payload, when we upload a new configuration to the network devices. In this code snippet, we are receiving the configuration therefore the payload section is empty.
The format of receiving or sending configuration can be XML or JSON in restconf protocol which will be configured in the “Accept” and “Content-Type” header fields.
And finally we send the request using “requests” library.
Here the http command “GET” is used to get the configuration. “headers”, “payload” and “authentication” information will also be given in request command to be send to the network device.
url filtering features
There are also some features in the url to filter and limit the request to more specific part of the configuration or statistics.
"https://192.168.2.91:443/restconf/data/Cisco-IOS-XE-native:native/router/router-ospf/ospf/process-id=1"
"https://192.168.2.91:443/restconf/data/Cisco-IOS-XE-interfaces-oper:interfaces/interface=GigabitEthernet1"
"https://192.168.2.91:443/restconf/data/openconfig-interfaces:interfaces?content=config"
"https://192.168.2.91:443/restconf/data/openconfig-interfaces:interfaces?content=nonconfig"
"https://192.168.2.91:443/restconf/data/openconfig-interfaces:interfaces/interface=GigabitEthernet1?content=nonconfig"
With equal sign “=”, we can limit the request to a more specific section, for example specific interface or OSPF process-id as first two url examples.
With “content=config” or “content=nonconfig” for openconfig yang data model, we are allowed to request configuration section or statistic section of the any specific part of network device like interfaces, if you need the configuration section or interface statistics.
The third and fourth url are using the content feature to limit the request to configuration or statistics.
For cisco yang data model, this method does not work since in cisco, configuration yang data models and statistics yang data model are completely different. In cisco the name of statistics yang data models have an extra “-oper” at the end in compare to configuration yang data models.
You are allowed also to combine the capability of equal “=” sign and “content” keyword, to extract the configuration or statistics information of a specific interface
The last url has combined both “equal” and “content” capability to limit the output to interface GigabitEthernet1 statistics.
restconf with python request library Code Example
This is the script that we run in this section with more than 10 url examples.
import requests import xmltodict from rich import print as rprint requests.packages.urllib3.disable_warnings() HOST = "192.168.2.91" PORT = "443" USER = "rayka" PASSWORD = "rayka-co.com" header1 = {"Accept": "application/yang-data+xml"} header2 = {"Accept": "application/yang-data+json"} url0 = f"https://{HOST}:{PORT}/restconf/data/ietf-interfaces:interfaces" url1 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-native:native" url2 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-native:native/ip/domain" url3 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-native:native/router" url4 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-native:native/router/router-ospf" url5 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-native:native/router/router-ospf/ospf" url6 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-native:native/router/router-ospf/ospf/process-id" url7 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-native:native/router/router-ospf/ospf/process-id=1" url75 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-interfaces-oper:interfaces" url8 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-interfaces-oper:interfaces/interface=GigabitEthernet1" url9 = f"https://{HOST}:{PORT}/restconf/data/openconfig-interfaces:interfaces?content=config" url10 = f"https://{HOST}:{PORT}/restconf/data/openconfig-interfaces:interfaces?content=nonconfig" url11 = f"https://{HOST}:{PORT}/restconf/data/openconfig-interfaces:interfaces/interface=GigabitEthernet1?content=nonconfig" url12 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-native:native/router/bgp" url13 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-native:native/ntp" url14 = f"https://{HOST}:{PORT}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list/Cisco-IOS-XE-acl:extended" url15 = f"https://{HOST}:{PORT}/restconf/data/openconfig-acl:acl?content=config" response = requests.get(url=url0, headers=header2, auth=(USER, PASSWORD), verify=False) # print result inf the format of text or original format rprint(response.text) rprint(response.content) # if the result is in the content of xml #xml_response = response.content #dic_response = xmltodict.parse(xml_response) #rprint(dic_response) # if the result is in the content of json #rprint(response.json()) #dic_response=response.json() #print(type(dic_response)) # with url11 #dic_response=dic_response["openconfig-interfaces:interface"]["state"]["counters"]["in-octets"] #for key,value in dic_response.items(): # print("key=",key) # print("value=",value) #rprint(dic_response)
This is the script that we run in this section with more than 10 url examples.
The structure of request and url is already explained.
The response of the request can be displayed with printing any of “response.text” or “response.content”. The “response.text” shows human readable output but “response.content” shows the original output in the format of xml or json, depending on the header accept field that you sent in your request.
I have used “rprint” from rich library instead of “print” only to see nicer output.
Let’s run the script and see the result.
result of response.text:
{ "ietf-interfaces:interfaces": { "interface": [ { "name": "GigabitEthernet1", "type": "iana-if-type:ethernetCsmacd", "enabled": true, "ietf-ip:ipv4": { "address": [ { "ip": "192.168.2.91", "netmask": "255.255.255.0" } ] }, "ietf-ip:ipv6": { } }, { "name": "Loopback100", "type": "iana-if-type:softwareLoopback", "enabled": true, "ietf-ip:ipv4": { "address": [ { "ip": "4.5.6.7", "netmask": "255.255.255.0" } ] }, "ietf-ip:ipv6": { } } ] } }
result of response.content:
b'{\n "ietf-interfaces:interfaces": {\n "interface": [\n {\n "name": "GigabitEthernet1",\n "type": "iana-if-type:ethernetCsmacd",\n "enabled": true,\n "ietf-ip:ipv4": {\n "address": [\n {\n "ip": "192.168.2.91",\n "netmask": "255.255.255.0"\n }\n ]\n },\n "ietf-ip:ipv6": {\n }\n },\n {\n "name": "Loopback100",\n "type": "iana-if-type:softwareLoopback",\n "enabled": true,\n "ietf-ip:ipv4": {\n "address": [\n {\n "ip": "4.5.6.7",\n "netmask": "255.255.255.0"\n }\n ]\n },\n "ietf-ip:ipv6": {\n }\n }\n ]\n }\n}\n'
convert xml and json output to dictionary
But to be able to process the output and access any part of the output, we need to convert the json or xml output to python dictionary format.
For xml output, we use “xmltodict.parse(xml_response)” from xmltodict python library to convert it to a dictionary which can be processed by python.
For json output, we use “response.json()” to convert it to a dictionary.
Just to check, let’s choose the header field once as xml and once as json and convert it to a dictionary format.
XML to dictionary:
header1 = {"Accept": "application/yang-data+xml"} url0 = f"https://{HOST}:{PORT}/restconf/data/ietf-interfaces:interfaces" response = requests.get(url=url0, headers=header1, auth=(USER, PASSWORD), verify=False) xml_response = response.content dic_response = xmltodict.parse(xml_response) rprint(dic_response)
OrderedDict([('interfaces', OrderedDict([('@xmlns', 'urn:ietf:params:xml:ns:yang:ietf-interfaces'), ('@xmlns:if', 'urn:ietf:params:xml:ns:yang:ietf-interfaces'), ('interface', [OrderedDict([('name', 'GigabitEthernet1'), ('type', OrderedDict([('@xmlns:ianaift', 'urn:ietf:params:xml:ns:yang:iana-if-type'), ('#text', 'ianaift:ethernetCsmacd')])), ('enabled', 'true'), ('ipv4', OrderedDict([('@xmlns', 'urn:ietf:params:xml:ns:yang:ietf-ip'), ('address', OrderedDict([('ip', '192.168.2.91'), ('netmask', '255.255.255.0')]))])), ('ipv6', OrderedDict([('@xmlns', 'urn:ietf:params:xml:ns:yang:ietf-ip')]))]), OrderedDict([('name', 'Loopback100'), ('type', OrderedDict([('@xmlns:ianaift', 'urn:ietf:params:xml:ns:yang:iana-if-type'), ('#text', 'ianaift:softwareLoopback')])), ('enabled', 'true'), ('ipv4', OrderedDict([('@xmlns', 'urn:ietf:params:xml:ns:yang:ietf-ip'), ('address', OrderedDict([('ip', '4.5.6.7'), ('netmask', '255.255.255.0')]))])), ('ipv6', OrderedDict([('@xmlns', 'urn:ietf:params:xml:ns:yang:ietf-ip')]))])])]))])
JSON to dictionary:
header2 = {"Accept": "application/yang-data+json"} url0 = f"https://{HOST}:{PORT}/restconf/data/ietf-interfaces:interfaces" response = requests.get(url=url0, headers=header2, auth=(USER, PASSWORD), verify=False) rprint(response.json()) rprint(type(response.json()))
{ 'ietf-interfaces:interfaces': { 'interface': [ { 'name': 'GigabitEthernet1', 'type': 'iana-if-type:ethernetCsmacd', 'enabled': True, 'ietf-ip:ipv4': {'address': [{'ip': '192.168.2.91', 'netmask': '255.255.255.0'}]}, 'ietf-ip:ipv6': {} }, { 'name': 'Loopback100', 'type': 'iana-if-type:softwareLoopback', 'enabled': True, 'ietf-ip:ipv4': {'address': [{'ip': '4.5.6.7', 'netmask': '255.255.255.0'}]}, 'ietf-ip:ipv6': {} } ] } } <class 'dict'>
When the xml or json is converted to a dictionary, then it can be easily process by python script to access any field in the output.
We have discussed this topic in the course “CLI based Network Automation using Python Nornir” and in the output of textfsm and genie parser, which give us dictionary output.
In our script, we use the url11 to receive GigabitEthernet1 interface statistics from the openconfig interfaces yang data model. But to go further and receive the specific counters of interface GigabitEthernet1 statistics, we use the [“openconfig-interfaces:interface”][“state”][“counters”] dictionary key sequence.
header2 = {"Accept": "application/yang-data+json"} url11 = f"https://{HOST}:{PORT}/restconf/data/openconfig-interfaces:interfaces/interface=GigabitEthernet1?content=nonconfig" response = requests.get(url=url11, headers=header2, auth=(USER, PASSWORD), verify=False) dic_response=response.json() dic_response=dic_response["openconfig-interfaces:interface"]["state"]["counters"] rprint(dic_response)
{ 'in-octets': '73329052', 'in-unicast-pkts': '479510', 'in-broadcast-pkts': '0', 'in-multicast-pkts': '0', 'in-discards': '0', 'in-errors': '0', 'in-unknown-protos': '0', 'in-fcs-errors': '0', 'out-octets': '490552', 'out-unicast-pkts': '4712', 'out-broadcast-pkts': '0', 'out-multicast-pkts': '0', 'out-discards': '0', 'out-errors': '0', 'last-clear': '1681812241000000000' }
The key sequences can be easily be extracted in the output of url11 json output or by looping over the output items and printing keys and values as I have shown in the script.
you can download the code related to sending restconf request through python request library from this link.