summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Fontein <felix@fontein.de>2019-09-30 10:47:02 +0200
committerToshio Kuratomi <a.badger@gmail.com>2019-10-12 09:17:09 -0700
commit36a96a628a53c143ff13cd9a9eb4ac90c600c9ce (patch)
tree8c16fa7ce382e6fbca76de9c4d89654e61f9a3c5
parent018d57078ec1d809f30e5df40c580ea58ed003ba (diff)
downloadansible-36a96a628a53c143ff13cd9a9eb4ac90c600c9ce.tar.gz
docker_container: fix idempotency for network IP addresses (#62928)
* Specifying IP addresses needs API version 1.22 or newer. * Simplify code. * Use IPAMConfig.IPv*Address instead of IPAddress and GlobalIPv6Address. * Add changelog. * Fix syntax errors. * Add integration test. * Don't rely on netaddr. * Normalize IPv6 addresses before comparison. * Install netaddr, and use it. (cherry picked from commit 62c0cae29a393859522fcb391562dc1edd73ce53)
-rw-r--r--changelogs/fragments/62928-docker_container-ip-address-idempotency.yml4
-rw-r--r--lib/ansible/modules/cloud/docker/docker_container.py23
-rw-r--r--test/integration/targets/docker_container/tasks/main.yml5
-rw-r--r--test/integration/targets/docker_container/tasks/tests/network.yml186
4 files changed, 207 insertions, 11 deletions
diff --git a/changelogs/fragments/62928-docker_container-ip-address-idempotency.yml b/changelogs/fragments/62928-docker_container-ip-address-idempotency.yml
new file mode 100644
index 0000000000..63fead2487
--- /dev/null
+++ b/changelogs/fragments/62928-docker_container-ip-address-idempotency.yml
@@ -0,0 +1,4 @@
+bugfixes:
+- "docker_container - fix idempotency for IP addresses for networks. The old implementation checked the effective
+ IP addresses assigned by the Docker daemon, and not the specified ones. This causes idempotency issues for
+ containers which are not running, since they have no effective IP addresses assigned."
diff --git a/lib/ansible/modules/cloud/docker/docker_container.py b/lib/ansible/modules/cloud/docker/docker_container.py
index b168c8c5da..a4127be799 100644
--- a/lib/ansible/modules/cloud/docker/docker_container.py
+++ b/lib/ansible/modules/cloud/docker/docker_container.py
@@ -2226,7 +2226,8 @@ class Container(DockerBaseClass):
connected_networks = self.container['NetworkSettings']['Networks']
for network in self.parameters.networks:
- if connected_networks.get(network['name'], None) is None:
+ network_info = connected_networks.get(network['name'])
+ if network_info is None:
different = True
differences.append(dict(
parameter=network,
@@ -2234,18 +2235,19 @@ class Container(DockerBaseClass):
))
else:
diff = False
- if network.get('ipv4_address') and network['ipv4_address'] != connected_networks[network['name']].get('IPAddress'):
+ network_info_ipam = network_info.get('IPAMConfig', {})
+ if network.get('ipv4_address') and network['ipv4_address'] != network_info_ipam.get('IPv4Address'):
diff = True
- if network.get('ipv6_address') and network['ipv6_address'] != connected_networks[network['name']].get('GlobalIPv6Address'):
+ if network.get('ipv6_address') and network['ipv6_address'] != network_info_ipam.get('IPv6Address'):
diff = True
if network.get('aliases'):
- if not compare_generic(network['aliases'], connected_networks[network['name']].get('Aliases'), 'allow_more_present', 'set'):
+ if not compare_generic(network['aliases'], network_info.get('Aliases'), 'allow_more_present', 'set'):
diff = True
if network.get('links'):
expected_links = []
for link, alias in network['links']:
expected_links.append("%s:%s" % (link, alias))
- if not compare_generic(expected_links, connected_networks[network['name']].get('Links'), 'allow_more_present', 'set'):
+ if not compare_generic(expected_links, network_info.get('Links'), 'allow_more_present', 'set'):
diff = True
if diff:
different = True
@@ -2253,10 +2255,10 @@ class Container(DockerBaseClass):
parameter=network,
container=dict(
name=network['name'],
- ipv4_address=connected_networks[network['name']].get('IPAddress'),
- ipv6_address=connected_networks[network['name']].get('GlobalIPv6Address'),
- aliases=connected_networks[network['name']].get('Aliases'),
- links=connected_networks[network['name']].get('Links')
+ ipv4_address=network_info_ipam.get('IPv4Address'),
+ ipv6_address=network_info_ipam.get('IPv6Address'),
+ aliases=network_info.get('Aliases'),
+ links=network_info.get('Links')
)
))
return different, differences
@@ -3121,7 +3123,8 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
pids_limit=dict(docker_py_version='1.10.0', docker_api_version='1.23'),
mounts=dict(docker_py_version='2.6.0', docker_api_version='1.25'),
# specials
- ipvX_address_supported=dict(docker_py_version='1.9.0', detect_usage=detect_ipvX_address_usage,
+ ipvX_address_supported=dict(docker_py_version='1.9.0', docker_api_version='1.22',
+ detect_usage=detect_ipvX_address_usage,
usage_msg='ipv4_address or ipv6_address in networks'),
stop_timeout=dict(), # see _get_additional_minimal_versions()
)
diff --git a/test/integration/targets/docker_container/tasks/main.yml b/test/integration/targets/docker_container/tasks/main.yml
index 02d9847ab2..16aaea684f 100644
--- a/test/integration/targets/docker_container/tasks/main.yml
+++ b/test/integration/targets/docker_container/tasks/main.yml
@@ -9,6 +9,11 @@
- debug:
msg: "Using container name prefix {{ cname_prefix }}"
+# Install netaddr
+- name: Install netaddr for ipaddr filter
+ pip:
+ name: netaddr
+
# Run the tests
- block:
- include_tasks: run-test.yml
diff --git a/test/integration/targets/docker_container/tasks/tests/network.yml b/test/integration/targets/docker_container/tasks/tests/network.yml
index f1412eeafe..80c05ba5e9 100644
--- a/test/integration/targets/docker_container/tasks/tests/network.yml
+++ b/test/integration/targets/docker_container/tasks/tests/network.yml
@@ -5,10 +5,11 @@
cname_h1: "{{ cname_prefix ~ '-network-h1' }}"
nname_1: "{{ cname_prefix ~ '-network-1' }}"
nname_2: "{{ cname_prefix ~ '-network-2' }}"
+ nname_3: "{{ cname_prefix ~ '-network-3' }}"
- name: Registering container name
set_fact:
cnames: "{{ cnames + [cname, cname_h1] }}"
- dnetworks: "{{ dnetworks + [nname_1, nname_2] }}"
+ dnetworks: "{{ dnetworks + [nname_1, nname_2, nname_3] }}"
- name: Create networks
docker_network:
@@ -21,6 +22,32 @@
loop_var: network_name
when: docker_py_version is version('1.10.0', '>=')
+- set_fact:
+ subnet_ipv4: "192.168.{{ 64 + (192 | random) }}.0/24"
+ subnet_ipv6: "fdb6:feea:{{ '%0.4x:%0.4x' | format(65536 | random, 65536 | random) }}::/64"
+
+- set_fact:
+ # If netaddr would be installed on the controller, one could do:
+ nname_3_ipv4_2: "{{ subnet_ipv4 | next_nth_usable(2) }}"
+ nname_3_ipv4_3: "{{ subnet_ipv4 | next_nth_usable(3) }}"
+ nname_3_ipv4_4: "{{ subnet_ipv4 | next_nth_usable(4) }}"
+ nname_3_ipv6_2: "{{ subnet_ipv6 | next_nth_usable(2) }}"
+ nname_3_ipv6_3: "{{ subnet_ipv6 | next_nth_usable(3) }}"
+ nname_3_ipv6_4: "{{ subnet_ipv6 | next_nth_usable(4) }}"
+
+- debug:
+ msg: "Chose random IPv4 subnet {{ subnet_ipv4 }} and random IPv6 subnet {{ subnet_ipv6 }}"
+
+- name: Create network with fixed IPv4 and IPv6 subnets
+ docker_network:
+ name: "{{ nname_3 }}"
+ enable_ipv6: yes
+ ipam_config:
+ - subnet: "{{ subnet_ipv4 }}"
+ - subnet: "{{ subnet_ipv6 }}"
+ state: present
+ when: docker_py_version is version('1.10.0', '>=')
+
####################################################################
## network_mode ####################################################
####################################################################
@@ -536,6 +563,162 @@
when: docker_py_version is version('1.10.0', '>=')
####################################################################
+## networks with IP address ########################################
+####################################################################
+
+- block:
+ - name: create container (stopped) with one network and fixed IP
+ docker_container:
+ image: alpine:3.8
+ command: '/bin/sh -c "sleep 10m"'
+ name: "{{ cname }}"
+ state: stopped
+ networks:
+ - name: "{{ nname_3 }}"
+ ipv4_address: "{{ nname_3_ipv4_2 }}"
+ ipv6_address: "{{ nname_3_ipv6_2 }}"
+ networks_cli_compatible: yes
+ register: networks_1
+
+ - name: create container (stopped) with one network and fixed IP (idempotent)
+ docker_container:
+ image: alpine:3.8
+ command: '/bin/sh -c "sleep 10m"'
+ name: "{{ cname }}"
+ state: stopped
+ networks:
+ - name: "{{ nname_3 }}"
+ ipv4_address: "{{ nname_3_ipv4_2 }}"
+ ipv6_address: "{{ nname_3_ipv6_2 }}"
+ networks_cli_compatible: yes
+ register: networks_2
+
+ - name: create container (stopped) with one network and fixed IP (different IPv4)
+ docker_container:
+ image: alpine:3.8
+ command: '/bin/sh -c "sleep 10m"'
+ name: "{{ cname }}"
+ state: stopped
+ networks:
+ - name: "{{ nname_3 }}"
+ ipv4_address: "{{ nname_3_ipv4_3 }}"
+ ipv6_address: "{{ nname_3_ipv6_2 }}"
+ networks_cli_compatible: yes
+ register: networks_3
+
+ - name: create container (stopped) with one network and fixed IP (different IPv6)
+ docker_container:
+ image: alpine:3.8
+ command: '/bin/sh -c "sleep 10m"'
+ name: "{{ cname }}"
+ state: stopped
+ networks:
+ - name: "{{ nname_3 }}"
+ ipv4_address: "{{ nname_3_ipv4_3 }}"
+ ipv6_address: "{{ nname_3_ipv6_3 }}"
+ networks_cli_compatible: yes
+ register: networks_4
+
+ - name: create container (started) with one network and fixed IP
+ docker_container:
+ name: "{{ cname }}"
+ state: started
+ register: networks_5
+
+ - name: create container (started) with one network and fixed IP (different IPv4)
+ docker_container:
+ image: alpine:3.8
+ command: '/bin/sh -c "sleep 10m"'
+ name: "{{ cname }}"
+ state: started
+ networks:
+ - name: "{{ nname_3 }}"
+ ipv4_address: "{{ nname_3_ipv4_4 }}"
+ ipv6_address: "{{ nname_3_ipv6_3 }}"
+ networks_cli_compatible: yes
+ force_kill: yes
+ register: networks_6
+
+ - name: create container (started) with one network and fixed IP (different IPv6)
+ docker_container:
+ image: alpine:3.8
+ command: '/bin/sh -c "sleep 10m"'
+ name: "{{ cname }}"
+ state: started
+ networks:
+ - name: "{{ nname_3 }}"
+ ipv4_address: "{{ nname_3_ipv4_4 }}"
+ ipv6_address: "{{ nname_3_ipv6_4 }}"
+ networks_cli_compatible: yes
+ force_kill: yes
+ register: networks_7
+
+ - name: create container (started) with one network and fixed IP (idempotent)
+ docker_container:
+ image: alpine:3.8
+ command: '/bin/sh -c "sleep 10m"'
+ name: "{{ cname }}"
+ state: started
+ networks:
+ - name: "{{ nname_3 }}"
+ ipv4_address: "{{ nname_3_ipv4_4 }}"
+ ipv6_address: "{{ nname_3_ipv6_4 }}"
+ networks_cli_compatible: yes
+ register: networks_8
+
+ - name: cleanup
+ docker_container:
+ name: "{{ cname }}"
+ state: absent
+ force_kill: yes
+ diff: no
+
+ - assert:
+ that:
+ - networks_1 is changed
+ - networks_1.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_2
+ - networks_1.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | ipaddr == nname_3_ipv6_2 | ipaddr
+ - networks_1.container.NetworkSettings.Networks[nname_3].IPAddress == ""
+ - networks_1.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == ""
+ - networks_2 is not changed
+ - networks_2.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_2
+ - networks_2.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | ipaddr == nname_3_ipv6_2 | ipaddr
+ - networks_2.container.NetworkSettings.Networks[nname_3].IPAddress == ""
+ - networks_2.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == ""
+ - networks_3 is changed
+ - networks_3.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_3
+ - networks_3.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | ipaddr == nname_3_ipv6_2 | ipaddr
+ - networks_3.container.NetworkSettings.Networks[nname_3].IPAddress == ""
+ - networks_3.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == ""
+ - networks_4 is changed
+ - networks_4.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_3
+ - networks_4.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | ipaddr == nname_3_ipv6_3 | ipaddr
+ - networks_4.container.NetworkSettings.Networks[nname_3].IPAddress == ""
+ - networks_4.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == ""
+ - networks_5 is changed
+ - networks_5.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_3
+ - networks_5.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | ipaddr == nname_3_ipv6_3 | ipaddr
+ - networks_5.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_3
+ - networks_5.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | ipaddr == nname_3_ipv6_3 | ipaddr
+ - networks_6 is changed
+ - networks_6.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_4
+ - networks_6.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | ipaddr == nname_3_ipv6_3 | ipaddr
+ - networks_6.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_4
+ - networks_6.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | ipaddr == nname_3_ipv6_3 | ipaddr
+ - networks_7 is changed
+ - networks_7.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_4
+ - networks_7.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | ipaddr == nname_3_ipv6_4 | ipaddr
+ - networks_7.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_4
+ - networks_7.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | ipaddr == nname_3_ipv6_4 | ipaddr
+ - networks_8 is not changed
+ - networks_8.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_4
+ - networks_8.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | ipaddr == nname_3_ipv6_4 | ipaddr
+ - networks_8.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_4
+ - networks_8.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | ipaddr == nname_3_ipv6_4 | ipaddr
+
+ when: docker_py_version is version('1.10.0', '>=')
+
+####################################################################
####################################################################
####################################################################
@@ -547,6 +730,7 @@
loop:
- "{{ nname_1 }}"
- "{{ nname_2 }}"
+ - "{{ nname_3 }}"
loop_control:
loop_var: network_name
when: docker_py_version is version('1.10.0', '>=')