Nornir Python Tutorial: Get Started With Network Automation

As networks grow in size and scope, automation has risen to the forefront of network management strategy. The ability to schedule, execute, and monitor tasks from a central location is a powerful tool for improving productivity, minimizing human error, and enhancing the overall performance of your network. And when it comes to native Python network automation, there’s one tool that stands out from the crowd: Nornir. 

Nornir is a network automation framework that provides a powerful, efficient, and flexible solution to network automation tasks. In this post you’ll see how to get started with Nornir, including integrating it with NetBox and querying a network device for configuration information. 

nornir pull quote 1

What Is Nornir?

Nornir is an open-source Python-based network automation and orchestration framework. It’s designed for automating and managing tasks across multiple network devices. It makes it easy to harness the power of Python programming for managing network infrastructure. 

Interested in joining a live overview or Nornir and how to use it for network automation? Join our webinar and demo on Tuesday, October 17. Register here: https://netboxlabs.com/events/live-webinar-getting-started-with-nornir-and-netbox-for-network-automation/

Nornir’s Core Capabilities

  1. Concurrency: Nornir has the ability to launch tasks concurrently. So, it can manage operations on multiple devices at the same time, increasing efficiency and speed.
  2. Plugins: Nornir has a pluggable architecture, which makes it possible to use plugins to adapt it to your specific needs. This includes inventory plugins to manage your devices, task plugins for executing jobs, and processor plugins to handle the results of your tasks.
  3. Pythonic: As a Python framework, Nornir can leverage Python’s capabilities, allowing for more flexible and powerful operations. You can use Python libraries, Python data types and structures, exception handling, etc., within your automation scripts.

Nornir Use Cases

Nornir is a robust tool for automating tasks such as configuration management, network discovery, state validation, and network deployment. This involves pushing configuration changes to hundreds of devices, extracting device information, or validating network states against a defined baseline. 

Nornir has several advantages over tools like Ansible, Salt, and Puppet: 

  1. Performance: With its capability to handle tasks concurrently, Nornir provides a significant performance boost compared to linear, node-by-node tools.
  2. Flexibility: Nornir is a Python framework; you can use Python libraries to tailor your network automation tasks to your specific needs. You’ll use Napalm, another Python automation library from the same developer, with Nornir to manage network devices such as Cisco routers.
  3. Integration: Being a Python framework also means that you can easily integrate Nornir with other systems due to its Python nature. We’ll demonstrate this below with NetBox.
  4. Learning curve: Many network automations use a domain-specific language (DSL), but with Nornir, if you’re already familiar with Python, you get started right away. You also have the full power of Python at your disposal, rather than being limited by the capabilities of a DSL.

nornir network

Nornir for Network Automation

Install Nornir

First, create a Python environment and install the libraries you’ll need to integrate Nornir and NetBox. 

Create a new directory and create a virtual environment in it. 

[egoebelbecker@ares src]$ mkdir nornir_netbox
[egoebelbecker@ares src]$ cd nornir_netbox
[egoebelbecker@ares nornir_netbox]$ python3 -m venv ./venv[egoebelbecker@ares nornir_netbox]$ source venv/bin/activate(venv) [egoebelbecker@ares nornir_netbox]$ 

You’ll need both Nornir and Napalm to manage nodes. Install these libraries, as well as the modules needed to integrate them. 

  • napalm
  • nornir
  • nornir-napalm
  • nornir-netbox
  • nornir-utils

This command will generate a lot of output and take a few minutes to complete. 

(venv) [egoebelbecker@ares nornir_netbox]$ pip install napalm nornir nornir-napalm nornir-netbox nornir-utils

Collecting napalmUsing cached napalm-4.1.0-py2.py3-none-any.whl (267 kB)
Collecting nornir
Using cached nornir-3.3.0-py3-none-any.whl (30 kB)
Collecting nornir-napalm
Using cached nornir_napalm-0.4.0-py3-none-any.whl (11 kB)
Collecting nornir-netbox
Using cached nornir_netbox-0.3.0-py3-none-any.whl (9.4 kB)
Collecting nornir-utils
Using cached nornir_utils-0.2.0-py3-none-any.whl (15 kB)
Requirement already satisfied: setuptools>=38.4.0 in ./venv/lib64/python3.11/site-packages (from napalm) (62.6.0)
Collecting cffi>=1.11.3
Using cached cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (462 kB)
Collecting paramiko>=2.6.0
Using cached paramiko-3.2.0-py3-none-any.whl (224 kB)

(output snipped for space)

We’ll return to this environment after installing and configuring NetBox. 

Install and Configure NetBox

The fastest and easiest way to run NetBox is with Docker Compose. You’ll find a working configuration here on GitHub. 

Clone this repository and follow the README to get a local NetBox instance running. 

The basic steps look like this: 

git clone -b release https://github.com/netbox-community/netbox-docker.git
cd netbox-docker
tee docker-compose.override.yml <<EOF
version: '3.4'
services:
netbox:
ports:
- 8000:8080
EOF

docker compose pull
docker compose up

Then, add the admin user from the command line. 

$ docker compose exec netbox /opt/netbox/netbox/manage.py createsuperuser
🧬 loaded config '/etc/netbox/config/configuration.py'
🧬 loaded config '/etc/netbox/config/extra.py'
🧬 loaded config '/etc/netbox/config/logging.py'
🧬 loaded config '/etc/netbox/config/plugins.py'
Username (leave blank to use 'unit'): admin
Email address: foo@foo.com
Password:
Password (again): 
Superuser created successfully.

NetBox Configuration

Navigate to your server with a browser and log in with the Superuser you added. 

You’ll see an empty inventory screen: 

netbox configuration

Now, it’s time to add a network device to your inventory so you can manage it with Nornir. 

This involves creating a site and defining device roles and types. Start by adding these entities to NetBox. 

First, navigate to Organization and add a site: 

organization

Now, with a site defined, you can add a device. 

This starts by adding a device manufacturer. Go to Devices and start there. 

devices

Now, create a Device Type: 

device type

Finally, create a Device Role. 

Be sure not to use dashes in the Slug field. 

device role

Add a Platform

Finally, you’ll need a Platform. Nornir and Napalm will use this field to determine how to manage your device. Add a platform named cisco_iosxr. 

platform

Add a Device to NetBox

Now, with the supporting fields created, you can add a device from the Devices menu. 

Add a router named sandbox-iosxr-1.cisco.com. This router is part of Cisco DevNet Sandbox

Be sure to add the cisco_iosxr platform you created above. 

device to netbox

NetBox API Key

Nornir requires an API token to connect to NetBox. 

Select API Tokens from the user menu, then select Add a Token from the API Tokens screen. 

netbox token

Fill out the form, leaving the Key and Allowed IPs blank. 

In a production environment, you would want to specify these values in order to limit the hosts that have access to this token. 

add a new token

Click Create, and you have a token.   

create api token

You’ll use your token to initialize Nornir. 

Network Automation with Nornir

Now, let’s connect Nornir and NetBox. 

Nornir and NetBox Inventory

The first step is to initialize Nornir. You do this by passing InitNornir a configuration file or an options dictionary. 

This code passes in an options dictionary with three values: 

  • NetBox server URL
  • NetBox API Token
  • An optional flag to ignore the NetBox server’s SSL status.

After it initializes Nornir, the script examines the Nornir inventory nr.inventory with a loop on line #15 that examines each host in the hosts field. 

from nornir import InitNornir
from pprint import pprint
nr = InitNornir(
    inventory={
        "plugin":"NetBoxInventory2",
        "options": {
            "nb_url": "http://localhost:8000",
            "nb_token": "{your token here}",
            "ssl_verify": False,
        }
    }
)
for host in nr.inventory.hosts.values():
    pprint(host.dict())

Here’s the output: 

(venv) [egoebelbecker@ares nornir_netbox]$ python inventory.py
{'connection_options': {},
 'data': {'airflow': {'label': 'Front to rear', 'value': 'front-to-rear'},
          'asset_tag': '123456',
          'cluster': None,
          'comments': '',
          'config_context': {},
          'created': '2023-05-05T18:14:57.270763Z',
          'custom_fields': {},
          'description': '',
          'device_role': {'display': 'Core Router',
                          'id': 2,
                          'name': 'Core Router',
                          'slug': 'core_router',
                          'url': 'http://localhost:8000/api/dcim/device-roles/2/'},
          'device_type': {'display': 'Cisco IOS-XRv 9000',
                          'id': 2,
                          'manufacturer': {'display': 'Cisco',
                                           'id': 2,
                                           'name': 'Cisco',
                                           'slug': 'cisco',
                                           'url': 'http://localhost:8000/api/dcim/manufacturers/2/'},
                          'model': 'Cisco IOS-XRv 9000',
                          'slug': 'cisco-ios-xrv-9000',
                          'url': 'http://localhost:8000/api/dcim/device-types/2/'},
          'display': 'sandbox-iosxr-1.cisco.com (123456)',
          'face': None,
          'id': 2,
          'last_updated': '2023-05-05T20:27:07.441840Z',
          'local_context_data': None,
          'location': None,
          'name': 'sandbox-iosxr-1.cisco.com',
          'parent_device': None,
          'platform': {'display': 'cisco_iosxr',
                       'id': 1,
                       'name': 'cisco_iosxr',
                       'slug': 'iosxr',
                       'url': 'http://localhost:8000/api/dcim/platforms/1/'},
          'position': None,
          'primary_ip': None,
          'primary_ip4': None,
          'primary_ip6': None,
          'rack': None,
          'serial': 'B550ED1D0D9',
          'site': {'display': 'Trick of the Tale',
                   'id': 1,
                   'name': 'Trick of the Tale',
                   'slug': 'trick-of-the-tale',
                   'url': 'http://localhost:8000/api/dcim/sites/1/'},
          'status': {'label': 'Active', 'value': 'active'},
          'tags': [],
          'tenant': None,
          'url': 'http://localhost:8000/api/dcim/devices/2/',
          'vc_position': None,
          'vc_priority': None,
          'virtual_chassis': None},
 'groups': ['site__trick-of-the-tale',
            'platform__iosxr',
            'device_role__core_router',
            'manufacturer__cisco',
            'device_type__cisco-ios-xrv-9000'],
 'hostname': 'sandbox-iosxr-1.cisco.com',
 'name': 'sandbox-iosxr-1.cisco.com',
 'password': None,
 'platform': 'cisco_iosxr',
 'port': None,
 'username': None}

