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
pull/47742/head
Dario Zanzico 6 years ago committed by John R Barker
parent bba8c23585
commit 2162d7d4de

@ -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'])

@ -0,0 +1,4 @@
shippable/posix/group2
skip/osx
skip/freebsd
destructive

@ -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

@ -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…
Cancel
Save