1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

iptables_state: fix race condition between module and its action plugin (#1140) (#1143)

* fix race condition between module and its action plugin

See https://github.com/ansible-collections/community.general/issues/1136.
Also remove irrelevant/unneeded display.v() and display.warning() around
connection reset.

* do not check for cookie if not in async mode

* add changelog fragment

(cherry picked from commit 3bc31f286e)

Co-authored-by: quidame <quidame@poivron.org>
This commit is contained in:
patchback[bot] 2020-10-20 22:46:12 +02:00 committed by GitHub
parent c826a81b40
commit 7da1f3ffea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 30 additions and 5 deletions

View file

@ -0,0 +1,4 @@
---
bugfixes:
- iptables_state - fix race condition between module and its action plugin
(https://github.com/ansible-collections/community.general/issues/1136).

View file

@ -125,15 +125,18 @@ class ActionModule(ActionBase):
module_args['_back'] = '%s/iptables.state' % async_dir module_args['_back'] = '%s/iptables.state' % async_dir
async_status_args = dict(_async_dir=async_dir) async_status_args = dict(_async_dir=async_dir)
confirm_cmd = 'rm -f %s' % module_args['_back'] confirm_cmd = 'rm -f %s' % module_args['_back']
starter_cmd = 'touch %s.starter' % module_args['_back']
remaining_time = max(task_async, max_timeout) remaining_time = max(task_async, max_timeout)
# do work! # do work!
result = merge_hash(result, self._execute_module(module_args=module_args, task_vars=task_vars, wrap_async=wrap_async)) result = merge_hash(result, self._execute_module(module_args=module_args, task_vars=task_vars, wrap_async=wrap_async))
# Then the 3-steps "go ahead or rollback": # Then the 3-steps "go ahead or rollback":
# - reset connection to ensure a persistent one will not be reused # 1. Catch early errors of the module (in asynchronous task) if any.
# - confirm the restored state by removing the backup on the remote # Touch a file on the target to signal the module to process now.
# - retrieve the results of the asynchronous task to return them # 2. Reset connection to ensure a persistent one will not be reused.
# 3. Confirm the restored state by removing the backup on the remote.
# Retrieve the results of the asynchronous task to return them.
if '_back' in module_args: if '_back' in module_args:
async_status_args['jid'] = result.get('ansible_job_id', None) async_status_args['jid'] = result.get('ansible_job_id', None)
if async_status_args['jid'] is None: if async_status_args['jid'] is None:
@ -143,12 +146,18 @@ class ActionModule(ActionBase):
# option type/value, missing required system command, etc. # option type/value, missing required system command, etc.
result = merge_hash(result, self._async_result(async_status_args, task_vars, 0)) result = merge_hash(result, self._async_result(async_status_args, task_vars, 0))
# The module is aware to not process the main iptables-restore
# command before finding (and deleting) the 'starter' cookie on
# the host, so the previous query will not reach ssh timeout.
garbage = self._low_level_execute_command(starter_cmd, sudoable=self.DEFAULT_SUDOABLE)
# As the main command is not yet executed on the target, here
# 'finished' means 'failed before main command be executed'.
if not result['finished']: if not result['finished']:
try: try:
self._connection.reset() self._connection.reset()
display.v("%s: reset connection" % (module_name))
except AttributeError: except AttributeError:
display.warning("Connection plugin does not allow to reset the connection.") pass
for x in range(max_timeout): for x in range(max_timeout):
time.sleep(1) time.sleep(1)

View file

@ -551,6 +551,18 @@ def main():
restored_state = state_to_restore restored_state = state_to_restore
else: else:
# Let time enough to the plugin to retrieve async status of the module
# in case of bad option type/value and the like.
if _back is not None:
b_starter = to_bytes('%s.starter' % _back, errors='surrogate_or_strict')
while True:
if os.path.exists(b_starter):
os.remove(b_starter)
break
else:
time.sleep(0.01)
continue
(rc, stdout, stderr) = module.run_command(MAINCOMMAND) (rc, stdout, stderr) = module.run_command(MAINCOMMAND)
if 'Another app is currently holding the xtables lock' in stderr: if 'Another app is currently holding the xtables lock' in stderr:
module.fail_json( module.fail_json(