Processing results

In this tutorial we are going to see how we can process the results of running tasks.

Let’s start with some code we have seen already:

[1]:
from nornir import InitNornir
from nornir.plugins.tasks import networking, text
from nornir.plugins.functions.text import print_title
import logging

nr = InitNornir(config_file="config.yaml", dry_run=True)
cmh = nr.filter(site="cmh", type="network_device")

def basic_configuration(task):
    # Transform inventory data to configuration via a template file
    r = task.run(task=text.template_file,
                 name="Base Configuration",
                 template="base.j2",
                 path=f"templates/{task.host.platform}",
                 severity_level=logging.DEBUG)

    # Save the compiled configuration into a host variable
    task.host["config"] = r.result

    # Deploy that configuration to the device using NAPALM
    task.run(task=networking.napalm_configure,
             name="Loading Configuration on the device",
             replace=False,
             configuration=task.host["config"],
             severity_level=logging.INFO)

Now, let’s call the task group so we can start inspecting the result object:

[2]:
result = cmh.run(task=basic_configuration)

The easy way

Most of the time you will just want to provide some feedback on what’s going on. For that you can use print_result function:

[3]:
from nornir.plugins.functions.text import print_result

print_result(result)
basic_configuration*************************************************************
* leaf00.cmh ** changed : True *************************************************
vvvv basic_configuration ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Loading Configuration on the device ** changed : True --------------------- INFO
@@ -7,6 +7,9 @@
    action bash sudo /mnt/flash/initialize_ma1.sh
 !
 transceiver qsfp default-mode 4x10G
+!
+hostname leaf00.cmh
+ip domain-name cmh.acme.local
 !
 spanning-tree mode mstp
 !
^^^^ END basic_configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* leaf01.cmh ** changed : True *************************************************
vvvv basic_configuration ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Loading Configuration on the device ** changed : True --------------------- INFO
[edit system]
-  host-name vsrx;
+  host-name leaf01.cmh;
+  domain-name cmh.acme.local;
^^^^ END basic_configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* spine00.cmh ** changed : True ************************************************
vvvv basic_configuration ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Loading Configuration on the device ** changed : True --------------------- INFO
@@ -7,6 +7,9 @@
    action bash sudo /mnt/flash/initialize_ma1.sh
 !
 transceiver qsfp default-mode 4x10G
+!
+hostname spine00.cmh
+ip domain-name cmh.acme.local
 !
 spanning-tree mode mstp
 !
^^^^ END basic_configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* spine01.cmh ** changed : True ************************************************
vvvv basic_configuration ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Loading Configuration on the device ** changed : True --------------------- INFO
[edit system]
-  host-name vsrx;
+  host-name spine01.cmh;
+  domain-name cmh.acme.local;
^^^^ END basic_configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

You should also be able to print a single host:

[4]:
print_result(result["spine00.cmh"])
vvvv basic_configuration ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Loading Configuration on the device ** changed : True --------------------- INFO
@@ -7,6 +7,9 @@
    action bash sudo /mnt/flash/initialize_ma1.sh
 !
 transceiver qsfp default-mode 4x10G
+!
+hostname spine00.cmh
+ip domain-name cmh.acme.local
 !
 spanning-tree mode mstp
 !
^^^^ END basic_configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Or even a single task:

[5]:
print_result(result["spine00.cmh"][2])
---- Loading Configuration on the device ** changed : True --------------------- INFO
@@ -7,6 +7,9 @@
    action bash sudo /mnt/flash/initialize_ma1.sh
 !
 transceiver qsfp default-mode 4x10G
+!
+hostname spine00.cmh
+ip domain-name cmh.acme.local
 !
 spanning-tree mode mstp
 !

As you probably noticed, not all the tasks were printed. If you check the tests, they all got a new argument severity_level. This let’s us flag tasks with any of the logging levels. Then print_result is able to following logging rules to print the results. By default only tasks marked as INFO will be printed (this is also the default for the tasks if none is specified). Note that a failed task will have its severity level changed to ERROR regardless of the one specified by the user.

Now let’s tell print_result to print tasks marked as DEBUG.

