diff options
author | Felix Fontein <felix@fontein.de> | 2019-09-30 10:47:02 +0200 |
---|---|---|
committer | Toshio Kuratomi <a.badger@gmail.com> | 2019-10-12 09:17:09 -0700 |
commit | 36a96a628a53c143ff13cd9a9eb4ac90c600c9ce (patch) | |
tree | 8c16fa7ce382e6fbca76de9c4d89654e61f9a3c5 | |
parent | 018d57078ec1d809f30e5df40c580ea58ed003ba (diff) | |
download | ansible-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)
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', '>=') |