From 75fa80f73c44b50edf5a84617c64d09ad04ff570 Mon Sep 17 00:00:00 2001 From: Matt Clay Date: Fri, 1 Jul 2016 14:52:45 -0700 Subject: [PATCH] Parse async response in async action. (#16534) * Parse async response in async action. * Add async test for non-JSON data before module output. * Fix existing async unit test. Resolves #16156 (cherry picked from commit 292785ff2bf557cafbe2bd18e95f2bd26cce0f0b) --- lib/ansible/executor/task_executor.py | 9 --------- lib/ansible/plugins/action/async.py | 7 +++++++ test/integration/Makefile | 9 ++++++++- test/integration/test_async.yml | 10 ++++++++++ test/units/executor/test_task_executor.py | 6 ++++-- 5 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 test/integration/test_async.yml diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index f677c9d6b3e..97437a05142 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -459,15 +459,6 @@ class TaskExecutor: vars_copy[self._task.register] = wrap_var(result.copy()) if self._task.async > 0: - # the async_wrapper module returns dumped JSON via its stdout - # response, so we parse it here and replace the result - try: - if 'skipped' in result and result['skipped'] or 'failed' in result and result['failed']: - return result - result = json.loads(result.get('stdout')) - except (TypeError, ValueError) as e: - return dict(failed=True, msg=u"The async task did not return valid JSON: %s" % to_unicode(e)) - if self._task.poll > 0: result = self._poll_async_result(result=result, templar=templar) diff --git a/lib/ansible/plugins/action/async.py b/lib/ansible/plugins/action/async.py index 4f7aa634ce2..d471c0f96dc 100644 --- a/lib/ansible/plugins/action/async.py +++ b/lib/ansible/plugins/action/async.py @@ -96,4 +96,11 @@ class ActionModule(ActionBase): result['changed'] = True + if 'skipped' in result and result['skipped'] or 'failed' in result and result['failed']: + return result + + # the async_wrapper module returns dumped JSON via its stdout + # response, so we parse it here and replace the result + result = self._parse_returned_data(result) + return result diff --git a/test/integration/Makefile b/test/integration/Makefile index a812ace890d..62343c10826 100644 --- a/test/integration/Makefile +++ b/test/integration/Makefile @@ -23,7 +23,7 @@ VAULT_PASSWORD_FILE = vault-password CONSUL_RUNNING := $(shell python consul_running.py) EUID := $(shell id -u -r) -all: setup test_test_infra parsing test_var_precedence unicode test_templating_settings environment test_connection non_destructive destructive includes blocks pull check_mode test_hash test_handlers test_group_by test_vault test_tags test_lookup_paths no_log test_gathering_facts +all: setup test_test_infra parsing test_var_precedence unicode test_templating_settings environment test_connection non_destructive destructive includes blocks pull check_mode test_hash test_handlers test_group_by test_vault test_tags test_lookup_paths no_log test_gathering_facts test_async test_test_infra: # ensure fail/assert work locally and can stop execution with non-zero exit code @@ -284,3 +284,10 @@ test_lookup_paths: setup no_log: setup # This test expects 7 loggable vars and 0 non loggable ones, if either mismatches it fails, run the ansible-playbook command to debug [ "$$(ansible-playbook no_log_local.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -vvvvv | awk --source 'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "6/0" ] + +test_async: + # Verify that extra data before module JSON output during async call is ignored. + LC_ALL=bogus ansible-playbook test_async.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -v $(TEST_FLAGS) + # Verify that the warning exists by examining debug output. + ANSIBLE_DEBUG=1 LC_ALL=bogus ansible-playbook test_async.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -v $(TEST_FLAGS) \ + | grep -q 'bash: warning: setlocale: LC_ALL: cannot change locale (bogus)' diff --git a/test/integration/test_async.yml b/test/integration/test_async.yml new file mode 100644 index 00000000000..ec370d7c7f2 --- /dev/null +++ b/test/integration/test_async.yml @@ -0,0 +1,10 @@ +- hosts: testhost3 + gather_facts: false + tasks: + # make sure non-JSON data before module output is ignored + - name: async ping with invalid locale via ssh + ping: + async: 3 + poll: 1 + register: result + - debug: var=result diff --git a/test/units/executor/test_task_executor.py b/test/units/executor/test_task_executor.py index 87704e21881..101b8967d2b 100644 --- a/test/units/executor/test_task_executor.py +++ b/test/units/executor/test_task_executor.py @@ -26,6 +26,7 @@ from ansible.errors import AnsibleError, AnsibleParserError from ansible.executor.task_executor import TaskExecutor from ansible.playbook.play_context import PlayContext from ansible.plugins import action_loader, lookup_loader +from ansible.parsing.yaml.objects import AnsibleUnicode from units.mock.loader import DictDataLoader @@ -355,6 +356,7 @@ class TestTaskExecutor(unittest.TestCase): # here: on Python 2 comparing MagicMock() > 0 returns True, and the # other reason is that if I specify 0 here, the test fails. ;) mock_task.async = 1 + mock_task.poll = 0 mock_play_context = MagicMock() mock_play_context.post_validate.return_value = None @@ -388,11 +390,11 @@ class TestTaskExecutor(unittest.TestCase): mock_action.run.return_value = dict(ansible_facts=dict()) res = te._execute() - mock_task.changed_when = "1 == 1" + mock_task.changed_when = MagicMock(return_value=AnsibleUnicode("1 == 1")) res = te._execute() mock_task.changed_when = None - mock_task.failed_when = "1 == 1" + mock_task.failed_when = MagicMock(return_value=AnsibleUnicode("1 == 1")) res = te._execute() mock_task.failed_when = None