router image

Using Nornir for OS Upgrades (Part 1)

By Kirk Byers
2019-05-02

Note: This is an updated version of the original "Using Nornir for OS Upgrades (Part 1)" article dated 2018-08-28. This article has been updated to reflect Nornir 2.x inventory format.


Previously, I wrote an article that Introduces Nornir

In this article, I want to expand on the Nornir introduction and do something more interesting. In particular, I want to use Nornir to Secure Copy files to a set of network devices. I then want to use that file transfer process and some additional code to perform an OS upgrade on two Cisco IOS routers (part 2).

I've written a short script with two helper functions that I'll be using in this article.

If you'd like to follow along you can simply 'wget' this file from GitHub and place it in the same directory as your scripts. Additionally, at the time of writing this article, you will potentially need to install the Netmiko develop branch to fix an NX-OS file transfer issue.


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'). Also note, I had to add quotes to some of my YAML strings to make it render reasonably on the web.


My groups.yaml is as follows:

---
cisco-ios:
  platform: 'cisco_ios'
  data:
    img: 'c880data-universalk9-mz.155-3.M8.bin'
    backup_img: 'c880data-universalk9-mz.154-2.T1.bin'

arista:
  platform: 'arista_eos'
  data:
    img: 'test_arista.txt'

nxos:
  platform: 'cisco_nxos'
  data:
    img: 'test_nxos.txt'   

I could have set the username and password in a 'defaults.yaml' section (as shown in the Nornir introduction article), 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 (twelve total devices, three 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).

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 import InitNornir
from nornir.plugins.tasks.networking import netmiko_file_transfer    

I also have a couple of minor functions that I am importing (from the "nornir_utilities" script): 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):

nr = InitNornir()   

I then call my nornir_set_creds(nr) 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 import InitNornir
from nornir.plugins.tasks.networking import netmiko_file_transfer

from nornir_utilities import nornir_set_creds, std_print


# Initialize Nornir object using default "SimpleInventory" plugin
nr = InitNornir()
nornir_set_creds(nr)
test_file = 'test_xfer.txt'

result = nr.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 nr.run() call a bit more closely. 'nr' 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: admin
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 transferred to all twelve of the remote devices.


At this point, we have a Nornir script that transfers a text file to twelve remote devices for three different platforms. In part two of this series (here), I expand on this to transfer the OS image for Cisco IOS, set and verify the boot variable, and then reload the remote device.



Reference code and inventory files used in this article (with some minor modifications).




If you want to learn more about network automation, Python, and Ansible—then join my email-list. I also periodically run a free Python for Network Engineers email course which you can sign-up for here.

router image

Kirk Byers

router image

Using Nornir for OS Upgrades (Part 1)

By Kirk Byers
2019-05-02

Note: This is an updated version of the original "Using Nornir for OS Upgrades (Part 1)" article dated 2018-08-28. This article has been updated to reflect Nornir 2.x inventory format.


Previously, I wrote an article that Introduces Nornir

In this article, I want to expand on the Nornir introduction and do something more interesting. In particular, I want to use Nornir to Secure Copy files to a set of network devices. I then want to use that file transfer process and some additional code to perform an OS upgrade on two Cisco IOS routers (part 2).

I've written a short script with two helper functions that I'll be using in this article.

If you'd like to follow along you can simply 'wget' this file from GitHub and place it in the same directory as your scripts. Additionally, at the time of writing this article, you will potentially need to install the Netmiko develop branch to fix an NX-OS file transfer issue.


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'). Also note, I had to add quotes to some of my YAML strings to make it render reasonably on the web.


My groups.yaml is as follows:

---
cisco-ios:
  platform: 'cisco_ios'
  data:
    img: 'c880data-universalk9-mz.155-3.M8.bin'
    backup_img: 'c880data-universalk9-mz.154-2.T1.bin'

arista:
  platform: 'arista_eos'
  data:
    img: 'test_arista.txt'

nxos:
  platform: 'cisco_nxos'
  data:
    img: 'test_nxos.txt'   

I could have set the username and password in a 'defaults.yaml' section (as shown in the Nornir introduction article), 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 (twelve total devices, three 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).

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 import InitNornir
from nornir.plugins.tasks.networking import netmiko_file_transfer    

I also have a couple of minor functions that I am importing (from the "nornir_utilities" script): 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):

nr = InitNornir()   

I then call my nornir_set_creds(nr) 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 import InitNornir
from nornir.plugins.tasks.networking import netmiko_file_transfer

from nornir_utilities import nornir_set_creds, std_print


# Initialize Nornir object using default "SimpleInventory" plugin
nr = InitNornir()
nornir_set_creds(nr)
test_file = 'test_xfer.txt'

result = nr.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 nr.run() call a bit more closely. 'nr' 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: admin
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 transferred to all twelve of the remote devices.


At this point, we have a Nornir script that transfers a text file to twelve remote devices for three different platforms. In part two of this series (here), I expand on this to transfer the OS image for Cisco IOS, set and verify the boot variable, and then reload the remote device.



Reference code and inventory files used in this article (with some minor modifications).




If you want to learn more about network automation, Python, and Ansible—then join my email-list. I also periodically run a free Python for Network Engineers email course which you can sign-up for here.

router image

Kirk Byers