处理任务结果
在这节中一起来看一下如何处理任务(Tasks)的运行结果。
先看下面的示例:
[1]:
import logging
from nornir import InitNornir
from nornir.core.task import Task, Result
nr = InitNornir(config_file="files/config.yaml")
spine_bj = nr.filter(site="bj", role="spine")
def count(task: Task, number: int) -> Result:
return Result(
host=task.host,
result=f"{[n for n in range(0, number)]}"
)
def say(task: Task, text: str) -> Result:
if task.host.name == "spine01.bj":
raise Exception(f"{task.host.name} 不能输出信息")
return Result(
host=task.host,
result=f"{task.host.name} says {text}"
)
这个示例与之前示例的区别是:通过 if 判断让主机 spine01.bj
强制抛出了一个错误信息。
再继续编写任务组:
[2]:
def greet_and_count(task: Task, number: int) -> Result:
task.run(
name="你好~",
severity_level=logging.DEBUG,
task=say,
text="Hi~",
)
task.run(
name="计数",
task=count,
number=number,
)
task.run(
name="再见",
severity_level=logging.DEBUG,
task=say,
text="byebye."
)
# 计算打招呼打了奇数次还是偶数次
even_or_odds = "even" if number % 2 == 1 else "odd"
return Result(
host=task.host,
result = f"{task.host} counted {even_or_odds} times!",
)
这个任务组与之前编写的任务组一样,不同的地方是添加了 severity_level=logging.DEBUG
来输出任务执行的日志。现在来运行一下任务组,并把运行结果赋值给 result
:
[3]:
result = spine_bj.run(
task=greet_and_count,
number=5
)
简单的任务处理方法
大多数情况下,如果只想知道任务的执行结果,可以使用 nornir_utils
里面的 print_result
函数,之前的示例中已经在使用它来查看结果了。
[4]:
from nornir_utils.plugins.functions import print_result
print_result(result)
greet_and_count*****************************************************************
* spine00.bj ** changed : False ************************************************
vvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
spine00.bj counted even times!
---- 计数 ** changed : False ----------------------------------------------------- INFO
[0, 1, 2, 3, 4]
^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* spine01.bj ** changed : False ************************************************
vvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ERROR
Subtask: 你好~ (failed)
---- 你好~ ** changed : False ---------------------------------------------------- ERROR
Traceback (most recent call last):
File "c:\program files\python38\lib\site-packages\nornir\core\task.py", line 99, in start
r = self.task(self, **self.params)
File "C:\Users\xdai\AppData\Local\Temp/ipykernel_16088/2400762698.py", line 17, in say
raise Exception(f"{task.host.name} 不能输出信息")
Exception: spine01.bj 不能输出信息
^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
从结果中可以看到两台 spine
设备的执行结果,显示出来了两台主机上 count
任务的执行结果及第二台主机 say
任务的结果,仍然有一些其他的结果没有显示出来,下文将说明原因。
现在来通过字典取值方式单独查看一下某台设备的任务执行结果:
[5]:
print_result(result["spine00.bj"])
vvvv spine00.bj: greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
spine00.bj counted even times!
---- 计数 ** changed : False ----------------------------------------------------- INFO
[0, 1, 2, 3, 4]
^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
或者查看某一个任务的执行结果:
[6]:
print_result(result["spine00.bj"][2])
---- spine00.bj: 计数 ** changed : False ----------------------------------------- INFO
[0, 1, 2, 3, 4]
从上面的几个处理结果的示例中可以看到,并不是所有的处理结果都显示出来了,这是因为指定了 severity_level
参数,可以用指定的日志级别来记录任务的执行结果。
print_result
可以按照日志规则打印结果,默认情况下,它只打印严重级别大于 INFO
的任务(如果任务中没有指定日志级别,默认值也是INFO
)。
如果任务执行失败的话,它的严重级别是 ERROR
,比 INFO
大,所以可以显示出来。上面的 spine02.bj
的第一个任务就是显示出来的错误信息。
日志级别排序:CRITICAL > ERROR > WARNING > INFO > DEBUG
可以通过设置 print_result
的参数来调整输出:
[7]:
print_result(result, severity_level=logging.DEBUG)
greet_and_count*****************************************************************
* spine00.bj ** changed : False ************************************************
vvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
spine00.bj counted even times!
---- 你好~ ** changed : False ---------------------------------------------------- DEBUG
spine00.bj says Hi~
---- 计数 ** changed : False ----------------------------------------------------- INFO
[0, 1, 2, 3, 4]
---- 再见 ** changed : False ----------------------------------------------------- DEBUG
spine00.bj says byebye.
^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* spine01.bj ** changed : False ************************************************
vvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ERROR
Subtask: 你好~ (failed)
---- 你好~ ** changed : False ---------------------------------------------------- ERROR
Traceback (most recent call last):
File "c:\program files\python38\lib\site-packages\nornir\core\task.py", line 99, in start
r = self.task(self, **self.params)
File "C:\Users\xdai\AppData\Local\Temp/ipykernel_16088/2400762698.py", line 17, in say
raise Exception(f"{task.host.name} 不能输出信息")
Exception: spine01.bj 不能输出信息
^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
现在通过给 print_result
传递参数,已经可以看到所有任务的执行结果了,从显示任务名那一行的内容最后可以看到日志级别。
更详细的任务处理方法
从上一小节的示例中,已经说明了如果处理任务的结果,现在详细说明一下。任务组(Task Groups)的返回结果是 AggregatedResult
对象,它是个类字典(dict-like)对象,所以可以像操作字典一样进行迭代或者访问。
[8]:
result
[8]:
AggregatedResult (greet_and_count): {'spine00.bj': MultiResult: [Result: "greet_and_count", Result: "你好~", Result: "计数", Result: "再见"], 'spine01.bj': MultiResult: [Result: "greet_and_count", Result: "你好~"]}
[9]:
result.keys()
[9]:
dict_keys(['spine00.bj', 'spine01.bj'])
[10]:
result["spine00.bj"]
[10]:
MultiResult: [Result: "greet_and_count", Result: "你好~", Result: "计数", Result: "再见"]
从上面的示例输出中可以看到,AggregatedResult
中的每个键都有一个MultiResult
对象。这个对象是一个类列表(list-like)的对象,里面存放着 Result
对象,可以使用列表的操作方式来迭代或访问 Result
对象:
[11]:
result["spine00.bj"][0]
[11]:
Result: "greet_and_count"
从 MultiResult
和 Result
中可以看到执行对象中是否有错误或变化:
[12]:
print(f'changed: {result["spine00.bj"].changed}')
print(f'failed: {result["spine00.bj"].failed}')
changed: False
failed: False
[13]:
print(f'changed: {result["spine01.bj"].changed}')
print(f'failed: {result["spine01.bj"].failed}')
changed: False
failed: True
如果运行前后对目标系统造成了改变,可以通过 diff
显示出来,当前示例中执行的任务组没有产生变化,所以输出为空:
[14]:
print(f'diff: {result["spine01.bj"].diff}')
diff: