summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/ansible/executor/task_queue_manager.py17
-rw-r--r--test/units/executor/test_task_queue_manager_callbacks.py141
2 files changed, 151 insertions, 7 deletions
diff --git a/lib/ansible/executor/task_queue_manager.py b/lib/ansible/executor/task_queue_manager.py
index 2e6948f1e0..8448e49d76 100644
--- a/lib/ansible/executor/task_queue_manager.py
+++ b/lib/ansible/executor/task_queue_manager.py
@@ -353,17 +353,20 @@ class TaskQueueManager:
for method in methods:
try:
- # temporary hack, required due to a change in the callback API, so
- # we don't break backwards compatibility with callbacks which were
- # designed to use the original API
+ # Previously, the `v2_playbook_on_start` callback API did not accept
+ # any arguments. In recent versions of the v2 callback API, the play-
+ # book that started execution is given. In order to support both of
+ # these method signatures, we need to use this `inspect` hack to send
+ # no arguments to the methods that don't accept them. This way, we can
+ # not break backwards compatibility until that API is deprecated.
# FIXME: target for removal and revert to the original code here after a year (2017-01-14)
if method_name == 'v2_playbook_on_start':
import inspect
- (f_args, f_varargs, f_keywords, f_defaults) = inspect.getargspec(method)
- if 'playbook' in f_args:
- method(*args, **kwargs)
- else:
+ argspec = inspect.getargspec(method)
+ if argspec.args == ['self']:
method()
+ else:
+ method(*args, **kwargs)
else:
method(*args, **kwargs)
except Exception as e:
diff --git a/test/units/executor/test_task_queue_manager_callbacks.py b/test/units/executor/test_task_queue_manager_callbacks.py
new file mode 100644
index 0000000000..bd92a69d77
--- /dev/null
+++ b/test/units/executor/test_task_queue_manager_callbacks.py
@@ -0,0 +1,141 @@
+# (c) 2016, Steve Kuznetsov <skuznets@redhat.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+
+from ansible.compat.tests import unittest
+from ansible.compat.tests.mock import MagicMock
+from ansible.executor.task_queue_manager import TaskQueueManager
+from ansible.playbook import Playbook
+from ansible.plugins.callback import CallbackBase
+
+__metaclass__ = type
+
+
+class TestTaskQueueManagerCallbacks(unittest.TestCase):
+ def setUp(self):
+ inventory = MagicMock()
+ variable_manager = MagicMock()
+ loader = MagicMock()
+ options = MagicMock()
+ passwords = []
+
+ self._tqm = TaskQueueManager(inventory, variable_manager, loader, options, passwords)
+ self._playbook = Playbook(loader)
+
+ # we use a MagicMock to register the result of the call we
+ # expect to `v2_playbook_on_call`. We don't mock out the
+ # method since we're testing code that uses `inspect` to
+ # look at that method's argspec and we want to ensure this
+ # test is easy to reason about.
+ self._register = MagicMock()
+
+ def tearDown(self):
+ pass
+
+ def test_task_queue_manager_callbacks_v2_playbook_on_start_legacy(self):
+ """
+ Assert that no exceptions are raised when sending a Playbook
+ start callback to a legacy callback module plugin.
+ """
+ register = self._register
+
+ class LegacyCallbackModule(CallbackBase):
+ """
+ This is a callback module with the legacy
+ method signature for `v2_playbook_on_start`.
+ """
+ CALLBACK_VERSION = 2.0
+ CALLBACK_TYPE = 'notification'
+ CALLBACK_NAME = 'legacy_module'
+
+ def v2_playbook_on_start(self):
+ register(self)
+
+ callback_module = LegacyCallbackModule()
+ self._tqm._callback_plugins.append(callback_module)
+ self._tqm.send_callback('v2_playbook_on_start', self._playbook)
+ register.assert_called_once_with(callback_module)
+
+ def test_task_queue_manager_callbacks_v2_playbook_on_start(self):
+ """
+ Assert that no exceptions are raised when sending a Playbook
+ start callback to a current callback module plugin.
+ """
+ register = self._register
+
+ class CallbackModule(CallbackBase):
+ """
+ This is a callback module with the current
+ method signature for `v2_playbook_on_start`.
+ """
+ CALLBACK_VERSION = 2.0
+ CALLBACK_TYPE = 'notification'
+ CALLBACK_NAME = 'current_module'
+
+ def v2_playbook_on_start(self, playbook):
+ register(self, playbook)
+
+ callback_module = CallbackModule()
+ self._tqm._callback_plugins.append(callback_module)
+ self._tqm.send_callback('v2_playbook_on_start', self._playbook)
+ register.assert_called_once_with(callback_module, self._playbook)
+
+ def test_task_queue_manager_callbacks_v2_playbook_on_start_wrapped(self):
+ """
+ Assert that no exceptions are raised when sending a Playbook
+ start callback to a wrapped current callback module plugin.
+ """
+ register = self._register
+
+ def wrap_callback(func):
+ """
+ This wrapper changes the exposed argument
+ names for a method from the original names
+ to (*args, **kwargs). This is used in order
+ to validate that wrappers which change par-
+ ameter names do not break the TQM callback
+ system.
+
+ :param func: function to decorate
+ :return: decorated function
+ """
+
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+
+ return wrapper
+
+ class WrappedCallbackModule(CallbackBase):
+ """
+ This is a callback module with the current
+ method signature for `v2_playbook_on_start`
+ wrapped in order to change the signature.
+ """
+ CALLBACK_VERSION = 2.0
+ CALLBACK_TYPE = 'notification'
+ CALLBACK_NAME = 'current_module'
+
+ @wrap_callback
+ def v2_playbook_on_start(self, playbook):
+ register(self, playbook)
+
+ callback_module = WrappedCallbackModule()
+ self._tqm._callback_plugins.append(callback_module)
+ self._tqm.send_callback('v2_playbook_on_start', self._playbook)
+ register.assert_called_once_with(callback_module, self._playbook)