Nornir

Nornir Result Object (IV)

Nornir Result Object (IV)
In: Nornir

So far in this Nornir series, we've covered what Nornir is, the problems it solves, and how to use it for tasks like connecting to multiple devices, managing inventory, and integrating Netmiko. In this part, we will explore the Nornir Result Object.

As we know, Nornir can run one or more tasks against one or more devices, providing results for each. But how exactly do we retrieve and interpret these results? For example, if you want to run two show commands on each device and save the outputs to a file or print the output to the terminal, how would you go about doing it?

Setting up the Environment

Since we covered the Nornir basics extensively in the previous parts, I won't spend much time on this. For the examples in this part, I will be working with three Arista devices but if you are using Cisco or any other vendor, you should be able to follow along. Here is the directory structure and the files we will be using.

.
├── config.yaml
├── defaults.yaml
├── groups.yaml
├── hosts.yaml
└── result_1.py

0 directories, 6 files
#config.yaml

---
inventory:
  plugin: SimpleInventory
  options:
    host_file: 'hosts.yaml'
    group_file: 'groups.yaml'
    defaults_file: 'defaults.yaml'

runner:
  plugin: threaded
  options:
    num_workers: 3
#hosts.yaml

---
r1:
  hostname: 192.168.100.206
  groups:
    - arista

r2:
  hostname: 192.168.100.207
  groups:
    - arista

r3:
  hostname: 192.168.100.208
  groups:
    - arista
#groups.yaml

---
arista:
  platform: arista_eos
#defaults.yaml

---
username: admin
password: admin

Introduction to Nornir Result Object

In the script below, we have two tasks, each runs a specific show commandon three Arista devices. The tasks execute sequentially - the first task retrieves the output from show ip interface brief | excl down and the second task gets the output from show ip arp, showing the ARP table entries.

After executing these commands on each device, the results are displayed in the terminal using the print_result plugin.

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

def get_output(task):
    task.run(task=netmiko_send_command, command_string='show ip interface brief | excl down')
    task.run(task=netmiko_send_command, command_string='show ip arp')

nr = InitNornir(config_file='config.yaml')
results = nr.run(task=get_output)

print_result(results)
image truncated for brevity
💡
When I say tasks run sequentially, I mean that within each device, Nornir runs the commands one after the other. Although it connects to the three devices at the same time, each task on a device is completed before the next one starts.

This works well for viewing results directly, but what if you want to capture the raw output from the device and use it for further processing? Simply printing the results variable as it is won't work. Here's what you see when you try to print it.

print(results)

AggregatedResult (get_output): {
'r1': MultiResult: [Result: "get_output", Result: "netmiko_send_command", Result: "netmiko_send_command"], 
'r2': MultiResult: [Result: "get_output", Result: "netmiko_send_command", Result: "netmiko_send_command"], 
'r3': MultiResult: [Result: "get_output", Result: "netmiko_send_command", Result: "netmiko_send_command"]
}

This output, which is of type <class 'nornir.core.task.AggregatedResult'>, consists of a collection of results for each device, each containing multiple task results.

Aggregated Result and Multi Result

In Nornir, when you run tasks across multiple devices, the results are organized into two main types - AggregatedResult and MultiResult.

  • AggregatedResult - This is a container that groups results by device. Each device entry in an AggregatedResult holds a MultiResult, which represents all the tasks that were run on that device.
  • MultiResult - A MultiResult is a list-like object containing the results of each individual task executed on a device. It helps you track each task's output and status separately within a single device's workflow.

As we discussed earlier, an AggregatedResult is a container that holds results from all tasks run against all devices. Looking into this container, you find three MultiResults

Within each MultiResult, you have three result objects (each is represented by a different colour, yellow, green, and blue in the diagram

The yellow represents our main function that contains two sub-tasks. When you examine the MultiResult for r1, it appears like a list containing three items - the first item (in yellow) is the main function, and the subsequent two are the individual sub-tasks (green and blue) generated by the function.

So, if you want to access the output for the second command (show ip arp) on r1, you'd navigate through the AggregatedResult, which is like a dictionary. You'd specify the key for the router, r1 in this case, and then look at the third item in the list (index 2) because it corresponds to the second sub-task.

print(results['r1'][2].result)

Address         Age (sec)  Hardware Addr   Interface
10.99.0.2         0:52:13  aac1.ab00.7c1d  Ethernet2

Similarly, if you want to see the first sub-task (show ip interface brief) output from r3, you would navigate through the result object like this.

print(results['r3'][1].result)
                                                                                   Address
Interface         IP Address               Status       Protocol            MTU    Owner  
----------------- ------------------------ ------------ -------------- ----------- -------
Ethernet1         172.16.1.3/24            up           up                 1500           
Ethernet2         10.99.0.6/30             up           up                 1500           
Loopback0         10.0.0.3/32              up           up                65535           
Management0       192.168.100.208/24       up           up                 1500

What Else Can We See from the Nornir Result Object?

The Nornir Result Object provides more than just raw output from devices. It includes several useful attributes related to each specific task. These attributes can tell you whether the task Failed. If it made any changes to the device and even the name of the task.

You can use the dir() function to view all available properties and methods of a result object. The following dir() method will list attributes like failed, changed, name, and more, showing you the various properties you can access to get more details about the execution of each task.

dir(results['r3'][1])

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'changed', 'diff', 'exception', 'failed', 'host', 'name', 'result', 'severity_level', 'stderr', 'stdout']
print(results['r3'][1].name)
# Output: netmiko_send_command
print(results['r3'][1].failed)
# Output: False
print(results['r3'][1].changed)
# Output: False

Closing Thoughts

I hope this all makes sense to you and if you have any comments or feedback, please let me know in the comments.

Written by
Suresh Vina
Tech enthusiast sharing Networking, Cloud & Automation insights. Join me in a welcoming space to learn & grow with simplicity and practicality.
Comments
More from Packetswitch
Table of Contents
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Packetswitch.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.