[6]:
print_result(result, severity_level=logging.DEBUG)
basic_configuration*************************************************************
* leaf00.cmh ** changed : True *************************************************
vvvv basic_configuration ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Base Configuration ** changed : False ------------------------------------- DEBUG
hostname leaf00.cmh
ip domain-name cmh.acme.local
---- Loading Configuration on the device ** changed : True --------------------- INFO
@@ -7,6 +7,9 @@
    action bash sudo /mnt/flash/initialize_ma1.sh
 !
 transceiver qsfp default-mode 4x10G
+!
+hostname leaf00.cmh
+ip domain-name cmh.acme.local
 !
 spanning-tree mode mstp
 !
^^^^ END basic_configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* leaf01.cmh ** changed : True *************************************************
vvvv basic_configuration ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Base Configuration ** changed : False ------------------------------------- DEBUG
system {
  host-name leaf01.cmh;
  domain-name cmh.acme.local;
}
---- Loading Configuration on the device ** changed : True --------------------- INFO
[edit system]
-  host-name vsrx;
+  host-name leaf01.cmh;
+  domain-name cmh.acme.local;
^^^^ END basic_configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* spine00.cmh ** changed : True ************************************************
vvvv basic_configuration ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Base Configuration ** changed : False ------------------------------------- DEBUG
hostname spine00.cmh
ip domain-name cmh.acme.local
---- Loading Configuration on the device ** changed : True --------------------- INFO
@@ -7,6 +7,9 @@
    action bash sudo /mnt/flash/initialize_ma1.sh
 !
 transceiver qsfp default-mode 4x10G
+!
+hostname spine00.cmh
+ip domain-name cmh.acme.local
 !
 spanning-tree mode mstp
 !
^^^^ END basic_configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* spine01.cmh ** changed : True ************************************************
vvvv basic_configuration ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Base Configuration ** changed : False ------------------------------------- DEBUG
system {
  host-name spine01.cmh;
  domain-name cmh.acme.local;
}
---- Loading Configuration on the device ** changed : True --------------------- INFO
[edit system]
-  host-name vsrx;
+  host-name spine01.cmh;
+  domain-name cmh.acme.local;
^^^^ END basic_configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The programmatic way

We have hinted at how to deal with result objects already, but let’s elaborate on that. To begin with, task groups will return an AggregatedResult. This object is a dict-like object you can use to iterate over or access hosts directly:

[7]:
result
[7]:
AggregatedResult (basic_configuration): {'spine00.cmh': MultiResult: [Result: "basic_configuration", Result: "Base Configuration", Result: "Loading Configuration on the device"], 'spine01.cmh': MultiResult: [Result: "basic_configuration", Result: "Base Configuration", Result: "Loading Configuration on the device"], 'leaf00.cmh': MultiResult: [Result: "basic_configuration", Result: "Base Configuration", Result: "Loading Configuration on the device"], 'leaf01.cmh': MultiResult: [Result: "basic_configuration", Result: "Base Configuration", Result: "Loading Configuration on the device"]}
[8]:
result.keys()
[8]:
dict_keys(['spine00.cmh', 'spine01.cmh', 'leaf00.cmh', 'leaf01.cmh'])
[9]:
result["spine00.cmh"]
[9]:
MultiResult: [Result: "basic_configuration", Result: "Base Configuration", Result: "Loading Configuration on the device"]

You probably noticed that inside each key in AggregatedResult there is a MultiResult object. This object is a list-like object you can use to iterate over or access any Result you want:

[10]:
result["spine00.cmh"][0]
[10]:
Result: "basic_configuration"

Both MultiResult and Result should clearly indicate if there was some error or change in the system:

[11]:
print("changed: ", result["spine00.cmh"].changed)
print("failed: ", result["spine00.cmh"].failed)
changed:  True
failed:  False

[12]:
print("changed: ", result["spine00.cmh"][0].changed)
print("failed: ", result["spine00.cmh"][0].failed)
changed:  False
failed:  False

You should be able to access any extra data a particular task might have set:

[13]:
print(result["spine00.cmh"][2].diff)
@@ -7,6 +7,9 @@
    action bash sudo /mnt/flash/initialize_ma1.sh
 !
 transceiver qsfp default-mode 4x10G
+!
+hostname spine00.cmh
+ip domain-name cmh.acme.local
 !
 spanning-tree mode mstp
 !

This latter will depend on the task executed so you will have to refer to the documentation of the task to see what might have been populated by it.