Add a 'finished' test for async jobs (#38325)

This provides a more convenient way for testing (async) jobs.

When used with a non-async job it will report a warning so the user is
aware that he may be doing something incorrect.

Since the 'finished' result value is an integer (!), the test is turning
this in a proper boolean.
This commit is contained in:
Dag Wieers 2018-07-24 23:21:28 +02:00 committed by GitHub
parent 5ecfb0b793
commit 939de473c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 9 deletions

View file

@ -26,11 +26,17 @@ from distutils.version import LooseVersion, StrictVersion
from ansible import errors from ansible import errors
try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()
def failed(result): def failed(result):
''' Test if task result yields failed ''' ''' Test if task result yields failed '''
if not isinstance(result, MutableMapping): if not isinstance(result, MutableMapping):
raise errors.AnsibleFilterError("The failed test expects a dictionary") raise errors.AnsibleFilterError("The 'failed' test expects a dictionary")
return result.get('failed', False) return result.get('failed', False)
@ -42,7 +48,7 @@ def success(result):
def changed(result): def changed(result):
''' Test if task result yields changed ''' ''' Test if task result yields changed '''
if not isinstance(result, MutableMapping): if not isinstance(result, MutableMapping):
raise errors.AnsibleFilterError("The changed test expects a dictionary") raise errors.AnsibleFilterError("The 'changed' test expects a dictionary")
if 'changed' not in result: if 'changed' not in result:
changed = False changed = False
if ( if (
@ -62,10 +68,24 @@ def changed(result):
def skipped(result): def skipped(result):
''' Test if task result yields skipped ''' ''' Test if task result yields skipped '''
if not isinstance(result, MutableMapping): if not isinstance(result, MutableMapping):
raise errors.AnsibleFilterError("The skipped test expects a dictionary") raise errors.AnsibleFilterError("The 'skipped' test expects a dictionary")
return result.get('skipped', False) return result.get('skipped', False)
def finished(result):
''' Test if async task has finished '''
if not isinstance(result, MutableMapping):
raise errors.AnsibleFilterError("The 'finished' test expects a dictionary")
if 'finished' in result:
# For async tasks return status
# NOTE: The value of finished it 0 or 1, not False or True :-/
return result.get('finished', 0) == 1
else:
# For non-async tasks warn user, but return as finished
display.warning("The 'finished' test expects an async task, but a non-async task was tested")
return True
def regex(value='', pattern='', ignorecase=False, multiline=False, match_type='search'): def regex(value='', pattern='', ignorecase=False, multiline=False, match_type='search'):
''' Expose `re` as a boolean filter using the `search` method by default. ''' Expose `re` as a boolean filter using the `search` method by default.
This is likely only useful for `search` and `match` which already This is likely only useful for `search` and `match` which already
@ -139,6 +159,9 @@ class TestModule(object):
'skipped': skipped, 'skipped': skipped,
'skip': skipped, 'skip': skipped,
# async testing
'finished': finished,
# regex # regex
'match': match, 'match': match,
'search': search, 'search': search,

View file

@ -33,13 +33,14 @@
- "'cmd' in async_result" - "'cmd' in async_result"
- "'delta' in async_result" - "'delta' in async_result"
- "'end' in async_result" - "'end' in async_result"
- "'finished' in async_result or async_result.finished == 1"
- "'rc' in async_result" - "'rc' in async_result"
- "'start' in async_result" - "'start' in async_result"
- "'stderr' in async_result" - "'stderr' in async_result"
- "'stdout' in async_result" - "'stdout' in async_result"
- "'stdout_lines' in async_result" - "'stdout_lines' in async_result"
- "async_result.rc == 0" - async_result.rc == 0
- async_result.finished == 1
- async_result is finished
- name: test async without polling - name: test async without polling
command: sleep 5 command: sleep 5
@ -54,7 +55,8 @@
that: that:
- "'ansible_job_id' in async_result" - "'ansible_job_id' in async_result"
- "'started' in async_result" - "'started' in async_result"
- "'finished' not in async_result or async_result.finished == 0" - async_result.finished == 0
- async_result is not finished
- name: test skipped task handling - name: test skipped task handling
command: /bin/true command: /bin/true
@ -79,7 +81,7 @@
- name: 'check on task started as a "fire-and-forget"' - name: 'check on task started as a "fire-and-forget"'
async_status: jid={{ fnf_task.ansible_job_id }} async_status: jid={{ fnf_task.ansible_job_id }}
register: fnf_result register: fnf_result
until: fnf_result.finished until: fnf_result is finished
retries: 10 retries: 10
delay: 1 delay: 1
@ -87,6 +89,7 @@
assert: assert:
that: that:
- fnf_result.finished - fnf_result.finished
- fnf_result is finished
- name: test graceful module failure - name: test graceful module failure
async_test: async_test:
@ -101,7 +104,8 @@
that: that:
- async_result.ansible_job_id is match('\d+\.\d+') - async_result.ansible_job_id is match('\d+\.\d+')
- async_result.finished == 1 - async_result.finished == 1
- async_result is changed == false - async_result is finished
- async_result is not changed
- async_result is failed - async_result is failed
- async_result.msg == 'failed gracefully' - async_result.msg == 'failed gracefully'
@ -118,8 +122,11 @@
that: that:
- async_result.ansible_job_id is match('\d+\.\d+') - async_result.ansible_job_id is match('\d+\.\d+')
- async_result.finished == 1 - async_result.finished == 1
- async_result is finished
- async_result.changed == false - async_result.changed == false
- async_result is failed == true - async_result is not changed
- async_result.failed == true
- async_result is failed
- async_result.stderr is search('failing via exception', multiline=True) - async_result.stderr is search('failing via exception', multiline=True)
- name: test leading junk before JSON - name: test leading junk before JSON
@ -134,7 +141,9 @@
that: that:
- async_result.ansible_job_id is match('\d+\.\d+') - async_result.ansible_job_id is match('\d+\.\d+')
- async_result.finished == 1 - async_result.finished == 1
- async_result is finished
- async_result.changed == true - async_result.changed == true
- async_result is changed
- async_result is successful - async_result is successful
- name: test trailing junk after JSON - name: test trailing junk after JSON
@ -149,6 +158,21 @@
that: that:
- async_result.ansible_job_id is match('\d+\.\d+') - async_result.ansible_job_id is match('\d+\.\d+')
- async_result.finished == 1 - async_result.finished == 1
- async_result is finished
- async_result.changed == true - async_result.changed == true
- async_result is changed
- async_result is successful - async_result is successful
- async_result.warnings[0] is search('trailing junk after module output') - async_result.warnings[0] is search('trailing junk after module output')
# NOTE: This should report a warning that cannot be tested
- name: test async properties on non-async task
command: sleep 1
register: non_async_result
- name: validate response
assert:
that:
- non_async_result is successful
- non_async_result is changed
- non_async_result is finished
- "'ansible_job_id' not in non_async_result"