In this blog post, we will look into the practical use of Restconf for managing Cisco Interface configurations. We will look at a simple Python script that fetches interface configs right from the switch. We'll also explore how to make configuration changes to the interfaces.
We're starting with the basics, assuming you have a basic understanding of Python, YANG models, and have some familiarity with Restconf. If Restconf is a new concept for you, no worries at all. I highly recommend you check out my earlier blog posts on this topic to get up to speed.
The Importance of Structured Data
When you're dealing with network devices and run those familiar 'show' commands, the output you get is typically unstructured or, at best, semi-structured. Working with this kind of data can be quite a hassle, especially when you're trying to parse it or extract specific details. It's like trying to find a needle in a haystack, but the haystack is made of words 🙂
However, with Restconf, the data you receive is nicely structured. It's organized in a way that makes it super easy to parse and work with. This structured approach is particularly useful when automating tasks or managing configurations across multiple devices. Once we dive into the examples in this post, you'll see just how much simpler and more efficient your workflow can be with structured data. It's like having everything neatly arranged on shelves instead of thrown into a big pile.
Restconf - Fetch Interface Configurations
In our first example, we're going to see how to fetch interface configuration from a Cisco 9300 switch, specifically the C9300-24UX model. The process involves a Python script that establishes a connection to the switch and returns the data in a well-structured JSON format.
import requests
import json
requests.packages.urllib3.disable_warnings()
username = 'admin'
password = 'password'
uri = 'https://10.1.1.1:443/restconf/data/Cisco-IOS-XE-native:native/interface/TenGigabitEthernet'
headers = {
"accept": "application/yang-data+json",
"Content-Type": "application/yang-data+json"
}
response = requests.get(
url=uri,
headers=headers,
auth=(username, password),
verify=False)
print(json.dumps(response.json(), indent=2))
The script begins by importing the necessary modules, requests
for HTTP requests and json
for handling JSON data.
The script then specifies the URI pointing to the Restconf API endpoint on the Cisco 9300 switch. If your switch has GigabitEthenet interfaces, replace the name accordingly. We then define our HTTP headers to indicate that we accept and send data in the application/yang-data+json
format.
Finally, the script sends a GET request to the specified URI with our headers and authentication details. Upon receiving the response, the script prints out the JSON data in a nicely formatted manner with proper indentation, making it easy to read and understand. The output was large so, I truncated by removing some of the interfaces.
#output
{
"Cisco-IOS-XE-native:TenGigabitEthernet": [
{
"name": "1/0/1"
},
{
"name": "1/0/5",
"switchport-config": {
"switchport": {
"Cisco-IOS-XE-switch:access": {
"vlan": {
"vlan": 10
}
},
"Cisco-IOS-XE-switch:mode": {
"access": {}
}
}
}
},
{
"name": "1/0/10",
"switchport-config": {
"switchport": {
"Cisco-IOS-XE-switch:mode": {
"trunk": {}
},
"Cisco-IOS-XE-switch:trunk": {
"allowed": {
"vlan": {
"vlans": 20
}
}
}
}
}
},
{
"name": "1/0/11",
"switchport-config": {
"switchport": {
"Cisco-IOS-XE-switch:access": {
"vlan": {
"vlan": 10
}
},
"Cisco-IOS-XE-switch:mode": {
"access": {}
}
}
}
},
{
"name": "1/0/12"
},
{
"name": "1/0/13"
},
{
"name": "1/0/14",
"switchport-config": {
"switchport": {
"Cisco-IOS-XE-switch:access": {
"vlan": {
"vlan": 10
}
},
"Cisco-IOS-XE-switch:mode": {
"access": {}
}
}
}
},
{
"name": "1/0/2"
},
{
"name": "1/0/20",
"switchport-config": {
"switchport": {
"Cisco-IOS-XE-switch:access": {
"vlan": {
"vlan": 10
}
},
"Cisco-IOS-XE-switch:mode": {
"access": {}
}
}
}
},
{
"name": "1/0/21"
},
{
"name": "1/1/7"
},
{
"name": "1/1/8"
}
]
}
Just for the purpose of this example, I configured one of the interfaces as a Trunk port and a couple of them as Access ports.
Restconf - Modify Interface Configurations
In this next example, we're moving from simply fetching interface configurations to actually modifying them using Restconf. We'll be changing the VLAN of the Te1/0/11
access port from VLAN 10 to VLAN 20. Let's break down the new elements in the provided Python script.
import requests
import json
import urllib.parse
requests.packages.urllib3.disable_warnings()
username = 'admin'
password = 'password'
encoded_interface = urllib.parse.quote('1/0/11', safe='')
uri = f'https://10.1.1.1:443/restconf/data/Cisco-IOS-XE-native:native/interface/TenGigabitEthernet={encoded_interface}/switchport-config/switchport/Cisco-IOS-XE-switch:access/vlan/vlan'
headers = {
"accept": "application/yang-data+json",
"Content-Type": "application/yang-data+json"
}
body = {
"vlan": "20"
}
response = requests.put(
url=uri,
data=json.dumps(body),
headers=headers,
auth=(username, password),
verify=False)
print(response)
The script begins similarly to the previous one, but there's a notable addition, the urllib.parse
module. This module is used here for encoding the interface identifier in the URL.
Why is this encoding necessary? The interface identifier 1/0/11
includes slashes (/
), which are special characters in URLs. In a URL, slashes are used to separate different parts of the address. To correctly pass the interface identifier as part of the URL, these slashes need to be encoded. The urllib.parse.quote
function does exactly that, ensuring the interface identifier is correctly interpreted by the Restconf API. Essentially, we are converting Te1/1/11
to 1%2F0%2F11
(/
gets replaced by %2F
).
If you want to learn more about URL Encoding, please check out my other blog post below.
The script then constructs the URI, which is specifically targeting the VLAN configuration of the Te1/0/11
interface. The encoded interface identifier is dynamically inserted into the URI.
A significant change in this script is the HTTP method used. While the previous script used requests.get
to retrieve data, this one uses requests.put
to modify data. The put
method is standard for updating or replacing resources on the target device.
The body
of the request is a Python dictionary specifying the new VLAN ("vlan": "20"
). This dictionary is then converted into a JSON-formatted string using json.dumps
. This conversion is necessary because the Restconf API expects the request body in JSON format.
We we look at the VLAN config again, we should see that the interface 1/0/11
should be associated with VLAN 20 as shown below.
{
"name": "1/0/11",
"description": "users-port",
"switchport-config": {
"switchport": {
"Cisco-IOS-XE-switch:access": {
"vlan": {
"vlan": 20
}
},
"Cisco-IOS-XE-switch:mode": {
"access": {}
}
}
}
}
interface TenGigabitEthernet1/0/11
switchport access vlan 20
switchport mode access
If you are wondering where am I getting these URIs and how do I know how the body
should look like, I'm just using the YANG suite as shown below.
Configure Interface Descriptions based on VLAN
In this final example, we're going to automate the process of finding and updating the description of all access ports that are associated with VLAN 10. The script uses structured data to identify and modify specific interfaces.
import requests
import json
import urllib.parse
requests.packages.urllib3.disable_warnings()
username = 'admin'
password = 'password'
uri = 'https://10.1.1.1:443/restconf/data/Cisco-IOS-XE-native:native/interface/TenGigabitEthernet'
headers = {
"accept": "application/yang-data+json",
"Content-Type": "application/yang-data+json"
}
response = requests.get(
url=uri,
headers=headers,
auth=(username, password),
verify=False)
# print(json.dumps(response.json(), indent=2))
for interface in response.json()['Cisco-IOS-XE-native:TenGigabitEthernet']:
interface_name = interface['name']
encoded_interface = urllib.parse.quote(interface_name, safe='')
desc_uri = f'https://10.1.255.50:443/restconf/data/Cisco-IOS-XE-native:native/interface/TenGigabitEthernet={encoded_interface}/description'
body = {
"description": "users-port"
}
if 'switchport-config' in interface and 'Cisco-IOS-XE-switch:access' in interface['switchport-config']['switchport']:
if interface['switchport-config']['switchport']['Cisco-IOS-XE-switch:access']['vlan']['vlan'] == 10:
post_response = requests.put(
url=desc_uri,
data=json.dumps(body),
headers = headers,
auth=(username, password),
verify=False
)
print(response)
The script starts by fetching all TenGigabitEthernet (please change this if you are working with GigabitEthernet for example) interface configurations from the switch, similar to our first example. The response contains structured data in JSON format, which is crucial for our next steps.
A significant part of this script lies in the conditional statement within the loop. The script checks if the interface has a switchport-config
and if it's configured as an access port (Cisco-IOS-XE-switch:access
). If these conditions are met, it further checks whether the VLAN assigned to this access port is VLAN 10.
If an interface passes these checks, meaning it's an access port on VLAN 10, the script proceeds to modify its description. It constructs a body
dictionary with the new description "users-port" and sends a PUT request to the specific URI of the interface, updating its description.
This example highlights the power of working with structured data. The script can easily inspect the configuration details of each interface and apply conditional logic to determine which interfaces to modify. It showcases how structured data, as provided by Restconf, simplifies tasks like searching, filtering, and updating configurations based on specific criteria.
Here is the config from the switch. Please note the interface Te1/0/11
didn't get the description because we changed the VLAN in the previous example 🙂
c9300#show interfaces description | incl user
Te1/0/5 down down users-port
Te1/0/14 down down users-port
Te1/0/20 down down users-port
Closing thoughts
That's a wrap on our journey with Cisco Restconf and Python. We've seen how Restconf transforms complex tasks into simpler ones, from fetching interface configurations to modifying VLANs and adding descriptions based on VLANs. Keep experimenting, and happy coding!