Skip to content

Commit d22e2fe

Browse files
committed
Add support for force_update in TaskTemplate
Add min version checks in create_service and update_service Signed-off-by: Joffrey F <joffrey@docker.com>
1 parent fc5cd1a commit d22e2fe

File tree

5 files changed

+71
-9
lines changed

5 files changed

+71
-9
lines changed

docker/api/service.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,24 @@ def create_service(
6262
'Labels': labels,
6363
'TaskTemplate': task_template,
6464
'Mode': mode,
65-
'UpdateConfig': update_config,
6665
'Networks': utils.convert_service_networks(networks),
6766
'EndpointSpec': endpoint_spec
6867
}
68+
69+
if update_config is not None:
70+
if utils.version_lt(self._version, '1.25'):
71+
if 'MaxFailureRatio' in update_config:
72+
raise errors.InvalidVersion(
73+
'UpdateConfig.max_failure_ratio is not supported in'
74+
' API version < 1.25'
75+
)
76+
if 'Monitor' in update_config:
77+
raise errors.InvalidVersion(
78+
'UpdateConfig.monitor is not supported in'
79+
' API version < 1.25'
80+
)
81+
data['UpdateConfig'] = update_config
82+
6983
return self._result(
7084
self._post_json(url, data=data, headers=headers), True
7185
)
@@ -230,6 +244,12 @@ def update_service(self, service, version, task_template=None, name=None,
230244
mode = ServiceMode(mode)
231245
data['Mode'] = mode
232246
if task_template is not None:
247+
if 'ForceUpdate' in task_template and utils.version_lt(
248+
self._version, '1.25'):
249+
raise errors.InvalidVersion(
250+
'force_update is not supported in API version < 1.25'
251+
)
252+
233253
image = task_template.get('ContainerSpec', {}).get('Image', None)
234254
if image is not None:
235255
registry, repo_name = auth.resolve_repository_name(image)
@@ -238,7 +258,19 @@ def update_service(self, service, version, task_template=None, name=None,
238258
headers['X-Registry-Auth'] = auth_header
239259
data['TaskTemplate'] = task_template
240260
if update_config is not None:
261+
if utils.version_lt(self._version, '1.25'):
262+
if 'MaxFailureRatio' in update_config:
263+
raise errors.InvalidVersion(
264+
'UpdateConfig.max_failure_ratio is not supported in'
265+
' API version < 1.25'
266+
)
267+
if 'Monitor' in update_config:
268+
raise errors.InvalidVersion(
269+
'UpdateConfig.monitor is not supported in'
270+
' API version < 1.25'
271+
)
241272
data['UpdateConfig'] = update_config
273+
242274
if networks is not None:
243275
data['Networks'] = utils.convert_service_networks(networks)
244276
if endpoint_spec is not None:

docker/types/services.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ class TaskTemplate(dict):
2121
restart_policy (RestartPolicy): Specification for the restart policy
2222
which applies to containers created as part of this service.
2323
placement (:py:class:`list`): A list of constraints.
24+
force_update (int): A counter that triggers an update even if no
25+
relevant parameters have been changed.
2426
"""
2527
def __init__(self, container_spec, resources=None, restart_policy=None,
26-
placement=None, log_driver=None):
28+
placement=None, log_driver=None, force_update=None):
2729
self['ContainerSpec'] = container_spec
2830
if resources:
2931
self['Resources'] = resources
@@ -36,6 +38,11 @@ def __init__(self, container_spec, resources=None, restart_policy=None,
3638
if log_driver:
3739
self['LogDriver'] = log_driver
3840

41+
if force_update is not None:
42+
if not isinstance(force_update, int):
43+
raise TypeError('force_update must be an integer')
44+
self['ForceUpdate'] = force_update
45+
3946
@property
4047
def container_spec(self):
4148
return self.get('ContainerSpec')

tests/helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ def force_leave_swarm(client):
7070
occasionally throws "context deadline exceeded" errors when leaving."""
7171
while True:
7272
try:
73-
return client.swarm.leave(force=True)
73+
if isinstance(client, docker.DockerClient):
74+
return client.swarm.leave(force=True)
75+
return client.leave_swarm(force=True) # elif APIClient
7476
except docker.errors.APIError as e:
7577
if e.explanation == "context deadline exceeded":
7678
continue

tests/integration/api_service_test.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
import docker
44

5-
from ..helpers import requires_api_version
5+
from ..helpers import force_leave_swarm, requires_api_version
66
from .base import BaseAPIIntegrationTest
77

88

99
class ServiceTest(BaseAPIIntegrationTest):
1010
def setUp(self):
1111
super(ServiceTest, self).setUp()
12-
self.client.leave_swarm(force=True)
12+
force_leave_swarm(self.client)
1313
self.init_swarm()
1414

1515
def tearDown(self):
@@ -19,7 +19,7 @@ def tearDown(self):
1919
self.client.remove_service(service['ID'])
2020
except docker.errors.APIError:
2121
pass
22-
self.client.leave_swarm(force=True)
22+
force_leave_swarm(self.client)
2323

2424
def get_service_name(self):
2525
return 'dockerpytest_{0:x}'.format(random.getrandbits(64))
@@ -296,3 +296,24 @@ def test_create_service_replicated_mode(self):
296296
assert 'Mode' in svc_info['Spec']
297297
assert 'Replicated' in svc_info['Spec']['Mode']
298298
assert svc_info['Spec']['Mode']['Replicated'] == {'Replicas': 5}
299+
300+
@requires_api_version('1.25')
301+
def test_update_service_force_update(self):
302+
container_spec = docker.types.ContainerSpec(
303+
'busybox', ['echo', 'hello']
304+
)
305+
task_tmpl = docker.types.TaskTemplate(container_spec)
306+
name = self.get_service_name()
307+
svc_id = self.client.create_service(task_tmpl, name=name)
308+
svc_info = self.client.inspect_service(svc_id)
309+
assert 'TaskTemplate' in svc_info['Spec']
310+
assert 'ForceUpdate' in svc_info['Spec']['TaskTemplate']
311+
assert svc_info['Spec']['TaskTemplate']['ForceUpdate'] == 0
312+
version_index = svc_info['Version']['Index']
313+
314+
task_tmpl = docker.types.TaskTemplate(container_spec, force_update=10)
315+
self.client.update_service(name, version_index, task_tmpl, name=name)
316+
svc_info = self.client.inspect_service(svc_id)
317+
new_index = svc_info['Version']['Index']
318+
assert new_index > version_index
319+
assert svc_info['Spec']['TaskTemplate']['ForceUpdate'] == 10

tests/integration/api_swarm_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
import docker
33
import pytest
44

5-
from ..helpers import requires_api_version
5+
from ..helpers import force_leave_swarm, requires_api_version
66
from .base import BaseAPIIntegrationTest
77

88

99
class SwarmTest(BaseAPIIntegrationTest):
1010
def setUp(self):
1111
super(SwarmTest, self).setUp()
12-
self.client.leave_swarm(force=True)
12+
force_leave_swarm(self.client)
1313

1414
def tearDown(self):
1515
super(SwarmTest, self).tearDown()
16-
self.client.leave_swarm(force=True)
16+
force_leave_swarm(self.client)
1717

1818
@requires_api_version('1.24')
1919
def test_init_swarm_simple(self):

0 commit comments

Comments
 (0)