From 8d981766a2c4bce75bfc1d0e248b56670e8034b4 Mon Sep 17 00:00:00 2001 From: Martin Krizek Date: Thu, 17 Oct 2024 13:41:52 +0200 Subject: [PATCH] Fix rescuing tasks failed by any_errors_fatal Fixes #84117 Fixes #84120 --- .../84117-any_errors_fatal-play_hosts.yml | 3 +++ lib/ansible/plugins/strategy/__init__.py | 1 + lib/ansible/plugins/strategy/linear.py | 25 ++++++++++++++++++- .../targets/any_errors_fatal/84117.yml | 16 ++++++++++++ .../targets/any_errors_fatal/runme.sh | 2 ++ 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/84117-any_errors_fatal-play_hosts.yml create mode 100644 test/integration/targets/any_errors_fatal/84117.yml diff --git a/changelogs/fragments/84117-any_errors_fatal-play_hosts.yml b/changelogs/fragments/84117-any_errors_fatal-play_hosts.yml new file mode 100644 index 00000000000..c803d661b59 --- /dev/null +++ b/changelogs/fragments/84117-any_errors_fatal-play_hosts.yml @@ -0,0 +1,3 @@ +bugfixes: + - Fix an issue where rescued hosts were excluded from subsequent plays when ``any_errors_fatal`` was set (https://github.com/ansible/ansible/issues/84117) + - Fix an issue where ``ansible_failed_task`` and ``ansible_failed_result`` were not available for hosts failed by ``any_errors_fatal`` (https://github.com/ansible/ansible/issues/84120) diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index 3eb5538b96d..bf22bbb5109 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -602,6 +602,7 @@ class StrategyBase: # if we're iterating on the rescue portion of a block then # we save the failed task in a special var for use # within the rescue/always + # FIXME this code is copied in the linear strategy, see the any_errors_fatal implementation there if iterator.is_any_block_rescuing(state_when_failed): self._tqm._stats.increment('rescued', original_host.name) iterator._play._removed_hosts.remove(original_host.name) diff --git a/lib/ansible/plugins/strategy/linear.py b/lib/ansible/plugins/strategy/linear.py index 372a05f0e1a..f988966535b 100644 --- a/lib/ansible/plugins/strategy/linear.py +++ b/lib/ansible/plugins/strategy/linear.py @@ -38,6 +38,7 @@ from ansible.plugins.loader import action_loader from ansible.plugins.strategy import StrategyBase from ansible.template import Templar from ansible.utils.display import Display +from ansible.utils.unsafe_proxy import wrap_var display = Display() @@ -311,17 +312,39 @@ class StrategyModule(StrategyBase): display.debug("checking for any_errors_fatal") failed_hosts = [] unreachable_hosts = [] + successful_host_results = {} for res in results: if res.is_failed(): failed_hosts.append(res._host.name) elif res.is_unreachable(): unreachable_hosts.append(res._host.name) + else: + successful_host_results[res._host.name] = res if any_errors_fatal and (failed_hosts or unreachable_hosts): for host in hosts_left: if host.name not in failed_hosts: - self._tqm._failed_hosts[host.name] = True + state_when_failed = iterator.get_state_for_host(host.name) iterator.mark_host_failed(host) + # FIXME this is a copy of the code from StrategyBase._process_pending_results() to handle rescued tasks + # since any_errors_fatal is implemented here after the results processing is done we need to do the same. + # Would it make sense to move any_errors_fatal functionality to StrategyBase._process_pending_results() + # and guard it with something like strategy.supports_any_errors_fatal when it is implemented? + if iterator.is_any_block_rescuing(state_when_failed): + self._tqm._stats.increment('rescued', host.name) + iterator._play._removed_hosts.remove(host.name) + r = successful_host_results[host.name]._result.copy() + r["failed"] = True + self._variable_manager.set_nonpersistent_facts( + host.name, + { + "ansible_failed_task": wrap_var(successful_host_results[host.name]._task.serialize()), + "ansible_failed_result": r, + }, + ) + else: + self._tqm._failed_hosts[host.name] = True + self._tqm._stats.increment('failures', host.name) display.debug("done checking for any_errors_fatal") display.debug("checking for max_fail_percentage") diff --git a/test/integration/targets/any_errors_fatal/84117.yml b/test/integration/targets/any_errors_fatal/84117.yml new file mode 100644 index 00000000000..082abf1f5fd --- /dev/null +++ b/test/integration/targets/any_errors_fatal/84117.yml @@ -0,0 +1,16 @@ +- hosts: testhost,testhost2 + gather_facts: false + any_errors_fatal: true + tasks: + - block: + - fail: + when: inventory_hostname == "testhost2" + rescue: + - assert: + that: + - ansible_failed_result.failed + + - assert: + that: + - '"testhost" in play_hosts' + - '"testhost2" in play_hosts' diff --git a/test/integration/targets/any_errors_fatal/runme.sh b/test/integration/targets/any_errors_fatal/runme.sh index 55381a73679..f8ca91a99c0 100755 --- a/test/integration/targets/any_errors_fatal/runme.sh +++ b/test/integration/targets/any_errors_fatal/runme.sh @@ -47,3 +47,5 @@ ansible-playbook -i inventory "$@" 80981.yml | tee out.txt [ "$(grep -c 'SHOULD NOT HAPPEN' out.txt)" -eq 0 ] [ "$(grep -c 'rescuedd' out.txt)" -eq 2 ] [ "$(grep -c 'recovered' out.txt)" -eq 2 ] + +ansible-playbook -i inventory "$@" 84117.yml