From ca6ee4c78980707a6ff83176d0e019a9b0ef052f Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Wed, 20 Apr 2016 09:06:53 -0400 Subject: [PATCH] FEATURE: handler listeners Fixes ansible/proposals#8 --- lib/ansible/executor/play_iterator.py | 3 -- lib/ansible/executor/task_queue_manager.py | 9 +++-- lib/ansible/playbook/handler.py | 4 +- lib/ansible/plugins/strategy/__init__.py | 39 ++++++++++++------- .../plugins/strategies/test_strategy_base.py | 14 ++++++- 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/lib/ansible/executor/play_iterator.py b/lib/ansible/executor/play_iterator.py index 72742c1a34..4ccc709e66 100644 --- a/lib/ansible/executor/play_iterator.py +++ b/lib/ansible/executor/play_iterator.py @@ -212,9 +212,6 @@ class PlayIterator: # plays won't try to advance) play_context.start_at_task = None - # Extend the play handlers list to include the handlers defined in roles - self._play.handlers.extend(play.compile_roles_handlers()) - def get_host_state(self, host): # Since we're using the PlayIterator to carry forward failed hosts, # in the event that a previous host was not in the current inventory diff --git a/lib/ansible/executor/task_queue_manager.py b/lib/ansible/executor/task_queue_manager.py index 651ee20d89..10f3b50d4b 100644 --- a/lib/ansible/executor/task_queue_manager.py +++ b/lib/ansible/executor/task_queue_manager.py @@ -93,6 +93,7 @@ class TaskQueueManager: # this dictionary is used to keep track of notified handlers self._notified_handlers = dict() + self._listening_handlers = dict() # dictionaries to keep track of failed/unreachable hosts self._failed_hosts = dict() @@ -128,6 +129,7 @@ class TaskQueueManager: # Zero the dictionary first by removing any entries there. # Proxied dicts don't support iteritems, so we have to use keys() self._notified_handlers.clear() + self._listening_handlers.clear() def _process_block(b): temp_list = [] @@ -146,6 +148,10 @@ class TaskQueueManager: for handler in handler_list: if handler not in self._notified_handlers: self._notified_handlers[handler] = [] + if handler.listen: + if handler.listen not in self._listening_handlers: + self._listening_handlers[handler.listen] = [] + self._listening_handlers[handler.listen].append(handler.get_name()) def load_callbacks(self): ''' @@ -303,9 +309,6 @@ class TaskQueueManager: def get_loader(self): return self._loader - def get_notified_handlers(self): - return self._notified_handlers - def get_workers(self): return self._workers[:] diff --git a/lib/ansible/playbook/handler.py b/lib/ansible/playbook/handler.py index c8c1572e48..a611b72259 100644 --- a/lib/ansible/playbook/handler.py +++ b/lib/ansible/playbook/handler.py @@ -20,11 +20,13 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type from ansible.errors import AnsibleError -#from ansible.inventory.host import Host +from ansible.playbook.attribute import FieldAttribute from ansible.playbook.task import Task class Handler(Task): + _listen = FieldAttribute(isa='list') + def __init__(self, block=None, role=None, task_include=None): self._flagged_hosts = [] diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index 41af1efdf8..8d42397c42 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -100,7 +100,8 @@ class StrategyBase: self._tqm = tqm self._inventory = tqm.get_inventory() self._workers = tqm.get_workers() - self._notified_handlers = tqm.get_notified_handlers() + self._notified_handlers = tqm._notified_handlers + self._listening_handlers = tqm._listening_handlers self._variable_manager = tqm.get_variable_manager() self._loader = tqm.get_loader() self._final_q = tqm._final_q @@ -319,7 +320,7 @@ class StrategyBase: original_host = get_original_host(task_result._host) original_task = iterator.get_original_task(original_host, task_result._task) - def search_handler_blocks(handler_blocks): + def search_handler_blocks(handler_name, handler_blocks): for handler_block in handler_blocks: for handler_task in handler_block.block: handler_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, task=handler_task) @@ -350,20 +351,32 @@ class StrategyBase: # roles and use the first one that matches the notify name target_handler = None if original_task._role: - target_handler = search_handler_blocks(original_task._role.get_handler_blocks()) + target_handler = search_handler_blocks(handler_name, original_task._role.get_handler_blocks()) if target_handler is None: - target_handler = search_handler_blocks(iterator._play.handlers) + target_handler = search_handler_blocks(handler_name, iterator._play.handlers) if target_handler is None: - raise AnsibleError("The requested handler '%s' was not found in any of the known handlers" % handler_name) + if handler_name in self._listening_handlers: + for listening_handler_name in self._listening_handlers[handler_name]: + listening_handler = None + if original_task._role: + listening_handler = search_handler_blocks(listening_handler_name, original_task._role.get_handler_blocks()) + if listening_handler is None: + listening_handler = search_handler_blocks(listening_handler_name, iterator._play.handlers) + if listening_handler is None: + raise AnsibleError("The requested handler listener '%s' was not found in any of the known handlers" % listening_handler_name) - # FIXME: this should be an error now in 2.1+ - if target_handler not in self._notified_handlers: - self._notified_handlers[target_handler] = [] - - if original_host not in self._notified_handlers[target_handler]: - self._notified_handlers[target_handler].append(original_host) - # FIXME: should this be a callback? - display.vv("NOTIFIED HANDLER %s" % (handler_name,)) + if original_host not in self._notified_handlers[listening_handler]: + self._notified_handlers[listening_handler].append(original_host) + display.vv("NOTIFIED HANDLER %s" % (listening_handler_name,)) + else: + raise AnsibleError("The requested handler '%s' was found in neither the main handlers list nor the listening handlers list" % handler_name) + else: + if target_handler in self._notified_handlers: + if original_host not in self._notified_handlers[target_handler]: + self._notified_handlers[target_handler].append(original_host) + # FIXME: should this be a callback? + display.vv("NOTIFIED HANDLER %s" % (handler_name,)) + else: elif result[0] == 'register_host_var': # essentially the same as 'set_host_var' below, however we diff --git a/test/units/plugins/strategies/test_strategy_base.py b/test/units/plugins/strategies/test_strategy_base.py index 132f3eb847..e079fa8d48 100644 --- a/test/units/plugins/strategies/test_strategy_base.py +++ b/test/units/plugins/strategies/test_strategy_base.py @@ -45,12 +45,16 @@ class TestStrategyBase(unittest.TestCase): mock_tqm = MagicMock(TaskQueueManager) mock_tqm._final_q = MagicMock() mock_tqm._options = MagicMock() + mock_tqm._notified_handlers = {} + mock_tqm._listening_handlers = {} strategy_base = StrategyBase(tqm=mock_tqm) def test_strategy_base_run(self): mock_tqm = MagicMock(TaskQueueManager) mock_tqm._final_q = MagicMock() mock_tqm._stats = MagicMock() + mock_tqm._notified_handlers = {} + mock_tqm._listening_handlers = {} mock_tqm.send_callback.return_value = None mock_iterator = MagicMock() @@ -62,6 +66,8 @@ class TestStrategyBase(unittest.TestCase): mock_tqm._failed_hosts = dict() mock_tqm._unreachable_hosts = dict() mock_tqm._options = MagicMock() + mock_tqm._notified_handlers = {} + mock_tqm._listening_handlers = {} strategy_base = StrategyBase(tqm=mock_tqm) mock_host = MagicMock() @@ -89,6 +95,8 @@ class TestStrategyBase(unittest.TestCase): mock_tqm = MagicMock() mock_tqm._final_q = MagicMock() + mock_tqm._notified_handlers = {} + mock_tqm._listening_handlers = {} mock_tqm.get_inventory.return_value = mock_inventory mock_play = MagicMock() @@ -153,6 +161,8 @@ class TestStrategyBase(unittest.TestCase): mock_tqm._failed_hosts = dict() mock_tqm._unreachable_hosts = dict() mock_tqm.send_callback.return_value = None + mock_tqm._notified_handlers = {} + mock_tqm._listening_handlers = {} queue_items = [] def _queue_empty(*args, **kwargs): @@ -197,6 +207,7 @@ class TestStrategyBase(unittest.TestCase): mock_play.handlers = [mock_handler_block] mock_tqm._notified_handlers = {mock_handler_task: []} + mock_tqm._listening_handlers = {} mock_group = MagicMock() mock_group.add_host.return_value = None @@ -225,7 +236,6 @@ class TestStrategyBase(unittest.TestCase): strategy_base._inventory = mock_inventory strategy_base._variable_manager = mock_var_mgr strategy_base._blocked_hosts = dict() - strategy_base._notified_handlers = dict() results = strategy_base._wait_on_pending_results(iterator=mock_iterator) self.assertEqual(len(results), 0) @@ -322,6 +332,8 @@ class TestStrategyBase(unittest.TestCase): mock_tqm = MagicMock() mock_tqm._final_q = MagicMock() + mock_tqm._notified_handlers = {} + mock_tqm._listening_handlers = {} strategy_base = StrategyBase(tqm=mock_tqm) strategy_base._loader = fake_loader