处理失败任务
任务执行失败是不可避免的,现在接着上一节的示例来看下如何处理失败的任务。
上一节中的示例代码:
[1]:
import logging
from nornir import InitNornir
from nornir.core.task import Task, Result
from nornir_utils.plugins.functions import print_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}"
)
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!",
)
result = spine_bj.run(
task=greet_and_count,
number=5
)
在这段示例代码中,任务 say
针对 spine01.bj
主机抛出了一个异常,这导致整个任务的执行结果是失败的:
[2]:
result.failed
[2]:
True
[3]:
# 查看是哪些主机导致了失败
result.failed_hosts
[3]:
{'spine01.bj': MultiResult: [Result: "greet_and_count", Result: "你好~"]}
如果任务发生了失败,可以通过 exception
显示异常信息:
[4]:
result["spine01.bj"].exception
[4]:
nornir.core.exceptions.NornirSubTaskError()
上一条命令显示结果是子任务错误,可以通过列表取值来查看错误信息:
[5]:
result["spine01.bj"][1].exception
[5]:
Exception('spine01.bj 不能输出信息')
想要查看更具体的信息,可以使用 print_result
查看具体的异常信息:
[6]:
print_result(result["spine01.bj"])
vvvv spine01.bj: greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvv 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_35768/1441132238.py", line 18, in say
raise Exception(f"{task.host.name} 不能输出信息")
Exception: spine01.bj 不能输出信息
^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
在处理任务执行结果的过程中,如果有执行出错的话,还会抛出 NornirExecutionError
异常,可以使用 raise_on_error
方法来引出这个异常,然后使用 try
子句进行处理:
[7]:
from nornir.core.exceptions import NornirExecutionError
try:
result.raise_on_error()
except NornirExecutionError:
print("ERROR!!!")
ERROR!!!
跳过失败的主机
Nornir 会跟踪记录任务执行失败的主机,然后不在该主机上运行其他新的任务。
现在定义一个新的任务,并使用之前示例筛选出来的主机组 spine_bj
来执行该任务。
这里需要注意一下: spine_bj
中有两个主机,但是之前示例中,spine01.bj
在执行任务组 greet_and_count
中失败了。
[8]:
spine_bj.inventory.hosts
[8]:
{'spine00.bj': Host: spine00.bj, 'spine01.bj': Host: spine01.bj}
[9]:
from nornir.core.task import Result
def hi(task: Task) -> Result:
return Result(
host=task.host,
result=f"{task.host.name}: Hi, I am still here!"
)
result = spine_bj.run(hi)
[10]:
print_result(result)
hi******************************************************************************
* spine00.bj ** changed : False ************************************************
vvvv hi ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
spine00.bj: Hi, I am still here!
^^^^ END hi ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
查看执行出来的结果,只有第一台主机 spine00.bj
成功执行了新的任务。
如果需要新任务在失败的主机上执行,需要在执行调用时添加 on_failed=True
:
[11]:
result = spine_bj.run(task=hi, on_failed=True)
print_result(result)
hi******************************************************************************
* spine00.bj ** changed : False ************************************************
vvvv hi ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
spine00.bj: Hi, I am still here!
^^^^ END hi ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* spine01.bj ** changed : False ************************************************
vvvv hi ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
spine01.bj: Hi, I am still here!
^^^^ END hi ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
如果只想在失败的主机上执行新任务,可以使用 on_good
参数: >上一个代码框中使用了 on_failed=True
,导致两个主机都执行成功了。如果想要验证 on_good
,需要再执行一下之前导致错误的任务组来看到这次的结果
[12]:
# 这是上一节示例中执行失败的任务组,再次执行一下,来验证 `on_good`
result = spine_bj.run(
task=greet_and_count,
number=5
)
[13]:
result = spine_bj.run(task=hi, on_failed=True, on_good=False)
print_result(result)
hi******************************************************************************
* spine01.bj ** changed : False ************************************************
vvvv hi ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
spine01.bj: Hi, I am still here!
^^^^ END hi ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
可以看到只在失败的主机上执行了新任务。
如何实现的呢?
为了实现这种效果,nornir 是通过在 data
对象中添加了 failed_hosts
字段来让任务之间共享失败的主机(有关 data
对象,可以回顾一下初始化 Nornir):
[14]:
nr.data.failed_hosts
[14]:
{'spine01.bj'}
如果要将某些主机标记为成功并让它们重新符合执行新任务的资格,可以使用函数 recovery_host
为某个主机单独执行此操作,或者使用 reset_failed_hosts
完全重置失败列表:
[15]:
nr.data.recover_host('spine01.bj')
nr.data.failed_hosts
[15]:
set()
[16]:
nr.data.reset_failed_hosts()
nr.data.failed_hosts
[16]:
set()
自动抛出异常
一般情况下,如果任务执行出错,只能在最终打印任务结果时看到错误信息,如果需要及时反馈或者处理失败的失误,可以在初始化 nornir 对象时添加 raise_on_error
来让任务出错时自动引发异常:
[17]:
nr = InitNornir(
config_file="files/config.yaml",
core = {"raise_on_error": True}
)
spine_bj = nr.filter(site='bj', role='spine')
try:
result = spine_bj.run(
task=greet_and_count,
number=5,
)
except NornirExecutionError:
print("ERROR!!!")
ERROR!!!
工作流
由任务组组成的工作流(Workflow)适用于大多数使用场景,因为它可以跳过出错的主机,并且 print_result
也提供了足够的信息来了解任务执行的结果。
对于更复杂的工作流,也可以通过 nornir 来实现,因为这个框架足够灵活,接下来就来看看强大的处理器。