mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Docker swarm service integration tests (#45674)
* integration test for docker_swarm_service * ensure stack de-initialization * Set default value for 'configs' parameter to None Docker-py uses None as a default value for configs. Using the same default here allows to create services on older docker setups (docker_api<1.30). * Set default value for 'update_order' parameter to None Docker-py uses None as a default value for update_order. Using the same default here allows to create services on older docker setups (docker_api<1.29) * Set default value for 'publish.mode' parameter to None Docker-py uses None as a default value for publish_mode. Using the same default here allows to create services on older docker setups (docker_api<1.32) * Allow tests to run on older version of docker. * remove workarounds for old docker versions * test correct swarm cleanup * changelog fragment for docker_swarm_service defaults change
This commit is contained in:
parent
bba8c23585
commit
2162d7d4de
7 changed files with 221 additions and 38 deletions
2
changelogs/fragments/docker-swarm-service-defaults.yml
Normal file
2
changelogs/fragments/docker-swarm-service-defaults.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- change default value for ``configs`` from ``[]`` to ``null`` and for ``update_order`` from ``stop-first`` to ``null``, matching docker API and allowing the module to interact with older docker daemons.
|
|
@ -177,7 +177,7 @@ options:
|
||||||
- List of dictionaries describing the service configs.
|
- List of dictionaries describing the service configs.
|
||||||
- Every item must be a dictionary exposing the keys config_id, config_name, filename, uid (defaults to 0), gid (defaults to 0), mode (defaults to 0o444)
|
- Every item must be a dictionary exposing the keys config_id, config_name, filename, uid (defaults to 0), gid (defaults to 0), mode (defaults to 0o444)
|
||||||
- Maps docker service --config option.
|
- Maps docker service --config option.
|
||||||
default: []
|
default: null
|
||||||
networks:
|
networks:
|
||||||
required: false
|
required: false
|
||||||
default: []
|
default: []
|
||||||
|
@ -189,8 +189,9 @@ options:
|
||||||
required: false
|
required: false
|
||||||
description:
|
description:
|
||||||
- List of dictionaries describing the service published ports.
|
- List of dictionaries describing the service published ports.
|
||||||
- Every item must be a dictionary exposing the keys published_port, target_port, protocol (defaults to 'tcp'), mode <ingress|host>, default to ingress.
|
- Every item must be a dictionary exposing the keys published_port, target_port, protocol (defaults to 'tcp')
|
||||||
- Only used with api_version >= 1.25
|
- Only used with api_version >= 1.25
|
||||||
|
- If api_version >= 1.32 the dictionaries can contain the attribute 'mode' set to 'ingress' or 'host' (default 'ingress').
|
||||||
replicas:
|
replicas:
|
||||||
required: false
|
required: false
|
||||||
default: -1
|
default: -1
|
||||||
|
@ -262,13 +263,11 @@ options:
|
||||||
- Maps to docker service --update-max-failure-ratio
|
- Maps to docker service --update-max-failure-ratio
|
||||||
update_order:
|
update_order:
|
||||||
required: false
|
required: false
|
||||||
default: stop-first
|
default: null
|
||||||
description:
|
description:
|
||||||
- Specifies the order of operations when rolling out an updated task.
|
- Specifies the order of operations when rolling out an updated task.
|
||||||
- Maps to docker service --update-order
|
- Maps to docker service --update-order
|
||||||
choices:
|
- Requires docker api version >= 1.29
|
||||||
- stop-first
|
|
||||||
- start-first
|
|
||||||
user:
|
user:
|
||||||
required: false
|
required: false
|
||||||
default: root
|
default: root
|
||||||
|
@ -496,7 +495,7 @@ class DockerService(DockerBaseClass):
|
||||||
self.mode = "replicated"
|
self.mode = "replicated"
|
||||||
self.user = "root"
|
self.user = "root"
|
||||||
self.mounts = []
|
self.mounts = []
|
||||||
self.configs = []
|
self.configs = None
|
||||||
self.secrets = []
|
self.secrets = []
|
||||||
self.constraints = []
|
self.constraints = []
|
||||||
self.networks = []
|
self.networks = []
|
||||||
|
@ -513,7 +512,7 @@ class DockerService(DockerBaseClass):
|
||||||
self.update_failure_action = "continue"
|
self.update_failure_action = "continue"
|
||||||
self.update_monitor = 5000000000
|
self.update_monitor = 5000000000
|
||||||
self.update_max_failure_ratio = 0.00
|
self.update_max_failure_ratio = 0.00
|
||||||
self.update_order = "stop-first"
|
self.update_order = None
|
||||||
|
|
||||||
def get_facts(self):
|
def get_facts(self):
|
||||||
return {
|
return {
|
||||||
|
@ -530,7 +529,7 @@ class DockerService(DockerBaseClass):
|
||||||
'env': self.env,
|
'env': self.env,
|
||||||
'force_update': self.force_update,
|
'force_update': self.force_update,
|
||||||
'log_driver': self.log_driver,
|
'log_driver': self.log_driver,
|
||||||
'log_driver_options ': self.log_driver_options,
|
'log_driver_options': self.log_driver_options,
|
||||||
'publish': self.publish,
|
'publish': self.publish,
|
||||||
'constraints': self.constraints,
|
'constraints': self.constraints,
|
||||||
'labels': self.labels,
|
'labels': self.labels,
|
||||||
|
@ -608,13 +607,13 @@ class DockerService(DockerBaseClass):
|
||||||
for param_p in ap['publish']:
|
for param_p in ap['publish']:
|
||||||
service_p = {}
|
service_p = {}
|
||||||
service_p['protocol'] = param_p.get('protocol', 'tcp')
|
service_p['protocol'] = param_p.get('protocol', 'tcp')
|
||||||
service_p['mode'] = param_p.get('mode', 'ingress')
|
service_p['mode'] = param_p.get('mode', None)
|
||||||
service_p['published_port'] = int(param_p['published_port'])
|
service_p['published_port'] = int(param_p['published_port'])
|
||||||
service_p['target_port'] = int(param_p['target_port'])
|
service_p['target_port'] = int(param_p['target_port'])
|
||||||
if service_p['protocol'] not in ['tcp', 'udp']:
|
if service_p['protocol'] not in ['tcp', 'udp']:
|
||||||
raise ValueError("got publish.protocol '%s', valid values:'tcp', 'udp'" %
|
raise ValueError("got publish.protocol '%s', valid values:'tcp', 'udp'" %
|
||||||
service_p['protocol'])
|
service_p['protocol'])
|
||||||
if service_p['mode'] not in ['ingress', 'host']:
|
if service_p['mode'] not in [None, 'ingress', 'host']:
|
||||||
raise ValueError("got publish.mode '%s', valid values:'ingress', 'host'" %
|
raise ValueError("got publish.mode '%s', valid values:'ingress', 'host'" %
|
||||||
service_p['mode'])
|
service_p['mode'])
|
||||||
s.publish.append(service_p)
|
s.publish.append(service_p)
|
||||||
|
@ -627,16 +626,18 @@ class DockerService(DockerBaseClass):
|
||||||
service_m['target'] = param_m['target']
|
service_m['target'] = param_m['target']
|
||||||
s.mounts.append(service_m)
|
s.mounts.append(service_m)
|
||||||
|
|
||||||
s.configs = []
|
s.configs = None
|
||||||
for param_m in ap['configs']:
|
if ap['configs']:
|
||||||
service_c = {}
|
s.configs = []
|
||||||
service_c['config_id'] = param_m['config_id']
|
for param_m in ap['configs']:
|
||||||
service_c['config_name'] = str(param_m['config_name'])
|
service_c = {}
|
||||||
service_c['filename'] = param_m.get('filename', service_c['config_name'])
|
service_c['config_id'] = param_m['config_id']
|
||||||
service_c['uid'] = int(param_m.get('uid', "0"))
|
service_c['config_name'] = str(param_m['config_name'])
|
||||||
service_c['gid'] = int(param_m.get('gid', "0"))
|
service_c['filename'] = param_m.get('filename', service_c['config_name'])
|
||||||
service_c['mode'] = param_m.get('mode', 0o444)
|
service_c['uid'] = int(param_m.get('uid', "0"))
|
||||||
s.configs.append(service_c)
|
service_c['gid'] = int(param_m.get('gid', "0"))
|
||||||
|
service_c['mode'] = param_m.get('mode', 0o444)
|
||||||
|
s.configs.append(service_c)
|
||||||
|
|
||||||
s.secrets = []
|
s.secrets = []
|
||||||
for param_m in ap['secrets']:
|
for param_m in ap['secrets']:
|
||||||
|
@ -754,18 +755,21 @@ class DockerService(DockerBaseClass):
|
||||||
read_only=mount_config['readonly'])
|
read_only=mount_config['readonly'])
|
||||||
)
|
)
|
||||||
|
|
||||||
configs = []
|
configs = None
|
||||||
for config_config in self.configs:
|
if self.configs:
|
||||||
configs.append(
|
configs = []
|
||||||
types.ConfigReference(
|
for config_config in self.configs:
|
||||||
config_id=config_config['config_id'],
|
configs.append(
|
||||||
config_name=config_config['config_name'],
|
types.ConfigReference(
|
||||||
filename=config_config.get('filename'),
|
config_id=config_config['config_id'],
|
||||||
uid=config_config.get('uid'),
|
config_name=config_config['config_name'],
|
||||||
gid=config_config.get('gid'),
|
filename=config_config.get('filename'),
|
||||||
mode=config_config.get('mode')
|
uid=config_config.get('uid'),
|
||||||
|
gid=config_config.get('gid'),
|
||||||
|
mode=config_config.get('mode')
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
secrets = []
|
secrets = []
|
||||||
for secret_config in self.secrets:
|
for secret_config in self.secrets:
|
||||||
secrets.append(
|
secrets.append(
|
||||||
|
@ -846,7 +850,10 @@ class DockerService(DockerBaseClass):
|
||||||
|
|
||||||
ports = {}
|
ports = {}
|
||||||
for port in self.publish:
|
for port in self.publish:
|
||||||
ports[int(port['published_port'])] = (int(port['target_port']), port['protocol'], port['mode'])
|
if port['mode']:
|
||||||
|
ports[int(port['published_port'])] = (int(port['target_port']), port['protocol'], port['mode'])
|
||||||
|
else:
|
||||||
|
ports[int(port['published_port'])] = (int(port['target_port']), port['protocol'])
|
||||||
endpoint_spec = types.EndpointSpec(mode=self.endpoint_mode, ports=ports)
|
endpoint_spec = types.EndpointSpec(mode=self.endpoint_mode, ports=ports)
|
||||||
return update_policy, task_template, networks, endpoint_spec, mode, self.labels
|
return update_policy, task_template, networks, endpoint_spec, mode, self.labels
|
||||||
|
|
||||||
|
@ -883,7 +890,9 @@ class DockerServiceManager():
|
||||||
ds.update_failure_action = update_config_data['FailureAction']
|
ds.update_failure_action = update_config_data['FailureAction']
|
||||||
ds.update_monitor = update_config_data['Monitor']
|
ds.update_monitor = update_config_data['Monitor']
|
||||||
ds.update_max_failure_ratio = update_config_data['MaxFailureRatio']
|
ds.update_max_failure_ratio = update_config_data['MaxFailureRatio']
|
||||||
ds.update_order = update_config_data['Order']
|
|
||||||
|
if 'Order' in update_config_data:
|
||||||
|
ds.update_order = update_config_data['Order']
|
||||||
|
|
||||||
dns_config = task_template_data['ContainerSpec'].get('DNSConfig', None)
|
dns_config = task_template_data['ContainerSpec'].get('DNSConfig', None)
|
||||||
if dns_config:
|
if dns_config:
|
||||||
|
@ -913,7 +922,7 @@ class DockerServiceManager():
|
||||||
for port in raw_data_endpoint_spec.get('Ports', []):
|
for port in raw_data_endpoint_spec.get('Ports', []):
|
||||||
ds.publish.append({
|
ds.publish.append({
|
||||||
'protocol': port['Protocol'],
|
'protocol': port['Protocol'],
|
||||||
'mode': port.get('PublishMode', 'ingress'),
|
'mode': port.get('PublishMode', None),
|
||||||
'published_port': int(port['PublishedPort']),
|
'published_port': int(port['PublishedPort']),
|
||||||
'target_port': int(port['TargetPort'])})
|
'target_port': int(port['TargetPort'])})
|
||||||
|
|
||||||
|
@ -1019,7 +1028,8 @@ class DockerServiceManager():
|
||||||
{'param': 'hostname', 'attribute': 'hostname', 'min_version': '1.25'},
|
{'param': 'hostname', 'attribute': 'hostname', 'min_version': '1.25'},
|
||||||
{'param': 'tty', 'attribute': 'tty', 'min_version': '1.25'},
|
{'param': 'tty', 'attribute': 'tty', 'min_version': '1.25'},
|
||||||
{'param': 'secrets', 'attribute': 'secrets', 'min_version': '1.25'},
|
{'param': 'secrets', 'attribute': 'secrets', 'min_version': '1.25'},
|
||||||
{'param': 'configs', 'attribute': 'configs', 'min_version': '1.30'}]
|
{'param': 'configs', 'attribute': 'configs', 'min_version': '1.30'},
|
||||||
|
{'param': 'update_order', 'attribute': 'update_order', 'min_version': '1.29'}]
|
||||||
params = self.client.module.params
|
params = self.client.module.params
|
||||||
empty_service = DockerService()
|
empty_service = DockerService()
|
||||||
for pv in parameters_versions:
|
for pv in parameters_versions:
|
||||||
|
@ -1113,7 +1123,7 @@ def main():
|
||||||
image=dict(type='str'),
|
image=dict(type='str'),
|
||||||
state=dict(default="present", choices=['present', 'absent']),
|
state=dict(default="present", choices=['present', 'absent']),
|
||||||
mounts=dict(default=[], type='list'),
|
mounts=dict(default=[], type='list'),
|
||||||
configs=dict(default=[], type='list'),
|
configs=dict(default=None, type='list'),
|
||||||
secrets=dict(default=[], type='list'),
|
secrets=dict(default=[], type='list'),
|
||||||
networks=dict(default=[], type='list'),
|
networks=dict(default=[], type='list'),
|
||||||
args=dict(default=[], type='list'),
|
args=dict(default=[], type='list'),
|
||||||
|
@ -1146,7 +1156,7 @@ def main():
|
||||||
update_failure_action=dict(default='continue', choices=['continue', 'pause']),
|
update_failure_action=dict(default='continue', choices=['continue', 'pause']),
|
||||||
update_monitor=dict(default=5000000000, type='int'),
|
update_monitor=dict(default=5000000000, type='int'),
|
||||||
update_max_failure_ratio=dict(default=0, type='float'),
|
update_max_failure_ratio=dict(default=0, type='float'),
|
||||||
update_order=dict(default='stop-first', choices=['stop-first', 'start-first']),
|
update_order=dict(default=None, type='string'),
|
||||||
user=dict(default='root'))
|
user=dict(default='root'))
|
||||||
required_if = [
|
required_if = [
|
||||||
('state', 'present', ['image'])
|
('state', 'present', ['image'])
|
||||||
|
|
4
test/integration/targets/docker_swarm_service/aliases
Normal file
4
test/integration/targets/docker_swarm_service/aliases
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
shippable/posix/group2
|
||||||
|
skip/osx
|
||||||
|
skip/freebsd
|
||||||
|
destructive
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
dependencies:
|
||||||
|
- setup_docker
|
|
@ -0,0 +1,4 @@
|
||||||
|
- include_tasks: test_swarm_service.yml
|
||||||
|
when:
|
||||||
|
- ansible_os_family != 'RedHat' or ansible_distribution_major_version != '6'
|
||||||
|
- ansible_distribution != 'Fedora' or ansible_distribution_major_version|int >= 26
|
|
@ -0,0 +1,122 @@
|
||||||
|
- name: Create a Swarm cluster
|
||||||
|
docker_swarm:
|
||||||
|
state: present
|
||||||
|
advertise_addr: "{{ansible_default_ipv4.address}}"
|
||||||
|
|
||||||
|
- name: Create a swarm service without name
|
||||||
|
register: output
|
||||||
|
docker_swarm_service:
|
||||||
|
state: present
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: assert failure when name not set
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- output is failed
|
||||||
|
- 'output.msg == "missing required arguments: name"'
|
||||||
|
|
||||||
|
- name: Remove an non-existing service
|
||||||
|
register: output
|
||||||
|
docker_swarm_service:
|
||||||
|
state: absent
|
||||||
|
name: non_existing_service
|
||||||
|
|
||||||
|
- name: assert output not changed when deleting non-existing service
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- output is not changed
|
||||||
|
|
||||||
|
- name: create sample service
|
||||||
|
register: output
|
||||||
|
docker_swarm_service:
|
||||||
|
name: test_service
|
||||||
|
endpoint_mode: dnsrr
|
||||||
|
image: busybox
|
||||||
|
args:
|
||||||
|
- sleep
|
||||||
|
- "3600"
|
||||||
|
|
||||||
|
- name: assert sample service is created
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- output is changed
|
||||||
|
|
||||||
|
- name: change service args
|
||||||
|
register: output
|
||||||
|
docker_swarm_service:
|
||||||
|
name: test_service
|
||||||
|
image: busybox
|
||||||
|
args:
|
||||||
|
- sleep
|
||||||
|
- "1800"
|
||||||
|
|
||||||
|
- name: assert service args are correct
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- output.ansible_docker_service.args == ['sleep', '1800']
|
||||||
|
|
||||||
|
- name: set service mode to global
|
||||||
|
register: output
|
||||||
|
docker_swarm_service:
|
||||||
|
name: test_service
|
||||||
|
image: busybox
|
||||||
|
endpoint_mode: vip
|
||||||
|
mode: global
|
||||||
|
args:
|
||||||
|
- sleep
|
||||||
|
- "1800"
|
||||||
|
|
||||||
|
- name: assert service mode changed caused service rebuild
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- output.rebuilt
|
||||||
|
|
||||||
|
- name: add published ports to service
|
||||||
|
register: output
|
||||||
|
docker_swarm_service:
|
||||||
|
name: test_service
|
||||||
|
image: busybox
|
||||||
|
mode: global
|
||||||
|
args:
|
||||||
|
- sleep
|
||||||
|
- "1800"
|
||||||
|
endpoint_mode: vip
|
||||||
|
publish:
|
||||||
|
- protocol: tcp
|
||||||
|
published_port: 60001
|
||||||
|
target_port: 60001
|
||||||
|
- protocol: udp
|
||||||
|
published_port: 60001
|
||||||
|
target_port: 60001
|
||||||
|
|
||||||
|
- name: assert service matches expectations
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- output.ansible_docker_service == service_expected_output
|
||||||
|
|
||||||
|
- name: delete sample service
|
||||||
|
register: output
|
||||||
|
docker_swarm_service:
|
||||||
|
name: test_service
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: assert service deletion returns changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- output is success
|
||||||
|
- output is changed
|
||||||
|
|
||||||
|
- name: Remove the Swarm cluster
|
||||||
|
docker_swarm:
|
||||||
|
state: absent
|
||||||
|
force: true
|
||||||
|
|
||||||
|
- name: Try reitializing the swarm cluster
|
||||||
|
docker_swarm:
|
||||||
|
state: present
|
||||||
|
advertise_addr: "{{ansible_default_ipv4.address}}"
|
||||||
|
|
||||||
|
- name: Clean the docker daemon status
|
||||||
|
docker_swarm:
|
||||||
|
state: absent
|
||||||
|
force: true
|
38
test/integration/targets/docker_swarm_service/vars/main.yml
Normal file
38
test/integration/targets/docker_swarm_service/vars/main.yml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
service_expected_output:
|
||||||
|
args: [sleep, '1800']
|
||||||
|
configs: null
|
||||||
|
constraints: []
|
||||||
|
container_labels: {}
|
||||||
|
dns: []
|
||||||
|
dns_options: []
|
||||||
|
dns_search: []
|
||||||
|
endpoint_mode: vip
|
||||||
|
env: []
|
||||||
|
force_update: null
|
||||||
|
hostname: ''
|
||||||
|
image: busybox
|
||||||
|
labels: {}
|
||||||
|
limit_cpu: 0.0
|
||||||
|
limit_memory: 0
|
||||||
|
log_driver: json-file
|
||||||
|
log_driver_options: {}
|
||||||
|
mode: global
|
||||||
|
mounts: []
|
||||||
|
networks: []
|
||||||
|
publish:
|
||||||
|
- {mode: null, protocol: tcp, published_port: 60001, target_port: 60001}
|
||||||
|
- {mode: null, protocol: udp, published_port: 60001, target_port: 60001}
|
||||||
|
replicas: null
|
||||||
|
reserve_cpu: 0.0
|
||||||
|
reserve_memory: 0
|
||||||
|
restart_policy: none
|
||||||
|
restart_policy_attempts: 0
|
||||||
|
restart_policy_delay: 0
|
||||||
|
restart_policy_window: 0
|
||||||
|
tty: false
|
||||||
|
update_delay: 10
|
||||||
|
update_failure_action: continue
|
||||||
|
update_max_failure_ratio: 0.0
|
||||||
|
update_monitor: 5000000000
|
||||||
|
update_order: null
|
||||||
|
update_parallelism: 1
|
Loading…
Reference in a new issue