Add support for publish mode for endpointspec ports

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2018-01-31 12:29:26 -08:00
parent 0750337f6a
commit 5347c168d0
4 changed files with 118 additions and 7 deletions

View File

@ -3,7 +3,7 @@ from .. import auth, errors, utils
from ..types import ServiceMode
def _check_api_features(version, task_template, update_config):
def _check_api_features(version, task_template, update_config, endpoint_spec):
def raise_version_error(param, min_version):
raise errors.InvalidVersion(
@ -23,6 +23,11 @@ def _check_api_features(version, task_template, update_config):
if 'Order' in update_config:
raise_version_error('UpdateConfig.order', '1.29')
if endpoint_spec is not None:
if utils.version_lt(version, '1.32') and 'Ports' in endpoint_spec:
if any(p.get('PublishMode') for p in endpoint_spec['Ports']):
raise_version_error('EndpointSpec.Ports[].mode', '1.32')
if task_template is not None:
if 'ForceUpdate' in task_template and utils.version_lt(
version, '1.25'):
@ -125,7 +130,9 @@ class ServiceApiMixin(object):
)
endpoint_spec = endpoint_config
_check_api_features(self._version, task_template, update_config)
_check_api_features(
self._version, task_template, update_config, endpoint_spec
)
url = self._url('/services/create')
headers = {}
@ -370,7 +377,9 @@ class ServiceApiMixin(object):
)
endpoint_spec = endpoint_config
_check_api_features(self._version, task_template, update_config)
_check_api_features(
self._version, task_template, update_config, endpoint_spec
)
if fetch_current_spec:
inspect_defaults = True

View File

@ -450,8 +450,9 @@ class EndpointSpec(dict):
``'vip'`` if not provided.
ports (dict): Exposed ports that this service is accessible on from the
outside, in the form of ``{ published_port: target_port }`` or
``{ published_port: (target_port, protocol) }``. Ports can only be
provided if the ``vip`` resolution mode is used.
``{ published_port: <port_config_tuple> }``. Port config tuple format
is ``(target_port [, protocol [, publish_mode]])``.
Ports can only be provided if the ``vip`` resolution mode is used.
"""
def __init__(self, mode=None, ports=None):
if ports:
@ -477,8 +478,15 @@ def convert_service_ports(ports):
if isinstance(v, tuple):
port_spec['TargetPort'] = v[0]
if len(v) == 2:
if len(v) >= 2 and v[1] is not None:
port_spec['Protocol'] = v[1]
if len(v) == 3:
port_spec['PublishMode'] = v[2]
if len(v) > 3:
raise ValueError(
'Service port configuration can have at most 3 elements: '
'(target_port, protocol, mode)'
)
else:
port_spec['TargetPort'] = v

View File

@ -353,7 +353,6 @@ class ServiceTest(BaseAPIIntegrationTest):
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']
for port in ports:
if port['PublishedPort'] == 12562:
@ -370,6 +369,26 @@ class ServiceTest(BaseAPIIntegrationTest):
assert len(ports) == 3
@requires_api_version('1.32')
def test_create_service_with_endpoint_spec_host_publish_mode(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, None, 'host'),
})
svc_id = self.client.create_service(
task_tmpl, name=name, endpoint_spec=endpoint_spec
)
svc_info = self.client.inspect_service(svc_id)
ports = svc_info['Spec']['EndpointSpec']['Ports']
assert len(ports) == 1
port = ports[0]
assert port['PublishedPort'] == 12357
assert port['TargetPort'] == 1990
assert port['Protocol'] == 'tcp'
assert port['PublishMode'] == 'host'
def test_create_service_with_env(self):
container_spec = docker.types.ContainerSpec(
BUSYBOX, ['true'], env={'DOCKER_PY_TEST': 1}

View File

@ -11,6 +11,7 @@ from docker.types import (
ContainerConfig, ContainerSpec, EndpointConfig, HostConfig, IPAMConfig,
IPAMPool, LogConfig, Mount, ServiceMode, Ulimit,
)
from docker.types.services import convert_service_ports
try:
from unittest import mock
@ -423,3 +424,77 @@ class MountTest(unittest.TestCase):
assert mount['Source'] == "C:/foo/bar"
assert mount['Target'] == "/baz"
assert mount['Type'] == 'bind'
class ServicePortsTest(unittest.TestCase):
def test_convert_service_ports_simple(self):
ports = {8080: 80}
assert convert_service_ports(ports) == [{
'Protocol': 'tcp',
'PublishedPort': 8080,
'TargetPort': 80,
}]
def test_convert_service_ports_with_protocol(self):
ports = {8080: (80, 'udp')}
assert convert_service_ports(ports) == [{
'Protocol': 'udp',
'PublishedPort': 8080,
'TargetPort': 80,
}]
def test_convert_service_ports_with_protocol_and_mode(self):
ports = {8080: (80, 'udp', 'ingress')}
assert convert_service_ports(ports) == [{
'Protocol': 'udp',
'PublishedPort': 8080,
'TargetPort': 80,
'PublishMode': 'ingress',
}]
def test_convert_service_ports_invalid(self):
ports = {8080: ('way', 'too', 'many', 'items', 'here')}
with pytest.raises(ValueError):
convert_service_ports(ports)
def test_convert_service_ports_no_protocol_and_mode(self):
ports = {8080: (80, None, 'host')}
assert convert_service_ports(ports) == [{
'Protocol': 'tcp',
'PublishedPort': 8080,
'TargetPort': 80,
'PublishMode': 'host',
}]
def test_convert_service_ports_multiple(self):
ports = {
8080: (80, None, 'host'),
9999: 99,
2375: (2375,)
}
converted_ports = convert_service_ports(ports)
assert {
'Protocol': 'tcp',
'PublishedPort': 8080,
'TargetPort': 80,
'PublishMode': 'host',
} in converted_ports
assert {
'Protocol': 'tcp',
'PublishedPort': 9999,
'TargetPort': 99,
} in converted_ports
assert {
'Protocol': 'tcp',
'PublishedPort': 2375,
'TargetPort': 2375,
} in converted_ports
assert len(converted_ports) == 3