[1]:
from nornir import InitNornir
nr = InitNornir(config_file="config.yaml")

Executing tasks

Now that you know how to initialize nornir and work with the inventory let’s see how we can leverage it to run tasks on groups of hosts.

Nornir ships a bunch of tasks you can use directly without having to code them yourself. You can check them out here.

Let’s start by executing the ls -la /tmp command on all the device in cmh of type host:

[2]:
from nornir.plugins.tasks import commands
from nornir.plugins.functions.text import print_result

cmh_hosts = nr.filter(site="cmh", role="host")

result = cmh_hosts.run(task=commands.remote_command,
                       command="ls -la /tmp")

print_result(result, vars=["stdout"])
remote_command******************************************************************
* host1.cmh ** changed : False *************************************************
vvvv remote_command ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
total 8
drwxrwxrwt  2 root root 4096 Oct 27 14:53 .
drwxr-xr-x 24 root root 4096 Oct 27 14:53 ..

^^^^ END remote_command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* host2.cmh ** changed : False *************************************************
vvvv remote_command ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
total 8
drwxrwxrwt  2 root root 4096 Oct 27 14:54 .
drwxr-xr-x 24 root root 4096 Oct 27 14:54 ..

^^^^ END remote_command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

So what have we done here? First we have imported the commands and text modules. Then we have narrowed down nornir to the hosts we want to operate on. Once we have selected the devices we wanted to operate on we have run two tasks:

  1. The task commands.remote_command which runs the specified command in the remote device.
  2. The function print_result which just prints on screen the result of an executed task or group of tasks.

Let’s try with another example:

[3]:
from nornir.plugins.tasks import networking

cmh_spines = nr.filter(site="bma", role="spine")
result = cmh_spines.run(task=networking.napalm_get,
                        getters=["facts"])
print_result(result)
napalm_get**********************************************************************
* spine00.bma ** changed : False ***********************************************
vvvv napalm_get ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
{ 'facts': { 'fqdn': 'localhost',
             'hostname': 'localhost',
             'interface_list': ['Ethernet1', 'Ethernet2', 'Management1'],
             'model': 'vEOS',
             'os_version': '4.20.1F-6820520.4201F',
             'serial_number': '',
             'uptime': 499,
             'vendor': 'Arista'}}
^^^^ END napalm_get ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* spine01.bma ** changed : False ***********************************************
vvvv napalm_get ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
{ 'facts': { 'fqdn': 'vsrx',
             'hostname': 'vsrx',
             'interface_list': [ 'ge-0/0/0',
                                 'gr-0/0/0',
                                 'ip-0/0/0',
                                 'lsq-0/0/0',
                                 'lt-0/0/0',
                                 'mt-0/0/0',
                                 'sp-0/0/0',
                                 'ge-0/0/1',
                                 'ge-0/0/2',
                                 '.local.',
                                 'dsc',
                                 'gre',
                                 'ipip',
                                 'irb',
                                 'lo0',
                                 'lsi',
                                 'mtun',
                                 'pimd',
                                 'pime',
                                 'pp0',
                                 'ppd0',
                                 'ppe0',
                                 'st0',
                                 'tap',
                                 'vlan'],
             'model': 'FIREFLY-PERIMETER',
             'os_version': '12.1X47-D20.7',
             'serial_number': '66c3cbe24e7b',
             'uptime': 385,
             'vendor': 'Juniper'}}
^^^^ END napalm_get ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Pretty much the same pattern, just different task on different devices.

What is a task

Let’s take a look at what a task is. In it’s simplest form a task is a function that takes at least a Task object as argument. For instance:

[4]:
def hi(task):
    print(f"hi! My name is {task.host.name} and I live in {task.host['site']}")

