From 2e003adbc8c14d815d9a1763c222a344a7267ad4 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Wed, 8 Jun 2016 10:11:34 -0500 Subject: [PATCH] Expand return code values returned by TQM and strategies This allows the PlaybookExecutor to receive more information regarding what happened internal to the TaskQueueManager and strategy, to determine things like whether or not the play iteration should stop. Fixes #15523 (cherry picked from commit fbec2d9692cdbbddda1f6ecb68bbca67e3b783b4) --- lib/ansible/executor/playbook_executor.py | 7 ++++++- lib/ansible/executor/task_queue_manager.py | 7 +++++++ lib/ansible/plugins/strategy/__init__.py | 18 ++++++++++-------- lib/ansible/plugins/strategy/linear.py | 12 ++++++++---- .../plugins/strategies/test_strategy_base.py | 13 +++++++++---- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/lib/ansible/executor/playbook_executor.py b/lib/ansible/executor/playbook_executor.py index 041f51bf70..6f38e8e8bc 100644 --- a/lib/ansible/executor/playbook_executor.py +++ b/lib/ansible/executor/playbook_executor.py @@ -141,6 +141,11 @@ class PlaybookExecutor: # and run it... result = self._tqm.run(play=play) + # break the play if the result equals the special return code + if result == self._tqm.RUN_FAILED_BREAK_PLAY: + result = self._tqm.RUN_FAILED_HOSTS + break_play = True + # check the number of failures here, to see if they're above the maximum # failure percentage allowed, or if any errors are fatal. If either of those # conditions are met, we break out, otherwise we only break out if the entire @@ -159,7 +164,7 @@ class PlaybookExecutor: # if the last result wasn't zero or 3 (some hosts were unreachable), # break out of the serial batch loop - if result not in (0, 3): + if result not in (self._tqm.RUN_OK, self._tqm.RUN_UNREACHABLE_HOSTS): break if break_play: diff --git a/lib/ansible/executor/task_queue_manager.py b/lib/ansible/executor/task_queue_manager.py index 8373264ef1..8fe7404328 100644 --- a/lib/ansible/executor/task_queue_manager.py +++ b/lib/ansible/executor/task_queue_manager.py @@ -58,6 +58,13 @@ class TaskQueueManager: which dispatches the Play's tasks to hosts. ''' + RUN_OK = 0 + RUN_ERROR = 1 + RUN_FAILED_HOSTS = 2 + RUN_UNREACHABLE_HOSTS = 3 + RUN_FAILED_BREAK_PLAY = 4 + RUN_UNKNOWN_ERROR = 255 + def __init__(self, inventory, variable_manager, loader, options, passwords, stdout_callback=None, run_additional_callbacks=True, run_tree=False): self._inventory = inventory diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index bb1cede6f3..630ba91ad0 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -120,7 +120,7 @@ class StrategyBase: def run(self, iterator, play_context, result=True): # save the failed/unreachable hosts, as the run_handlers() # method will clear that information during its execution - failed_hosts = self._tqm._failed_hosts.keys() + failed_hosts = iterator.get_failed_hosts() unreachable_hosts = self._tqm._unreachable_hosts.keys() display.debug("running handlers") @@ -128,18 +128,20 @@ class StrategyBase: # now update with the hosts (if any) that failed or were # unreachable during the handler execution phase - failed_hosts = set(failed_hosts).union(self._tqm._failed_hosts.keys()) + failed_hosts = set(failed_hosts).union(iterator.get_failed_hosts()) unreachable_hosts = set(unreachable_hosts).union(self._tqm._unreachable_hosts.keys()) # return the appropriate code, depending on the status hosts after the run - if len(unreachable_hosts) > 0: - return 3 + if not isinstance(result, bool) and result != self._tqm.RUN_OK: + return result + elif len(unreachable_hosts) > 0: + return self._tqm.RUN_UNREACHABLE_HOSTS elif len(failed_hosts) > 0: - return 2 - elif not result: - return 1 + return self._tqm.RUN_FAILED_HOSTS + elif isinstance(result, bool) and not result: + return self._tqm.RUN_ERROR else: - return 0 + return self._tqm.RUN_OK def get_hosts_remaining(self, play): return [host for host in self._inventory.get_hosts(play.hosts) diff --git a/lib/ansible/plugins/strategy/linear.py b/lib/ansible/plugins/strategy/linear.py index 2d3396a3b2..b0ba34cae1 100644 --- a/lib/ansible/plugins/strategy/linear.py +++ b/lib/ansible/plugins/strategy/linear.py @@ -271,7 +271,7 @@ class StrategyModule(StrategyBase): if not work_to_do and len(iterator.get_failed_hosts()) > 0: display.debug("out of hosts to run on") self._tqm.send_callback('v2_playbook_on_no_hosts_remaining') - result = False + result = self._tqm.RUN_ERROR break try: @@ -284,7 +284,7 @@ class StrategyModule(StrategyBase): variable_manager=self._variable_manager ) except AnsibleError as e: - return False + return self._tqm.RUN_ERROR include_failure = False if len(included_files) > 0: @@ -354,13 +354,15 @@ class StrategyModule(StrategyBase): failed_hosts.append(res._host.name) # if any_errors_fatal and we had an error, mark all hosts as failed - if any_errors_fatal and len(failed_hosts) > 0: + if any_errors_fatal and (len(failed_hosts) > 0 or len(self._tqm._unreachable_hosts.keys()) > 0): for host in hosts_left: # don't double-mark hosts, or the iterator will potentially # fail them out of the rescue/always states if host.name not in failed_hosts: self._tqm._failed_hosts[host.name] = True iterator.mark_host_failed(host) + self._tqm.send_callback('v2_playbook_on_no_hosts_remaining') + return self._tqm.RUN_FAILED_BREAK_PLAY display.debug("done checking for any_errors_fatal") display.debug("checking for max_fail_percentage") @@ -374,12 +376,14 @@ class StrategyModule(StrategyBase): if host.name not in failed_hosts: self._tqm._failed_hosts[host.name] = True iterator.mark_host_failed(host) + self._tqm.send_callback('v2_playbook_on_no_hosts_remaining') + return self._tqm.RUN_FAILED_BREAK_PLAY display.debug("done checking for max_fail_percentage") except (IOError, EOFError) as e: display.debug("got IOError/EOFError in task loop: %s" % e) # most likely an abort, return failed - return False + return self._tqm.RUN_UNKNOWN_ERROR # run the base class run() method, which executes the cleanup function # and runs any outstanding handlers which have been triggered diff --git a/test/units/plugins/strategies/test_strategy_base.py b/test/units/plugins/strategies/test_strategy_base.py index 9ea944a2a1..338bcc1fd6 100644 --- a/test/units/plugins/strategies/test_strategy_base.py +++ b/test/units/plugins/strategies/test_strategy_base.py @@ -64,12 +64,17 @@ class TestStrategyBase(unittest.TestCase): mock_tqm._options = MagicMock() strategy_base = StrategyBase(tqm=mock_tqm) - self.assertEqual(strategy_base.run(iterator=mock_iterator, play_context=mock_play_context), 0) - self.assertEqual(strategy_base.run(iterator=mock_iterator, play_context=mock_play_context, result=False), 1) + mock_host = MagicMock() + mock_host.name = 'host1' + + self.assertEqual(strategy_base.run(iterator=mock_iterator, play_context=mock_play_context), mock_tqm.RUN_OK) + self.assertEqual(strategy_base.run(iterator=mock_iterator, play_context=mock_play_context, result=False), mock_tqm.RUN_ERROR) mock_tqm._failed_hosts = dict(host1=True) - self.assertEqual(strategy_base.run(iterator=mock_iterator, play_context=mock_play_context, result=False), 2) + mock_iterator.get_failed_hosts.return_value = [mock_host] + self.assertEqual(strategy_base.run(iterator=mock_iterator, play_context=mock_play_context, result=False), mock_tqm.RUN_FAILED_HOSTS) mock_tqm._unreachable_hosts = dict(host1=True) - self.assertEqual(strategy_base.run(iterator=mock_iterator, play_context=mock_play_context, result=False), 3) + mock_iterator.get_failed_hosts.return_value = [] + self.assertEqual(strategy_base.run(iterator=mock_iterator, play_context=mock_play_context, result=False), mock_tqm.RUN_UNREACHABLE_HOSTS) def test_strategy_base_get_hosts(self): mock_hosts = []