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.
This commit is contained in:
parent
a79f7e575a
commit
62c0cae29a
4 changed files with 207 additions and 11 deletions
|
@ -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."
|
|
@ -2197,7 +2197,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,
|
||||
|
@ -2205,18 +2206,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
|
||||
|
@ -2224,10 +2226,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
|
||||
|
@ -3092,7 +3094,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()
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ####################################################
|
||||
####################################################################
|
||||
|
@ -535,6 +562,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', '>=')
|
||||
|
|
Loading…
Reference in a new issue