mirror of https://github.com/docker/docker-py.git
Merge pull request #1424 from shin-/service_params_1.25
Add new (1.25) service params
This commit is contained in:
commit
fd1ab6cef4
|
@ -62,10 +62,24 @@ class ServiceApiMixin(object):
|
||||||
'Labels': labels,
|
'Labels': labels,
|
||||||
'TaskTemplate': task_template,
|
'TaskTemplate': task_template,
|
||||||
'Mode': mode,
|
'Mode': mode,
|
||||||
'UpdateConfig': update_config,
|
|
||||||
'Networks': utils.convert_service_networks(networks),
|
'Networks': utils.convert_service_networks(networks),
|
||||||
'EndpointSpec': endpoint_spec
|
'EndpointSpec': endpoint_spec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if update_config is not None:
|
||||||
|
if utils.version_lt(self._version, '1.25'):
|
||||||
|
if 'MaxFailureRatio' in update_config:
|
||||||
|
raise errors.InvalidVersion(
|
||||||
|
'UpdateConfig.max_failure_ratio is not supported in'
|
||||||
|
' API version < 1.25'
|
||||||
|
)
|
||||||
|
if 'Monitor' in update_config:
|
||||||
|
raise errors.InvalidVersion(
|
||||||
|
'UpdateConfig.monitor is not supported in'
|
||||||
|
' API version < 1.25'
|
||||||
|
)
|
||||||
|
data['UpdateConfig'] = update_config
|
||||||
|
|
||||||
return self._result(
|
return self._result(
|
||||||
self._post_json(url, data=data, headers=headers), True
|
self._post_json(url, data=data, headers=headers), True
|
||||||
)
|
)
|
||||||
|
@ -230,6 +244,12 @@ class ServiceApiMixin(object):
|
||||||
mode = ServiceMode(mode)
|
mode = ServiceMode(mode)
|
||||||
data['Mode'] = mode
|
data['Mode'] = mode
|
||||||
if task_template is not None:
|
if task_template is not None:
|
||||||
|
if 'ForceUpdate' in task_template and utils.version_lt(
|
||||||
|
self._version, '1.25'):
|
||||||
|
raise errors.InvalidVersion(
|
||||||
|
'force_update is not supported in API version < 1.25'
|
||||||
|
)
|
||||||
|
|
||||||
image = task_template.get('ContainerSpec', {}).get('Image', None)
|
image = task_template.get('ContainerSpec', {}).get('Image', None)
|
||||||
if image is not None:
|
if image is not None:
|
||||||
registry, repo_name = auth.resolve_repository_name(image)
|
registry, repo_name = auth.resolve_repository_name(image)
|
||||||
|
@ -238,7 +258,19 @@ class ServiceApiMixin(object):
|
||||||
headers['X-Registry-Auth'] = auth_header
|
headers['X-Registry-Auth'] = auth_header
|
||||||
data['TaskTemplate'] = task_template
|
data['TaskTemplate'] = task_template
|
||||||
if update_config is not None:
|
if update_config is not None:
|
||||||
|
if utils.version_lt(self._version, '1.25'):
|
||||||
|
if 'MaxFailureRatio' in update_config:
|
||||||
|
raise errors.InvalidVersion(
|
||||||
|
'UpdateConfig.max_failure_ratio is not supported in'
|
||||||
|
' API version < 1.25'
|
||||||
|
)
|
||||||
|
if 'Monitor' in update_config:
|
||||||
|
raise errors.InvalidVersion(
|
||||||
|
'UpdateConfig.monitor is not supported in'
|
||||||
|
' API version < 1.25'
|
||||||
|
)
|
||||||
data['UpdateConfig'] = update_config
|
data['UpdateConfig'] = update_config
|
||||||
|
|
||||||
if networks is not None:
|
if networks is not None:
|
||||||
data['Networks'] = utils.convert_service_networks(networks)
|
data['Networks'] = utils.convert_service_networks(networks)
|
||||||
if endpoint_spec is not None:
|
if endpoint_spec is not None:
|
||||||
|
|
|
@ -21,9 +21,11 @@ class TaskTemplate(dict):
|
||||||
restart_policy (RestartPolicy): Specification for the restart policy
|
restart_policy (RestartPolicy): Specification for the restart policy
|
||||||
which applies to containers created as part of this service.
|
which applies to containers created as part of this service.
|
||||||
placement (:py:class:`list`): A list of constraints.
|
placement (:py:class:`list`): A list of constraints.
|
||||||
|
force_update (int): A counter that triggers an update even if no
|
||||||
|
relevant parameters have been changed.
|
||||||
"""
|
"""
|
||||||
def __init__(self, container_spec, resources=None, restart_policy=None,
|
def __init__(self, container_spec, resources=None, restart_policy=None,
|
||||||
placement=None, log_driver=None):
|
placement=None, log_driver=None, force_update=None):
|
||||||
self['ContainerSpec'] = container_spec
|
self['ContainerSpec'] = container_spec
|
||||||
if resources:
|
if resources:
|
||||||
self['Resources'] = resources
|
self['Resources'] = resources
|
||||||
|
@ -36,6 +38,11 @@ class TaskTemplate(dict):
|
||||||
if log_driver:
|
if log_driver:
|
||||||
self['LogDriver'] = log_driver
|
self['LogDriver'] = log_driver
|
||||||
|
|
||||||
|
if force_update is not None:
|
||||||
|
if not isinstance(force_update, int):
|
||||||
|
raise TypeError('force_update must be an integer')
|
||||||
|
self['ForceUpdate'] = force_update
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def container_spec(self):
|
def container_spec(self):
|
||||||
return self.get('ContainerSpec')
|
return self.get('ContainerSpec')
|
||||||
|
@ -233,8 +240,14 @@ class UpdateConfig(dict):
|
||||||
failure_action (string): Action to take if an updated task fails to
|
failure_action (string): Action to take if an updated task fails to
|
||||||
run, or stops running during the update. Acceptable values are
|
run, or stops running during the update. Acceptable values are
|
||||||
``continue`` and ``pause``. Default: ``continue``
|
``continue`` and ``pause``. Default: ``continue``
|
||||||
|
monitor (int): Amount of time to monitor each updated task for
|
||||||
|
failures, in nanoseconds.
|
||||||
|
max_failure_ratio (float): The fraction of tasks that may fail during
|
||||||
|
an update before the failure action is invoked, specified as a
|
||||||
|
floating point number between 0 and 1. Default: 0
|
||||||
"""
|
"""
|
||||||
def __init__(self, parallelism=0, delay=None, failure_action='continue'):
|
def __init__(self, parallelism=0, delay=None, failure_action='continue',
|
||||||
|
monitor=None, max_failure_ratio=None):
|
||||||
self['Parallelism'] = parallelism
|
self['Parallelism'] = parallelism
|
||||||
if delay is not None:
|
if delay is not None:
|
||||||
self['Delay'] = delay
|
self['Delay'] = delay
|
||||||
|
@ -244,6 +257,20 @@ class UpdateConfig(dict):
|
||||||
)
|
)
|
||||||
self['FailureAction'] = failure_action
|
self['FailureAction'] = failure_action
|
||||||
|
|
||||||
|
if monitor is not None:
|
||||||
|
if not isinstance(monitor, int):
|
||||||
|
raise TypeError('monitor must be an integer')
|
||||||
|
self['Monitor'] = monitor
|
||||||
|
|
||||||
|
if max_failure_ratio is not None:
|
||||||
|
if not isinstance(max_failure_ratio, (float, int)):
|
||||||
|
raise TypeError('max_failure_ratio must be a float')
|
||||||
|
if max_failure_ratio > 1 or max_failure_ratio < 0:
|
||||||
|
raise errors.InvalidArgument(
|
||||||
|
'max_failure_ratio must be a number between 0 and 1'
|
||||||
|
)
|
||||||
|
self['MaxFailureRatio'] = max_failure_ratio
|
||||||
|
|
||||||
|
|
||||||
class RestartConditionTypesEnum(object):
|
class RestartConditionTypesEnum(object):
|
||||||
_values = (
|
_values = (
|
||||||
|
|
|
@ -70,7 +70,9 @@ def force_leave_swarm(client):
|
||||||
occasionally throws "context deadline exceeded" errors when leaving."""
|
occasionally throws "context deadline exceeded" errors when leaving."""
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
return client.swarm.leave(force=True)
|
if isinstance(client, docker.DockerClient):
|
||||||
|
return client.swarm.leave(force=True)
|
||||||
|
return client.leave_swarm(force=True) # elif APIClient
|
||||||
except docker.errors.APIError as e:
|
except docker.errors.APIError as e:
|
||||||
if e.explanation == "context deadline exceeded":
|
if e.explanation == "context deadline exceeded":
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -2,14 +2,14 @@ import random
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
|
|
||||||
from ..helpers import requires_api_version
|
from ..helpers import force_leave_swarm, requires_api_version
|
||||||
from .base import BaseAPIIntegrationTest
|
from .base import BaseAPIIntegrationTest
|
||||||
|
|
||||||
|
|
||||||
class ServiceTest(BaseAPIIntegrationTest):
|
class ServiceTest(BaseAPIIntegrationTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ServiceTest, self).setUp()
|
super(ServiceTest, self).setUp()
|
||||||
self.client.leave_swarm(force=True)
|
force_leave_swarm(self.client)
|
||||||
self.init_swarm()
|
self.init_swarm()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -19,7 +19,7 @@ class ServiceTest(BaseAPIIntegrationTest):
|
||||||
self.client.remove_service(service['ID'])
|
self.client.remove_service(service['ID'])
|
||||||
except docker.errors.APIError:
|
except docker.errors.APIError:
|
||||||
pass
|
pass
|
||||||
self.client.leave_swarm(force=True)
|
force_leave_swarm(self.client)
|
||||||
|
|
||||||
def get_service_name(self):
|
def get_service_name(self):
|
||||||
return 'dockerpytest_{0:x}'.format(random.getrandbits(64))
|
return 'dockerpytest_{0:x}'.format(random.getrandbits(64))
|
||||||
|
@ -155,6 +155,23 @@ class ServiceTest(BaseAPIIntegrationTest):
|
||||||
assert update_config['Delay'] == uc['Delay']
|
assert update_config['Delay'] == uc['Delay']
|
||||||
assert update_config['FailureAction'] == uc['FailureAction']
|
assert update_config['FailureAction'] == uc['FailureAction']
|
||||||
|
|
||||||
|
@requires_api_version('1.25')
|
||||||
|
def test_create_service_with_update_config_monitor(self):
|
||||||
|
container_spec = docker.types.ContainerSpec('busybox', ['true'])
|
||||||
|
task_tmpl = docker.types.TaskTemplate(container_spec)
|
||||||
|
update_config = docker.types.UpdateConfig(
|
||||||
|
monitor=300000000, max_failure_ratio=0.4
|
||||||
|
)
|
||||||
|
name = self.get_service_name()
|
||||||
|
svc_id = self.client.create_service(
|
||||||
|
task_tmpl, update_config=update_config, name=name
|
||||||
|
)
|
||||||
|
svc_info = self.client.inspect_service(svc_id)
|
||||||
|
assert 'UpdateConfig' in svc_info['Spec']
|
||||||
|
uc = svc_info['Spec']['UpdateConfig']
|
||||||
|
assert update_config['Monitor'] == uc['Monitor']
|
||||||
|
assert update_config['MaxFailureRatio'] == uc['MaxFailureRatio']
|
||||||
|
|
||||||
def test_create_service_with_restart_policy(self):
|
def test_create_service_with_restart_policy(self):
|
||||||
container_spec = docker.types.ContainerSpec('busybox', ['true'])
|
container_spec = docker.types.ContainerSpec('busybox', ['true'])
|
||||||
policy = docker.types.RestartPolicy(
|
policy = docker.types.RestartPolicy(
|
||||||
|
@ -279,3 +296,24 @@ class ServiceTest(BaseAPIIntegrationTest):
|
||||||
assert 'Mode' in svc_info['Spec']
|
assert 'Mode' in svc_info['Spec']
|
||||||
assert 'Replicated' in svc_info['Spec']['Mode']
|
assert 'Replicated' in svc_info['Spec']['Mode']
|
||||||
assert svc_info['Spec']['Mode']['Replicated'] == {'Replicas': 5}
|
assert svc_info['Spec']['Mode']['Replicated'] == {'Replicas': 5}
|
||||||
|
|
||||||
|
@requires_api_version('1.25')
|
||||||
|
def test_update_service_force_update(self):
|
||||||
|
container_spec = docker.types.ContainerSpec(
|
||||||
|
'busybox', ['echo', 'hello']
|
||||||
|
)
|
||||||
|
task_tmpl = docker.types.TaskTemplate(container_spec)
|
||||||
|
name = self.get_service_name()
|
||||||
|
svc_id = self.client.create_service(task_tmpl, name=name)
|
||||||
|
svc_info = self.client.inspect_service(svc_id)
|
||||||
|
assert 'TaskTemplate' in svc_info['Spec']
|
||||||
|
assert 'ForceUpdate' in svc_info['Spec']['TaskTemplate']
|
||||||
|
assert svc_info['Spec']['TaskTemplate']['ForceUpdate'] == 0
|
||||||
|
version_index = svc_info['Version']['Index']
|
||||||
|
|
||||||
|
task_tmpl = docker.types.TaskTemplate(container_spec, force_update=10)
|
||||||
|
self.client.update_service(name, version_index, task_tmpl, name=name)
|
||||||
|
svc_info = self.client.inspect_service(svc_id)
|
||||||
|
new_index = svc_info['Version']['Index']
|
||||||
|
assert new_index > version_index
|
||||||
|
assert svc_info['Spec']['TaskTemplate']['ForceUpdate'] == 10
|
||||||
|
|
|
@ -2,18 +2,18 @@ import copy
|
||||||
import docker
|
import docker
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ..helpers import requires_api_version
|
from ..helpers import force_leave_swarm, requires_api_version
|
||||||
from .base import BaseAPIIntegrationTest
|
from .base import BaseAPIIntegrationTest
|
||||||
|
|
||||||
|
|
||||||
class SwarmTest(BaseAPIIntegrationTest):
|
class SwarmTest(BaseAPIIntegrationTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(SwarmTest, self).setUp()
|
super(SwarmTest, self).setUp()
|
||||||
self.client.leave_swarm(force=True)
|
force_leave_swarm(self.client)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(SwarmTest, self).tearDown()
|
super(SwarmTest, self).tearDown()
|
||||||
self.client.leave_swarm(force=True)
|
force_leave_swarm(self.client)
|
||||||
|
|
||||||
@requires_api_version('1.24')
|
@requires_api_version('1.24')
|
||||||
def test_init_swarm_simple(self):
|
def test_init_swarm_simple(self):
|
||||||
|
|
Loading…
Reference in New Issue