Previously I wrote an article on Introducing Nornir.
In this article, I want to expand on Nornir and do something more interesting.
Unfortunately, I am going to switch from using Nornir 1.1 to using Nornir 2.0. There are some meaningful differences between the two versions. Additionally, Nornir 2.0 is not released yet so I am going to be working out of the 2.0 branch in GitHub.
Nornir 2.0 Install Process
[All in a Python virtual environment] $ git clone https://github.com/nornir-automation/nornir Cloning into 'nornir'... # CD into the repo $ cd nornir/ # Verify which branch I am on $ git branch * develop $ git fetch origin # Create a 2.0 branch that tracks the origin/2.0 branch $ git checkout -b 2.0 origin/2.0 Branch 2.0 set up to track remote branch 2.0 from origin. Switched to a new branch '2.0' # Verify we are on the 2.0 branch $ git branch * 2.0 develop # Install Nornir and its dependencies (including development dependencies) $ pip install -r ./requirements-dev.txt $ pip install -e .
Nornir Inventory
Now that I have Nornir installed, let’s look at my Nornir Inventory.
Note, as per my previous Nornir Introduction article, I am using the Nornir SimpleInventory plugin. Here, I am going to show you my new inventory files (hosts.yaml and groups.yaml files). In this environment, I have two Cisco IOS routers, eight Arista vEOS switches, and two NX-OSv switches.
My hosts.yaml file is as follows:
--- pynet-rtr1: hostname: cisco1.domain.com groups: - cisco-ios pynet-rtr2: hostname: cisco2.domain.com groups: - cisco-ios arista1: hostname: arista1.domain.com groups: - arista arista2: hostname: arista2.domain.com groups: - arista arista3: hostname: arista3.domain.com groups: - arista arista4: hostname: arista4.domain.com groups: - arista arista5: hostname: arista5.domain.com groups: - arista arista6: hostname: arista6.domain.com groups: - arista arista7: hostname: arista7.domain.com groups: - arista arista8: hostname: arista8.domain.com groups: - arista nxos1: hostname: nxos1.domain.com groups: - nxos nxos2: hostname: nxos2.domain.com groups: - nxos
Note, each host belongs to a group that corresponds to its platform-type (‘cisco-ios’, ‘arista’, ‘nxos’).
My groups.yaml is as follows:
--- defaults: {} # username: admin # password: cisco-ios: platform: cisco_ios img: c880data-universalk9-mz.155-3.M8.bin backup_img: c880data-universalk9-mz.154-2.T1.bin arista: platform: arista_eos img: test_arista.txt nxos: platform: cisco_nxos img: test_nxos.txt
I could have set the username and password in the ‘defaults’ section (as shown by my commented out lines), but instead I am going to pass the username and password in using a function. This is so I can store my inventory in GitHub without having the credential embedded in my inventory.
The ‘platform’ specified in the inventory was intentionally chosen to match the Netmiko ‘device_type’.
Finally, the ‘img’ variable indicates the filename that I am going to transfer. In this article, I am ultimately going to transfer a real image file to the Cisco IOS devices and just .txt files to the other devices.
Different platforms have different OS upgrade processes. I am going to show the file transfer process (Secure Copy) for all of the above devices (12 total devices, 3 different platforms).
After that, I will show the remaining steps that would be needed to complete the OS upgrade on the two Cisco IOS device. This second part will be in a later article.
File Transfer to the Twelve Devices
Now let’s get a file transfer working to the twelve devices. At this point, I am going to transfer a simple text file as it will be much quicker to test and validate (i.e. the file transfer and MD5 will happen much faster).
Now Nornir has a set of imports that we need to perform in our Python code. This includes InitNornir which simplifies the Nornir initialization (basically the use of our SimpleInventory and the creation of a Nornir object). Additionally, we are going to need to import the ‘netmiko_file_transfer’ plugin. Our imports initially look as follows:
from nornir.core import InitNornir from nornir.plugins.tasks.networking import netmiko_file_transfer
I also have a couple of minor functions that I am importing: one that handles setting the username/password and one that prints out the output from the task. I add the following import:
from nornir_utilities import nornir_set_creds, std_print
I then need to create the Nornir object (remember this is what parses my inventory):
norn = InitNornir(config_file="nornir.yml")
I then call my nornir_set_creds(norn) function passing in the Nornir object.
At this point, I am ready to execute the ‘netmiko_file_transfer’ task.
Nornir has threading automatically built into it so Nornir will automatically transfer the file to the twelve devices in parallel.
My entire code at this point is as follows:
from nornir.core import InitNornir from nornir.plugins.tasks.networking import netmiko_file_transfer from nornir_utilities import nornir_set_creds, std_print # Initialize Nornir object using hosts.yaml and groups.yaml norn = InitNornir(config_file="nornir.yml") nornir_set_creds(norn) test_file = 'test_file4.txt' result = norn.run( task=netmiko_file_transfer, source_file=test_file, dest_file=test_file, direction='put', num_workers=20, ) std_print(result)
Now let’s look at this norn.run() call a bit more closely. ‘norn’ is the Nornir object and then you call the ‘run’ method on that object. Internally, this will cause Nornir to run the task that we specify generally concurrently. The task is the first argument that you pass into ‘run’. Above, our task is the ‘netmiko_file_transfer’ task.
You can find the tasks that are built-into Nornir here and you can also create your own tasks.
Note, there is a bit of magic that happens when we execute this task. Behind the scenes Nornir automatically creates a Netmiko connection that will be open for the duration of the script. There are ways you can manually control the creation/closing of the Netmiko connections. The three arguments ‘source_file’, ‘dest_file’, and ‘direction’ are all passed into the netmiko_file_transfer task and tell Netmiko what to do.
You can look at the netmiko_file_transfer plugin code and see that I use Netmiko’s file_transfer() function and that I pass the arguments it (including **kwargs arguments).
Now the final argument ‘num_workers=20’ tells Nornir-run() to use a thread pool of size twenty.
One thing to note here is that for debugging with Pdb, it is very helpful to set num_workers=1 (i.e. to disable concurrency). Also Pdb is going to be very helpful when using Nornir. See this earlier article I wrote on Pdb.
Let’s now execute this entire program and see what happens:
$ python netmiko_file_transfer.py Enter username: pyclass Password: -------------------------------------------------- pynet-rtr1 True -------------------------------------------------- -------------------------------------------------- pynet-rtr2 True -------------------------------------------------- -------------------------------------------------- arista1 True -------------------------------------------------- -------------------------------------------------- arista2 True -------------------------------------------------- -------------------------------------------------- arista3 True -------------------------------------------------- -------------------------------------------------- arista4 True -------------------------------------------------- -------------------------------------------------- arista5 True -------------------------------------------------- -------------------------------------------------- arista6 True -------------------------------------------------- -------------------------------------------------- arista7 True -------------------------------------------------- -------------------------------------------------- arista8 True -------------------------------------------------- -------------------------------------------------- nxos1 True -------------------------------------------------- -------------------------------------------------- nxos2 True --------------------------------------------------
I also verified that the file was, in fact, transferred to all twelve of the remote devices.
At this point, we have a Nornir script that transfers the same text file to twelve remote devices for three different platforms. In a subsequent article, I will expand on this and handle transferring the image for Cisco IOS, setting and verifying the boot variable, and handle the reload operation. I have already done this, but I still need to create an article for it.
Reference code and inventory files used in this article (with some very minor modifications).