docker_container: configure removal wait timeout (#66144)

* Add support for timeout while waiting for state.

* Allow to limit removal wait time.

* Add changelog.

* Forgot version_added.

* Add some check mode tests.

* Use removal_wait_timeout in tests.
This commit is contained in:
Felix Fontein 2020-01-04 17:56:59 +01:00 committed by GitHub
parent 9a13d56b26
commit b0b00b555f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 17 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- "docker_container - allow to configure timeout when the module waits for a container's removal."

View file

@ -685,6 +685,17 @@ options:
- Use with present and started states to force the re-creation of an existing container. - Use with present and started states to force the re-creation of an existing container.
type: bool type: bool
default: no default: no
removal_wait_timeout:
description:
- When removing an existing container, the docker daemon API call exists after the container
is scheduled for removal. Removal usually is very fast, but it can happen that during high I/O
load, removal can take longer. By default, the module will wait until the container has been
removed before trying to (re-)create it, however long this takes.
- By setting this option, the module will wait at most this many seconds for the container to be
removed. If the container is still in the removal phase after this many seconds, the module will
fail.
type: float
version_added: "2.10"
restart: restart:
description: description:
- Use with started state to force a matching container to be stopped and restarted. - Use with started state to force a matching container to be stopped and restarted.
@ -1281,6 +1292,7 @@ class TaskParameters(DockerBaseClass):
self.pull = None self.pull = None
self.read_only = None self.read_only = None
self.recreate = None self.recreate = None
self.removal_wait_timeout = None
self.restart = None self.restart = None
self.restart_retries = None self.restart_retries = None
self.restart_policy = None self.restart_policy = None
@ -2610,25 +2622,33 @@ 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): def wait_for_state(self, container_id, complete_states=None, wait_states=None, accept_removal=False, max_wait=None):
delay = 1.0 delay = 1.0
total_wait = 0
while True: while True:
# Inspect container # Inspect container
result = self.client.get_container_by_id(container_id) result = self.client.get_container_by_id(container_id)
if result is None: if result is None:
if accept_removal: if accept_removal:
return return
msg = 'Encontered vanished container while waiting for container {0}' msg = 'Encontered vanished container while waiting for container "{0}"'
self.fail(msg.format(container_id)) self.fail(msg.format(container_id))
# Check container state # Check container state
state = result.get('State', {}).get('Status') state = result.get('State', {}).get('Status')
if complete_states is not None and state in complete_states: if complete_states is not None and state in complete_states:
return return
if wait_states is not None and state not in wait_states: if wait_states is not None and state not in wait_states:
msg = 'Encontered unexpected state "{1}" while waiting for container {0}' msg = 'Encontered unexpected state "{1}" while waiting for container "{0}"'
self.fail(msg.format(container_id, state)) self.fail(msg.format(container_id, state))
# Wait # Wait
if max_wait is not None:
if total_wait > max_wait:
msg = 'Timeout of {1} seconds exceeded while waiting for container "{0}"'
self.fail(msg.format(container_id, max_wait))
if total_wait + delay > max_wait:
delay = max_wait - total_wait
sleep(delay) sleep(delay)
total_wait += delay
# Exponential backoff, but never wait longer than 10 seconds # Exponential backoff, but never wait longer than 10 seconds
# (1.1**24 < 10, 1.1**25 > 10, so it will take 25 iterations # (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 # until the maximal 10 seconds delay is reached. By then, the
@ -2659,7 +2679,8 @@ class ContainerManager(DockerBaseClass):
self.diff_tracker.add('exists', parameter=True, active=False) self.diff_tracker.add('exists', parameter=True, active=False)
if container.removing and not self.check_mode: if container.removing and not self.check_mode:
# Wait for container to be removed before trying to create it # Wait for container to be removed before trying to create it
self.wait_for_state(container.Id, wait_states=['removing'], accept_removal=True) self.wait_for_state(
container.Id, wait_states=['removing'], accept_removal=True, max_wait=self.parameters.removal_wait_timeout)
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
@ -2686,7 +2707,8 @@ class ContainerManager(DockerBaseClass):
self.container_stop(container.Id) self.container_stop(container.Id)
self.container_remove(container.Id) self.container_remove(container.Id)
if not self.check_mode: if not self.check_mode:
self.wait_for_state(container.Id, wait_states=['removing'], accept_removal=True) self.wait_for_state(
container.Id, wait_states=['removing'], accept_removal=True, max_wait=self.parameters.removal_wait_timeout)
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
@ -3055,7 +3077,7 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
__NON_CONTAINER_PROPERTY_OPTIONS = tuple([ __NON_CONTAINER_PROPERTY_OPTIONS = tuple([
'env_file', 'force_kill', 'keep_volumes', 'ignore_image', 'name', 'pull', 'purge_networks', 'env_file', 'force_kill', 'keep_volumes', 'ignore_image', 'name', 'pull', 'purge_networks',
'recreate', 'restart', 'state', 'trust_image_content', 'networks', 'cleanup', 'kill_signal', 'recreate', 'restart', 'state', 'trust_image_content', 'networks', 'cleanup', 'kill_signal',
'output_logs', 'paused' 'output_logs', 'paused', 'removal_wait_timeout'
] + list(DOCKER_COMMON_ARGS.keys())) ] + list(DOCKER_COMMON_ARGS.keys()))
def _parse_comparisons(self): def _parse_comparisons(self):
@ -3368,6 +3390,7 @@ def main():
purge_networks=dict(type='bool', default=False), purge_networks=dict(type='bool', default=False),
read_only=dict(type='bool'), read_only=dict(type='bool'),
recreate=dict(type='bool', default=False), recreate=dict(type='bool', default=False),
removal_wait_timeout=dict(type='float'),
restart=dict(type='bool', default=False), restart=dict(type='bool', default=False),
restart_policy=dict(type='str', choices=['no', 'on-failure', 'always', 'unless-stopped']), restart_policy=dict(type='str', choices=['no', 'on-failure', 'always', 'unless-stopped']),
restart_retries=dict(type='int'), restart_retries=dict(type='int'),

