Kaydet (Commit) 32392b7d authored tarafından Joffrey F's avatar Joffrey F Kaydeden (comit) GitHub

Merge pull request #1390 from docker/1388-format-service-mode

Convert mode argument to valid structure in create_service
import warnings import warnings
from .. import auth, errors, utils from .. import auth, errors, utils
from ..types import ServiceMode
class ServiceApiMixin(object): class ServiceApiMixin(object):
...@@ -18,8 +19,8 @@ class ServiceApiMixin(object): ...@@ -18,8 +19,8 @@ class ServiceApiMixin(object):
name (string): User-defined name for the service. Optional. name (string): User-defined name for the service. Optional.
labels (dict): A map of labels to associate with the service. labels (dict): A map of labels to associate with the service.
Optional. Optional.
mode (string): Scheduling mode for the service (``replicated`` or mode (ServiceMode): Scheduling mode for the service (replicated
``global``). Defaults to ``replicated``. or global). Defaults to replicated.
update_config (UpdateConfig): Specification for the update strategy update_config (UpdateConfig): Specification for the update strategy
of the service. Default: ``None`` of the service. Default: ``None``
networks (:py:class:`list`): List of network names or IDs to attach networks (:py:class:`list`): List of network names or IDs to attach
...@@ -49,6 +50,9 @@ class ServiceApiMixin(object): ...@@ -49,6 +50,9 @@ class ServiceApiMixin(object):
raise errors.DockerException( raise errors.DockerException(
'Missing mandatory Image key in ContainerSpec' 'Missing mandatory Image key in ContainerSpec'
) )
if mode and not isinstance(mode, dict):
mode = ServiceMode(mode)
registry, repo_name = auth.resolve_repository_name(image) registry, repo_name = auth.resolve_repository_name(image)
auth_header = auth.get_config_header(self, registry) auth_header = auth.get_config_header(self, registry)
if auth_header: if auth_header:
...@@ -191,8 +195,8 @@ class ServiceApiMixin(object): ...@@ -191,8 +195,8 @@ class ServiceApiMixin(object):
name (string): New name for the service. Optional. name (string): New name for the service. Optional.
labels (dict): A map of labels to associate with the service. labels (dict): A map of labels to associate with the service.
Optional. Optional.
mode (string): Scheduling mode for the service (``replicated`` or mode (ServiceMode): Scheduling mode for the service (replicated
``global``). Defaults to ``replicated``. or global). Defaults to replicated.
update_config (UpdateConfig): Specification for the update strategy update_config (UpdateConfig): Specification for the update strategy
of the service. Default: ``None``. of the service. Default: ``None``.
networks (:py:class:`list`): List of network names or IDs to attach networks (:py:class:`list`): List of network names or IDs to attach
...@@ -222,6 +226,8 @@ class ServiceApiMixin(object): ...@@ -222,6 +226,8 @@ class ServiceApiMixin(object):
if labels is not None: if labels is not None:
data['Labels'] = labels data['Labels'] = labels
if mode is not None: if mode is not None:
if not isinstance(mode, dict):
mode = ServiceMode(mode)
data['Mode'] = mode data['Mode'] = mode
if task_template is not None: if task_template is not None:
image = task_template.get('ContainerSpec', {}).get('Image', None) image = task_template.get('ContainerSpec', {}).get('Image', None)
......
...@@ -4,6 +4,6 @@ from .healthcheck import Healthcheck ...@@ -4,6 +4,6 @@ from .healthcheck import Healthcheck
from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig
from .services import ( from .services import (
ContainerSpec, DriverConfig, EndpointSpec, Mount, Resources, RestartPolicy, ContainerSpec, DriverConfig, EndpointSpec, Mount, Resources, RestartPolicy,
TaskTemplate, UpdateConfig ServiceMode, TaskTemplate, UpdateConfig
) )
from .swarm import SwarmSpec, SwarmExternalCA from .swarm import SwarmSpec, SwarmExternalCA
...@@ -348,3 +348,38 @@ def convert_service_ports(ports): ...@@ -348,3 +348,38 @@ def convert_service_ports(ports):
result.append(port_spec) result.append(port_spec)
return result 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 ...@@ -110,5 +110,6 @@ Configuration types
.. autoclass:: Mount .. autoclass:: Mount
.. autoclass:: Resources .. autoclass:: Resources
.. autoclass:: RestartPolicy .. autoclass:: RestartPolicy
.. autoclass:: ServiceMode
.. autoclass:: TaskTemplate .. autoclass:: TaskTemplate
.. autoclass:: UpdateConfig .. autoclass:: UpdateConfig
...@@ -251,3 +251,31 @@ class ServiceTest(BaseAPIIntegrationTest): ...@@ -251,3 +251,31 @@ class ServiceTest(BaseAPIIntegrationTest):
con_spec = svc_info['Spec']['TaskTemplate']['ContainerSpec'] con_spec = svc_info['Spec']['TaskTemplate']['ContainerSpec']
assert 'Env' in con_spec assert 'Env' in con_spec
assert con_spec['Env'] == ['DOCKER_PY_TEST=1'] 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 ...@@ -7,7 +7,8 @@ import pytest
from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.constants import DEFAULT_DOCKER_API_VERSION
from docker.errors import InvalidArgument, InvalidVersion from docker.errors import InvalidArgument, InvalidVersion
from docker.types import ( from docker.types import (
EndpointConfig, HostConfig, IPAMConfig, IPAMPool, LogConfig, Mount, Ulimit, EndpointConfig, HostConfig, IPAMConfig, IPAMPool, LogConfig, Mount,
ServiceMode, Ulimit,
) )
try: try:
...@@ -260,7 +261,35 @@ class IPAMConfigTest(unittest.TestCase): ...@@ -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): def test_parse_mount_string_ro(self):
mount = Mount.parse_mount_string("/foo/bar:/baz:ro") mount = Mount.parse_mount_string("/foo/bar:/baz:ro")
assert mount['Source'] == "/foo/bar" assert mount['Source'] == "/foo/bar"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment