Kaydet (Commit) be2ae8df authored tarafından Joffrey F's avatar Joffrey F

WIP

Signed-off-by: 's avatarJoffrey F <joffrey@docker.com>
üst 65d900ec
import warnings
from .. import errors from .. import errors
from .. import utils from .. import utils
from ..auth import auth from ..auth import auth
...@@ -7,8 +9,16 @@ class ServiceApiMixin(object): ...@@ -7,8 +9,16 @@ class ServiceApiMixin(object):
@utils.minimum_version('1.24') @utils.minimum_version('1.24')
def create_service( def create_service(
self, task_template, name=None, labels=None, mode=None, self, task_template, name=None, labels=None, mode=None,
update_config=None, networks=None, endpoint_config=None update_config=None, networks=None, endpoint_config=None,
endpoint_spec=None
): ):
if endpoint_config is not None:
warnings.warn(
'endpoint_config has been renamed to endpoint_spec.',
DeprecationWarning
)
endpoint_spec = endpoint_config
url = self._url('/services/create') url = self._url('/services/create')
headers = {} headers = {}
image = task_template.get('ContainerSpec', {}).get('Image', None) image = task_template.get('ContainerSpec', {}).get('Image', None)
...@@ -26,8 +36,8 @@ class ServiceApiMixin(object): ...@@ -26,8 +36,8 @@ class ServiceApiMixin(object):
'TaskTemplate': task_template, 'TaskTemplate': task_template,
'Mode': mode, 'Mode': mode,
'UpdateConfig': update_config, 'UpdateConfig': update_config,
'Networks': networks, 'Networks': utils.convert_service_networks(networks),
'Endpoint': endpoint_config 'EndpointSpec': endpoint_spec
} }
return self._result( return self._result(
self._post_json(url, data=data, headers=headers), True self._post_json(url, data=data, headers=headers), True
......
# flake8: noqa # flake8: noqa
from .containers import LogConfig, Ulimit from .containers import LogConfig, Ulimit
from .services import ( from .services import (
ContainerSpec, DriverConfig, Mount, Resources, RestartPolicy, TaskTemplate, ContainerSpec, DriverConfig, EndpointSpec, Mount, Resources, RestartPolicy,
UpdateConfig TaskTemplate, UpdateConfig
) )
from .swarm import SwarmSpec, SwarmExternalCA from .swarm import SwarmSpec, SwarmExternalCA
...@@ -12,6 +12,8 @@ class TaskTemplate(dict): ...@@ -12,6 +12,8 @@ class TaskTemplate(dict):
if restart_policy: if restart_policy:
self['RestartPolicy'] = restart_policy self['RestartPolicy'] = restart_policy
if placement: if placement:
if isinstance(placement, list):
placement = {'Constraints': placement}
self['Placement'] = placement self['Placement'] = placement
if log_driver: if log_driver:
self['LogDriver'] = log_driver self['LogDriver'] = log_driver
...@@ -179,3 +181,37 @@ class DriverConfig(dict): ...@@ -179,3 +181,37 @@ class DriverConfig(dict):
self['Name'] = name self['Name'] = name
if options: if options:
self['Options'] = options self['Options'] = options
class EndpointSpec(dict):
def __init__(self, mode=None, ports=None):
if ports:
self['Ports'] = convert_service_ports(ports)
if mode:
self['Mode'] = mode
def convert_service_ports(ports):
if isinstance(ports, list):
return ports
if not isinstance(ports, dict):
raise TypeError(
'Invalid type for ports, expected dict or list'
)
result = []
for k, v in six.iteritems(ports):
port_spec = {
'Protocol': 'tcp',
'PublishedPort': k
}
if isinstance(v, tuple):
port_spec['TargetPort'] = v[0]
if len(v) == 2:
port_spec['Protocol'] = v[1]
else:
port_spec['TargetPort'] = v
result.append(port_spec)
return result
...@@ -6,6 +6,7 @@ from .utils import ( ...@@ -6,6 +6,7 @@ from .utils import (
create_host_config, create_container_config, parse_bytes, ping_registry, create_host_config, create_container_config, parse_bytes, ping_registry,
parse_env_file, version_lt, version_gte, decode_json_header, split_command, parse_env_file, version_lt, version_gte, decode_json_header, split_command,
create_ipam_config, create_ipam_pool, parse_devices, normalize_links, create_ipam_config, create_ipam_pool, parse_devices, normalize_links,
convert_service_networks,
) )
from ..types import LogConfig, Ulimit from ..types import LogConfig, Ulimit
......
...@@ -376,6 +376,20 @@ def convert_tmpfs_mounts(tmpfs): ...@@ -376,6 +376,20 @@ def convert_tmpfs_mounts(tmpfs):
return result return result
def convert_service_networks(networks):
if not networks:
return networks
if not isinstance(networks, list):
raise TypeError('networks parameter must be a list.')
result = []
for n in networks:
if isinstance(n, six.string_types):
n = {'Target': n}
result.append(n)
return result
def parse_repository_tag(repo_name): def parse_repository_tag(repo_name):
parts = repo_name.rsplit('@', 1) parts = repo_name.rsplit('@', 1)
if len(parts) == 2: if len(parts) == 2:
......
...@@ -82,7 +82,7 @@ Create a service. ...@@ -82,7 +82,7 @@ Create a service.
See the [UpdateConfig class](#UpdateConfig) for details. Default: `None`. See the [UpdateConfig class](#UpdateConfig) for details. Default: `None`.
* networks (list): List of network names or IDs to attach the service to. * networks (list): List of network names or IDs to attach the service to.
Default: `None`. Default: `None`.
* endpoint_config (dict): Properties that can be configured to access and load * endpoint_spec (dict): Properties that can be configured to access and load
balance a service. Default: `None`. balance a service. Default: `None`.
**Returns:** A dictionary containing an `ID` key for the newly created service. **Returns:** A dictionary containing an `ID` key for the newly created service.
...@@ -174,6 +174,20 @@ and for the `driver_config` in a volume `Mount`. ...@@ -174,6 +174,20 @@ and for the `driver_config` in a volume `Mount`.
* name (string): Name of the logging driver to use. * name (string): Name of the logging driver to use.
* options (dict): Driver-specific options. Default: `None`. * options (dict): Driver-specific options. Default: `None`.
#### EndpointSpec
An `EndpointSpec` object describes properties to access and load-balance a
service.
**Params:**
* mode (string): The mode of resolution to use for internal load balancing
between tasks (`'vip'` or `'dnsrr'`). Defaults to `'vip'` if not provided.
* ports (dict): Exposed ports that this service is accessible on from the
outside, in the form of `{ target_port: published_port }` or
`{ target_port: (published_port, protocol) }`. Ports can only be provided if
the `vip` resolution mode is used.
#### Mount #### Mount
A `Mount` object describes a mounted folder's configuration inside a A `Mount` object describes a mounted folder's configuration inside a
......
...@@ -169,3 +169,65 @@ class ServiceTest(BaseIntegrationTest): ...@@ -169,3 +169,65 @@ class ServiceTest(BaseIntegrationTest):
svc_info = self.client.inspect_service(svc_id) svc_info = self.client.inspect_service(svc_id)
assert 'RestartPolicy' in svc_info['Spec']['TaskTemplate'] assert 'RestartPolicy' in svc_info['Spec']['TaskTemplate']
assert policy == svc_info['Spec']['TaskTemplate']['RestartPolicy'] assert policy == svc_info['Spec']['TaskTemplate']['RestartPolicy']
def test_create_service_with_custom_networks(self):
net1 = self.client.create_network(
'dockerpytest_1', driver='overlay', ipam={'Driver': 'default'}
)
self.tmp_networks.append(net1['Id'])
net2 = self.client.create_network(
'dockerpytest_2', driver='overlay', ipam={'Driver': 'default'}
)
self.tmp_networks.append(net2['Id'])
container_spec = docker.types.ContainerSpec('busybox', ['true'])
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(
task_tmpl, name=name, networks=[
'dockerpytest_1', {'Target': 'dockerpytest_2'}
]
)
svc_info = self.client.inspect_service(svc_id)
assert 'Networks' in svc_info['Spec']
assert svc_info['Spec']['Networks'] == [
{'Target': net1['Id']}, {'Target': net2['Id']}
]
def test_create_service_with_placement(self):
node_id = self.client.nodes()[0]['ID']
container_spec = docker.types.ContainerSpec('busybox', ['true'])
task_tmpl = docker.types.TaskTemplate(
container_spec, placement=['node.id=={}'.format(node_id)]
)
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'] ==
{'Constraints': ['node.id=={}'.format(node_id)]})
def test_create_service_with_endpoint_spec(self):
container_spec = docker.types.ContainerSpec('busybox', ['true'])
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
endpoint_spec = docker.types.EndpointSpec(ports={
12357: (1990, 'udp'),
12562: (678,),
53243: 8080,
})
svc_id = self.client.create_service(
task_tmpl, name=name, endpoint_spec=endpoint_spec
)
svc_info = self.client.inspect_service(svc_id)
print(svc_info)
ports = svc_info['Spec']['EndpointSpec']['Ports']
assert {
'PublishedPort': 12562, 'TargetPort': 678, 'Protocol': 'tcp'
} in ports
assert {
'PublishedPort': 53243, 'TargetPort': 8080, 'Protocol': 'tcp'
} in ports
assert {
'PublishedPort': 12357, 'TargetPort': 1990, 'Protocol': 'udp'
} in ports
assert len(ports) == 3
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