Using nornir to provision SROS
I wanted to explore Nornir as an alternative to configure my SROS lab. There are very few examples of people using Nornir for Nokia SR, so I will post this here, and hopefully, someone has some use for it.
This post will show you how to install Nornir and some simple examples for configuring Nokia SROS using NETCONF. Since this is my first time using Nornir, you probably shouldn't expect my examples to be best practice, but at least they work :)
The basics, setting up virtual environment and installation of Nornir
I started by creating a virtual environment for python on my Linux server. Then I installed Nornir in this virtual environment.
Create the virtual environment
larsg@e-syslog:~/nornir$ virtualenv virtnornir
created virtual environment CPython3.7.5.final.0-64 in 199ms
creator CPython3Posix(dest=/home/larsg/nornir/virtnornir, clear=False, global=False)
seeder FromAppData(download=False, pip=latest, setuptools=latest, wheel=latest, via=copy, app_data_dir=/home/larsg/.local/share/virtualenv/seed-app-data/v1.0.1)
activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
larsg@e-syslog:~/nornir$ ls
virtnornir
Activate the virtualenv
Install nornir
(virtnornir) larsg@e-syslog:~/nornir$ pip install nornir
Collecting nornir
Using cached nornir-2.4.0-py3-none-any.whl (136 kB)
Collecting mypy_extensions<0.5.0,>=0.4.1
Using cached mypy_extensions-0.4.3-py2.py3-none-any.whl (4.5 kB)
Collecting typing_extensions<4.0,>=3.7
Using cached typing_extensions-3.7.4.2-py3-none-any.whl (22 kB)
Collecting colorama<0.5.0,>=0.4.1
Using cached colorama-0.4.3-py2.py3-none-any.whl (15 kB)
Collecting ruamel.yaml<0.17,>=0.16
Using cached ruamel.yaml-0.16.10-py2.py3-none-any.whl (111 kB)
Collecting jinja2<3,>=2
Using cached Jinja2-2.11.2-py2.py3-none-any.whl (125 kB)
Collecting requests<3,>=2
Downloading requests-2.24.0-py2.py3-none-any.whl (61 kB)
|████████████████████████████████| 61 kB 693 kB/s
Collecting netmiko>=2.4
<output omitted>
First look at Nornir
This is a sample script that has the inventory in the script. You can also use files or get the inventory via external APIs, but for my first tests, I used the built-in capabilities of Nornir. I define "r1" and that NETCONF is available on port 830. The script then connects to my 7750 and gets the description from port 1/1/c1/1. I had some trouble getting the syntax of the XML correct, but the below configuration works.
After printing the output from the description on port 1/1/c1/1 the script does the following:
- Lock the candidate configuration
- Applies the configuration from the "snippet" variable
- Commits the configuration
- Unlocks the candidate config
After that, the script reads the description of port 1/1/c1/1 again, and prints it out so we can verify that the change happened.
First, import Nornir and some functions. Then the simple definition of my 7x50 router, R1.
from nornir import InitNornir
from nornir.core.task import Result, Task
from nornir.plugins.functions.text import print_result
nr = InitNornir(
inventory={
"options": {
"hosts": {
"r1": {
"hostname": "r1",
"port": 830,
"platform": "SROS",
"connection_options": {
"netconf": {"extras": {"hostkey_verify": False}}
},
}
}
}
}
)
Portfilter definition and NETCONF connection.
Here I define “netconf_code” function to run a Nornir task. We then connect to the 7750 SR using NETCONF via the "task.host.get_connection". “portfilter” is the actual string we send to the SR. It filters out just the description of port 1/1/c1/1. The last line then prints the result of the NETCONF execution.
def netconf_code(task: Task) -> Result:
manager = task.host.get_connection("netconf", task.nornir.config)
# Define the port description filter:
portfilter= "<configure xmlns=\"urn:nokia.com:sros:ns:yang:sr:conf\"><port><port-id>1/1/c1/1</port-id><description></description></port></configure>"
#Retrive the current description
print(manager.get(filter=("subtree", portfilter)))
Changing the port description
The first thing we do is locking the candidate configuration. Then we have the actual XML snippet that sets port description on port 1/1/c1/1.
#Lock the candidate configuration
manager.lock("candidate")
snippet = """
<config>
<configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf">
<port operation="merge">
<port-id>1/1/c1/1</port-id>
<description>NORNIR-TEST</description>
</port>
</configure>
</config>
"""
#Edit the configuration
res = manager.edit_config(target="candidate",config=snippet)
#Print
print(res)
Committing the change and execution of the task
Here we commit the changes, unlock the candidate configuration, and then run the portfilter bit again. The final line is Nornirs built-in function, "print_result" that outputs the actual result of the task execution.
#Commit the changes
manager.commit()
#Unlock candidate configuration
manager.unlock("candidate")
#Retrive the current description
print(manager.get(filter=("subtree", portfilter)))
print_result(nr.run(task=netconf_code))
Running the script:
Here is the output from the first part, where we get the description of port 1/1/c1/1.
(virtnornir) larsg@e-syslog:~/nornir$ python base.py
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply message-id="urn:uuid:04bd1120-da2a-4bf3-89e3-4261ad233db1" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf">
<port>
<port-id>1/1/c1/1</port-id>
<description>EMPTY</description>
</port>
</configure>
</data>
</rpc-reply>
Reply
Below is the output of the change operation.
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply message-id="urn:uuid:6294a879-44d9-4b98-9c33-d75e145087c3" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
Final output
Here is the final result, where we once more query the SR for the port description of port 1/1/c1/1. We can see that it changed to NORNIR-TEST. The output after that is the result of the "print_result" function in Nornir.
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply message-id="urn:uuid:f0a96857-e536-4f49-99f0-f96ff4873d42" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf">
<port>
<port-id>1/1/c1/1</port-id>
<description>NORNIR-TEST</description>
</port>
</configure>
</data>
</rpc-reply>
netconf_code********************************************************************
* r1 ** changed : False ********************************************************
vvvv netconf_code ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
^^^^ END netconf_code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Summary
And that’s it. This is a very brief intro to Nornir and how to use it to provision Nokia SROS. I will continue with some more advanced scripts and configuration in another blog post. In the next Nornir post, I will demonstrate how to use inventory from a file, and using Jinja2 templates to configure Nokia 7750 SR.