Before reading this post, I highly recommend that you check out our first NETCONF post we did if you are new to this subject. In this post I explained the fundamentals of NETCONF and YANG models which make for great background knowledge before getting into Python automation.
For todays exercise, we will write a Python program which will configure an interface on a Cisco CSR 1000v router. Our solution will use a Jinja template for the configuration data model and the NETCONF protocol for pushing our rendered configuration payload to the router - it is my hope that this post will shed light on how this technology can be implemented in a scalable manner by decoupling configuration templates from the python codebase.
To begin, let's spin up a fresh Cisco CSR 1000v router. We'll then need to configure credentials and enable NETCONF.
conf t
!
username admin privilege 15 secret admin
!
netconf-yang
We'll also configure an interface on the router for our NETCONF client to connect to.
interface GigabitEthernet1
ip address 192.168.159.10 255.255.255.0
no shut
exit
!
end
Next, install Python on your workstation if you don't already have it.
With that done, let's open up a terminal and install the "ncclient" package. This package will provide us with a NETCONF client that we will use to manage our session with the router.
pip install ncclient
We will now create a new file for our python program - we entitled ours "cisco-automation-tutorial.py". At the head of the file, import the dependencies our program will utilize.
# Filename: cisco-automation-tutorial.py
# Command to run the program: python cisco-automation-tutorial.py
# Import the required dependencies
from ncclient import manager
from jinja2 import Template
Next up, we'll establish the NETCONF session to our router using the "connect" method.
# Establish our NETCONF Session
m = manager.connect(host='192.168.159.10', port=830, username='admin',
password='admin', device_params={'name': 'csr'})
In order to configure our device with NETCONF we will first have to understand how to structure the configuration data within our RPC payload. Data structures in NETCONF are defined by YANG models and the CSR router actually supports many different options to choose from. For example, OpenConfig models and IETF models are both compatible with the IOS-XE software family.
For todays exercise, we will use the IOS-XE native YANG model.
To get a quick feel for the native model, add the python code below to your program.
# Create a configuration filter
interface_filter = '''
<filter>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<interface>
<GigabitEthernet>
<name>1</name>
</GigabitEthernet>
</interface>
</native>
</filter>
'''
# Execute the get-config RPC
result = m.get_config('running', interface_filter)
print(result)
This code is essentially the same as the CLI command "show running-config interface GigabitEthernet 1"
. When we execute the program using python cisco-automation-tutorial.py
we get the output below.
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:f4241f31-5098-475c-9d01-bcf34df25643"
xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<interface>
<GigabitEthernet>
<name xmlns:nc='urn:ietf:params:xml:ns:netconf:base:1.0'>1</name>
<ip>
<address>
<primary>
<address>192.168.159.10</address>
<mask>255.255.255.0</mask>
</primary>
</address>
</ip>
<mop>
<enabled>false</enabled>
<sysid>false</sysid>
</mop>
<negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
<auto>true</auto>
</negotiation>
</GigabitEthernet>
</interface>
</native>
</data>
</rpc-reply>
You can see from the RPC reply that we have now revealed the IOS-XE data structure of an interface.
We may now go on to use this model as a template for configuring other interfaces. From the RPC reply, copy and paste everything inside the config
tag to a new file entitled "interface.xml". This will become our Jinja template. Next, replace the IP address, subnet mask and interface index with Jinja variables using the double curly braces syntax. Once done, you should end up with a file like the one below. Ensure the file is placed in the same directory as your python program.
<config>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<interface>
<GigabitEthernet>
<name xmlns:nc='urn:ietf:params:xml:ns:netconf:base:1.0'>{{ INTERFACE_INDEX }}</name>
<ip>
<address>
<primary>
<address>{{ IP_ADDRESS }}</address>
<mask>{{ SUBNET_MASK }}</mask>
</primary>
</address>
</ip>
<mop>
<enabled>false</enabled>
<sysid>false</sysid>
</mop>
<negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
<auto>true</auto>
</negotiation>
</GigabitEthernet>
</interface>
</native>
</config>
Returning to our python program, we may now configure any interface on our router with two steps.
- Template Rendering - Render the Jinja template with desired variable values.
-
NETCONF Transaction - Send the rendered object as the payload of an
edit-config
RPC.
To do this in our python program, add the code below. For our example, we are configuring the interface GigabitEthernet 2 with an address of 10.0.0.1/30.
# Render our Jinja template
interface_template = Template(open('interface.xml').read())
interface_rendered = interface_template.render(
INTERFACE_INDEX='2',
IP_ADDRESS='10.0.0.1',
SUBNET_MASK='255.255.255.252'
)
# Execute the edit-config RPC
result = m.edit_config(target='running', config=interface_rendered)
print(result)
After the execution of the program, you should see and "ok" RPC reply from the router indicating that the transaction completed successfully.
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:2db17593-b51d-4ab2-be28-a268441d6af1"
xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
Finally, verify that the configuration actually exists on the router with the familiar CLI command.
Router#show running-config interface GigabitEthernet 2
Building configuration...
Current configuration : 129 bytes
!
interface GigabitEthernet2
ip address 10.0.0.1 255.255.255.252
shutdown
negotiation auto
no mop enabled
no mop sysid
end
Although the example we showed today was quite trivial, I hope it inspires you to think of the possibilities of how this technology can be scaled. For example, you could build an API back-end with atomic Jinja templates for configuring absolutely anything and everything on a network.
Multiple front-end apps could then be built which consume this configuration service - these front end tools could be network orchestrators, change management software, order-to-activation tools, policy managers and much more. This technology truly does enable the digital transformation of companies part of the networking industry.
Finally, if you haven't heard of Ultra Config Generator I would highly recommend that you check it out. It is essentially an out of the box solution for the technology we just discussed which allows companies to rapidly digitize their configuration processes. We designed the product to allow network engineers to generate network configuration in a highly flexible, efficient and elegant manner. Our customers love the application and I hope that you will too.
Thank you very much for reading and if you like the content feel free to follow.
Take care until next time!
Alec
Top comments (2)
This is a very nice one and gives in-depth information. I am really happy with the quality and presentation of the article. Iโd really like to appreciate the efforts you get with writing this post. Thanks for sharing.
CCNA course in Pune
Great Post. I really learned a lot of things from this blog.
also, check Python Course in Nagpur