So, with a single line of code, you can connect Nornir to your NetBox inventory! 

Managing a Host with Nornir

Now, let’s use Nornir and Napalm to connect to the router and query it for device specific information (facts), such as serial number, model, interfaces, etc. 

This script imports Napalm and retrieves the proper driver to communicate with the Cisco device. 

Then it loops through each host in the inventory and checks the platform type. If it matches the driver type, it initializes it inside the loop. 

The driver needs a username and password to communicate with the device. For this example, they’re hard-coded, along with the hostname. In production code you’d read the hostname from configuration and retrieve the secrets from a vault or other secrets manager to supply the credentials at runtime. 

On line #21 the script connects to the device, then it calls get_facts() to retrieve information from the device. 

from nornir import InitNornir
import napalm
from pprint import pprint
nr = InitNornir(
    inventory={
        "plugin":"NetBoxInventory2",
        "options": {
            "nb_url": "http://localhost:8000",
            "nb_token": "f10396fef59bf007cae2dd43bf21cc35b9581381",
            "ssl_verify": False,
        }
    }
)
driver_iosxr = napalm.get_network_driver("iosxr")
for host in nr.inventory.hosts.values():
    if host.platform == "cisco_iosxr":
        device = driver_iosxr(hostname="sandbox-iosxr-1.cisco.com", username="XXX", password="XXX")
        device.open()
        device_facts = device.get_facts()
        pprint(device_facts)

Here's the output: 

(venv) [egoebelbecker@ares nornir_netbox]$ python inventory.py 
{'fqdn': 'iosxr1',
 'hostname': 'iosxr1',
 'interface_list': ['BVI507',
                    'Bundle-Ether11',
                    'Bundle-Ether12',
                    'Bundle-Ether13',
                    'Bundle-Ether14',
                    'Bundle-Ether14.23',
                    'Bundle-Ether14.55',
                    'Bundle-Ether15',
                    'Bundle-Ether16',
                    'Bundle-Ether17',
                    'Bundle-Ether18',
                    'Bundle-Ether19',
                    'Bundle-Ether20',
                    'Bundle-Ether40'
                    'Bundle-Ether444',
                    'GigabitEthernet0/0/0/0',
                    'GigabitEthernet0/0/0/0.200',
                    'GigabitEthernet0/0/0/0.375',
                    'GigabitEthernet0/0/0/1',
                    'GigabitEthernet0/0/0/2',
                    'GigabitEthernet0/0/0/3',
                    'GigabitEthernet0/0/0/4',
                    'GigabitEthernet0/0/0/5',
                    'GigabitEthernet0/0/0/6',
                    'Loopback0',
                    'Loopback100',
                    'Loopback111',
                    'Loopback300',
                    'Loopback301',
                    'Loopback302',
                    'Loopback303',
                    'Loopback304',
                    'Loopback34',
                    'Loopback35',
                    'Loopback400',
                    'Loopback401',
                    'Loopback404',
                    'Loopback405',
                    'Loopback44',
                    'Loopback555',
                    'Loopback99',
                    'MgmtEth0/RP0/CPU0/0',
                    'Null0'],
 'model': 'R-IOSXRV9000-CC',
 'os_version': '7.3.2',
 'serial_number': 'B550ED1D0D9',
 'uptime': 421328.0,
 'vendor': 'Cisco'}

By adding code to load the correct driver for all the devices in your inventory, you could use this script as the foundation for a tool to retrieve device data across your entire network. 

Nornir Pull quote 02

Python Native Network Automation With Nornir

This brief tutorial demonstrated how simple it is to integrate Nornir with NetBox and use this link to manage devices. With a few lines of code, we retrieved an inventory from NetBox, examined device properties, and used the properties to determine how to connect to and manage a device. 

This is just a hint of what you can accomplish with Nornir, Napalm, and NetBox. With access to your device inventory and the power of Python at your fingertips, you can update and backup configuration, deploy new devices, upgrade OS versions, and more. For more network automation tutorials, events and news, join our mailing list.  

This post was written by Eric Goebelbecker. Eric has worked in the financial markets in New York City for 25 years, developing infrastructure for market data and financial information exchange (FIX) protocol networks. He loves to talk about what makes teams effective (or not so effective!).

Share the Post:

Related Posts