diff --git a/changelogs/fragments/65576-fix-free-strategy-handler-filtering.yaml b/changelogs/fragments/65576-fix-free-strategy-handler-filtering.yaml new file mode 100644 index 00000000000..87f5fbe0958 --- /dev/null +++ b/changelogs/fragments/65576-fix-free-strategy-handler-filtering.yaml @@ -0,0 +1,4 @@ +bugfixes: + - free strategy - Include failed hosts when filtering notified hosts for handlers. The strategy base should + determine whether or not to run handlers on those hosts depending on whether forcing handlers is enabled + (https://github.com/ansible/ansible/issues/65254). diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index 2275e5590d1..1750e3da9b7 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -911,7 +911,10 @@ class StrategyBase: if notified_hosts is None: notified_hosts = handler.notified_hosts[:] + # strategy plugins that filter hosts need access to the iterator to identify failed hosts + failed_hosts = self._filter_notified_failed_hosts(iterator, notified_hosts) notified_hosts = self._filter_notified_hosts(notified_hosts) + notified_hosts += failed_hosts if len(notified_hosts) > 0: saved_name = handler.name @@ -991,6 +994,9 @@ class StrategyBase: display.debug("done running handlers, result is: %s" % result) return result + def _filter_notified_failed_hosts(self, iterator, notified_hosts): + return [] + def _filter_notified_hosts(self, notified_hosts): ''' Filter notified hosts accordingly to strategy diff --git a/lib/ansible/plugins/strategy/free.py b/lib/ansible/plugins/strategy/free.py index 264c0e10956..7ed21183cf6 100644 --- a/lib/ansible/plugins/strategy/free.py +++ b/lib/ansible/plugins/strategy/free.py @@ -50,6 +50,11 @@ class StrategyModule(StrategyBase): # This strategy manages throttling on its own, so we don't want it done in queue_task ALLOW_BASE_THROTTLING = False + def _filter_notified_failed_hosts(self, iterator, notified_hosts): + + # If --force-handlers is used we may act on hosts that have failed + return [host for host in notified_hosts if iterator.is_failed(host)] + def _filter_notified_hosts(self, notified_hosts): ''' Filter notified hosts accordingly to strategy diff --git a/test/integration/targets/handlers/runme.sh b/test/integration/targets/handlers/runme.sh index b87e342de48..59c81bcec09 100755 --- a/test/integration/targets/handlers/runme.sh +++ b/test/integration/targets/handlers/runme.sh @@ -17,33 +17,42 @@ ansible-playbook test_listening_handlers.yml -i inventory.handlers -v "$@" [ "$(ansible-playbook test_handlers.yml -i inventory.handlers -v "$@" --tags scenario2 -l A \ | grep -E -o 'RUNNING HANDLER \[test_handlers : .*?]')" = "RUNNING HANDLER [test_handlers : test handler]" ] -# Not forcing, should only run on successful host -[ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags normal \ -| grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_B" ] +# Test forcing handlers using the linear and free strategy +for strategy in linear free; do -# Forcing from command line -[ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags normal --force-handlers \ -| grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_A CALLED_HANDLER_B" ] + export ANSIBLE_STRATEGY=$strategy -# Forcing from command line, should only run later tasks on unfailed hosts -[ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags normal --force-handlers \ -| grep -E -o CALLED_TASK_. | sort | uniq | xargs)" = "CALLED_TASK_B CALLED_TASK_D CALLED_TASK_E" ] + # Not forcing, should only run on successful host + [ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags normal \ + | grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_B" ] -# Forcing from command line, should call handlers even if all hosts fail -[ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags normal --force-handlers -e fail_all=yes \ -| grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_A CALLED_HANDLER_B" ] + # Forcing from command line + [ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags normal --force-handlers \ + | grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_A CALLED_HANDLER_B" ] -# Forcing from ansible.cfg -[ "$(ANSIBLE_FORCE_HANDLERS=true ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags normal \ -| grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_A CALLED_HANDLER_B" ] + # Forcing from command line, should only run later tasks on unfailed hosts + [ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags normal --force-handlers \ + | grep -E -o CALLED_TASK_. | sort | uniq | xargs)" = "CALLED_TASK_B CALLED_TASK_D CALLED_TASK_E" ] -# Forcing true in play -[ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags force_true_in_play \ -| grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_A CALLED_HANDLER_B" ] + # Forcing from command line, should call handlers even if all hosts fail + [ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags normal --force-handlers -e fail_all=yes \ + | grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_A CALLED_HANDLER_B" ] -# Forcing false in play, which overrides command line -[ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags force_false_in_play --force-handlers \ -| grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_B" ] + # Forcing from ansible.cfg + [ "$(ANSIBLE_FORCE_HANDLERS=true ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags normal \ + | grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_A CALLED_HANDLER_B" ] + + # Forcing true in play + [ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags force_true_in_play \ + | grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_A CALLED_HANDLER_B" ] + + # Forcing false in play, which overrides command line + [ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags force_false_in_play --force-handlers \ + | grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_B" ] + + unset ANSIBLE_STRATEGY + +done [ "$(ansible-playbook test_handlers_include.yml -i ../../inventory -v "$@" --tags playbook_include_handlers \ | grep -E -o 'RUNNING HANDLER \[.*?]')" = "RUNNING HANDLER [test handler]" ]