[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:
- The task
commands.remote_command
which runs the specifiedcommand
in the remote device. - 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.