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.
This commit is contained in:
parent
17ef253ad1
commit
4df5bdb11e
3 changed files with 57 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)."
|
|
@ -517,6 +517,17 @@ class AnsibleDockerClient(Client):
|
||||||
msg = 'Cannot %s with your configuration.' % (usg, )
|
msg = 'Cannot %s with your configuration.' % (usg, )
|
||||||
self.fail(msg)
|
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):
|
def get_container(self, name=None):
|
||||||
'''
|
'''
|
||||||
Lookup a container and return the inspection results.
|
Lookup a container and return the inspection results.
|
||||||
|
@ -546,17 +557,10 @@ class AnsibleDockerClient(Client):
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.fail("Error retrieving container list: %s" % exc)
|
self.fail("Error retrieving container list: %s" % exc)
|
||||||
|
|
||||||
if result is not None:
|
if result is None:
|
||||||
try:
|
return None
|
||||||
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)
|
|
||||||
|
|
||||||
return result
|
return self.get_container_by_id(result['Id'])
|
||||||
|
|
||||||
def get_network(self, name=None, network_id=None):
|
def get_network(self, name=None, network_id=None):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -1107,6 +1107,7 @@ import re
|
||||||
import shlex
|
import shlex
|
||||||
import traceback
|
import traceback
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
from ansible.module_utils.common.text.formatters import human_to_bytes
|
from ansible.module_utils.common.text.formatters import human_to_bytes
|
||||||
from ansible.module_utils.docker.common import (
|
from ansible.module_utils.docker.common import (
|
||||||
|
@ -2001,6 +2002,12 @@ class Container(DockerBaseClass):
|
||||||
def exists(self):
|
def exists(self):
|
||||||
return True if self.container else False
|
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
|
@property
|
||||||
def running(self):
|
def running(self):
|
||||||
if self.container and self.container.get('State'):
|
if self.container and self.container.get('State'):
|
||||||
|
@ -2603,6 +2610,31 @@ class ContainerManager(DockerBaseClass):
|
||||||
self.results['ansible_facts'] = {'docker_container': self.facts}
|
self.results['ansible_facts'] = {'docker_container': self.facts}
|
||||||
self.results['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):
|
def present(self, state):
|
||||||
container = self._get_container(self.parameters.name)
|
container = self._get_container(self.parameters.name)
|
||||||
was_running = container.running
|
was_running = container.running
|
||||||
|
@ -2616,12 +2648,18 @@ class ContainerManager(DockerBaseClass):
|
||||||
# image ID.
|
# image ID.
|
||||||
image = self._get_image()
|
image = self._get_image()
|
||||||
self.log(image, pretty_print=True)
|
self.log(image, pretty_print=True)
|
||||||
if not container.exists:
|
if not container.exists or container.removing:
|
||||||
# New container
|
# 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:
|
if not self.parameters.image:
|
||||||
self.fail('Cannot create container when image is not specified!')
|
self.fail('Cannot create container when image is not specified!')
|
||||||
self.diff_tracker.add('exists', parameter=True, active=False)
|
self.diff_tracker.add('exists', parameter=True, active=False)
|
||||||
|
if container.removing:
|
||||||
|
# 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)
|
new_container = self.container_create(self.parameters.image, self.parameters.create_parameters)
|
||||||
if new_container:
|
if new_container:
|
||||||
container = new_container
|
container = new_container
|
||||||
|
@ -2647,6 +2685,7 @@ class ContainerManager(DockerBaseClass):
|
||||||
if container.running:
|
if container.running:
|
||||||
self.container_stop(container.Id)
|
self.container_stop(container.Id)
|
||||||
self.container_remove(container.Id)
|
self.container_remove(container.Id)
|
||||||
|
self.wait_for_state(container.Id, wait_states=['removing'], accept_removal=True)
|
||||||
new_container = self.container_create(image_to_use, self.parameters.create_parameters)
|
new_container = self.container_create(image_to_use, self.parameters.create_parameters)
|
||||||
if new_container:
|
if new_container:
|
||||||
container = new_container
|
container = new_container
|
||||||
|
|
Loading…
Reference in a new issue