处理任务结果

    先看下面的示例:

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

    这个示例与之前示例的区别是:通过 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. return Result(
    21. host=task.host,
    22. result = f"{task.host} counted {even_or_odds} times!",
    23. )

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

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

    大多数情况下,如果只想知道任务的执行结果,可以使用 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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

    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])

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

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

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

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

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