Miscellaneous fixes (#85012)

* Add help_text to play_hosts deprecation

* clean up TaskResult type handling

(cherry picked from commit 1b6b910439)
pull/85040/head
Matt Davis 8 months ago committed by Matt Martz
parent 98009c811b
commit 82ea3addce
No known key found for this signature in database
GPG Key ID: 40832D88E9FC91D8

@ -32,6 +32,7 @@ from ansible._internal import _task
from ansible.errors import AnsibleConnectionFailure, AnsibleError from ansible.errors import AnsibleConnectionFailure, AnsibleError
from ansible.executor.task_executor import TaskExecutor 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_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.inventory.host import Host
from ansible.module_utils.common.collections import is_sequence from ansible.module_utils.common.collections import is_sequence
from ansible.module_utils.common.text.converters import to_text 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 # put the result on the result queue
display.debug("sending task result for task %s" % self._task._uuid) display.debug("sending task result for task %s" % self._task._uuid)
try: try:
self._final_q.send_task_result( self._final_q.send_task_result(TaskResult(
self._host.name, host=self._host,
self._task._uuid, task=self._task,
executor_result, return_data=executor_result,
task_fields=self._task.dump_attrs(), task_fields=self._task.dump_attrs(),
) ))
except Exception as ex: except Exception as ex:
try: try:
raise AnsibleError("Task result omitted due to queue send failure.") from ex raise AnsibleError("Task result omitted due to queue send failure.") from ex
except Exception as ex_wrapper: except Exception as ex_wrapper:
self._final_q.send_task_result( self._final_q.send_task_result(TaskResult(
self._host.name, host=self._host,
self._task._uuid, task=self._task,
ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure return_data=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 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) 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.vars = dict()
self._host.groups = [] self._host.groups = []
self._final_q.send_task_result( self._final_q.send_task_result(TaskResult(
self._host.name, host=self._host,
self._task._uuid, task=self._task,
dict(unreachable=True), return_data=return_data,
task_fields=self._task.dump_attrs(), task_fields=self._task.dump_attrs(),
) ))
except Exception as e: except Exception as ex:
if not isinstance(e, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(e, TemplateNotFound): if not isinstance(ex, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(ex, TemplateNotFound):
try: try:
self._host.vars = dict() self._host.vars = dict()
self._host.groups = [] self._host.groups = []
self._final_q.send_task_result( self._final_q.send_task_result(TaskResult(
self._host.name, host=self._host,
self._task._uuid, task=self._task,
dict(failed=True, exception=to_text(traceback.format_exc()), stdout=''), return_data=ActionBase.result_dict_from_exception(ex),
task_fields=self._task.dump_attrs(), task_fields=self._task.dump_attrs(),
) ))
except Exception: 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())) display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc()))
finally: finally:
self._clean_up() self._clean_up()

@ -44,6 +44,9 @@ from ansible.vars.clean import namespace_facts, clean_facts
from ansible.vars.manager import _deprecate_top_level_fact from ansible.vars.manager import _deprecate_top_level_fact
from ansible._internal._errors import _captured from ansible._internal._errors import _captured
if t.TYPE_CHECKING:
from ansible.executor.task_queue_manager import FinalQueue
display = Display() display = Display()
@ -79,7 +82,7 @@ class TaskExecutor:
class. 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._host = host
self._task = task self._task = task
self._job_vars = job_vars self._job_vars = job_vars
@ -362,9 +365,9 @@ class TaskExecutor:
task_fields['connection'] = getattr(self._connection, 'ansible_name') task_fields['connection'] = getattr(self._connection, 'ansible_name')
tr = TaskResult( tr = TaskResult(
self._host.name, host=self._host,
self._task._uuid, task=self._task,
res, return_data=res,
task_fields=task_fields, task_fields=task_fields,
) )
@ -666,17 +669,23 @@ class TaskExecutor:
if result.get('failed'): if result.get('failed'):
self._final_q.send_callback( self._final_q.send_callback(
'v2_runner_on_async_failed', 'v2_runner_on_async_failed',
TaskResult(self._host.name, TaskResult(
self._task._uuid, host=self._host,
result, task=self._task,
task_fields=self._task.dump_attrs())) return_data=result,
task_fields=self._task.dump_attrs(),
),
)
else: else:
self._final_q.send_callback( self._final_q.send_callback(
'v2_runner_on_async_ok', 'v2_runner_on_async_ok',
TaskResult(self._host.name, TaskResult(
self._task._uuid, host=self._host,
result, task=self._task,
task_fields=self._task.dump_attrs())) return_data=result,
task_fields=self._task.dump_attrs(),
),
)
if 'ansible_facts' in result and self._task.action not in C._ACTION_DEBUG: if 'ansible_facts' in result and self._task.action not in C._ACTION_DEBUG:
if self._task.action in C._ACTION_WITH_CLEAN_FACTS: if self._task.action in C._ACTION_WITH_CLEAN_FACTS:
@ -757,11 +766,11 @@ class TaskExecutor:
self._final_q.send_callback( self._final_q.send_callback(
'v2_runner_retry', 'v2_runner_retry',
TaskResult( TaskResult(
self._host.name, host=self._host,
self._task._uuid, task=self._task,
result, return_data=result,
task_fields=self._task.dump_attrs() task_fields=self._task.dump_attrs()
) ),
) )
time.sleep(delay) time.sleep(delay)
self._handler = self._get_action_handler(templar=templar) self._handler = self._get_action_handler(templar=templar)
@ -927,9 +936,9 @@ class TaskExecutor:
self._final_q.send_callback( self._final_q.send_callback(
'v2_runner_on_async_poll', 'v2_runner_on_async_poll',
TaskResult( TaskResult(
self._host.name, host=self._host,
async_task._uuid, task=async_task,
async_result, return_data=async_result,
task_fields=async_task.dump_attrs(), task_fields=async_task.dump_attrs(),
), ),
) )

@ -17,6 +17,7 @@
from __future__ import annotations from __future__ import annotations
import dataclasses
import os import os
import sys import sys
import tempfile import tempfile
@ -31,7 +32,7 @@ from ansible.errors import AnsibleError, ExitCode, AnsibleCallbackError
from ansible._internal._errors._handler import ErrorHandler from ansible._internal._errors._handler import ErrorHandler
from ansible.executor.play_iterator import PlayIterator from ansible.executor.play_iterator import PlayIterator
from ansible.executor.stats import AggregateStats 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.inventory.data import InventoryData
from ansible.module_utils.six import string_types from ansible.module_utils.six import string_types
from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.common.text.converters import to_native
@ -58,11 +59,10 @@ STDERR_FILENO = 2
display = Display() display = Display()
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
class CallbackSend: class CallbackSend:
def __init__(self, method_name, *args, **kwargs): method_name: str
self.method_name = method_name thin_task_result: ThinTaskResult
self.args = args
self.kwargs = kwargs
class DisplaySend: class DisplaySend:
@ -87,19 +87,11 @@ class FinalQueue(multiprocessing.queues.SimpleQueue):
kwargs['ctx'] = multiprocessing_context kwargs['ctx'] = multiprocessing_context
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def send_callback(self, method_name, *args, **kwargs): def send_callback(self, method_name: str, task_result: TaskResult) -> None:
self.put( self.put(CallbackSend(method_name=method_name, thin_task_result=task_result.as_thin()))
CallbackSend(method_name, *args, **kwargs),
)
def send_task_result(self, *args, **kwargs): def send_task_result(self, task_result: TaskResult) -> None:
if isinstance(args[0], TaskResult): self.put(task_result.as_thin())
tr = args[0]
else:
tr = TaskResult(*args, **kwargs)
self.put(
tr,
)
def send_display(self, method, *args, **kwargs): def send_display(self, method, *args, **kwargs):
self.put( self.put(

@ -4,12 +4,16 @@
from __future__ import annotations from __future__ import annotations
import dataclasses
import typing as t import typing as t
from ansible import constants as C from ansible import constants as C
from ansible.parsing.dataloader import DataLoader
from ansible.vars.clean import module_response_deepcopy, strip_internal_keys 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') _IGNORE = ('failed', 'skipped')
_PRESERVE = ('attempts', 'changed', 'retries', '_ansible_no_log') _PRESERVE = ('attempts', 'changed', 'retries', '_ansible_no_log')
_SUB_PRESERVE = {'_ansible_delegated_vars': ('ansible_host', 'ansible_port', 'ansible_user', 'ansible_connection')} _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: class TaskResult:
""" """
This class is responsible for interpreting the resulting data This class is responsible for interpreting the resulting data
@ -30,19 +44,20 @@ class TaskResult:
the result of a given task. 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._host = host
self._task = task self._task = task
self._result = return_data
if isinstance(return_data, dict): self._task_fields = task_fields
self._result = return_data.copy()
else: def as_thin(self) -> ThinTaskResult:
self._result = DataLoader().load(return_data) """Return a `ThinTaskResult` from this instance."""
return ThinTaskResult(
if task_fields is None: host_name=self._host.name,
self._task_fields = dict() task_uuid=self._task._uuid,
else: return_data=self._result,
self._task_fields = task_fields task_fields=self._task_fields,
)
@property @property
def task_name(self): def task_name(self):

@ -35,9 +35,8 @@ from ansible import context
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleParserError, AnsibleTemplateError from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleParserError, AnsibleTemplateError
from ansible.executor.play_iterator import IteratingStates, PlayIterator from ansible.executor.play_iterator import IteratingStates, PlayIterator
from ansible.executor.process.worker import WorkerProcess 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.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.common.text.converters import to_text
from ansible.module_utils.connection import Connection, ConnectionError from ansible.module_utils.connection import Connection, ConnectionError
from ansible.playbook.handler import Handler from ansible.playbook.handler import Handler
@ -89,7 +88,9 @@ def _get_item_vars(result, task):
return item_vars return item_vars
def results_thread_main(strategy): def results_thread_main(strategy: StrategyBase) -> None:
value: object
while True: while True:
try: try:
result = strategy._final_q.get() result = strategy._final_q.get()
@ -99,13 +100,10 @@ def results_thread_main(strategy):
dmethod = getattr(display, result.method) dmethod = getattr(display, result.method)
dmethod(*result.args, **result.kwargs) dmethod(*result.args, **result.kwargs)
elif isinstance(result, CallbackSend): elif isinstance(result, CallbackSend):
for arg in result.args: task_result = strategy.convert_thin_task_result(result.thin_task_result)
if isinstance(arg, TaskResult): strategy._tqm.send_callback(result.method_name, task_result)
strategy.normalize_task_result(arg) elif isinstance(result, ThinTaskResult):
break result = strategy.convert_thin_task_result(result)
strategy._tqm.send_callback(result.method_name, *result.args, **result.kwargs)
elif isinstance(result, TaskResult):
strategy.normalize_task_result(result)
with strategy._results_lock: with strategy._results_lock:
strategy._results.append(result) strategy._results.append(result)
elif isinstance(result, PromptSend): elif isinstance(result, PromptSend):
@ -447,39 +445,33 @@ class StrategyBase:
for target_host in host_list: for target_host in host_list:
_set_host_facts(target_host, always_facts) _set_host_facts(target_host, always_facts)
def normalize_task_result(self, task_result): def convert_thin_task_result(self, thin_task_result: ThinTaskResult) -> TaskResult:
"""Normalize a TaskResult to reference actual Host and Task objects """Return a `TaskResult` created from a `ThinTaskResult`."""
when only given the ``Host.name``, or the ``Task._uuid`` host = self._inventory.get_host(thin_task_result.host_name)
queue_cache_entry = (host.name, thin_task_result.task_uuid)
Only the ``Host.name`` and ``Task._uuid`` are commonly sent back from
the ``TaskExecutor`` or ``WorkerProcess`` due to performance concerns
Mutates the original object try:
""" found_task = self._queued_task_cache[queue_cache_entry]['task']
except KeyError:
if isinstance(task_result._host, string_types): # This should only happen due to an implicit task created by the
# If the value is a string, it is ``Host.name`` # TaskExecutor, restrict this behavior to the explicit use case
task_result._host = self._inventory.get_host(to_text(task_result._host)) # 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): task.from_attrs(thin_task_result.task_fields)
# 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
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]: 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] handlers = [h for b in reversed(iterator._play.handlers) for h in b.block]
@ -869,7 +861,7 @@ class StrategyBase:
r._result['failed'] = True r._result['failed'] = True
for host in included_file._hosts: 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._stats.increment('failures', host.name)
self._tqm.send_callback('v2_runner_on_failed', tr) self._tqm.send_callback('v2_runner_on_failed', tr)
raise AnsibleError(reason) from e raise AnsibleError(reason) from e
@ -1083,7 +1075,7 @@ class StrategyBase:
else: else:
display.vv(f"META: {header}") display.vv(f"META: {header}")
res = TaskResult(target_host, task, result) res = TaskResult(target_host, task, result, {})
if skipped: if skipped:
self._tqm.send_callback('v2_runner_on_skipped', res) self._tqm.send_callback('v2_runner_on_skipped', res)
return [res] return [res]

@ -983,8 +983,8 @@ class Display(metaclass=Singleton):
msg: str, msg: str,
private: bool = False, private: bool = False,
seconds: int | None = None, seconds: int | None = None,
interrupt_input: c.Container[bytes] | None = None, interrupt_input: c.Iterable[bytes] | None = None,
complete_input: c.Container[bytes] | None = None, complete_input: c.Iterable[bytes] | None = None,
) -> bytes: ) -> bytes:
if self._final_q: if self._final_q:
from ansible.executor.process.worker import current_worker from ansible.executor.process.worker import current_worker
@ -1039,8 +1039,8 @@ class Display(metaclass=Singleton):
self, self,
echo: bool = False, echo: bool = False,
seconds: int | None = None, seconds: int | None = None,
interrupt_input: c.Container[bytes] | None = None, interrupt_input: c.Iterable[bytes] | None = None,
complete_input: c.Container[bytes] | None = None, complete_input: c.Iterable[bytes] | None = None,
) -> bytes: ) -> bytes:
if self._final_q: if self._final_q:
raise NotImplementedError raise NotImplementedError

