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.
|
||||
- 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.
|
||||
default: []
|
||||
default: null
|
||||
networks:
|
||||
required: false
|
||||
default: []
|
||||
|
@ -189,8 +189,9 @@ options:
|
|||
required: false
|
||||
description:
|
||||
- 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
|
||||
- If api_version >= 1.32 the dictionaries can contain the attribute 'mode' set to 'ingress' or 'host' (default 'ingress').
|
||||
replicas:
|
||||
required: false
|
||||
default: -1
|
||||
|
@ -262,13 +263,11 @@ options:
|
|||
- Maps to docker service --update-max-failure-ratio
|
||||
update_order:
|
||||
required: false
|
||||
default: stop-first
|
||||
default: null
|
||||
description:
|
||||
- Specifies the order of operations when rolling out an updated task.
|
||||
- Maps to docker service --update-order
|
||||
choices:
|
||||
- stop-first
|
||||
- start-first
|
||||
- Requires docker api version >= 1.29
|
||||
user:
|
||||
required: false
|
||||
default: root
|
||||
|
@ -496,7 +495,7 @@ class DockerService(DockerBaseClass):
|
|||
self.mode = "replicated"
|
||||
self.user = "root"
|
||||
self.mounts = []
|
||||
self.configs = []
|
||||
self.configs = None
|
||||
self.secrets = []
|
||||
self.constraints = []
|
||||
self.networks = []
|
||||
|
@ -513,7 +512,7 @@ class DockerService(DockerBaseClass):
|
|||
self.update_failure_action = "continue"
|
||||
self.update_monitor = 5000000000
|
||||
self.update_max_failure_ratio = 0.00
|
||||
self.update_order = "stop-first"
|
||||
self.update_order = None
|
||||
|
||||
def get_facts(self):
|
||||
return {
|
||||
|
@ -530,7 +529,7 @@ class DockerService(DockerBaseClass):
|
|||
'env': self.env,
|
||||
'force_update': self.force_update,
|
||||
'log_driver': self.log_driver,
|
||||
'log_driver_options ': self.log_driver_options,
|
||||
'log_driver_options': self.log_driver_options,
|
||||
'publish': self.publish,
|
||||
'constraints': self.constraints,
|
||||
'labels': self.labels,
|
||||
|
@ -608,13 +607,13 @@ class DockerService(DockerBaseClass):
|
|||
for param_p in ap['publish']:
|
||||
service_p = {}
|
||||
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['target_port'] = int(param_p['target_port'])
|
||||
if service_p['protocol'] not in ['tcp', 'udp']:
|
||||
raise ValueError("got publish.protocol '%s', valid values:'tcp', 'udp'" %
|
||||
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'" %
|
||||
service_p['mode'])
|
||||
s.publish.append(service_p)
|
||||
|
@ -627,16 +626,18 @@ class DockerService(DockerBaseClass):
|
|||
service_m['target'] = param_m['target']
|
||||
s.mounts.append(service_m)
|
||||
|
||||
s.configs = []
|
||||
for param_m in ap['configs']:
|
||||
service_c = {}
|
||||
service_c['config_id'] = param_m['config_id']
|
||||
service_c['config_name'] = str(param_m['config_name'])
|
||||
service_c['filename'] = param_m.get('filename', service_c['config_name'])
|
||||
service_c['uid'] = int(param_m.get('uid', "0"))
|
||||
service_c['gid'] = int(param_m.get('gid', "0"))
|
||||
service_c['mode'] = param_m.get('mode', 0o444)
|
||||
s.configs.append(service_c)
|
||||
s.configs = None
|
||||
if ap['configs']:
|
||||
s.configs = []
|
||||
for param_m in ap['configs']:
|
||||
service_c = {}
|
||||
service_c['config_id'] = param_m['config_id']
|
||||
service_c['config_name'] = str(param_m['config_name'])
|
||||
service_c['filename'] = param_m.get('filename', service_c['config_name'])
|
||||
service_c['uid'] = int(param_m.get('uid', "0"))
|
||||
service_c['gid'] = int(param_m.get('gid', "0"))
|
||||
service_c['mode'] = param_m.get('mode', 0o444)
|
||||
s.configs.append(service_c)
|
||||
|
||||
s.secrets = []
|
||||
for param_m in ap['secrets']:
|
||||
|
@ -754,18 +755,21 @@ class DockerService(DockerBaseClass):
|
|||
read_only=mount_config['readonly'])
|
||||
)
|
||||
|
||||
configs = []
|
||||
for config_config in self.configs:
|
||||
configs.append(
|
||||
types.ConfigReference(
|
||||
config_id=config_config['config_id'],
|
||||
config_name=config_config['config_name'],
|
||||
filename=config_config.get('filename'),
|
||||
uid=config_config.get('uid'),
|
||||
gid=config_config.get('gid'),
|
||||
mode=config_config.get('mode')
|
||||
configs = None
|
||||
if self.configs:
|
||||
configs = []
|
||||
for config_config in self.configs:
|
||||
configs.append(
|
||||
types.ConfigReference(
|
||||
config_id=config_config['config_id'],
|
||||
config_name=config_config['config_name'],
|
||||
filename=config_config.get('filename'),
|
||||
uid=config_config.get('uid'),
|
||||
gid=config_config.get('gid'),
|
||||
mode=config_config.get('mode')
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
secrets = []
|
||||
for secret_config in self.secrets:
|
||||
secrets.append(
|
||||
|
@ -846,7 +850,10 @@ class DockerService(DockerBaseClass):
|
|||
|
||||
ports = {}
|
||||
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)
|
||||
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_monitor = update_config_data['Monitor']
|
||||
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)
|
||||
if dns_config:
|
||||
|
@ -913,7 +922,7 @@ class DockerServiceManager():
|
|||
for port in raw_data_endpoint_spec.get('Ports', []):
|
||||
ds.publish.append({
|
||||
'protocol': port['Protocol'],
|
||||
'mode': port.get('PublishMode', 'ingress'),
|
||||
'mode': port.get('PublishMode', None),
|
||||
'published_port': int(port['PublishedPort']),
|
||||
'target_port': int(port['TargetPort'])})
|
||||
|
||||
|
@ -1019,7 +1028,8 @@ class DockerServiceManager():
|
|||
{'param': 'hostname', 'attribute': 'hostname', 'min_version': '1.25'},
|
||||
{'param': 'tty', 'attribute': 'tty', '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
|
||||
empty_service = DockerService()
|
||||
for pv in parameters_versions:
|
||||
|
@ -1113,7 +1123,7 @@ def main():
|
|||
image=dict(type='str'),
|
||||
state=dict(default="present", choices=['present', 'absent']),
|
||||
mounts=dict(default=[], type='list'),
|
||||
configs=dict(default=[], type='list'),
|
||||
configs=dict(default=None, type='list'),
|
||||
secrets=dict(default=[], type='list'),
|
||||
networks=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_monitor=dict(default=5000000000, type='int'),
|
||||
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'))
|
||||
required_if = [
|
||||
('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