Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.8] docker_container: wait for removal if removal is in process #66118

Merged
merged 2 commits into from Jan 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,2 @@
bugfixes:
- "docker_container - wait for removal of container if docker API returns early (https://github.com/ansible/ansible/issues/65811)."
24 changes: 14 additions & 10 deletions lib/ansible/module_utils/docker/common.py
Expand Up @@ -513,6 +513,17 @@ def _get_minimal_versions(self, option_minimal_versions, ignore_params=None):
msg = 'Cannot %s with your configuration.' % (usg, )
self.fail(msg)

def get_container_by_id(self, container_id):
try:
self.log("Inspecting container Id %s" % container_id)
result = self.inspect_container(container=container_id)
self.log("Completed container inspection")
return result
except NotFound as dummy:
return None
except Exception as exc:
self.fail("Error inspecting container: %s" % exc)

def get_container(self, name=None):
'''
Lookup a container and return the inspection results.
Expand Down Expand Up @@ -542,17 +553,10 @@ def get_container(self, name=None):
except Exception as exc:
self.fail("Error retrieving container list: %s" % exc)

if result is not None:
try:
self.log("Inspecting container Id %s" % result['Id'])
result = self.inspect_container(container=result['Id'])
self.log("Completed container inspection")
except NotFound as dummy:
return None
except Exception as exc:
self.fail("Error inspecting container: %s" % exc)
if result is None:
return None

return result
return self.get_container_by_id(result['Id'])

def get_network(self, name=None, id=None):
'''
Expand Down
44 changes: 42 additions & 2 deletions lib/ansible/modules/cloud/docker/docker_container.py
Expand Up @@ -960,6 +960,7 @@
import shlex
import traceback
from distutils.version import LooseVersion
from time import sleep

from ansible.module_utils.common.text.formatters import human_to_bytes
from ansible.module_utils.docker.common import (
Expand Down Expand Up @@ -1765,6 +1766,12 @@ def fail(self, msg):
def exists(self):
return True if self.container else False

@property
def removing(self):
if self.container and self.container.get('State'):
return self.container['State'].get('Status') == 'removing'
return False

@property
def running(self):
if self.container and self.container.get('State'):
Expand Down Expand Up @@ -2332,6 +2339,31 @@ def __init__(self, client):
self.results['ansible_facts'] = {'docker_container': self.facts}
self.results['container'] = self.facts

def wait_for_state(self, container_id, complete_states=None, wait_states=None, accept_removal=False):
delay = 1.0
while True:
# Inspect container
result = self.client.get_container_by_id(container_id)
if result is None:
if accept_removal:
return
msg = 'Encontered vanished container while waiting for container {0}'
self.fail(msg.format(container_id))
# Check container state
state = result.get('State', {}).get('Status')
if complete_states is not None and state in complete_states:
return
if wait_states is not None and state not in wait_states:
msg = 'Encontered unexpected state "{1}" while waiting for container {0}'
self.fail(msg.format(container_id, state))
# Wait
sleep(delay)
# Exponential backoff, but never wait longer than 10 seconds
# (1.1**24 < 10, 1.1**25 > 10, so it will take 25 iterations
# until the maximal 10 seconds delay is reached. By then, the
# code will have slept for ~1.5 minutes.)
delay = min(delay * 1.1, 10)

def present(self, state):
container = self._get_container(self.parameters.name)
was_running = container.running
Expand All @@ -2345,12 +2377,18 @@ def present(self, state):
# image ID.
image = self._get_image()
self.log(image, pretty_print=True)
if not container.exists:
if not container.exists or container.removing:
# New container
self.log('No container found')
if container.removing:
self.log('Found container in removal phase')
else:
self.log('No container found')
if not self.parameters.image:
self.fail('Cannot create container when image is not specified!')
self.diff_tracker.add('exists', parameter=True, active=False)
if container.removing and not self.check_mode:
# Wait for container to be removed before trying to create it
self.wait_for_state(container.Id, wait_states=['removing'], accept_removal=True)
new_container = self.container_create(self.parameters.image, self.parameters.create_parameters)
if new_container:
container = new_container
Expand All @@ -2376,6 +2414,8 @@ def present(self, state):
if container.running:
self.container_stop(container.Id)
self.container_remove(container.Id)
if not self.check_mode:
self.wait_for_state(container.Id, wait_states=['removing'], accept_removal=True)
new_container = self.container_create(image_to_use, self.parameters.create_parameters)
if new_container:
container = new_container
Expand Down