处理失败任务

    上一节中的示例代码:

    1. from nornir import InitNornir
    2. from nornir.core.task import Task, Result
    3. from nornir_utils.plugins.functions import print_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. )
    18. def greet_and_count(task: Task, number: int) -> Result:
    19. task.run(
    20. name="你好~",
    21. severity_level=logging.DEBUG,
    22. task=say,
    23. text="Hi~",
    24. )
    25. task.run(
    26. name="计数",
    27. task=count,
    28. number=number,
    29. )
    30. task.run(
    31. name="再见",
    32. severity_level=logging.DEBUG,
    33. task=say,
    34. text="byebye."
    35. )
    36. # 计算打招呼打了奇数次还是偶数次
    37. even_or_odds = "even" if number % 2 == 1 else "odd"
    38. return Result(
    39. host=task.host,
    40. )
    41. result = spine_bj.run(
    42. task=greet_and_count,
    43. number=5
    44. )

    在这段示例代码中,任务 say 针对 spine01.bj 主机抛出了一个异常,这导致整个任务的执行结果是失败的:

    1. [2]:
    1. result.failed
    1. True
    1. [3]:
    1. # 查看是哪些主机导致了失败
    2. result.failed_hosts
    1. [3]:
    1. {'spine01.bj': MultiResult: [Result: "greet_and_count", Result: "你好~"]}

    如果任务发生了失败,可以通过 exception 显示异常信息:

    1. [4]:
    1. result["spine01.bj"].exception
    1. [4]:
    1. nornir.core.exceptions.NornirSubTaskError()

    上一条命令显示结果是子任务错误,可以通过列表取值来查看错误信息:

    1. [5]:
    1. result["spine01.bj"][1].exception
    1. [5]:
    1. Exception('spine01.bj 不能输出信息')

    想要查看更具体的信息,可以使用 print_result 查看具体的异常信息:

    1. print_result(result["spine01.bj"])
    1. vvvv spine01.bj: greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvv ERROR
    2. Subtask: 你好~ (failed)
    3. ---- 你好~ ** changed : False ---------------------------------------------------- ERROR
    4. Traceback (most recent call last):
    5. File "c:\program files\python38\lib\site-packages\nornir\core\task.py", line 99, in start
    6. r = self.task(self, **self.params)
    7. File "C:\Users\xdai\AppData\Local\Temp/ipykernel_35768/1441132238.py", line 18, in say
    8. raise Exception(f"{task.host.name} 不能输出信息")
    9. Exception: spine01.bj 不能输出信息
    10. ^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    1. [7]:
    1. from nornir.core.exceptions import NornirExecutionError
    2. try:
    3. result.raise_on_error()
    4. except NornirExecutionError:
    5. print("ERROR!!!")
    1. ERROR!!!

    Nornir 会跟踪记录任务执行失败的主机,然后不在该主机上运行其他新的任务。

    现在定义一个新的任务,并使用之前示例筛选出来的主机组 spine_bj 来执行该任务。

    这里需要注意一下: spine_bj 中有两个主机,但是之前示例中,spine01.bj 在执行任务组 greet_and_count 中失败了。

    1. [8]:
    1. spine_bj.inventory.hosts
    1. [8]:
    1. {'spine00.bj': Host: spine00.bj, 'spine01.bj': Host: spine01.bj}
    1. [9]:
    1. from nornir.core.task import Result
    2. def hi(task: Task) -> Result:
    3. host=task.host,
    4. result=f"{task.host.name}: Hi, I am still here!"
    5. )
    6. result = spine_bj.run(hi)
    1. [10]:
    1. hi******************************************************************************
    2. * spine00.bj ** changed : False ************************************************
    3. vvvv hi ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
    4. spine00.bj: Hi, I am still here!
    5. ^^^^ END hi ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    查看执行出来的结果,只有第一台主机 spine00.bj 成功执行了新的任务。

    如果需要新任务在失败的主机上执行,需要在执行调用时添加 on_failed=True

    1. [11]:
    1. result = spine_bj.run(task=hi, on_failed=True)
    2. print_result(result)
    1. hi******************************************************************************
    2. * spine00.bj ** changed : False ************************************************
    3. vvvv hi ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
    4. spine00.bj: Hi, I am still here!
    5. ^^^^ END hi ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    6. * spine01.bj ** changed : False ************************************************
    7. vvvv hi ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
    8. spine01.bj: Hi, I am still here!
    9. ^^^^ END hi ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    1. # 这是上一节示例中执行失败的任务组,再次执行一下,来验证 `on_good`
    2. result = spine_bj.run(
    3. task=greet_and_count,
    4. number=5
    5. )
    1. [13]:
    1. result = spine_bj.run(task=hi, on_failed=True, on_good=False)
    2. print_result(result)
    1. hi******************************************************************************
    2. * spine01.bj ** changed : False ************************************************
    3. vvvv hi ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
    4. spine01.bj: Hi, I am still here!
    5. ^^^^ END hi ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    可以看到只在失败的主机上执行了新任务。

    如何实现的呢?

    为了实现这种效果,nornir 是通过在 data 对象中添加了 failed_hosts 字段来让任务之间共享失败的主机(有关 data 对象,可以回顾一下):

    1. [14]:
    1. nr.data.failed_hosts
    1. [14]:
    1. {'spine01.bj'}

    如果要将某些主机标记为成功并让它们重新符合执行新任务的资格,可以使用函数 recovery_host 为某个主机单独执行此操作,或者使用 reset_failed_hosts 完全重置失败列表:

    1. [15]:
    1. nr.data.recover_host('spine01.bj')
    2. nr.data.failed_hosts
    1. [15]:
    1. set()
    1. [16]:
    1. nr.data.reset_failed_hosts()
    2. nr.data.failed_hosts
    1. [16]:
    1. set()

    一般情况下,如果任务执行出错,只能在最终打印任务结果时看到错误信息,如果需要及时反馈或者处理失败的失误,可以在初始化 nornir 对象时添加 raise_on_error 来让任务出错时自动引发异常:

      1. ERROR!!!

      对于更复杂的工作流,也可以通过 nornir 来实现,因为这个框架足够灵活,接下来就来看看强大的处理器。