diff --git a/lib/ansible/executor/process/worker.py b/lib/ansible/executor/process/worker.py index 96fb2c687cf..ed7cafc5f57 100644 --- a/lib/ansible/executor/process/worker.py +++ b/lib/ansible/executor/process/worker.py @@ -32,6 +32,7 @@ from ansible._internal import _task from ansible.errors import AnsibleConnectionFailure, AnsibleError from ansible.executor.task_executor import TaskExecutor from ansible.executor.task_queue_manager import FinalQueue, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO +from ansible.executor.task_result import TaskResult from ansible.inventory.host import Host from ansible.module_utils.common.collections import is_sequence from ansible.module_utils.common.text.converters import to_text @@ -256,48 +257,52 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin # put the result on the result queue display.debug("sending task result for task %s" % self._task._uuid) try: - self._final_q.send_task_result( - self._host.name, - self._task._uuid, - executor_result, + self._final_q.send_task_result(TaskResult( + host=self._host, + task=self._task, + return_data=executor_result, task_fields=self._task.dump_attrs(), - ) + )) except Exception as ex: try: raise AnsibleError("Task result omitted due to queue send failure.") from ex except Exception as ex_wrapper: - self._final_q.send_task_result( - self._host.name, - self._task._uuid, - ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure - {}, # The failure pickling may have been caused by the task attrs, omit for safety - ) + self._final_q.send_task_result(TaskResult( + host=self._host, + task=self._task, + return_data=ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure + task_fields={}, # The failure pickling may have been caused by the task attrs, omit for safety + )) display.debug("done sending task result for task %s" % self._task._uuid) - except AnsibleConnectionFailure: + except AnsibleConnectionFailure as ex: + return_data = ActionBase.result_dict_from_exception(ex) + return_data.pop('failed') + return_data.update(unreachable=True) + self._host.vars = dict() self._host.groups = [] - self._final_q.send_task_result( - self._host.name, - self._task._uuid, - dict(unreachable=True), + self._final_q.send_task_result(TaskResult( + host=self._host, + task=self._task, + return_data=return_data, task_fields=self._task.dump_attrs(), - ) + )) - except Exception as e: - if not isinstance(e, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(e, TemplateNotFound): + except Exception as ex: + if not isinstance(ex, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(ex, TemplateNotFound): try: self._host.vars = dict() self._host.groups = [] - self._final_q.send_task_result( - self._host.name, - self._task._uuid, - dict(failed=True, exception=to_text(traceback.format_exc()), stdout=''), + self._final_q.send_task_result(TaskResult( + host=self._host, + task=self._task, + return_data=ActionBase.result_dict_from_exception(ex), task_fields=self._task.dump_attrs(), - ) + )) except Exception: - display.debug(u"WORKER EXCEPTION: %s" % to_text(e)) + display.debug(u"WORKER EXCEPTION: %s" % to_text(ex)) display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc())) finally: self._clean_up() diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index ef292dac9f7..21e492401d4 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -44,6 +44,9 @@ from ansible.vars.clean import namespace_facts, clean_facts from ansible.vars.manager import _deprecate_top_level_fact from ansible._internal._errors import _captured +if t.TYPE_CHECKING: + from ansible.executor.task_queue_manager import FinalQueue + display = Display() @@ -79,7 +82,7 @@ class TaskExecutor: class. """ - def __init__(self, host, task: Task, job_vars, play_context, loader, shared_loader_obj, final_q, variable_manager): + def __init__(self, host, task: Task, job_vars, play_context, loader, shared_loader_obj, final_q: FinalQueue, variable_manager): self._host = host self._task = task self._job_vars = job_vars @@ -362,9 +365,9 @@ class TaskExecutor: task_fields['connection'] = getattr(self._connection, 'ansible_name') tr = TaskResult( - self._host.name, - self._task._uuid, - res, + host=self._host, + task=self._task, + return_data=res, task_fields=task_fields, ) @@ -666,17 +669,23 @@ class TaskExecutor: if result.get('failed'): self._final_q.send_callback( 'v2_runner_on_async_failed', - TaskResult(self._host.name, - self._task._uuid, - result, - task_fields=self._task.dump_attrs())) + TaskResult( + host=self._host, + task=self._task, + return_data=result, + task_fields=self._task.dump_attrs(), + ), + ) else: self._final_q.send_callback( 'v2_runner_on_async_ok', - TaskResult(self._host.name, - self._task._uuid, - result, - task_fields=self._task.dump_attrs())) + TaskResult( + host=self._host, + task=self._task, + return_data=result, + task_fields=self._task.dump_attrs(), + ), + ) if 'ansible_facts' in result and self._task.action not in C._ACTION_DEBUG: if self._task.action in C._ACTION_WITH_CLEAN_FACTS: @@ -757,11 +766,11 @@ class TaskExecutor: self._final_q.send_callback( 'v2_runner_retry', TaskResult( - self._host.name, - self._task._uuid, - result, + host=self._host, + task=self._task, + return_data=result, task_fields=self._task.dump_attrs() - ) + ), ) time.sleep(delay) self._handler = self._get_action_handler(templar=templar) @@ -927,9 +936,9 @@ class TaskExecutor: self._final_q.send_callback( 'v2_runner_on_async_poll', TaskResult( - self._host.name, - async_task._uuid, - async_result, + host=self._host, + task=async_task, + return_data=async_result, task_fields=async_task.dump_attrs(), ), ) diff --git a/lib/ansible/executor/task_queue_manager.py b/lib/ansible/executor/task_queue_manager.py index 3079d3ecc42..c26b9896cd6 100644 --- a/lib/ansible/executor/task_queue_manager.py +++ b/lib/ansible/executor/task_queue_manager.py @@ -17,6 +17,7 @@ from __future__ import annotations +import dataclasses import os import sys import tempfile @@ -31,7 +32,7 @@ from ansible.errors import AnsibleError, ExitCode, AnsibleCallbackError from ansible._internal._errors._handler import ErrorHandler from ansible.executor.play_iterator import PlayIterator from ansible.executor.stats import AggregateStats -from ansible.executor.task_result import TaskResult +from ansible.executor.task_result import TaskResult, ThinTaskResult from ansible.inventory.data import InventoryData from ansible.module_utils.six import string_types from ansible.module_utils.common.text.converters import to_native @@ -58,11 +59,10 @@ STDERR_FILENO = 2 display = Display() +@dataclasses.dataclass(frozen=True, kw_only=True, slots=True) class CallbackSend: - def __init__(self, method_name, *args, **kwargs): - self.method_name = method_name - self.args = args - self.kwargs = kwargs + method_name: str + thin_task_result: ThinTaskResult class DisplaySend: @@ -87,19 +87,11 @@ class FinalQueue(multiprocessing.queues.SimpleQueue): kwargs['ctx'] = multiprocessing_context super().__init__(*args, **kwargs) - def send_callback(self, method_name, *args, **kwargs): - self.put( - CallbackSend(method_name, *args, **kwargs), - ) + def send_callback(self, method_name: str, task_result: TaskResult) -> None: + self.put(CallbackSend(method_name=method_name, thin_task_result=task_result.as_thin())) - def send_task_result(self, *args, **kwargs): - if isinstance(args[0], TaskResult): - tr = args[0] - else: - tr = TaskResult(*args, **kwargs) - self.put( - tr, - ) + def send_task_result(self, task_result: TaskResult) -> None: + self.put(task_result.as_thin()) def send_display(self, method, *args, **kwargs): self.put( diff --git a/lib/ansible/executor/task_result.py b/lib/ansible/executor/task_result.py index 986ffd2e494..c1c76e0da52 100644 --- a/lib/ansible/executor/task_result.py +++ b/lib/ansible/executor/task_result.py @@ -4,12 +4,16 @@ from __future__ import annotations +import dataclasses import typing as t from ansible import constants as C -from ansible.parsing.dataloader import DataLoader from ansible.vars.clean import module_response_deepcopy, strip_internal_keys +if t.TYPE_CHECKING: + from ansible.inventory.host import Host + from ansible.playbook.task import Task + _IGNORE = ('failed', 'skipped') _PRESERVE = ('attempts', 'changed', 'retries', '_ansible_no_log') _SUB_PRESERVE = {'_ansible_delegated_vars': ('ansible_host', 'ansible_port', 'ansible_user', 'ansible_connection')} @@ -23,6 +27,16 @@ CLEAN_EXCEPTIONS = ( ) +@dataclasses.dataclass(frozen=True, kw_only=True, slots=True) +class ThinTaskResult: + """A thin version of `TaskResult` which can be sent over the worker queue.""" + + host_name: str + task_uuid: str + return_data: dict[str, object] + task_fields: dict[str, object] + + class TaskResult: """ This class is responsible for interpreting the resulting data @@ -30,19 +44,20 @@ class TaskResult: the result of a given task. """ - def __init__(self, host, task, return_data, task_fields=None): + def __init__(self, host: Host, task: Task, return_data: dict[str, object], task_fields: dict[str, object]) -> None: self._host = host self._task = task - - if isinstance(return_data, dict): - self._result = return_data.copy() - else: - self._result = DataLoader().load(return_data) - - if task_fields is None: - self._task_fields = dict() - else: - self._task_fields = task_fields + self._result = return_data + self._task_fields = task_fields + + def as_thin(self) -> ThinTaskResult: + """Return a `ThinTaskResult` from this instance.""" + return ThinTaskResult( + host_name=self._host.name, + task_uuid=self._task._uuid, + return_data=self._result, + task_fields=self._task_fields, + ) @property def task_name(self): diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index ba48d7ee70e..ed5b2f1102a 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -35,9 +35,8 @@ from ansible import context from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleParserError, AnsibleTemplateError from ansible.executor.play_iterator import IteratingStates, PlayIterator from ansible.executor.process.worker import WorkerProcess -from ansible.executor.task_result import TaskResult +from ansible.executor.task_result import TaskResult, ThinTaskResult from ansible.executor.task_queue_manager import CallbackSend, DisplaySend, PromptSend, TaskQueueManager -from ansible.module_utils.six import string_types from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.connection import Connection, ConnectionError from ansible.playbook.handler import Handler @@ -89,7 +88,9 @@ def _get_item_vars(result, task): return item_vars -def results_thread_main(strategy): +def results_thread_main(strategy: StrategyBase) -> None: + value: object + while True: try: result = strategy._final_q.get() @@ -99,13 +100,10 @@ def results_thread_main(strategy): dmethod = getattr(display, result.method) dmethod(*result.args, **result.kwargs) elif isinstance(result, CallbackSend): - for arg in result.args: - if isinstance(arg, TaskResult): - strategy.normalize_task_result(arg) - break - strategy._tqm.send_callback(result.method_name, *result.args, **result.kwargs) - elif isinstance(result, TaskResult): - strategy.normalize_task_result(result) + task_result = strategy.convert_thin_task_result(result.thin_task_result) + strategy._tqm.send_callback(result.method_name, task_result) + elif isinstance(result, ThinTaskResult): + result = strategy.convert_thin_task_result(result) with strategy._results_lock: strategy._results.append(result) elif isinstance(result, PromptSend): @@ -447,39 +445,33 @@ class StrategyBase: for target_host in host_list: _set_host_facts(target_host, always_facts) - def normalize_task_result(self, task_result): - """Normalize a TaskResult to reference actual Host and Task objects - when only given the ``Host.name``, or the ``Task._uuid`` - - Only the ``Host.name`` and ``Task._uuid`` are commonly sent back from - the ``TaskExecutor`` or ``WorkerProcess`` due to performance concerns + def convert_thin_task_result(self, thin_task_result: ThinTaskResult) -> TaskResult: + """Return a `TaskResult` created from a `ThinTaskResult`.""" + host = self._inventory.get_host(thin_task_result.host_name) + queue_cache_entry = (host.name, thin_task_result.task_uuid) - Mutates the original object - """ - - if isinstance(task_result._host, string_types): - # If the value is a string, it is ``Host.name`` - task_result._host = self._inventory.get_host(to_text(task_result._host)) + try: + found_task = self._queued_task_cache[queue_cache_entry]['task'] + except KeyError: + # This should only happen due to an implicit task created by the + # TaskExecutor, restrict this behavior to the explicit use case + # of an implicit async_status task + if thin_task_result.task_fields.get('action') != 'async_status': + raise + + task = Task() + else: + task = found_task.copy(exclude_parent=True, exclude_tasks=True) + task._parent = found_task._parent - if isinstance(task_result._task, string_types): - # If the value is a string, it is ``Task._uuid`` - queue_cache_entry = (task_result._host.name, task_result._task) - try: - found_task = self._queued_task_cache[queue_cache_entry]['task'] - except KeyError: - # This should only happen due to an implicit task created by the - # TaskExecutor, restrict this behavior to the explicit use case - # of an implicit async_status task - if task_result._task_fields.get('action') != 'async_status': - raise - original_task = Task() - else: - original_task = found_task.copy(exclude_parent=True, exclude_tasks=True) - original_task._parent = found_task._parent - original_task.from_attrs(task_result._task_fields) - task_result._task = original_task + task.from_attrs(thin_task_result.task_fields) - return task_result + return TaskResult( + host=host, + task=task, + return_data=thin_task_result.return_data, + task_fields=thin_task_result.task_fields, + ) def search_handlers_by_notification(self, notification: str, iterator: PlayIterator) -> t.Generator[Handler, None, None]: handlers = [h for b in reversed(iterator._play.handlers) for h in b.block] @@ -869,7 +861,7 @@ class StrategyBase: r._result['failed'] = True for host in included_file._hosts: - tr = TaskResult(host=host, task=included_file._task, return_data=dict(failed=True, reason=reason)) + tr = TaskResult(host=host, task=included_file._task, return_data=dict(failed=True, reason=reason), task_fields={}) self._tqm._stats.increment('failures', host.name) self._tqm.send_callback('v2_runner_on_failed', tr) raise AnsibleError(reason) from e @@ -1083,7 +1075,7 @@ class StrategyBase: else: display.vv(f"META: {header}") - res = TaskResult(target_host, task, result) + res = TaskResult(target_host, task, result, {}) if skipped: self._tqm.send_callback('v2_runner_on_skipped', res) return [res] diff --git a/lib/ansible/utils/display.py b/lib/ansible/utils/display.py index ba5b00d6616..46d8c94b713 100644 --- a/lib/ansible/utils/display.py +++ b/lib/ansible/utils/display.py @@ -983,8 +983,8 @@ class Display(metaclass=Singleton): msg: str, private: bool = False, seconds: int | None = None, - interrupt_input: c.Container[bytes] | None = None, - complete_input: c.Container[bytes] | None = None, + interrupt_input: c.Iterable[bytes] | None = None, + complete_input: c.Iterable[bytes] | None = None, ) -> bytes: if self._final_q: from ansible.executor.process.worker import current_worker @@ -1039,8 +1039,8 @@ class Display(metaclass=Singleton): self, echo: bool = False, seconds: int | None = None, - interrupt_input: c.Container[bytes] | None = None, - complete_input: c.Container[bytes] | None = None, + interrupt_input: c.Iterable[bytes] | None = None, + complete_input: c.Iterable[bytes] | None = None, ) -> bytes: if self._final_q: raise NotImplementedError diff --git a/lib/ansible/vars/manager.py b/lib/ansible/vars/manager.py index 1c1ad76874c..f23d0ec62fb 100644 --- a/lib/ansible/vars/manager.py +++ b/lib/ansible/vars/manager.py @@ -479,8 +479,9 @@ class VariableManager: variables['play_hosts'] = deprecate_value( value=variables['ansible_play_batch'], - msg='Use `ansible_play_batch` instead of `play_hosts`.', + msg='The `play_hosts` magic variable is deprecated.', removal_version='2.23', + help_text='Use `ansible_play_batch` instead.', ) # Set options vars diff --git a/test/units/executor/test_task_result.py b/test/units/executor/test_task_result.py index efbee5174f6..80945deca77 100644 --- a/test/units/executor/test_task_result.py +++ b/test/units/executor/test_task_result.py @@ -18,7 +18,7 @@ from __future__ import annotations import unittest -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock from ansible.executor.task_result import TaskResult @@ -29,32 +29,28 @@ class TestTaskResult(unittest.TestCase): mock_task = MagicMock() # test loading a result with a dict - tr = TaskResult(mock_host, mock_task, dict()) - - # test loading a result with a JSON string - with patch('ansible.parsing.dataloader.DataLoader.load') as p: - tr = TaskResult(mock_host, mock_task, '{}') + tr = TaskResult(mock_host, mock_task, {}, {}) def test_task_result_is_changed(self): mock_host = MagicMock() mock_task = MagicMock() # test with no changed in result - tr = TaskResult(mock_host, mock_task, dict()) + tr = TaskResult(mock_host, mock_task, {}, {}) self.assertFalse(tr.is_changed()) # test with changed in the result - tr = TaskResult(mock_host, mock_task, dict(changed=True)) + tr = TaskResult(mock_host, mock_task, dict(changed=True), {}) self.assertTrue(tr.is_changed()) # test with multiple results but none changed mock_task.loop = 'foo' - tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True])) + tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]), {}) self.assertFalse(tr.is_changed()) # test with multiple results and one changed mock_task.loop = 'foo' - tr = TaskResult(mock_host, mock_task, dict(results=[dict(changed=False), dict(changed=True), dict(some_key=False)])) + tr = TaskResult(mock_host, mock_task, dict(results=[dict(changed=False), dict(changed=True), dict(some_key=False)]), {}) self.assertTrue(tr.is_changed()) def test_task_result_is_skipped(self): @@ -62,35 +58,35 @@ class TestTaskResult(unittest.TestCase): mock_task = MagicMock() # test with no skipped in result - tr = TaskResult(mock_host, mock_task, dict()) + tr = TaskResult(mock_host, mock_task, dict(), {}) self.assertFalse(tr.is_skipped()) # test with skipped in the result - tr = TaskResult(mock_host, mock_task, dict(skipped=True)) + tr = TaskResult(mock_host, mock_task, dict(skipped=True), {}) self.assertTrue(tr.is_skipped()) # test with multiple results but none skipped mock_task.loop = 'foo' - tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True])) + tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]), {}) self.assertFalse(tr.is_skipped()) # test with multiple results and one skipped mock_task.loop = 'foo' - tr = TaskResult(mock_host, mock_task, dict(results=[dict(skipped=False), dict(skipped=True), dict(some_key=False)])) + tr = TaskResult(mock_host, mock_task, dict(results=[dict(skipped=False), dict(skipped=True), dict(some_key=False)]), {}) self.assertFalse(tr.is_skipped()) # test with multiple results and all skipped mock_task.loop = 'foo' - tr = TaskResult(mock_host, mock_task, dict(results=[dict(skipped=True), dict(skipped=True), dict(skipped=True)])) + tr = TaskResult(mock_host, mock_task, dict(results=[dict(skipped=True), dict(skipped=True), dict(skipped=True)]), {}) self.assertTrue(tr.is_skipped()) # test with multiple squashed results (list of strings) # first with the main result having skipped=False mock_task.loop = 'foo' - tr = TaskResult(mock_host, mock_task, dict(results=["a", "b", "c"], skipped=False)) + tr = TaskResult(mock_host, mock_task, dict(results=["a", "b", "c"], skipped=False), {}) self.assertFalse(tr.is_skipped()) # then with the main result having skipped=True - tr = TaskResult(mock_host, mock_task, dict(results=["a", "b", "c"], skipped=True)) + tr = TaskResult(mock_host, mock_task, dict(results=["a", "b", "c"], skipped=True), {}) self.assertTrue(tr.is_skipped()) def test_task_result_is_unreachable(self): @@ -98,21 +94,21 @@ class TestTaskResult(unittest.TestCase): mock_task = MagicMock() # test with no unreachable in result - tr = TaskResult(mock_host, mock_task, dict()) + tr = TaskResult(mock_host, mock_task, {}, {}) self.assertFalse(tr.is_unreachable()) # test with unreachable in the result - tr = TaskResult(mock_host, mock_task, dict(unreachable=True)) + tr = TaskResult(mock_host, mock_task, dict(unreachable=True), {}) self.assertTrue(tr.is_unreachable()) # test with multiple results but none unreachable mock_task.loop = 'foo' - tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True])) + tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]), {}) self.assertFalse(tr.is_unreachable()) # test with multiple results and one unreachable mock_task.loop = 'foo' - tr = TaskResult(mock_host, mock_task, dict(results=[dict(unreachable=False), dict(unreachable=True), dict(some_key=False)])) + tr = TaskResult(mock_host, mock_task, dict(results=[dict(unreachable=False), dict(unreachable=True), dict(some_key=False)]), {}) self.assertTrue(tr.is_unreachable()) def test_task_result_is_failed(self): @@ -120,21 +116,21 @@ class TestTaskResult(unittest.TestCase): mock_task = MagicMock() # test with no failed in result - tr = TaskResult(mock_host, mock_task, dict()) + tr = TaskResult(mock_host, mock_task, dict(), {}) self.assertFalse(tr.is_failed()) # test failed result with rc values (should not matter) - tr = TaskResult(mock_host, mock_task, dict(rc=0)) + tr = TaskResult(mock_host, mock_task, dict(rc=0), {}) self.assertFalse(tr.is_failed()) - tr = TaskResult(mock_host, mock_task, dict(rc=1)) + tr = TaskResult(mock_host, mock_task, dict(rc=1), {}) self.assertFalse(tr.is_failed()) # test with failed in result - tr = TaskResult(mock_host, mock_task, dict(failed=True)) + tr = TaskResult(mock_host, mock_task, dict(failed=True), {}) self.assertTrue(tr.is_failed()) # test with failed_when in result - tr = TaskResult(mock_host, mock_task, dict(failed_when_result=True)) + tr = TaskResult(mock_host, mock_task, dict(failed_when_result=True), {}) self.assertTrue(tr.is_failed()) def test_task_result_no_log(self): @@ -142,7 +138,7 @@ class TestTaskResult(unittest.TestCase): mock_task = MagicMock() # no_log should remove secrets - tr = TaskResult(mock_host, mock_task, dict(_ansible_no_log=True, secret='DONTSHOWME')) + tr = TaskResult(mock_host, mock_task, dict(_ansible_no_log=True, secret='DONTSHOWME'), {}) clean = tr.clean_copy() self.assertTrue('secret' not in clean._result) @@ -160,7 +156,8 @@ class TestTaskResult(unittest.TestCase): attempts=5, changed=False, foo='bar', - ) + ), + task_fields={}, ) clean = tr.clean_copy() self.assertTrue('retries' in clean._result) diff --git a/test/units/playbook/test_included_file.py b/test/units/playbook/test_included_file.py index 1d2fa37f60f..34223d064d8 100644 --- a/test/units/playbook/test_included_file.py +++ b/test/units/playbook/test_included_file.py @@ -22,6 +22,8 @@ import os import pytest from unittest.mock import MagicMock + +from ansible.inventory.host import Host from units.mock.loader import DictDataLoader from ansible.playbook import Play @@ -105,8 +107,8 @@ def test_included_file_instantiation(): def test_process_include_tasks_results(mock_iterator, mock_variable_manager): - hostname = "testhost1" - hostname2 = "testhost2" + host1 = Host("testhost1") + host2 = Host("testhost2") parent_task_ds = {'debug': 'msg=foo'} parent_task = Task.load(parent_task_ds) @@ -117,8 +119,8 @@ def test_process_include_tasks_results(mock_iterator, mock_variable_manager): return_data = {'include': 'include_test.yml'} # The task in the TaskResult has to be a TaskInclude so it has a .static attr - result1 = task_result.TaskResult(host=hostname, task=loaded_task, return_data=return_data) - result2 = task_result.TaskResult(host=hostname2, task=loaded_task, return_data=return_data) + result1 = task_result.TaskResult(host=host1, task=loaded_task, return_data=return_data, task_fields={}) + result2 = task_result.TaskResult(host=host2, task=loaded_task, return_data=return_data, task_fields={}) results = [result1, result2] fake_loader = DictDataLoader({'include_test.yml': ""}) @@ -127,14 +129,14 @@ def test_process_include_tasks_results(mock_iterator, mock_variable_manager): assert isinstance(res, list) assert len(res) == 1 assert res[0]._filename == os.path.join(os.getcwd(), 'include_test.yml') - assert res[0]._hosts == ['testhost1', 'testhost2'] + assert res[0]._hosts == [host1, host2] assert res[0]._args == {} assert res[0]._vars == {} def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager): - hostname = "testhost1" - hostname2 = "testhost2" + host1 = Host("testhost1") + host2 = Host("testhost2") parent_task_ds = {'debug': 'msg=foo'} parent_task = Task.load(parent_task_ds) @@ -150,10 +152,10 @@ def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager): return_data = {'include': 'include_test.yml'} # The task in the TaskResult has to be a TaskInclude so it has a .static attr - result1 = task_result.TaskResult(host=hostname, task=loaded_task, return_data=return_data) + result1 = task_result.TaskResult(host=host1, task=loaded_task, return_data=return_data, task_fields={}) return_data = {'include': 'other_include_test.yml'} - result2 = task_result.TaskResult(host=hostname2, task=loaded_child_task, return_data=return_data) + result2 = task_result.TaskResult(host=host2, task=loaded_child_task, return_data=return_data, task_fields={}) results = [result1, result2] fake_loader = DictDataLoader({'include_test.yml': "", @@ -164,8 +166,8 @@ def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager): assert res[0]._filename == os.path.join(os.getcwd(), 'include_test.yml') assert res[1]._filename == os.path.join(os.getcwd(), 'other_include_test.yml') - assert res[0]._hosts == ['testhost1'] - assert res[1]._hosts == ['testhost2'] + assert res[0]._hosts == [host1] + assert res[1]._hosts == [host2] assert res[0]._args == {} assert res[1]._args == {} @@ -175,8 +177,8 @@ def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager): def test_process_include_tasks_simulate_free(mock_iterator, mock_variable_manager): - hostname = "testhost1" - hostname2 = "testhost2" + host1 = Host("testhost1") + host2 = Host("testhost2") parent_task_ds = {'debug': 'msg=foo'} parent_task1 = Task.load(parent_task_ds) @@ -191,8 +193,8 @@ def test_process_include_tasks_simulate_free(mock_iterator, mock_variable_manage return_data = {'include': 'include_test.yml'} # The task in the TaskResult has to be a TaskInclude so it has a .static attr - result1 = task_result.TaskResult(host=hostname, task=loaded_task1, return_data=return_data) - result2 = task_result.TaskResult(host=hostname2, task=loaded_task2, return_data=return_data) + result1 = task_result.TaskResult(host=host1, task=loaded_task1, return_data=return_data, task_fields={}) + result2 = task_result.TaskResult(host=host2, task=loaded_task2, return_data=return_data, task_fields={}) results = [result1, result2] fake_loader = DictDataLoader({'include_test.yml': ""}) @@ -203,8 +205,8 @@ def test_process_include_tasks_simulate_free(mock_iterator, mock_variable_manage assert res[0]._filename == os.path.join(os.getcwd(), 'include_test.yml') assert res[1]._filename == os.path.join(os.getcwd(), 'include_test.yml') - assert res[0]._hosts == ['testhost1'] - assert res[1]._hosts == ['testhost2'] + assert res[0]._hosts == [host1] + assert res[1]._hosts == [host2] assert res[0]._args == {} assert res[1]._args == {} @@ -281,10 +283,14 @@ def test_process_include_simulate_free_block_role_tasks(mock_iterator, mock_vari result1 = task_result.TaskResult(host=hostname, task=include_role1, - return_data=include_role1_ds) + return_data=include_role1_ds, + task_fields={}, + ) result2 = task_result.TaskResult(host=hostname2, task=include_role2, - return_data=include_role2_ds) + return_data=include_role2_ds, + task_fields={}, + ) results = [result1, result2] res = IncludedFile.process_include_results(results, diff --git a/test/units/plugins/callback/test_callback.py b/test/units/plugins/callback/test_callback.py index f134cf6e462..0956994ba45 100644 --- a/test/units/plugins/callback/test_callback.py +++ b/test/units/plugins/callback/test_callback.py @@ -52,7 +52,7 @@ class TestCallback(unittest.TestCase): self.assertIs(cb._display, display_mock) def test_host_label(self): - result = TaskResult(host=Host('host1'), task=mock_task, return_data={}) + result = TaskResult(host=Host('host1'), task=mock_task, return_data={}, task_fields={}) self.assertEqual(CallbackBase.host_label(result), 'host1') @@ -62,6 +62,7 @@ class TestCallback(unittest.TestCase): host=Host('host1'), task=mock_task, return_data={'_ansible_delegated_vars': {'ansible_host': 'host2'}}, + task_fields={}, ) self.assertEqual(CallbackBase.host_label(result), 'host1 -> host2')