Skip to content
Back to Blog
Automation

Nornir: Python Framework for Network Automation

Use Nornir with Netmiko and NAPALM plugins for parallel network automation: inventory management, task plugins, and result processing.

Nov 2025
13 min read

Introduction

Nornir is a Python framework for network automation. Unlike Ansible, Nornir is pure Python — you write Python code (not YAML playbooks) to automate network devices. This gives you the full power of Python for complex logic, error handling, and integration with other systems.

Why Nornir?

  • Pure Python — no domain-specific language to learn
  • Multi-threaded by default — runs tasks on all devices simultaneously
  • Pluggable — supports Netmiko, NAPALM, Scrapli connections
  • Easy to integrate with databases, APIs, monitoring systems
  • Better for complex automation logic than Ansible YAML

Installation

BASH
pip install nornir nornir-utils nornir-netmiko nornir-napalm

Inventory Setup

YAML
# inventory/hosts.yaml
core-switch-01:
  hostname: 192.168.1.1
  platform: ios
  groups:
    - cisco_ios

edge-router-01:
  hostname: 192.168.1.254
  platform: ios
  groups:
    - cisco_ios

mikrotik-01:
  hostname: 192.168.1.2
  platform: routeros
  groups:
    - mikrotik

# inventory/groups.yaml
cisco_ios:
  username: admin
  password: cisco123
  connection_options:
    netmiko:
      extras:
        device_type: cisco_ios
        timeout: 30

mikrotik:
  username: admin
  password: mikrotik123
  connection_options:
    netmiko:
      extras:
        device_type: mikrotik_routeros

Basic Nornir Script

PYTHON
from nornir import InitNornir
from nornir_netmiko.tasks import netmiko_send_command
from nornir_utils.plugins.functions import print_result

# Initialize Nornir
nr = InitNornir(
    runner={"plugin": "threaded", "options": {"num_workers": 20}},
    inventory={
        "plugin": "SimpleInventory",
        "options": {
            "host_file": "inventory/hosts.yaml",
            "group_file": "inventory/groups.yaml",
        },
    },
)

# Run command on ALL devices simultaneously
result = nr.run(
    task=netmiko_send_command,
    command_string="show version"
)

# Print results
print_result(result)

Filtering Devices

PYTHON
from nornir.core.filter import F

# Filter by group
cisco_devices = nr.filter(F(groups__contains="cisco_ios"))

# Filter by hostname pattern
core_switches = nr.filter(F(hostname__startswith="192.168.1.1"))

# Filter by custom data attribute
# hosts.yaml: data: role: core
core_devices = nr.filter(F(data__role="core"))

Running Configuration Changes

PYTHON
from nornir_netmiko.tasks import netmiko_send_config

def configure_ntp(task):
    '''Configure NTP servers on Cisco IOS devices.'''
    ntp_config = [
        "ntp server 192.168.1.100 prefer",
        "ntp server 192.168.1.101",
    ]

    result = task.run(
        task=netmiko_send_config,
        config_commands=ntp_config
    )

    # Save config after change
    task.run(
        task=netmiko_send_command,
        command_string="write memory"
    )

    return result

# Run on all Cisco devices
result = nr.filter(F(groups__contains="cisco_ios")).run(task=configure_ntp)
print_result(result)

Error Handling

PYTHON
result = nr.run(task=configure_ntp)

# Check for failures
if result.failed:
    print("Failed devices:")
    for hostname, task_result in result.failed_hosts.items():
        print(f"  {hostname}: {task_result[0].exception}")

# Print only results that have issues
for hostname, task_result in result.items():
    if task_result.failed:
        print(f"FAILED: {hostname}")
    else:
        print(f"OK: {hostname}")

Collecting Data and Building Reports

PYTHON
import json
from nornir_netmiko.tasks import netmiko_send_command

def collect_interfaces(task):
    result = task.run(
        task=netmiko_send_command,
        command_string="show interfaces status"
    )
    # Parse output (or use ntc-templates for structured output)
    return result

# Collect from all devices
result = nr.run(task=collect_interfaces)

# Build inventory report
inventory_data = {}
for hostname, task_result in result.items():
    inventory_data[hostname] = task_result[0].result

with open("interface_report.json", "w") as f:
    json.dump(inventory_data, f, indent=2)

Using NAPALM for Structured Data

PYTHON
from nornir_napalm.plugins.tasks import napalm_get

# Get structured interface data (no manual parsing!)
result = nr.run(
    task=napalm_get,
    getters=["interfaces", "bgp_neighbors", "arp_table"]
)

for hostname, task_result in result.items():
    interfaces = task_result[0].result["interfaces"]
    for iface_name, iface_data in interfaces.items():
        if not iface_data["is_up"]:
            print(f"{hostname}: {iface_name} is DOWN")

Integrating with Netbox (Source of Truth)

PYTHON
import pynetbox
from nornir import InitNornir

# Pull inventory from Netbox instead of YAML files
nb = pynetbox.api("http://netbox.company.com", token="your-token")

# Build Nornir hosts from Netbox
hosts = {}
for device in nb.dcim.devices.filter(status="active"):
    hosts[device.name] = {
        "hostname": str(device.primary_ip.address).split("/")[0],
        "platform": device.platform.slug,
        "data": {
            "site": device.site.name,
            "role": device.device_role.name,
        }
    }

Summary

  • Nornir is pure Python — use it when you need complex logic
  • Multi-threaded by default — fast on large networks
  • Use Netmiko for SSH connections, NAPALM for structured data
  • Always handle failures — not all devices will respond the same way
  • Combine with Netbox for a proper source of truth for your inventory