nr.run(task=hi, num_workers=1)
hi! My name is host1.cmh and I live in cmh
hi! My name is host2.cmh and I live in cmh
hi! My name is spine00.cmh and I live in cmh
hi! My name is spine01.cmh and I live in cmh
hi! My name is leaf00.cmh and I live in cmh
hi! My name is leaf01.cmh and I live in cmh
hi! My name is host1.bma and I live in bma
hi! My name is host2.bma and I live in bma
hi! My name is spine00.bma and I live in bma
hi! My name is spine01.bma and I live in bma
hi! My name is leaf00.bma and I live in bma
hi! My name is leaf01.bma and I live in bma

[4]:
AggregatedResult (hi): {'host1.cmh': MultiResult: [Result: "hi"], 'host2.cmh': MultiResult: [Result: "hi"], 'spine00.cmh': MultiResult: [Result: "hi"], 'spine01.cmh': MultiResult: [Result: "hi"], 'leaf00.cmh': MultiResult: [Result: "hi"], 'leaf01.cmh': MultiResult: [Result: "hi"], 'host1.bma': MultiResult: [Result: "hi"], 'host2.bma': MultiResult: [Result: "hi"], 'spine00.bma': MultiResult: [Result: "hi"], 'spine01.bma': MultiResult: [Result: "hi"], 'leaf00.bma': MultiResult: [Result: "hi"], 'leaf01.bma': MultiResult: [Result: "hi"]}

The task object has access to nornir, host and dry_run attributes.

You can call other tasks from within a task:

[5]:
def available_resources(task):
    task.run(task=commands.remote_command,
             name="Available disk",
             command="df -h")
    task.run(task=commands.remote_command,
             name="Available memory",
             command="free -m")

result = cmh_hosts.run(task=available_resources)

print_result(result, vars=["stdout"])
available_resources*************************************************************
* host1.cmh ** changed : False *************************************************
vvvv available_resources ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Available disk ** changed : False ----------------------------------------- INFO
Filesystem                  Size  Used Avail Use% Mounted on
/dev/mapper/precise64-root   79G  2.2G   73G   3% /
udev                        174M  4.0K  174M   1% /dev
tmpfs                        74M  284K   73M   1% /run
none                        5.0M     0  5.0M   0% /run/lock
none                        183M     0  183M   0% /run/shm
/dev/sda1                   228M   25M  192M  12% /boot
vagrant                     373G  251G  122G  68% /vagrant

---- Available memory ** changed : False --------------------------------------- INFO
             total       used       free     shared    buffers     cached
Mem:           365         87        277          0          8         36
-/+ buffers/cache:         42        322
Swap:          767          0        767

^^^^ END available_resources ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* host2.cmh ** changed : False *************************************************
vvvv available_resources ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Available disk ** changed : False ----------------------------------------- INFO
Filesystem                  Size  Used Avail Use% Mounted on
/dev/mapper/precise64-root   79G  2.2G   73G   3% /
udev                        174M  4.0K  174M   1% /dev
tmpfs                        74M  284K   73M   1% /run
none                        5.0M     0  5.0M   0% /run/lock
none                        183M     0  183M   0% /run/shm
/dev/sda1                   228M   25M  192M  12% /boot
vagrant                     373G  251G  122G  68% /vagrant

---- Available memory ** changed : False --------------------------------------- INFO
             total       used       free     shared    buffers     cached
Mem:           365         87        277          0          8         36
-/+ buffers/cache:         42        322
Swap:          767          0        767

^^^^ END available_resources ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

You probably noticed in your previous example that you can name your tasks.

Your task can also accept any extra arguments you may need:

[6]:
def count(task, to):
    print(f"{task.host.name}: {list(range(0, to))}")

cmh_hosts.run(task=count,
              num_workers=1,
              to=10)
cmh_hosts.run(task=count,
              num_workers=1,
              to=20)
host1.cmh: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
host2.cmh: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
host1.cmh: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
host2.cmh: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

[6]:
AggregatedResult (count): {'host1.cmh': MultiResult: [Result: "count"], 'host2.cmh': MultiResult: [Result: "count"]}

Tasks vs Functions

You probably noticed we introduced the concept of a function when we talked about print_result. The difference between tasks and functions is that tasks are meant to be run per host while functions are helper functions meant to be run globally.