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-yangconf t ! username admin privilege 15 secret admin ! netconf-yangconf 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 GigabitEthernet1ip address 192.168.159.10 255.255.255.0no shutexit!endinterface GigabitEthernet1 ip address 192.168.159.10 255.255.255.0 no shut exit ! endinterface 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 ncclientpip install ncclientpip 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.
<span># Filename: cisco-automation-tutorial.py # Command to run the program: python cisco-automation-tutorial.py </span><span># Import the required dependencies </span><span>from</span> <span>ncclient</span> <span>import</span> <span>manager</span><span>from</span> <span>jinja2</span> <span>import</span> <span>Template</span><span># Filename: cisco-automation-tutorial.py # Command to run the program: python cisco-automation-tutorial.py </span> <span># Import the required dependencies </span><span>from</span> <span>ncclient</span> <span>import</span> <span>manager</span> <span>from</span> <span>jinja2</span> <span>import</span> <span>Template</span># 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.
<span># Establish our NETCONF Session </span><span>m</span> <span>=</span> <span>manager</span><span>.</span><span>connect</span><span>(</span><span>host</span><span>=</span><span>'192.168.159.10'</span><span>,</span> <span>port</span><span>=</span><span>830</span><span>,</span> <span>username</span><span>=</span><span>'admin'</span><span>,</span><span>password</span><span>=</span><span>'admin'</span><span>,</span> <span>device_params</span><span>=</span><span>{</span><span>'name'</span><span>:</span> <span>'csr'</span><span>})</span><span># Establish our NETCONF Session </span><span>m</span> <span>=</span> <span>manager</span><span>.</span><span>connect</span><span>(</span><span>host</span><span>=</span><span>'192.168.159.10'</span><span>,</span> <span>port</span><span>=</span><span>830</span><span>,</span> <span>username</span><span>=</span><span>'admin'</span><span>,</span> <span>password</span><span>=</span><span>'admin'</span><span>,</span> <span>device_params</span><span>=</span><span>{</span><span>'name'</span><span>:</span> <span>'csr'</span><span>})</span># 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.
<span># Create a configuration filter </span><span>interface_filter</span> <span>=</span> <span>''' <filter> <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native"> <interface> <GigabitEthernet> <name>1</name> </GigabitEthernet> </interface> </native> </filter> '''</span><span># Execute the get-config RPC </span><span>result</span> <span>=</span> <span>m</span><span>.</span><span>get_config</span><span>(</span><span>'running'</span><span>,</span> <span>interface_filter</span><span>)</span><span>print</span><span>(</span><span>result</span><span>)</span><span># Create a configuration filter </span><span>interface_filter</span> <span>=</span> <span>''' <filter> <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native"> <interface> <GigabitEthernet> <name>1</name> </GigabitEthernet> </interface> </native> </filter> '''</span> <span># Execute the get-config RPC </span><span>result</span> <span>=</span> <span>m</span><span>.</span><span>get_config</span><span>(</span><span>'running'</span><span>,</span> <span>interface_filter</span><span>)</span> <span>print</span><span>(</span><span>result</span><span>)</span># 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.
<span><?xml version="1.0" encoding="UTF-8"?></span><span><rpc-reply</span> <span>xmlns=</span><span>"urn:ietf:params:xml:ns:netconf:base:1.0"</span> <span>message-id=</span><span>"urn:uuid:f4241f31-5098-475c-9d01-bcf34df25643"</span><span>xmlns:nc=</span><span>"urn:ietf:params:xml:ns:netconf:base:1.0"</span><span>></span><span><data></span><span><native</span> <span>xmlns=</span><span>"http://cisco.com/ns/yang/Cisco-IOS-XE-native"</span><span>></span><span><interface></span><span><GigabitEthernet></span><span><name</span> <span>xmlns:nc=</span><span>'urn:ietf:params:xml:ns:netconf:base:1.0'</span><span>></span>1<span></name></span><span><ip></span><span><address></span><span><primary></span><span><address></span>192.168.159.10<span></address></span><span><mask></span>255.255.255.0<span></mask></span><span></primary></span><span></address></span><span></ip></span><span><mop></span><span><enabled></span>false<span></enabled></span><span><sysid></span>false<span></sysid></span><span></mop></span><span><negotiation</span> <span>xmlns=</span><span>"http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet"</span><span>></span><span><auto></span>true<span></auto></span><span></negotiation></span><span></GigabitEthernet></span><span></interface></span><span></native></span><span></data></span><span></rpc-reply></span><span><?xml version="1.0" encoding="UTF-8"?></span> <span><rpc-reply</span> <span>xmlns=</span><span>"urn:ietf:params:xml:ns:netconf:base:1.0"</span> <span>message-id=</span><span>"urn:uuid:f4241f31-5098-475c-9d01-bcf34df25643"</span> <span>xmlns:nc=</span><span>"urn:ietf:params:xml:ns:netconf:base:1.0"</span><span>></span> <span><data></span> <span><native</span> <span>xmlns=</span><span>"http://cisco.com/ns/yang/Cisco-IOS-XE-native"</span><span>></span> <span><interface></span> <span><GigabitEthernet></span> <span><name</span> <span>xmlns:nc=</span><span>'urn:ietf:params:xml:ns:netconf:base:1.0'</span><span>></span>1<span></name></span> <span><ip></span> <span><address></span> <span><primary></span> <span><address></span>192.168.159.10<span></address></span> <span><mask></span>255.255.255.0<span></mask></span> <span></primary></span> <span></address></span> <span></ip></span> <span><mop></span> <span><enabled></span>false<span></enabled></span> <span><sysid></span>false<span></sysid></span> <span></mop></span> <span><negotiation</span> <span>xmlns=</span><span>"http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet"</span><span>></span> <span><auto></span>true<span></auto></span> <span></negotiation></span> <span></GigabitEthernet></span> <span></interface></span> <span></native></span> <span></data></span> <span></rpc-reply></span><?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.
<span><config></span><span><native</span> <span>xmlns=</span><span>"http://cisco.com/ns/yang/Cisco-IOS-XE-native"</span><span>></span><span><interface></span><span><GigabitEthernet></span><span><name</span> <span>xmlns:nc=</span><span>'urn:ietf:params:xml:ns:netconf:base:1.0'</span><span>></span>{{ INTERFACE_INDEX }}<span></name></span><span><ip></span><span><address></span><span><primary></span><span><address></span>{{ IP_ADDRESS }}<span></address></span><span><mask></span>{{ SUBNET_MASK }}<span></mask></span><span></primary></span><span></address></span><span></ip></span><span><mop></span><span><enabled></span>false<span></enabled></span><span><sysid></span>false<span></sysid></span><span></mop></span><span><negotiation</span> <span>xmlns=</span><span>"http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet"</span><span>></span><span><auto></span>true<span></auto></span><span></negotiation></span><span></GigabitEthernet></span><span></interface></span><span></native></span><span></config></span><span><config></span> <span><native</span> <span>xmlns=</span><span>"http://cisco.com/ns/yang/Cisco-IOS-XE-native"</span><span>></span> <span><interface></span> <span><GigabitEthernet></span> <span><name</span> <span>xmlns:nc=</span><span>'urn:ietf:params:xml:ns:netconf:base:1.0'</span><span>></span>{{ INTERFACE_INDEX }}<span></name></span> <span><ip></span> <span><address></span> <span><primary></span> <span><address></span>{{ IP_ADDRESS }}<span></address></span> <span><mask></span>{{ SUBNET_MASK }}<span></mask></span> <span></primary></span> <span></address></span> <span></ip></span> <span><mop></span> <span><enabled></span>false<span></enabled></span> <span><sysid></span>false<span></sysid></span> <span></mop></span> <span><negotiation</span> <span>xmlns=</span><span>"http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet"</span><span>></span> <span><auto></span>true<span></auto></span> <span></negotiation></span> <span></GigabitEthernet></span> <span></interface></span> <span></native></span> <span></config></span><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.
<span># Render our Jinja template </span><span>interface_template</span> <span>=</span> <span>Template</span><span>(</span><span>open</span><span>(</span><span>'interface.xml'</span><span>).</span><span>read</span><span>())</span><span>interface_rendered</span> <span>=</span> <span>interface_template</span><span>.</span><span>render</span><span>(</span><span>INTERFACE_INDEX</span><span>=</span><span>'2'</span><span>,</span><span>IP_ADDRESS</span><span>=</span><span>'10.0.0.1'</span><span>,</span><span>SUBNET_MASK</span><span>=</span><span>'255.255.255.252'</span><span>)</span><span># Execute the edit-config RPC </span><span>result</span> <span>=</span> <span>m</span><span>.</span><span>edit_config</span><span>(</span><span>target</span><span>=</span><span>'running'</span><span>,</span> <span>config</span><span>=</span><span>interface_rendered</span><span>)</span><span>print</span><span>(</span><span>result</span><span>)</span><span># Render our Jinja template </span><span>interface_template</span> <span>=</span> <span>Template</span><span>(</span><span>open</span><span>(</span><span>'interface.xml'</span><span>).</span><span>read</span><span>())</span> <span>interface_rendered</span> <span>=</span> <span>interface_template</span><span>.</span><span>render</span><span>(</span> <span>INTERFACE_INDEX</span><span>=</span><span>'2'</span><span>,</span> <span>IP_ADDRESS</span><span>=</span><span>'10.0.0.1'</span><span>,</span> <span>SUBNET_MASK</span><span>=</span><span>'255.255.255.252'</span> <span>)</span> <span># Execute the edit-config RPC </span><span>result</span> <span>=</span> <span>m</span><span>.</span><span>edit_config</span><span>(</span><span>target</span><span>=</span><span>'running'</span><span>,</span> <span>config</span><span>=</span><span>interface_rendered</span><span>)</span> <span>print</span><span>(</span><span>result</span><span>)</span># 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.
<span><?xml version="1.0" encoding="UTF-8"?></span><span><rpc-reply</span> <span>xmlns=</span><span>"urn:ietf:params:xml:ns:netconf:base:1.0"</span> <span>message-id=</span><span>"urn:uuid:2db17593-b51d-4ab2-be28-a268441d6af1"</span><span>xmlns:nc=</span><span>"urn:ietf:params:xml:ns:netconf:base:1.0"</span><span>></span><span><ok/></span><span></rpc-reply></span><span><?xml version="1.0" encoding="UTF-8"?></span> <span><rpc-reply</span> <span>xmlns=</span><span>"urn:ietf:params:xml:ns:netconf:base:1.0"</span> <span>message-id=</span><span>"urn:uuid:2db17593-b51d-4ab2-be28-a268441d6af1"</span> <span>xmlns:nc=</span><span>"urn:ietf:params:xml:ns:netconf:base:1.0"</span><span>></span> <span><ok/></span> <span></rpc-reply></span><?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 2Building configuration...Current configuration : 129 bytes!interface GigabitEthernet2ip address 10.0.0.1 255.255.255.252shutdownnegotiation autono mop enabledno mop sysidendRouter#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 endRouter#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
原文链接:Python Automation on Cisco Routers in 2019 – NETCONF, YANG & Jinja2
暂无评论内容