Kaydet (Commit) b2d08e64 authored tarafından Viktor Adam's avatar Viktor Adam

Service model update changes

Signed-off-by: 's avatarViktor Adam <rycus86@gmail.com>
üst 6e5eb2eb
...@@ -363,8 +363,15 @@ class ServiceApiMixin(object): ...@@ -363,8 +363,15 @@ class ServiceApiMixin(object):
data = {} data = {}
headers = {} headers = {}
data['Name'] = name if name is not None else current.get('Name') if name is not None:
data['Labels'] = labels if labels is not None else current.get('Labels') data['Name'] = name
else:
data['Name'] = current.get('Name')
if labels is not None:
data['Labels'] = labels
else:
data['Labels'] = current.get('Labels')
if mode is not None: if mode is not None:
if not isinstance(mode, dict): if not isinstance(mode, dict):
...@@ -373,23 +380,17 @@ class ServiceApiMixin(object): ...@@ -373,23 +380,17 @@ class ServiceApiMixin(object):
else: else:
data['Mode'] = current.get('Mode') data['Mode'] = current.get('Mode')
merged_task_template = current.get('TaskTemplate', {}) data['TaskTemplate'] = self._merge_task_template(
if task_template is not None: current.get('TaskTemplate', {}), task_template
for task_template_key, task_template_value in task_template.items(): )
if task_template_key == 'ContainerSpec':
if 'ContainerSpec' not in merged_task_template: container_spec = data['TaskTemplate'].get('ContainerSpec', {})
merged_task_template['ContainerSpec'] = {} image = container_spec.get('Image', None)
for container_spec_key, container_spec_value in task_template['ContainerSpec'].items(): if image is not None:
merged_task_template['ContainerSpec'][container_spec_key] = container_spec_value registry, repo_name = auth.resolve_repository_name(image)
else: auth_header = auth.get_config_header(self, registry)
merged_task_template[task_template_key] = task_template_value if auth_header:
image = merged_task_template.get('ContainerSpec', {}).get('Image', None) headers['X-Registry-Auth'] = auth_header
if image is not None:
registry, repo_name = auth.resolve_repository_name(image)
auth_header = auth.get_config_header(self, registry)
if auth_header:
headers['X-Registry-Auth'] = auth_header
data['TaskTemplate'] = merged_task_template
if update_config is not None: if update_config is not None:
data['UpdateConfig'] = update_config data['UpdateConfig'] = update_config
...@@ -397,11 +398,15 @@ class ServiceApiMixin(object): ...@@ -397,11 +398,15 @@ class ServiceApiMixin(object):
data['UpdateConfig'] = current.get('UpdateConfig') data['UpdateConfig'] = current.get('UpdateConfig')
if networks is not None: if networks is not None:
data['TaskTemplate']['Networks'] = utils.convert_service_networks(networks) converted_networks = utils.convert_service_networks(networks)
else: data['TaskTemplate']['Networks'] = converted_networks
existing_networks = current.get('TaskTemplate', {}).get('Networks') or current.get('Networks') elif data['TaskTemplate'].get('Networks') is None:
if existing_networks is not None: current_task_template = current.get('TaskTemplate', {})
data['TaskTemplate']['Networks'] = existing_networks current_networks = current_task_template.get('Networks')
if current_networks is None:
current_networks = current.get('Networks')
if current_networks is not None:
data['TaskTemplate']['Networks'] = current_networks
if endpoint_spec is not None: if endpoint_spec is not None:
data['EndpointSpec'] = endpoint_spec data['EndpointSpec'] = endpoint_spec
...@@ -413,3 +418,17 @@ class ServiceApiMixin(object): ...@@ -413,3 +418,17 @@ class ServiceApiMixin(object):
) )
self._raise_for_status(resp) self._raise_for_status(resp)
return True return True
@staticmethod
def _merge_task_template(current, override):
merged = current.copy()
if override is not None:
for ts_key, ts_value in override.items():
if ts_key == 'ContainerSpec':
if 'ContainerSpec' not in merged:
merged['ContainerSpec'] = {}
for cs_key, cs_value in override['ContainerSpec'].items():
merged['ContainerSpec'][cs_key] = cs_value
else:
merged[ts_key] = ts_value
return merged
...@@ -251,6 +251,7 @@ CONTAINER_SPEC_KWARGS = [ ...@@ -251,6 +251,7 @@ CONTAINER_SPEC_KWARGS = [
# kwargs to copy straight over to TaskTemplate # kwargs to copy straight over to TaskTemplate
TASK_TEMPLATE_KWARGS = [ TASK_TEMPLATE_KWARGS = [
'networks',
'resources', 'resources',
'restart_policy', 'restart_policy',
] ]
...@@ -261,7 +262,6 @@ CREATE_SERVICE_KWARGS = [ ...@@ -261,7 +262,6 @@ CREATE_SERVICE_KWARGS = [
'labels', 'labels',
'mode', 'mode',
'update_config', 'update_config',
'networks',
'endpoint_spec', 'endpoint_spec',
] ]
...@@ -295,6 +295,15 @@ def _get_create_service_kwargs(func_name, kwargs): ...@@ -295,6 +295,15 @@ def _get_create_service_kwargs(func_name, kwargs):
'Options': kwargs.pop('log_driver_options', {}) 'Options': kwargs.pop('log_driver_options', {})
} }
if func_name == 'update':
if 'force_update' in kwargs:
task_template_kwargs['force_update'] = kwargs.pop('force_update')
# use the current spec by default if updating the service
# through the model
use_current_spec = kwargs.pop('use_current_spec', True)
create_kwargs['use_current_spec'] = use_current_spec
# All kwargs should have been consumed by this point, so raise # All kwargs should have been consumed by this point, so raise
# error if any are left # error if any are left
if kwargs: if kwargs:
......
...@@ -4,7 +4,7 @@ from .. import errors ...@@ -4,7 +4,7 @@ from .. import errors
from ..constants import IS_WINDOWS_PLATFORM from ..constants import IS_WINDOWS_PLATFORM
from ..utils import ( from ..utils import (
check_resource, format_environment, format_extra_hosts, parse_bytes, check_resource, format_environment, format_extra_hosts, parse_bytes,
split_command, split_command, convert_service_networks,
) )
...@@ -26,11 +26,14 @@ class TaskTemplate(dict): ...@@ -26,11 +26,14 @@ class TaskTemplate(dict):
placement (Placement): Placement instructions for the scheduler. placement (Placement): Placement instructions for the scheduler.
If a list is passed instead, it is assumed to be a list of If a list is passed instead, it is assumed to be a list of
constraints as part of a :py:class:`Placement` object. constraints as part of a :py:class:`Placement` object.
networks (:py:class:`list`): List of network names or IDs to attach
the containers to.
force_update (int): A counter that triggers an update even if no force_update (int): A counter that triggers an update even if no
relevant parameters have been changed. 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, force_update=None): placement=None, log_driver=None, networks=None,
force_update=None):
self['ContainerSpec'] = container_spec self['ContainerSpec'] = container_spec
if resources: if resources:
self['Resources'] = resources self['Resources'] = resources
...@@ -42,6 +45,8 @@ class TaskTemplate(dict): ...@@ -42,6 +45,8 @@ class TaskTemplate(dict):
self['Placement'] = placement self['Placement'] = placement
if log_driver: if log_driver:
self['LogDriver'] = log_driver self['LogDriver'] = log_driver
if networks:
self['Networks'] = convert_service_networks(networks)
if force_update is not None: if force_update is not None:
if not isinstance(force_update, int): if not isinstance(force_update, int):
......
import unittest import unittest
import docker import docker
import pytest
from .. import helpers from .. import helpers
from .base import TEST_API_VERSION from .base import TEST_API_VERSION
...@@ -36,6 +35,25 @@ class ServiceTest(unittest.TestCase): ...@@ -36,6 +35,25 @@ class ServiceTest(unittest.TestCase):
assert "alpine" in container_spec['Image'] assert "alpine" in container_spec['Image']
assert container_spec['Labels'] == {'container': 'label'} assert container_spec['Labels'] == {'container': 'label'}
def test_create_with_network(self):
client = docker.from_env(version=TEST_API_VERSION)
name = helpers.random_name()
network = client.networks.create(
helpers.random_name(), driver='overlay'
)
service = client.services.create(
# create arguments
name=name,
# ContainerSpec arguments
image="alpine",
command="sleep 300",
networks=[network.id]
)
assert 'Networks' in service.attrs['Spec']['TaskTemplate']
networks = service.attrs['Spec']['TaskTemplate']['Networks']
assert len(networks) == 1
assert networks[0]['Target'] == network.id
def test_get(self): def test_get(self):
client = docker.from_env(version=TEST_API_VERSION) client = docker.from_env(version=TEST_API_VERSION)
name = helpers.random_name() name = helpers.random_name()
...@@ -82,7 +100,6 @@ class ServiceTest(unittest.TestCase): ...@@ -82,7 +100,6 @@ class ServiceTest(unittest.TestCase):
assert len(tasks) == 1 assert len(tasks) == 1
assert tasks[0]['ServiceID'] == service2.id assert tasks[0]['ServiceID'] == service2.id
@pytest.mark.skip(reason="Makes Swarm unstable?")
def test_update(self): def test_update(self):
client = docker.from_env(version=TEST_API_VERSION) client = docker.from_env(version=TEST_API_VERSION)
service = client.services.create( service = client.services.create(
...@@ -101,3 +118,105 @@ class ServiceTest(unittest.TestCase): ...@@ -101,3 +118,105 @@ class ServiceTest(unittest.TestCase):
service.reload() service.reload()
container_spec = service.attrs['Spec']['TaskTemplate']['ContainerSpec'] container_spec = service.attrs['Spec']['TaskTemplate']['ContainerSpec']
assert container_spec['Command'] == ["sleep", "600"] assert container_spec['Command'] == ["sleep", "600"]
def test_update_retains_service_labels(self):
client = docker.from_env(version=TEST_API_VERSION)
service = client.services.create(
# create arguments
name=helpers.random_name(),
labels={'service.label': 'SampleLabel'},
# ContainerSpec arguments
image="alpine",
command="sleep 300"
)
service.update(
# create argument
name=service.name,
# ContainerSpec argument
command="sleep 600"
)
service.reload()
labels = service.attrs['Spec']['Labels']
assert labels == {'service.label': 'SampleLabel'}
def test_update_retains_container_labels(self):
client = docker.from_env(version=TEST_API_VERSION)
service = client.services.create(
# create arguments
name=helpers.random_name(),
# ContainerSpec arguments
image="alpine",
command="sleep 300",
container_labels={'container.label': 'SampleLabel'}
)
service.update(
# create argument
name=service.name,
# ContainerSpec argument
command="sleep 600"
)
service.reload()
container_spec = service.attrs['Spec']['TaskTemplate']['ContainerSpec']
assert container_spec['Labels'] == {'container.label': 'SampleLabel'}
def test_update_remove_service_labels(self):
client = docker.from_env(version=TEST_API_VERSION)
service = client.services.create(
# create arguments
name=helpers.random_name(),
labels={'service.label': 'SampleLabel'},
# ContainerSpec arguments
image="alpine",
command="sleep 300"
)
service.update(
# create argument
name=service.name,
labels={},
# ContainerSpec argument
command="sleep 600"
)
service.reload()
assert not service.attrs['Spec'].get('Labels')
def test_scale_service(self):
client = docker.from_env(version=TEST_API_VERSION)
service = client.services.create(
# create arguments
name=helpers.random_name(),
# ContainerSpec arguments
image="alpine",
command="sleep 300"
)
assert len(service.tasks()) == 1
service.update(
# create argument
name=service.name,
mode=docker.types.ServiceMode('replicated', replicas=2),
# ContainerSpec argument
command="sleep 600"
)
service.reload()
assert len(service.tasks()) >= 2
@helpers.requires_api_version('1.25')
def test_restart_service(self):
client = docker.from_env(version=TEST_API_VERSION)
service = client.services.create(
# create arguments
name=helpers.random_name(),
# ContainerSpec arguments
image="alpine",
command="sleep 300"
)
initial_version = service.version
service.update(
# create argument
name=service.name,
# task template argument
force_update=10,
# ContainerSpec argument
command="sleep 600"
)
service.reload()
assert service.version > initial_version
...@@ -35,18 +35,18 @@ class CreateServiceKwargsTest(unittest.TestCase): ...@@ -35,18 +35,18 @@ class CreateServiceKwargsTest(unittest.TestCase):
'labels': {'key': 'value'}, 'labels': {'key': 'value'},
'mode': 'global', 'mode': 'global',
'update_config': {'update': 'config'}, 'update_config': {'update': 'config'},
'networks': ['somenet'],
'endpoint_spec': {'blah': 'blah'}, 'endpoint_spec': {'blah': 'blah'},
} }
assert set(task_template.keys()) == set([ assert set(task_template.keys()) == set([
'ContainerSpec', 'Resources', 'RestartPolicy', 'Placement', 'ContainerSpec', 'Resources', 'RestartPolicy', 'Placement',
'LogDriver' 'LogDriver', 'Networks'
]) ])
assert task_template['Placement'] == {'Constraints': ['foo=bar']} assert task_template['Placement'] == {'Constraints': ['foo=bar']}
assert task_template['LogDriver'] == { assert task_template['LogDriver'] == {
'Name': 'logdriver', 'Name': 'logdriver',
'Options': {'foo': 'bar'} 'Options': {'foo': 'bar'}
} }
assert task_template['Networks'] == [{'Target': 'somenet'}]
assert set(task_template['ContainerSpec'].keys()) == set([ assert set(task_template['ContainerSpec'].keys()) == set([
'Image', 'Command', 'Args', 'Hostname', 'Env', 'Dir', 'User', 'Image', 'Command', 'Args', 'Hostname', 'Env', 'Dir', 'User',
'Labels', 'Mounts', 'StopGracePeriod' 'Labels', 'Mounts', 'StopGracePeriod'
......
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