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 = []