mirror of https://github.com/docker/docker-py.git
Merge pull request #1390 from docker/1388-format-service-mode
Convert mode argument to valid structure in create_service
This commit is contained in:
commit
32392b7df0
|
@ -1,5 +1,6 @@
|
|||
import warnings
|
||||
from .. import auth, errors, utils
|
||||
from ..types import ServiceMode
|
||||
|
||||
|
||||
class ServiceApiMixin(object):
|
||||
|
@ -18,8 +19,8 @@ class ServiceApiMixin(object):
|
|||
name (string): User-defined name for the service. Optional.
|
||||
labels (dict): A map of labels to associate with the service.
|
||||
Optional.
|
||||
mode (string): Scheduling mode for the service (``replicated`` or
|
||||
``global``). Defaults to ``replicated``.
|
||||
mode (ServiceMode): Scheduling mode for the service (replicated
|
||||
or global). Defaults to replicated.
|
||||
update_config (UpdateConfig): Specification for the update strategy
|
||||
of the service. Default: ``None``
|
||||
networks (:py:class:`list`): List of network names or IDs to attach
|
||||
|
@ -49,6 +50,9 @@ class ServiceApiMixin(object):
|
|||
raise errors.DockerException(
|
||||
'Missing mandatory Image key in ContainerSpec'
|
||||
)
|
||||
if mode and not isinstance(mode, dict):
|
||||
mode = ServiceMode(mode)
|
||||
|
||||
registry, repo_name = auth.resolve_repository_name(image)
|
||||
auth_header = auth.get_config_header(self, registry)
|
||||
if auth_header:
|
||||
|
@ -191,8 +195,8 @@ class ServiceApiMixin(object):
|
|||
name (string): New name for the service. Optional.
|
||||
labels (dict): A map of labels to associate with the service.
|
||||
Optional.
|
||||
mode (string): Scheduling mode for the service (``replicated`` or
|
||||
``global``). Defaults to ``replicated``.
|
||||
mode (ServiceMode): Scheduling mode for the service (replicated
|
||||
or global). Defaults to replicated.
|
||||
update_config (UpdateConfig): Specification for the update strategy
|
||||
of the service. Default: ``None``.
|
||||
networks (:py:class:`list`): List of network names or IDs to attach
|
||||
|
@ -222,6 +226,8 @@ class ServiceApiMixin(object):
|
|||
if labels is not None:
|
||||
data['Labels'] = labels
|
||||
if mode is not None:
|
||||
if not isinstance(mode, dict):
|
||||
mode = ServiceMode(mode)
|
||||
data['Mode'] = mode
|
||||
if task_template is not None:
|
||||
image = task_template.get('ContainerSpec', {}).get('Image', None)
|
||||
|
|
|
@ -4,6 +4,6 @@ from .healthcheck import Healthcheck
|
|||
from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig
|
||||
from .services import (
|
||||
ContainerSpec, DriverConfig, EndpointSpec, Mount, Resources, RestartPolicy,
|
||||
TaskTemplate, UpdateConfig
|
||||
ServiceMode, TaskTemplate, UpdateConfig
|
||||
)
|
||||
from .swarm import SwarmSpec, SwarmExternalCA
|
||||
|
|
|
@ -348,3 +348,38 @@ def convert_service_ports(ports):
|
|||
|
||||
result.append(port_spec)
|
||||
return result
|
||||
|
||||
|
||||
class ServiceMode(dict):
|
||||
"""
|
||||
Indicate whether a service should be deployed as a replicated or global
|
||||
service, and associated parameters
|
||||
|
||||
Args:
|
||||
mode (string): Can be either ``replicated`` or ``global``
|
||||
replicas (int): Number of replicas. For replicated services only.
|
||||
"""
|
||||
def __init__(self, mode, replicas=None):
|
||||
if mode not in ('replicated', 'global'):
|
||||
raise errors.InvalidArgument(
|
||||
'mode must be either "replicated" or "global"'
|
||||
)
|
||||
if mode != 'replicated' and replicas is not None:
|
||||
raise errors.InvalidArgument(
|
||||
'replicas can only be used for replicated mode'
|
||||
)
|
||||
self[mode] = {}
|
||||
if replicas:
|
||||
self[mode]['Replicas'] = replicas
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
if 'global' in self:
|
||||
return 'global'
|
||||
return 'replicated'
|
||||
|
||||
@property
|
||||
def replicas(self):
|
||||
if self.mode != 'replicated':
|
||||
return None
|
||||
return self['replicated'].get('Replicas')
|
||||
|
|
|
@ -110,5 +110,6 @@ Configuration types
|
|||
.. autoclass:: Mount
|
||||
.. autoclass:: Resources
|
||||
.. autoclass:: RestartPolicy
|
||||
.. autoclass:: ServiceMode
|
||||
.. autoclass:: TaskTemplate
|
||||
.. autoclass:: UpdateConfig
|
||||
|
|
|
@ -251,3 +251,31 @@ class ServiceTest(BaseAPIIntegrationTest):
|
|||
con_spec = svc_info['Spec']['TaskTemplate']['ContainerSpec']
|
||||
assert 'Env' in con_spec
|
||||
assert con_spec['Env'] == ['DOCKER_PY_TEST=1']
|
||||
|
||||
def test_create_service_global_mode(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, mode='global'
|
||||
)
|
||||
svc_info = self.client.inspect_service(svc_id)
|
||||
assert 'Mode' in svc_info['Spec']
|
||||
assert 'Global' in svc_info['Spec']['Mode']
|
||||
|
||||
def test_create_service_replicated_mode(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,
|
||||
mode=docker.types.ServiceMode('replicated', 5)
|
||||
)
|
||||
svc_info = self.client.inspect_service(svc_id)
|
||||
assert 'Mode' in svc_info['Spec']
|
||||
assert 'Replicated' in svc_info['Spec']['Mode']
|
||||
assert svc_info['Spec']['Mode']['Replicated'] == {'Replicas': 5}
|
||||
|
|
|
@ -7,7 +7,8 @@ import pytest
|
|||
from docker.constants import DEFAULT_DOCKER_API_VERSION
|
||||
from docker.errors import InvalidArgument, InvalidVersion
|
||||
from docker.types import (
|
||||
EndpointConfig, HostConfig, IPAMConfig, IPAMPool, LogConfig, Mount, Ulimit,
|
||||
EndpointConfig, HostConfig, IPAMConfig, IPAMPool, LogConfig, Mount,
|
||||
ServiceMode, Ulimit,
|
||||
)
|
||||
|
||||
try:
|
||||
|
@ -260,7 +261,35 @@ class IPAMConfigTest(unittest.TestCase):
|
|||
})
|
||||
|
||||
|
||||
class TestMounts(unittest.TestCase):
|
||||
class ServiceModeTest(unittest.TestCase):
|
||||
def test_replicated_simple(self):
|
||||
mode = ServiceMode('replicated')
|
||||
assert mode == {'replicated': {}}
|
||||
assert mode.mode == 'replicated'
|
||||
assert mode.replicas is None
|
||||
|
||||
def test_global_simple(self):
|
||||
mode = ServiceMode('global')
|
||||
assert mode == {'global': {}}
|
||||
assert mode.mode == 'global'
|
||||
assert mode.replicas is None
|
||||
|
||||
def test_global_replicas_error(self):
|
||||
with pytest.raises(InvalidArgument):
|
||||
ServiceMode('global', 21)
|
||||
|
||||
def test_replicated_replicas(self):
|
||||
mode = ServiceMode('replicated', 21)
|
||||
assert mode == {'replicated': {'Replicas': 21}}
|
||||
assert mode.mode == 'replicated'
|
||||
assert mode.replicas == 21
|
||||
|
||||
def test_invalid_mode(self):
|
||||
with pytest.raises(InvalidArgument):
|
||||
ServiceMode('foobar')
|
||||
|
||||
|
||||
class MountTest(unittest.TestCase):
|
||||
def test_parse_mount_string_ro(self):
|
||||
mount = Mount.parse_mount_string("/foo/bar:/baz:ro")
|
||||
assert mount['Source'] == "/foo/bar"
|
||||
|
|
Loading…
Reference in New Issue