处理任务结果

在这节中一起来看一下如何处理任务(Tasks)的运行结果。

先看下面的示例:

  1. [1]:
  1. import logging
  2. from nornir import InitNornir
  3. from nornir.core.task import Task, Result
  4. nr = InitNornir(config_file="files/config.yaml")
  5. spine_bj = nr.filter(site="bj", role="spine")
  6. def count(task: Task, number: int) -> Result:
  7. return Result(
  8. host=task.host,
  9. result=f"{[n for n in range(0, number)]}"
  10. )
  11. def say(task: Task, text: str) -> Result:
  12. if task.host.name == "spine01.bj":
  13. raise Exception(f"{task.host.name} 不能输出信息")
  14. return Result(
  15. host=task.host,
  16. result=f"{task.host.name} says {text}"
  17. )

这个示例与之前示例的区别是:通过 if 判断让主机 spine01.bj 强制抛出了一个错误信息。

再继续编写任务组:

  1. [2]:
  1. def greet_and_count(task: Task, number: int) -> Result:
  2. task.run(
  3. name="你好~",
  4. severity_level=logging.DEBUG,
  5. task=say,
  6. text="Hi~",
  7. )
  8. task.run(
  9. name="计数",
  10. task=count,
  11. number=number,
  12. )
  13. task.run(
  14. name="再见",
  15. severity_level=logging.DEBUG,
  16. task=say,
  17. text="byebye."
  18. )
  19. # 计算打招呼打了奇数次还是偶数次
  20. even_or_odds = "even" if number % 2 == 1 else "odd"
  21. return Result(
  22. host=task.host,
  23. result = f"{task.host} counted {even_or_odds} times!",
  24. )

这个任务组与之前编写的任务组一样,不同的地方是添加了 severity_level=logging.DEBUG 来输出任务执行的日志。现在来运行一下任务组,并把运行结果赋值给 result

  1. [3]:
  1. result = spine_bj.run(
  2. task=greet_and_count,
  3. number=5
  4. )

简单的任务处理方法

大多数情况下,如果只想知道任务的执行结果,可以使用 nornir_utils 里面的 print_result 函数,之前的示例中已经在使用它来查看结果了。

  1. [4]:
  1. from nornir_utils.plugins.functions import print_result
  2. print_result(result)
  1. greet_and_count*****************************************************************
  2. * spine00.bj ** changed : False ************************************************
  3. vvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
  4. spine00.bj counted even times!
  5. ---- 计数 ** changed : False ----------------------------------------------------- INFO
  6. [0, 1, 2, 3, 4]
  7. ^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  8. * spine01.bj ** changed : False ************************************************
  9. vvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ERROR
  10. Subtask: 你好~ (failed)
  11. ---- 你好~ ** changed : False ---------------------------------------------------- ERROR
  12. Traceback (most recent call last):
  13. File "c:\program files\python38\lib\site-packages\nornir\core\task.py", line 99, in start
  14. r = self.task(self, **self.params)
  15. File "C:\Users\xdai\AppData\Local\Temp/ipykernel_16088/2400762698.py", line 17, in say
  16. raise Exception(f"{task.host.name} 不能输出信息")
  17. Exception: spine01.bj 不能输出信息
  18. ^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

从结果中可以看到两台 spine 设备的执行结果,显示出来了两台主机上 count 任务的执行结果及第二台主机 say 任务的结果,仍然有一些其他的结果没有显示出来,下文将说明原因。

现在来通过字典取值方式单独查看一下某台设备的任务执行结果:

  1. [5]:
  1. print_result(result["spine00.bj"])
  1. vvvv spine00.bj: greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
  2. spine00.bj counted even times!
  3. ---- 计数 ** changed : False ----------------------------------------------------- INFO
  4. [0, 1, 2, 3, 4]
  5. ^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

或者查看某一个任务的执行结果:

  1. [6]:
  1. print_result(result["spine00.bj"][2])
  1. ---- spine00.bj: 计数 ** changed : False ----------------------------------------- INFO
  2. [0, 1, 2, 3, 4]