@ -479,8 +479,9 @@ class VariableManager:
variables['play_hosts'] = deprecate_value( variables['play_hosts'] = deprecate_value(
value=variables['ansible_play_batch'], 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', removal_version='2.23',
help_text='Use `ansible_play_batch` instead.',
) )
# Set options vars # Set options vars

@ -18,7 +18,7 @@
from __future__ import annotations from __future__ import annotations
import unittest import unittest
from unittest.mock import patch, MagicMock from unittest.mock import MagicMock
from ansible.executor.task_result import TaskResult from ansible.executor.task_result import TaskResult
@ -29,32 +29,28 @@ class TestTaskResult(unittest.TestCase):
mock_task = MagicMock() mock_task = MagicMock()
# test loading a result with a dict # test loading a result with a dict
tr = TaskResult(mock_host, mock_task, dict()) tr = TaskResult(mock_host, mock_task, {}, {})
# test loading a result with a JSON string
with patch('ansible.parsing.dataloader.DataLoader.load') as p:
tr = TaskResult(mock_host, mock_task, '{}')
def test_task_result_is_changed(self): def test_task_result_is_changed(self):
mock_host = MagicMock() mock_host = MagicMock()
mock_task = MagicMock() mock_task = MagicMock()
# test with no changed in result # test with no changed in result
tr = TaskResult(mock_host, mock_task, dict()) tr = TaskResult(mock_host, mock_task, {}, {})
self.assertFalse(tr.is_changed()) self.assertFalse(tr.is_changed())
# test with changed in the result # 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()) self.assertTrue(tr.is_changed())
# test with multiple results but none changed # test with multiple results but none changed
mock_task.loop = 'foo' 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()) self.assertFalse(tr.is_changed())
# test with multiple results and one changed # test with multiple results and one changed
mock_task.loop = 'foo' 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()) self.assertTrue(tr.is_changed())
def test_task_result_is_skipped(self): def test_task_result_is_skipped(self):
@ -62,35 +58,35 @@ class TestTaskResult(unittest.TestCase):
mock_task = MagicMock() mock_task = MagicMock()
# test with no skipped in result # 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()) self.assertFalse(tr.is_skipped())
# test with skipped in the result # 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()) self.assertTrue(tr.is_skipped())
# test with multiple results but none skipped # test with multiple results but none skipped
mock_task.loop = 'foo' 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()) self.assertFalse(tr.is_skipped())
# test with multiple results and one skipped # test with multiple results and one skipped
mock_task.loop = 'foo' 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()) self.assertFalse(tr.is_skipped())
# test with multiple results and all skipped # test with multiple results and all skipped
mock_task.loop = 'foo' 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()) self.assertTrue(tr.is_skipped())
# test with multiple squashed results (list of strings) # test with multiple squashed results (list of strings)
# first with the main result having skipped=False # first with the main result having skipped=False
mock_task.loop = 'foo' 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()) self.assertFalse(tr.is_skipped())
# then with the main result having skipped=True # 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()) self.assertTrue(tr.is_skipped())
def test_task_result_is_unreachable(self): def test_task_result_is_unreachable(self):
@ -98,21 +94,21 @@ class TestTaskResult(unittest.TestCase):
mock_task = MagicMock() mock_task = MagicMock()
# test with no unreachable in result # test with no unreachable in result
tr = TaskResult(mock_host, mock_task, dict()) tr = TaskResult(mock_host, mock_task, {}, {})
self.assertFalse(tr.is_unreachable()) self.assertFalse(tr.is_unreachable())
# test with unreachable in the result # 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()) self.assertTrue(tr.is_unreachable())
# test with multiple results but none unreachable # test with multiple results but none unreachable
mock_task.loop = 'foo' 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()) self.assertFalse(tr.is_unreachable())
# test with multiple results and one unreachable # test with multiple results and one unreachable
mock_task.loop = 'foo' 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()) self.assertTrue(tr.is_unreachable())
def test_task_result_is_failed(self): def test_task_result_is_failed(self):
@ -120,21 +116,21 @@ class TestTaskResult(unittest.TestCase):
mock_task = MagicMock() mock_task = MagicMock()
# test with no failed in result # 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()) self.assertFalse(tr.is_failed())
# test failed result with rc values (should not matter) # 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()) 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()) self.assertFalse(tr.is_failed())
# test with failed in result # 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()) self.assertTrue(tr.is_failed())
# test with failed_when in result # 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()) self.assertTrue(tr.is_failed())
def test_task_result_no_log(self): def test_task_result_no_log(self):
@ -142,7 +138,7 @@ class TestTaskResult(unittest.TestCase):
mock_task = MagicMock() mock_task = MagicMock()
# no_log should remove secrets # 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() clean = tr.clean_copy()
self.assertTrue('secret' not in clean._result) self.assertTrue('secret' not in clean._result)
@ -160,7 +156,8 @@ class TestTaskResult(unittest.TestCase):
attempts=5, attempts=5,
changed=False, changed=False,
foo='bar', foo='bar',
) ),
task_fields={},
) )
clean = tr.clean_copy() clean = tr.clean_copy()
self.assertTrue('retries' in clean._result) self.assertTrue('retries' in clean._result)

@ -22,6 +22,8 @@ import os
import pytest import pytest
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ansible.inventory.host import Host
from units.mock.loader import DictDataLoader from units.mock.loader import DictDataLoader
from ansible.playbook import Play 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): def test_process_include_tasks_results(mock_iterator, mock_variable_manager):
hostname = "testhost1" host1 = Host("testhost1")
hostname2 = "testhost2" host2 = Host("testhost2")
parent_task_ds = {'debug': 'msg=foo'} parent_task_ds = {'debug': 'msg=foo'}
parent_task = Task.load(parent_task_ds) 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'} return_data = {'include': 'include_test.yml'}
# The task in the TaskResult has to be a TaskInclude so it has a .static attr # 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={})
result2 = task_result.TaskResult(host=hostname2, task=loaded_task, return_data=return_data) result2 = task_result.TaskResult(host=host2, task=loaded_task, return_data=return_data, task_fields={})
results = [result1, result2] results = [result1, result2]
fake_loader = DictDataLoader({'include_test.yml': ""}) 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 isinstance(res, list)
assert len(res) == 1 assert len(res) == 1
assert res[0]._filename == os.path.join(os.getcwd(), 'include_test.yml') 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]._args == {}
assert res[0]._vars == {} assert res[0]._vars == {}
def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager): def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager):
hostname = "testhost1" host1 = Host("testhost1")
hostname2 = "testhost2" host2 = Host("testhost2")
parent_task_ds = {'debug': 'msg=foo'} parent_task_ds = {'debug': 'msg=foo'}
parent_task = Task.load(parent_task_ds) 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'} return_data = {'include': 'include_test.yml'}
# The task in the TaskResult has to be a TaskInclude so it has a .static attr # 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'} 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] results = [result1, result2]
fake_loader = DictDataLoader({'include_test.yml': "", 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[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[1]._filename == os.path.join(os.getcwd(), 'other_include_test.yml')
assert res[0]._hosts == ['testhost1'] assert res[0]._hosts == [host1]
assert res[1]._hosts == ['testhost2'] assert res[1]._hosts == [host2]
assert res[0]._args == {} assert res[0]._args == {}
assert res[1]._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): def test_process_include_tasks_simulate_free(mock_iterator, mock_variable_manager):
hostname = "testhost1" host1 = Host("testhost1")
hostname2 = "testhost2" host2 = Host("testhost2")
parent_task_ds = {'debug': 'msg=foo'} parent_task_ds = {'debug': 'msg=foo'}
parent_task1 = Task.load(parent_task_ds) 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'} return_data = {'include': 'include_test.yml'}
# The task in the TaskResult has to be a TaskInclude so it has a .static attr # 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) result1 = task_result.TaskResult(host=host1, task=loaded_task1, return_data=return_data, task_fields={})
result2 = task_result.TaskResult(host=hostname2, task=loaded_task2, return_data=return_data) result2 = task_result.TaskResult(host=host2, task=loaded_task2, return_data=return_data, task_fields={})
results = [result1, result2] results = [result1, result2]
fake_loader = DictDataLoader({'include_test.yml': ""}) 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[0]._filename == os.path.join(os.getcwd(), 'include_test.yml')
assert res[1]._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[0]._hosts == [host1]
assert res[1]._hosts == ['testhost2'] assert res[1]._hosts == [host2]
assert res[0]._args == {} assert res[0]._args == {}
assert res[1]._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, result1 = task_result.TaskResult(host=hostname,
task=include_role1, task=include_role1,
return_data=include_role1_ds) return_data=include_role1_ds,
task_fields={},
)
result2 = task_result.TaskResult(host=hostname2, result2 = task_result.TaskResult(host=hostname2,
task=include_role2, task=include_role2,
return_data=include_role2_ds) return_data=include_role2_ds,
task_fields={},
)
results = [result1, result2] results = [result1, result2]
res = IncludedFile.process_include_results(results, res = IncludedFile.process_include_results(results,

@ -52,7 +52,7 @@ class TestCallback(unittest.TestCase):
self.assertIs(cb._display, display_mock) self.assertIs(cb._display, display_mock)
def test_host_label(self): 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') self.assertEqual(CallbackBase.host_label(result), 'host1')
@ -62,6 +62,7 @@ class TestCallback(unittest.TestCase):
host=Host('host1'), host=Host('host1'),
task=mock_task, task=mock_task,
return_data={'_ansible_delegated_vars': {'ansible_host': 'host2'}}, return_data={'_ansible_delegated_vars': {'ansible_host': 'host2'}},
task_fields={},
) )
self.assertEqual(CallbackBase.host_label(result), 'host1 -> host2') self.assertEqual(CallbackBase.host_label(result), 'host1 -> host2')

Loading…
Cancel
Save