Skip to content

Commit

Permalink
[2.8] docker_container: wait for removal if removal is in process (#6…
Browse files Browse the repository at this point in the history
…6118)

* docker_container: wait for removal if removal is in process (#65854)

* Allow to inspect containers directly.

* Wait for containers to be removed before recreating them.

* Also wait for containers to be removed before creating them.

* Add changelog.

(cherry picked from commit 4df5bdb)

* Don't wait for removal during check mode. (#66145)

(cherry picked from commit 14e32c8)
  • Loading branch information
felixfontein authored and mattclay committed Jan 10, 2020
1 parent fc9d6ea commit facf938
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 12 deletions.
@@ -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

0 comments on commit facf938

Please sign in to comment.