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']
|
connected_networks = self.container['NetworkSettings']['Networks']
|
||||||
for network in self.parameters.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
|
different = True
|
||||||
differences.append(dict(
|
differences.append(dict(
|
||||||
parameter=network,
|
parameter=network,
|
||||||
|
@ -2205,18 +2206,19 @@ class Container(DockerBaseClass):
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
diff = False
|
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
|
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
|
diff = True
|
||||||
if network.get('aliases'):
|
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
|
diff = True
|
||||||
if network.get('links'):
|
if network.get('links'):
|
||||||
expected_links = []
|
expected_links = []
|
||||||
for link, alias in network['links']:
|
for link, alias in network['links']:
|
||||||
expected_links.append("%s:%s" % (link, alias))
|
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
|
diff = True
|
||||||
if diff:
|
if diff:
|
||||||
different = True
|
different = True
|
||||||
|
@ -2224,10 +2226,10 @@ class Container(DockerBaseClass):
|
||||||
parameter=network,
|
parameter=network,
|
||||||
container=dict(
|
container=dict(
|
||||||
name=network['name'],
|
name=network['name'],
|
||||||
ipv4_address=connected_networks[network['name']].get('IPAddress'),
|
ipv4_address=network_info_ipam.get('IPv4Address'),
|
||||||
ipv6_address=connected_networks[network['name']].get('GlobalIPv6Address'),
|
ipv6_address=network_info_ipam.get('IPv6Address'),
|
||||||
aliases=connected_networks[network['name']].get('Aliases'),
|
aliases=network_info.get('Aliases'),
|
||||||
links=connected_networks[network['name']].get('Links')
|
links=network_info.get('Links')
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
return different, differences
|
return different, differences
|
||||||
|
@ -3092,7 +3094,8 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
|
||||||
pids_limit=dict(docker_py_version='1.10.0', docker_api_version='1.23'),
|
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'),
|
mounts=dict(docker_py_version='2.6.0', docker_api_version='1.25'),
|
||||||
# specials
|
# 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'),
|
usage_msg='ipv4_address or ipv6_address in networks'),
|
||||||
stop_timeout=dict(), # see _get_additional_minimal_versions()
|
stop_timeout=dict(), # see _get_additional_minimal_versions()
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,11 @@
|
||||||
- debug:
|
- debug:
|
||||||
msg: "Using container name prefix {{ cname_prefix }}"
|
msg: "Using container name prefix {{ cname_prefix }}"
|
||||||
|
|
||||||
|
# Install netaddr
|
||||||
|
- name: Install netaddr for ipaddr filter
|
||||||
|
pip:
|
||||||
|
name: netaddr
|
||||||
|
|
||||||
# Run the tests
|
# Run the tests
|
||||||
- block:
|
- block:
|
||||||
- include_tasks: run-test.yml
|
- include_tasks: run-test.yml
|
||||||
|
|
|
@ -5,10 +5,11 @@
|
||||||
cname_h1: "{{ cname_prefix ~ '-network-h1' }}"
|
cname_h1: "{{ cname_prefix ~ '-network-h1' }}"
|
||||||
nname_1: "{{ cname_prefix ~ '-network-1' }}"
|
nname_1: "{{ cname_prefix ~ '-network-1' }}"
|
||||||
nname_2: "{{ cname_prefix ~ '-network-2' }}"
|
nname_2: "{{ cname_prefix ~ '-network-2' }}"
|
||||||
|
nname_3: "{{ cname_prefix ~ '-network-3' }}"
|
||||||
- name: Registering container name
|
- name: Registering container name
|
||||||
set_fact:
|
set_fact:
|
||||||
cnames: "{{ cnames + [cname, cname_h1] }}"
|
cnames: "{{ cnames + [cname, cname_h1] }}"
|
||||||
dnetworks: "{{ dnetworks + [nname_1, nname_2] }}"
|
dnetworks: "{{ dnetworks + [nname_1, nname_2, nname_3] }}"
|
||||||
|
|
||||||
- name: Create networks
|
- name: Create networks
|
||||||
docker_network:
|
docker_network:
|
||||||
|
@ -21,6 +22,32 @@
|
||||||
loop_var: network_name
|
loop_var: network_name
|
||||||
when: docker_py_version is version('1.10.0', '>=')
|
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 ####################################################
|
## network_mode ####################################################
|
||||||
####################################################################
|
####################################################################
|
||||||
|
@ -535,6 +562,162 @@
|
||||||
|
|
||||||
when: docker_py_version is version('1.10.0', '>=')
|
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:
|
loop:
|
||||||
- "{{ nname_1 }}"
|
- "{{ nname_1 }}"
|
||||||
- "{{ nname_2 }}"
|
- "{{ nname_2 }}"
|
||||||
|
- "{{ nname_3 }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
loop_var: network_name
|
loop_var: network_name
|
||||||
when: docker_py_version is version('1.10.0', '>=')
|
when: docker_py_version is version('1.10.0', '>=')
|
||||||
|
|
Loading…
Reference in a new issue