mirror of https://github.com/docker/docker-py.git
Merge pull request #1615 from docker/service-placement
Add support for placement preferences and platforms in TaskTemplate
This commit is contained in:
commit
fb16d14544
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue