summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoffrey F <joffrey@docker.com>2017-05-16 19:05:32 -0700
committerJoffrey F <joffrey@docker.com>2017-05-17 13:48:30 -0700
commit9cc021dfa684ab1a614d473e78f9c4c0fc960585 (patch)
tree67524aef4988fec576e1cabf7dc513d00255f5d9
parent7880c5af1de66ed4555a30eeb19dc0093536f2f0 (diff)
downloaddocker-py-service-placement.tar.gz
Add support for placement preferences and platforms in TaskTemplateservice-placement
Signed-off-by: Joffrey F <joffrey@docker.com>
-rw-r--r--docker/api/service.py69
-rw-r--r--docker/types/__init__.py4
-rw-r--r--docker/types/services.py31
-rw-r--r--tests/integration/api_service_test.py43
4 files changed, 115 insertions, 32 deletions
diff --git a/docker/api/service.py b/docker/api/service.py
index 4972c16..aea93cb 100644
--- a/docker/api/service.py
+++ b/docker/api/service.py
@@ -3,6 +3,43 @@ from .. import auth, errors, utils
from ..types import ServiceMode
+def _check_api_features(version, task_template, update_config):
+ if update_config is not None:
+ if utils.version_lt(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'
+ )
+
+ if task_template is not None:
+ if 'ForceUpdate' in task_template and utils.version_lt(
+ version, '1.25'):
+ raise errors.InvalidVersion(
+ 'force_update is not supported in API version < 1.25'
+ )
+
+ if task_template.get('Placement'):
+ if utils.version_lt(version, '1.30'):
+ if task_template['Placement'].get('Platforms'):
+ raise errors.InvalidVersion(
+ 'Placement.platforms is not supported in'
+ ' API version < 1.30'
+ )
+
+ if utils.version_lt(version, '1.27'):
+ if task_template['Placement'].get('Preferences'):
+ raise errors.InvalidVersion(
+ 'Placement.preferences is not supported in'
+ ' API version < 1.27'
+ )
+
+
class ServiceApiMixin(object):
@utils.minimum_version('1.24')
def create_service(
@@ -43,6 +80,8 @@ class ServiceApiMixin(object):
)
endpoint_spec = endpoint_config
+ _check_api_features(self._version, task_template, update_config)
+
url = self._url('/services/create')
headers = {}
image = task_template.get('ContainerSpec', {}).get('Image', None)
@@ -67,17 +106,6 @@ class ServiceApiMixin(object):
}
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(
@@ -282,6 +310,8 @@ class ServiceApiMixin(object):
)
endpoint_spec = endpoint_config
+ _check_api_features(self._version, task_template, update_config)
+
url = self._url('/services/{0}/update', service)
data = {}
headers = {}
@@ -294,12 +324,6 @@ class ServiceApiMixin(object):
mode = ServiceMode(mode)
data['Mode'] = mode
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)
if image is not None:
registry, repo_name = auth.resolve_repository_name(image)
@@ -308,17 +332,6 @@ class ServiceApiMixin(object):
headers['X-Registry-Auth'] = auth_header
data['TaskTemplate'] = task_template
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
if networks is not None:
diff --git a/docker/types/__init__.py b/docker/types/__init__.py
index 0e88776..edc919d 100644
--- a/docker/types/__init__.py
+++ b/docker/types/__init__.py
@@ -3,7 +3,7 @@ from .containers import ContainerConfig, HostConfig, LogConfig, Ulimit
from .healthcheck import Healthcheck
from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig
from .services import (
- ContainerSpec, DriverConfig, EndpointSpec, Mount, Resources, RestartPolicy,
- SecretReference, ServiceMode, TaskTemplate, UpdateConfig
+ ContainerSpec, DriverConfig, EndpointSpec, Mount, Placement, Resources,
+ RestartPolicy, SecretReference, ServiceMode, TaskTemplate, UpdateConfig
)
from .swarm import SwarmSpec, SwarmExternalCA
diff --git a/docker/types/services.py b/docker/types/services.py
index 012f7b0..7456a42 100644
--- a/docker/types/services.py
+++ b/docker/types/services.py
@@ -20,7 +20,9 @@ class TaskTemplate(dict):
individual container created as part of the service.
restart_policy (RestartPolicy): Specification for the restart policy
which applies to containers created as part of this service.
- placement (:py:class:`list`): A list of constraints.
+ placement (Placement): Placement instructions for the scheduler.
+ If a list is passed instead, it is assumed to be a list of
+ constraints as part of a :py:class:`Placement` object.
force_update (int): A counter that triggers an update even if no
relevant parameters have been changed.
"""
@@ -33,7 +35,7 @@ class TaskTemplate(dict):
self['RestartPolicy'] = restart_policy
if placement:
if isinstance(placement, list):
- placement = {'Constraints': placement}
+ placement = Placement(constraints=placement)
self['Placement'] = placement
if log_driver:
self['LogDriver'] = log_driver
@@ -452,3 +454,28 @@ class SecretReference(dict):
'GID': gid or '0',
'Mode': mode
}
+
+
+class Placement(dict):
+ """
+ Placement constraints to be used as part of a :py:class:`TaskTemplate`
+
+ Args:
+ constraints (list): A list of constraints
+ preferences (list): Preferences provide a way to make the
+ scheduler aware of factors such as topology. They are provided
+ in order from highest to lowest precedence.
+ platforms (list): A list of platforms expressed as ``(arch, os)``
+ tuples
+ """
+ def __init__(self, constraints=None, preferences=None, platforms=None):
+ if constraints is not None:
+ self['Constraints'] = constraints
+ if preferences is not None:
+ self['Preferences'] = preferences
+ if platforms:
+ self['Platforms'] = []
+ for plat in platforms:
+ self['Platforms'].append({
+ 'Architecture': plat[0], 'OS': plat[1]
+ })
diff --git a/tests/integration/api_service_test.py b/tests/integration/api_service_test.py
index 914e516..8ac852d 100644
--- a/tests/integration/api_service_test.py
+++ b/tests/integration/api_service_test.py
@@ -270,6 +270,49 @@ class ServiceTest(BaseAPIIntegrationTest):
assert (svc_info['Spec']['TaskTemplate']['Placement'] ==
{'Constraints': ['node.id=={}'.format(node_id)]})
+ def test_create_service_with_placement_object(self):
+ node_id = self.client.nodes()[0]['ID']
+ container_spec = docker.types.ContainerSpec(BUSYBOX, ['true'])
+ placemt = docker.types.Placement(
+ constraints=['node.id=={}'.format(node_id)]
+ )
+ task_tmpl = docker.types.TaskTemplate(
+ container_spec, placement=placemt
+ )
+ name = self.get_service_name()
+ svc_id = self.client.create_service(task_tmpl, name=name)
+ svc_info = self.client.inspect_service(svc_id)
+ assert 'Placement' in svc_info['Spec']['TaskTemplate']
+ assert svc_info['Spec']['TaskTemplate']['Placement'] == placemt
+
+ @requires_api_version('1.30')
+ def test_create_service_with_placement_platform(self):
+ container_spec = docker.types.ContainerSpec(BUSYBOX, ['true'])
+ placemt = docker.types.Placement(platforms=[('x86_64', 'linux')])
+ task_tmpl = docker.types.TaskTemplate(
+ container_spec, placement=placemt
+ )
+ name = self.get_service_name()
+ svc_id = self.client.create_service(task_tmpl, name=name)
+ svc_info = self.client.inspect_service(svc_id)
+ assert 'Placement' in svc_info['Spec']['TaskTemplate']
+ assert svc_info['Spec']['TaskTemplate']['Placement'] == placemt
+
+ @requires_api_version('1.27')
+ def test_create_service_with_placement_preferences(self):
+ container_spec = docker.types.ContainerSpec(BUSYBOX, ['true'])
+ placemt = docker.types.Placement(preferences=[
+ {'Spread': {'SpreadDescriptor': 'com.dockerpy.test'}}
+ ])
+ task_tmpl = docker.types.TaskTemplate(
+ container_spec, placement=placemt
+ )
+ name = self.get_service_name()
+ svc_id = self.client.create_service(task_tmpl, name=name)
+ svc_info = self.client.inspect_service(svc_id)
+ assert 'Placement' in svc_info['Spec']['TaskTemplate']
+ assert svc_info['Spec']['TaskTemplate']['Placement'] == placemt
+
def test_create_service_with_endpoint_spec(self):
container_spec = docker.types.ContainerSpec(BUSYBOX, ['true'])
task_tmpl = docker.types.TaskTemplate(container_spec)