View file

@ -182,7 +182,7 @@
force_kill: yes force_kill: yes
register: recreate_1 register: recreate_1
- name: Recreating container (created, recreate) - name: Recreating container (created, recreate, check mode)
docker_container: docker_container:
image: alpine:3.8 image: alpine:3.8
command: '/bin/sh -c "sleep 10m"' command: '/bin/sh -c "sleep 10m"'
@ -191,6 +191,17 @@
state: present state: present
force_kill: yes force_kill: yes
register: recreate_2 register: recreate_2
check_mode: yes
- name: Recreating container (created, recreate)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
recreate: yes
state: present
force_kill: yes
register: recreate_3
- name: Recreating container (started) - name: Recreating container (started)
docker_container: docker_container:
@ -199,7 +210,19 @@
name: "{{ cname }}" name: "{{ cname }}"
state: started state: started
force_kill: yes force_kill: yes
register: recreate_3 register: recreate_4
- name: Recreating container (started, recreate, check mode)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
recreate: yes
removal_wait_timeout: 10
state: started
force_kill: yes
register: recreate_5
check_mode: yes
- name: Recreating container (started, recreate) - name: Recreating container (started, recreate)
docker_container: docker_container:
@ -207,9 +230,10 @@
command: '/bin/sh -c "sleep 10m"' command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}" name: "{{ cname }}"
recreate: yes recreate: yes
removal_wait_timeout: 10
state: started state: started
force_kill: yes force_kill: yes
register: recreate_4 register: recreate_6
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -219,18 +243,22 @@
diff: no diff: no
- debug: var=recreate_1 - debug: var=recreate_1
- debug: var=recreate_2
- debug: var=recreate_3 - debug: var=recreate_3
- debug: var=recreate_4 - debug: var=recreate_4
- debug: var=recreate_6
- assert: - assert:
that: that:
- recreate_2 is changed - recreate_2 is changed
- recreate_3 is changed - recreate_3 is changed
- recreate_4 is changed - recreate_4 is changed
- recreate_1.container.Id != recreate_2.container.Id - recreate_5 is changed
- recreate_2.container.Id == recreate_3.container.Id - recreate_6 is changed
- recreate_3.container.Id != recreate_4.container.Id - recreate_1.container.Id == recreate_2.container.Id
- recreate_1.container.Id != recreate_3.container.Id
- recreate_3.container.Id == recreate_4.container.Id
- recreate_4.container.Id == recreate_5.container.Id
- recreate_4.container.Id != recreate_6.container.Id
#################################################################### ####################################################################
## Restarting ###################################################### ## Restarting ######################################################
@ -247,7 +275,7 @@
- /tmp/tmp - /tmp/tmp
register: restart_1 register: restart_1
- name: Restarting (restart) - name: Restarting (restart, check mode)
docker_container: docker_container:
image: alpine:3.8 image: alpine:3.8
command: '/bin/sh -c "sleep 10m"' command: '/bin/sh -c "sleep 10m"'
@ -257,6 +285,18 @@
stop_timeout: 1 stop_timeout: 1
force_kill: yes force_kill: yes
register: restart_2 register: restart_2
check_mode: yes
- name: Restarting (restart)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
restart: yes
state: started
stop_timeout: 1
force_kill: yes
register: restart_3
- name: Restarting (verify volumes) - name: Restarting (verify volumes)
docker_container: docker_container:
@ -267,7 +307,7 @@
stop_timeout: 1 stop_timeout: 1
volumes: volumes:
- /tmp/tmp - /tmp/tmp
register: restart_3 register: restart_4
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -280,8 +320,9 @@
that: that:
- restart_1 is changed - restart_1 is changed
- restart_2 is changed - restart_2 is changed
- restart_1.container.Id == restart_2.container.Id - restart_3 is changed
- restart_3 is not changed - restart_1.container.Id == restart_3.container.Id
- restart_4 is not changed
#################################################################### ####################################################################
## Stopping ######################################################## ## Stopping ########################################################