从上面的几个处理结果的示例中可以看到,并不是所有的处理结果都显示出来了,这是因为指定了 severity_level 参数,可以用指定的日志级别来记录任务的执行结果。

print_result 可以按照日志规则打印结果,默认情况下,它只打印严重级别大于 INFO 的任务(如果任务中没有指定日志级别,默认值也是INFO)。

如果任务执行失败的话,它的严重级别是 ERROR,比 INFO 大,所以可以显示出来。上面的 spine02.bj 的第一个任务就是显示出来的错误信息。

日志级别排序:CRITICAL > ERROR > WARNING > INFO > DEBUG

可以通过设置 print_result 的参数来调整输出:

  1. [7]:
  1. print_result(result, severity_level=logging.DEBUG)
  1. greet_and_count*****************************************************************
  2. * spine00.bj ** changed : False ************************************************
  3. vvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
  4. spine00.bj counted even times!
  5. ---- 你好~ ** changed : False ---------------------------------------------------- DEBUG
  6. spine00.bj says Hi~
  7. ---- 计数 ** changed : False ----------------------------------------------------- INFO
  8. [0, 1, 2, 3, 4]
  9. ---- 再见 ** changed : False ----------------------------------------------------- DEBUG
  10. spine00.bj says byebye.
  11. ^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  12. * spine01.bj ** changed : False ************************************************
  13. vvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ERROR
  14. Subtask: 你好~ (failed)
  15. ---- 你好~ ** changed : False ---------------------------------------------------- ERROR
  16. Traceback (most recent call last):
  17. File "c:\program files\python38\lib\site-packages\nornir\core\task.py", line 99, in start
  18. r = self.task(self, **self.params)
  19. File "C:\Users\xdai\AppData\Local\Temp/ipykernel_16088/2400762698.py", line 17, in say
  20. raise Exception(f"{task.host.name} 不能输出信息")
  21. Exception: spine01.bj 不能输出信息
  22. ^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

现在通过给 print_result 传递参数,已经可以看到所有任务的执行结果了,从显示任务名那一行的内容最后可以看到日志级别。

更详细的任务处理方法

从上一小节的示例中,已经说明了如果处理任务的结果,现在详细说明一下。任务组(Task Groups)的返回结果是 AggregatedResult 对象,它是个类字典(dict-like)对象,所以可以像操作字典一样进行迭代或者访问。

  1. [8]:
  1. result
  1. [8]:
  1. AggregatedResult (greet_and_count): {'spine00.bj': MultiResult: [Result: "greet_and_count", Result: "你好~", Result: "计数", Result: "再见"], 'spine01.bj': MultiResult: [Result: "greet_and_count", Result: "你好~"]}
  1. [9]:
  1. result.keys()
  1. [9]:
  1. dict_keys(['spine00.bj', 'spine01.bj'])
  1. [10]:
  1. result["spine00.bj"]
  1. [10]:
  1. MultiResult: [Result: "greet_and_count", Result: "你好~", Result: "计数", Result: "再见"]

从上面的示例输出中可以看到,AggregatedResult 中的每个键都有一个MultiResult 对象。这个对象是一个类列表(list-like)的对象,里面存放着 Result 对象,可以使用列表的操作方式来迭代或访问 Result 对象:

  1. [11]:
  1. result["spine00.bj"][0]
  1. [11]:
  1. Result: "greet_and_count"

MultiResultResult 中可以看到执行对象中是否有错误或变化:

  1. [12]:
  1. print(f'changed: {result["spine00.bj"].changed}')
  2. print(f'failed: {result["spine00.bj"].failed}')
  1. changed: False
  2. failed: False
  1. [13]:
  1. print(f'changed: {result["spine01.bj"].changed}')
  2. print(f'failed: {result["spine01.bj"].failed}')
  1. changed: False
  2. failed: True

如果运行前后对目标系统造成了改变,可以通过 diff 显示出来,当前示例中执行的任务组没有产生变化,所以输出为空:

  1. [14]:
  1. print(f'diff: {result["spine01.bj"].diff}')
  1. diff: