Support opening container's port
Introduce an option to expose container's port(s). In particular, it introduces a parameter 'exposed_ports' on creating container. The value is in the form: ``{"<port>/<protocol>": {}}``. For example, it can be ``{"80": {}}`` or ``{"80/tcp": {}}``. The format is designed to align with docker's option 'ExposedPorts'. If this parameter is provided, Zun will create a security group for the container. The security group will be populated a set of rules to open those exposed ports. This feature is a managed security group feature (Zun manages the security group of the container). Obviously, it cannot be used with the 'security_groups' parameter, in which users are responsible to manage the security group. Change-Id: Id713ce602dca8e74089d4a5eea8df41ea8784db4 Partial-Implements: blueprint support-port-bindings
This commit is contained in:
parent
1efb2b9645
commit
44dc51f8df
@ -60,6 +60,7 @@ Request
|
|||||||
- mounts: mounts
|
- mounts: mounts
|
||||||
- privileged: privileged-request
|
- privileged: privileged-request
|
||||||
- healthcheck: healthcheck-request
|
- healthcheck: healthcheck-request
|
||||||
|
- exposed_ports: exposed_ports
|
||||||
|
|
||||||
Request Example
|
Request Example
|
||||||
----------------
|
----------------
|
||||||
|
@ -482,6 +482,18 @@ exec_url:
|
|||||||
The URL to start an exec instance.
|
The URL to start an exec instance.
|
||||||
in: body
|
in: body
|
||||||
type: dict
|
type: dict
|
||||||
|
exposed_ports:
|
||||||
|
description: |
|
||||||
|
A list of dictionary data to specify how to expose container's ports.
|
||||||
|
If this parameter is specified, Zun will create a security group with
|
||||||
|
a set of rules to open the ports that should be exposed, and associate
|
||||||
|
the security group to the container. The value is in the form of
|
||||||
|
``{"<port>/<protocol>: {}"}``, where the ``port`` is the container's
|
||||||
|
port and ``protocol`` is either ``tcp`` or ``udp``. If ``protocol``
|
||||||
|
is not provided, ``tcp`` will be used.
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
type: object
|
||||||
fixed_ips:
|
fixed_ips:
|
||||||
description: |
|
description: |
|
||||||
A list of fixed IP addresses with subnet IDs and other detailed
|
A list of fixed IP addresses with subnet IDs and other detailed
|
||||||
|
@ -398,6 +398,12 @@ class ContainersController(base.Controller):
|
|||||||
if container_dict.get('restart_policy'):
|
if container_dict.get('restart_policy'):
|
||||||
utils.check_for_restart_policy(container_dict)
|
utils.check_for_restart_policy(container_dict)
|
||||||
|
|
||||||
|
exposed_ports = container_dict.pop('exposed_ports', None)
|
||||||
|
if exposed_ports is not None:
|
||||||
|
api_utils.version_check('exposed_ports', '1.24')
|
||||||
|
exposed_ports = utils.build_exposed_ports(exposed_ports)
|
||||||
|
container_dict['exposed_ports'] = exposed_ports
|
||||||
|
|
||||||
container_dict['status'] = consts.CREATING
|
container_dict['status'] = consts.CREATING
|
||||||
extra_spec = {}
|
extra_spec = {}
|
||||||
extra_spec['hints'] = container_dict.get('hints', None)
|
extra_spec['hints'] = container_dict.get('hints', None)
|
||||||
|
@ -39,6 +39,7 @@ _legacy_container_properties = {
|
|||||||
'auto_heal': parameter_types.boolean,
|
'auto_heal': parameter_types.boolean,
|
||||||
'privileged': parameter_types.boolean,
|
'privileged': parameter_types.boolean,
|
||||||
'healthcheck': parameter_types.healthcheck,
|
'healthcheck': parameter_types.healthcheck,
|
||||||
|
'exposed_ports': parameter_types.exposed_ports,
|
||||||
}
|
}
|
||||||
|
|
||||||
legacy_container_create = {
|
legacy_container_create = {
|
||||||
@ -54,7 +55,14 @@ _container_properties['command'] = parameter_types.command_list
|
|||||||
container_create = {
|
container_create = {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': _container_properties,
|
'properties': _container_properties,
|
||||||
|
'allOf': [
|
||||||
|
{
|
||||||
'required': ['image'],
|
'required': ['image'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'not': {'required': ['security_groups', 'exposed_ports']}
|
||||||
|
}
|
||||||
|
],
|
||||||
'additionalProperties': False
|
'additionalProperties': False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,6 +196,10 @@ healthcheck = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exposed_ports = {
|
||||||
|
'type': ['object', 'null']
|
||||||
|
}
|
||||||
|
|
||||||
mounts = {
|
mounts = {
|
||||||
'type': ['array', 'null'],
|
'type': ['array', 'null'],
|
||||||
'items': {
|
'items': {
|
||||||
|
@ -56,10 +56,11 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||||||
* 1.21 - Add support privileged
|
* 1.21 - Add support privileged
|
||||||
* 1.22 - Add healthcheck to container create
|
* 1.22 - Add healthcheck to container create
|
||||||
* 1.23 - Add attribute 'type' to parameter 'mounts'
|
* 1.23 - Add attribute 'type' to parameter 'mounts'
|
||||||
|
* 1.24 - Add exposed_ports to container
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BASE_VER = '1.1'
|
BASE_VER = '1.1'
|
||||||
CURRENT_MAX_VER = '1.23'
|
CURRENT_MAX_VER = '1.24'
|
||||||
|
|
||||||
|
|
||||||
class Version(object):
|
class Version(object):
|
||||||
|
@ -188,3 +188,17 @@ user documentation.
|
|||||||
|
|
||||||
Add support for file injection when creating a container.
|
Add support for file injection when creating a container.
|
||||||
The content of the file is sent to Zun server via parameter 'mounts'.
|
The content of the file is sent to Zun server via parameter 'mounts'.
|
||||||
|
|
||||||
|
1.24
|
||||||
|
----
|
||||||
|
|
||||||
|
Add a parameter 'exposed_ports' to the request of creating a container.
|
||||||
|
This parameter is of the following form:
|
||||||
|
|
||||||
|
"exposed_ports": { "<port>/<protocol>: {}" }
|
||||||
|
|
||||||
|
where 'port' is the container's port and 'protocol' is either 'tcp' or 'udp'.
|
||||||
|
If this parameter is specified, Zun will create a security group and open
|
||||||
|
the exposed port. This parameter cannot be used together with the
|
||||||
|
'security_groups' parameter because Zun will manage the security groups of
|
||||||
|
the container.
|
||||||
|
@ -487,6 +487,36 @@ def check_for_restart_policy(container_dict):
|
|||||||
container_dict.get('restart_policy')['MaximumRetryCount'] = '0'
|
container_dict.get('restart_policy')['MaximumRetryCount'] = '0'
|
||||||
|
|
||||||
|
|
||||||
|
def build_exposed_ports(ports):
|
||||||
|
|
||||||
|
def validate_protocol(protocol):
|
||||||
|
if protocol not in ('tcp', 'udp'):
|
||||||
|
raise exception.InvalidValue(_(
|
||||||
|
"value %s is an invalid protocol") % protocol)
|
||||||
|
|
||||||
|
def validate_port(port):
|
||||||
|
try:
|
||||||
|
int(port)
|
||||||
|
except ValueError:
|
||||||
|
msg = _("value %s is invalid as publish port.") % port
|
||||||
|
raise exception.InvalidValue(msg)
|
||||||
|
|
||||||
|
exposed_ports = {}
|
||||||
|
for key, value in ports.items():
|
||||||
|
try:
|
||||||
|
port, protocol = key.split('/')
|
||||||
|
except ValueError:
|
||||||
|
port, protocol = key, 'tcp'
|
||||||
|
|
||||||
|
validate_protocol(protocol)
|
||||||
|
validate_port(port)
|
||||||
|
|
||||||
|
key = '/'.join([port, protocol])
|
||||||
|
exposed_ports[key] = value
|
||||||
|
|
||||||
|
return exposed_ports
|
||||||
|
|
||||||
|
|
||||||
def build_requested_networks(context, nets):
|
def build_requested_networks(context, nets):
|
||||||
"""Build requested networks by calling neutron client
|
"""Build requested networks by calling neutron client
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import functools
|
|||||||
import types
|
import types
|
||||||
|
|
||||||
from docker import errors
|
from docker import errors
|
||||||
|
from neutronclient.common import exceptions as n_exc
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
@ -285,6 +286,7 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
# host_config['pid_mode'] = 'container:%s' % sandbox_id
|
# host_config['pid_mode'] = 'container:%s' % sandbox_id
|
||||||
host_config['ipc_mode'] = 'container:%s' % sandbox_id
|
host_config['ipc_mode'] = 'container:%s' % sandbox_id
|
||||||
else:
|
else:
|
||||||
|
self._process_exposed_ports(network_api.neutron_api, container)
|
||||||
self._process_networking_config(
|
self._process_networking_config(
|
||||||
context, container, requested_networks, host_config,
|
context, container, requested_networks, host_config,
|
||||||
kwargs, docker)
|
kwargs, docker)
|
||||||
@ -343,6 +345,16 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
def get_host_default_base_size(self):
|
def get_host_default_base_size(self):
|
||||||
return self.base_device_size
|
return self.base_device_size
|
||||||
|
|
||||||
|
def _process_exposed_ports(self, neutron_api, container):
|
||||||
|
if not container.exposed_ports:
|
||||||
|
return
|
||||||
|
|
||||||
|
secgroup_name = self._get_secgorup_name(container.uuid)
|
||||||
|
secgroup_id = neutron_api.create_security_group({'security_group': {
|
||||||
|
"name": secgroup_name}})['security_group']['id']
|
||||||
|
neutron_api.expose_ports(secgroup_id, container.exposed_ports)
|
||||||
|
container.security_groups = [secgroup_id]
|
||||||
|
|
||||||
def _process_networking_config(self, context, container,
|
def _process_networking_config(self, context, container,
|
||||||
requested_networks, host_config,
|
requested_networks, host_config,
|
||||||
container_kwargs, docker):
|
container_kwargs, docker):
|
||||||
@ -380,6 +392,9 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
self._get_or_create_docker_network(
|
self._get_or_create_docker_network(
|
||||||
context, network_api, rq_network['network'])
|
context, network_api, rq_network['network'])
|
||||||
|
|
||||||
|
def _get_secgorup_name(self, container_uuid):
|
||||||
|
return consts.NAME_PREFIX + container_uuid
|
||||||
|
|
||||||
def _get_binds(self, context, requested_volumes):
|
def _get_binds(self, context, requested_volumes):
|
||||||
binds = {}
|
binds = {}
|
||||||
for volume in requested_volumes:
|
for volume in requested_volumes:
|
||||||
@ -419,6 +434,8 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
network_api = zun_network.api(context=context,
|
network_api = zun_network.api(context=context,
|
||||||
docker_api=docker)
|
docker_api=docker)
|
||||||
self._cleanup_network_for_container(container, network_api)
|
self._cleanup_network_for_container(container, network_api)
|
||||||
|
self._cleanup_exposed_ports(network_api.neutron_api,
|
||||||
|
container)
|
||||||
if container.container_id:
|
if container.container_id:
|
||||||
docker.remove_container(container.container_id,
|
docker.remove_container(container.container_id,
|
||||||
force=force)
|
force=force)
|
||||||
@ -438,6 +455,15 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
network_api.disconnect_container_from_network(
|
network_api.disconnect_container_from_network(
|
||||||
container, docker_net, neutron_network_id=neutron_net)
|
container, docker_net, neutron_network_id=neutron_net)
|
||||||
|
|
||||||
|
def _cleanup_exposed_ports(self, neutron_api, container):
|
||||||
|
if not container.exposed_ports:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
neutron_api.delete_security_group(container.security_groups[0])
|
||||||
|
except n_exc.NeutronClientException:
|
||||||
|
LOG.exception("Failed to delete security group")
|
||||||
|
|
||||||
def check_container_exist(self, container):
|
def check_container_exist(self, container):
|
||||||
with docker_utils.docker_client() as docker:
|
with docker_utils.docker_client() as docker:
|
||||||
docker_containers = [c['Id']
|
docker_containers = [c['Id']
|
||||||
@ -975,6 +1001,7 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
'hostname': name[:63],
|
'hostname': name[:63],
|
||||||
'volumes': volumes,
|
'volumes': volumes,
|
||||||
}
|
}
|
||||||
|
self._process_exposed_ports(network_api.neutron_api, container)
|
||||||
self._process_networking_config(
|
self._process_networking_config(
|
||||||
context, container, requested_networks, host_config,
|
context, container, requested_networks, host_config,
|
||||||
kwargs, docker)
|
kwargs, docker)
|
||||||
@ -1042,6 +1069,7 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
with docker_utils.docker_client() as docker:
|
with docker_utils.docker_client() as docker:
|
||||||
network_api = zun_network.api(context=context, docker_api=docker)
|
network_api = zun_network.api(context=context, docker_api=docker)
|
||||||
self._cleanup_network_for_container(container, network_api)
|
self._cleanup_network_for_container(container, network_api)
|
||||||
|
self._cleanup_exposed_ports(network_api.neutron_api, container)
|
||||||
try:
|
try:
|
||||||
docker.remove_container(sandbox_id, force=True)
|
docker.remove_container(sandbox_id, force=True)
|
||||||
except errors.APIError as api_error:
|
except errors.APIError as api_error:
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""add_exposed_ports_to_container
|
||||||
|
|
||||||
|
Revision ID: 02134de8e7d3
|
||||||
|
Revises: a019998b09b5
|
||||||
|
Create Date: 2018-08-19 19:29:51.636559
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '02134de8e7d3'
|
||||||
|
down_revision = 'a019998b09b5'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
import zun
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('container',
|
||||||
|
sa.Column('exposed_ports',
|
||||||
|
zun.db.sqlalchemy.models.JSONEncodedDict(),
|
||||||
|
nullable=True))
|
@ -172,6 +172,7 @@ class Container(Base):
|
|||||||
started_at = Column(DateTime)
|
started_at = Column(DateTime)
|
||||||
privileged = Column(Boolean, default=False)
|
privileged = Column(Boolean, default=False)
|
||||||
healthcheck = Column(JSONEncodedDict)
|
healthcheck = Column(JSONEncodedDict)
|
||||||
|
exposed_ports = Column(JSONEncodedDict)
|
||||||
|
|
||||||
|
|
||||||
class VolumeMapping(Base):
|
class VolumeMapping(Base):
|
||||||
|
@ -189,18 +189,6 @@ class KuryrNetwork(network.Network):
|
|||||||
if requested_network.get('port'):
|
if requested_network.get('port'):
|
||||||
neutron_port_id = requested_network.get('port')
|
neutron_port_id = requested_network.get('port')
|
||||||
neutron_port = self.neutron_api.get_neutron_port(neutron_port_id)
|
neutron_port = self.neutron_api.get_neutron_port(neutron_port_id)
|
||||||
# NOTE(hongbin): If existing port is specified, security_group_ids
|
|
||||||
# is ignored because existing port already has security groups.
|
|
||||||
# We might revisit this behaviour later. Alternatively, we could
|
|
||||||
# either throw an exception or overwrite the port's security
|
|
||||||
# groups.
|
|
||||||
if not container.security_groups:
|
|
||||||
container.security_groups = []
|
|
||||||
if neutron_port.get('security_groups'):
|
|
||||||
for sg in neutron_port['security_groups']:
|
|
||||||
if sg not in container.security_groups:
|
|
||||||
container.security_groups += [sg]
|
|
||||||
|
|
||||||
# update device_id in port
|
# update device_id in port
|
||||||
port_req_body = {'port': {'device_id': container.uuid}}
|
port_req_body = {'port': {'device_id': container.uuid}}
|
||||||
self.neutron_api.update_port(neutron_port_id, port_req_body)
|
self.neutron_api.update_port(neutron_port_id, port_req_body)
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from neutron_lib import constants as n_const
|
from neutron_lib import constants as n_const
|
||||||
|
from neutronclient.common import exceptions as n_exceptions
|
||||||
from neutronclient.neutron import v2_0 as neutronv20
|
from neutronclient.neutron import v2_0 as neutronv20
|
||||||
|
from oslo_log import log as logging
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
from zun.common import clients
|
from zun.common import clients
|
||||||
@ -19,6 +21,9 @@ from zun.common import exception
|
|||||||
from zun.common.i18n import _
|
from zun.common.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NeutronAPI(object):
|
class NeutronAPI(object):
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
@ -98,3 +103,26 @@ class NeutronAPI(object):
|
|||||||
binding_vif_type = port.get('binding:vif_type')
|
binding_vif_type = port.get('binding:vif_type')
|
||||||
if binding_vif_type == 'binding_failed':
|
if binding_vif_type == 'binding_failed':
|
||||||
raise exception.PortBindingFailed(port=port['id'])
|
raise exception.PortBindingFailed(port=port['id'])
|
||||||
|
|
||||||
|
def expose_ports(self, secgroup_id, ports):
|
||||||
|
for port in ports:
|
||||||
|
port, proto = port.split('/')
|
||||||
|
secgroup_rule = {
|
||||||
|
'security_group_id': secgroup_id,
|
||||||
|
'direction': 'ingress',
|
||||||
|
'port_range_min': port,
|
||||||
|
'port_range_max': port,
|
||||||
|
'protocol': proto
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.create_security_group_rule({
|
||||||
|
'security_group_rule': secgroup_rule})
|
||||||
|
except n_exceptions.NeutronClientException as ex:
|
||||||
|
LOG.error("Error happened during creating a "
|
||||||
|
"Neutron security group "
|
||||||
|
"rule: %s", ex)
|
||||||
|
self.delete_security_group(secgroup_id)
|
||||||
|
raise exception.ZunException(_(
|
||||||
|
"Could not create required security group rules %s "
|
||||||
|
"for setting up exported port.") % secgroup_rule)
|
||||||
|
@ -67,7 +67,8 @@ class Container(base.ZunPersistentObject, base.ZunObject):
|
|||||||
# Version 1.34: Add privileged to container
|
# Version 1.34: Add privileged to container
|
||||||
# Version 1.35: Add 'healthcheck' attribute
|
# Version 1.35: Add 'healthcheck' attribute
|
||||||
# Version 1.36: Add 'get_count' method
|
# Version 1.36: Add 'get_count' method
|
||||||
VERSION = '1.36'
|
# Version 1.37: Add 'exposed_ports' attribute
|
||||||
|
VERSION = '1.37'
|
||||||
|
|
||||||
fields = {
|
fields = {
|
||||||
'id': fields.IntegerField(),
|
'id': fields.IntegerField(),
|
||||||
@ -107,6 +108,7 @@ class Container(base.ZunPersistentObject, base.ZunObject):
|
|||||||
'auto_heal': fields.BooleanField(nullable=True),
|
'auto_heal': fields.BooleanField(nullable=True),
|
||||||
'capsule_id': fields.IntegerField(nullable=True),
|
'capsule_id': fields.IntegerField(nullable=True),
|
||||||
'started_at': fields.DateTimeField(tzinfo_aware=False, nullable=True),
|
'started_at': fields.DateTimeField(tzinfo_aware=False, nullable=True),
|
||||||
|
'exposed_ports': z_fields.JsonField(nullable=True),
|
||||||
'exec_instances': fields.ListOfObjectsField('ExecInstance',
|
'exec_instances': fields.ListOfObjectsField('ExecInstance',
|
||||||
nullable=True),
|
nullable=True),
|
||||||
'privileged': fields.BooleanField(nullable=True),
|
'privileged': fields.BooleanField(nullable=True),
|
||||||
|
@ -26,7 +26,7 @@ from zun.tests.unit.db import base
|
|||||||
|
|
||||||
|
|
||||||
PATH_PREFIX = '/v1'
|
PATH_PREFIX = '/v1'
|
||||||
CURRENT_VERSION = "container 1.23"
|
CURRENT_VERSION = "container 1.24"
|
||||||
|
|
||||||
|
|
||||||
class FunctionalTest(base.DbTestCase):
|
class FunctionalTest(base.DbTestCase):
|
||||||
|
@ -28,7 +28,7 @@ class TestRootController(api_base.FunctionalTest):
|
|||||||
'default_version':
|
'default_version':
|
||||||
{'id': 'v1',
|
{'id': 'v1',
|
||||||
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
|
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
|
||||||
'max_version': '1.23',
|
'max_version': '1.24',
|
||||||
'min_version': '1.1',
|
'min_version': '1.1',
|
||||||
'status': 'CURRENT'},
|
'status': 'CURRENT'},
|
||||||
'description': 'Zun is an OpenStack project which '
|
'description': 'Zun is an OpenStack project which '
|
||||||
@ -37,7 +37,7 @@ class TestRootController(api_base.FunctionalTest):
|
|||||||
'versions': [{'id': 'v1',
|
'versions': [{'id': 'v1',
|
||||||
'links': [{'href': 'http://localhost/v1/',
|
'links': [{'href': 'http://localhost/v1/',
|
||||||
'rel': 'self'}],
|
'rel': 'self'}],
|
||||||
'max_version': '1.23',
|
'max_version': '1.24',
|
||||||
'min_version': '1.1',
|
'min_version': '1.1',
|
||||||
'status': 'CURRENT'}]}
|
'status': 'CURRENT'}]}
|
||||||
|
|
||||||
|
@ -87,6 +87,24 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.post('/v1/containers?run=true', params=params,
|
self.post('/v1/containers?run=true', params=params,
|
||||||
content_type='application/json')
|
content_type='application/json')
|
||||||
|
|
||||||
|
@patch('zun.network.neutron.NeutronAPI.get_available_network')
|
||||||
|
@patch('zun.compute.api.API.container_create')
|
||||||
|
def test_run_container_wrong_exposed_ports(
|
||||||
|
self, mock_container_create, mock_mock_neutron_get_network):
|
||||||
|
params = ('{"name": "MyDocker", "image": "ubuntu",'
|
||||||
|
'"exposed_ports": {"foo": {}}}')
|
||||||
|
with self.assertRaisesRegex(AppError,
|
||||||
|
"value foo is invalid as publish port"):
|
||||||
|
self.post('/v1/containers?run=true', params=params,
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
params = ('{"name": "MyDocker", "image": "ubuntu",'
|
||||||
|
'"exposed_ports": {"80/foo": {}}}')
|
||||||
|
with self.assertRaisesRegex(AppError,
|
||||||
|
"value foo is an invalid protocol"):
|
||||||
|
self.post('/v1/containers?run=true', params=params,
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
def test_run_container_runtime_wrong_api_version(self):
|
def test_run_container_runtime_wrong_api_version(self):
|
||||||
params = ('{"name": "MyDocker", "image": "ubuntu",'
|
params = ('{"name": "MyDocker", "image": "ubuntu",'
|
||||||
'"command": "env", "memory": "512",'
|
'"command": "env", "memory": "512",'
|
||||||
@ -282,6 +300,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
'"runtime": "runc", "hostname": "testhost",'
|
'"runtime": "runc", "hostname": "testhost",'
|
||||||
'"disk": 20, "restart_policy": {"Name": "no"},'
|
'"disk": 20, "restart_policy": {"Name": "no"},'
|
||||||
'"nets": [{"network": "testpublicnet"}],'
|
'"nets": [{"network": "testpublicnet"}],'
|
||||||
|
'"exposed_ports": {"80/tcp": {}, "22": {}},'
|
||||||
'"mounts": [{"source": "s", "destination": "d"}]}')
|
'"mounts": [{"source": "s", "destination": "d"}]}')
|
||||||
response = self.post('/v1/containers/',
|
response = self.post('/v1/containers/',
|
||||||
params=params,
|
params=params,
|
||||||
@ -311,6 +330,10 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock_container_create.call_args[1]['requested_volumes']
|
mock_container_create.call_args[1]['requested_volumes']
|
||||||
self.assertEqual(1, len(requested_volumes))
|
self.assertEqual(1, len(requested_volumes))
|
||||||
self.assertEqual(fake_volume_id, requested_volumes[0].volume_id)
|
self.assertEqual(fake_volume_id, requested_volumes[0].volume_id)
|
||||||
|
exposed_ports = mock_container_create.call_args[0][1].exposed_ports
|
||||||
|
self.assertEqual(2, len(exposed_ports))
|
||||||
|
self.assertIn("80/tcp", exposed_ports)
|
||||||
|
self.assertIn("22/tcp", exposed_ports)
|
||||||
|
|
||||||
# Delete the container we created
|
# Delete the container we created
|
||||||
def side_effect(*args, **kwargs):
|
def side_effect(*args, **kwargs):
|
||||||
|
@ -94,6 +94,8 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
self.driver.images(repo='test')
|
self.driver.images(repo='test')
|
||||||
self.mock_docker.images.assert_called_once_with('test', False)
|
self.mock_docker.images.assert_called_once_with('test', False)
|
||||||
|
|
||||||
|
@mock.patch('neutronclient.v2_0.client.Client.create_security_group')
|
||||||
|
@mock.patch('zun.network.neutron.NeutronAPI.expose_ports')
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
'.connect_container_to_network')
|
'.connect_container_to_network')
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
@ -104,7 +106,9 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
self, mock_save,
|
self, mock_save,
|
||||||
mock_get_security_group_ids,
|
mock_get_security_group_ids,
|
||||||
mock_create_or_update_port,
|
mock_create_or_update_port,
|
||||||
mock_connect):
|
mock_connect,
|
||||||
|
mock_expose_ports,
|
||||||
|
mock_create_security_group):
|
||||||
self.mock_docker.create_host_config = mock.Mock(
|
self.mock_docker.create_host_config = mock.Mock(
|
||||||
return_value={'Id1': 'val1', 'key2': 'val2'})
|
return_value={'Id1': 'val1', 'key2': 'val2'})
|
||||||
self.mock_docker.create_container = mock.Mock(
|
self.mock_docker.create_container = mock.Mock(
|
||||||
@ -122,6 +126,8 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
volumes = []
|
volumes = []
|
||||||
fake_port = {'mac_address': 'fake_mac'}
|
fake_port = {'mac_address': 'fake_mac'}
|
||||||
mock_create_or_update_port.return_value = ([], fake_port)
|
mock_create_or_update_port.return_value = ([], fake_port)
|
||||||
|
mock_create_security_group.return_value = {
|
||||||
|
'security_group': {'id': 'fake-id'}}
|
||||||
# DockerDriver with supported storage driver - overlay2
|
# DockerDriver with supported storage driver - overlay2
|
||||||
self.driver._host.sp_disk_quota = True
|
self.driver._host.sp_disk_quota = True
|
||||||
self.driver._host.storage_driver = 'overlay2'
|
self.driver._host.storage_driver = 'overlay2'
|
||||||
@ -161,6 +167,8 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
self.assertEqual(result_container.status,
|
self.assertEqual(result_container.status,
|
||||||
consts.CREATED)
|
consts.CREATED)
|
||||||
|
|
||||||
|
@mock.patch('neutronclient.v2_0.client.Client.create_security_group')
|
||||||
|
@mock.patch('zun.network.neutron.NeutronAPI.expose_ports')
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
'.connect_container_to_network')
|
'.connect_container_to_network')
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
@ -171,7 +179,9 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
self, mock_save,
|
self, mock_save,
|
||||||
mock_get_security_group_ids,
|
mock_get_security_group_ids,
|
||||||
mock_create_or_update_port,
|
mock_create_or_update_port,
|
||||||
mock_connect):
|
mock_connect,
|
||||||
|
mock_expose_ports,
|
||||||
|
mock_create_security_group):
|
||||||
self.mock_docker.create_host_config = mock.Mock(
|
self.mock_docker.create_host_config = mock.Mock(
|
||||||
return_value={'Id1': 'val1', 'key2': 'val2'})
|
return_value={'Id1': 'val1', 'key2': 'val2'})
|
||||||
self.mock_docker.create_container = mock.Mock(
|
self.mock_docker.create_container = mock.Mock(
|
||||||
@ -189,6 +199,8 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
volumes = []
|
volumes = []
|
||||||
fake_port = {'mac_address': 'fake_mac'}
|
fake_port = {'mac_address': 'fake_mac'}
|
||||||
mock_create_or_update_port.return_value = ([], fake_port)
|
mock_create_or_update_port.return_value = ([], fake_port)
|
||||||
|
mock_create_security_group.return_value = {
|
||||||
|
'security_group': {'id': 'fake-id'}}
|
||||||
# DockerDriver with supported storage driver - overlay2
|
# DockerDriver with supported storage driver - overlay2
|
||||||
self.driver._host.sp_disk_quota = True
|
self.driver._host.sp_disk_quota = True
|
||||||
self.driver._host.storage_driver = 'devicemapper'
|
self.driver._host.storage_driver = 'devicemapper'
|
||||||
@ -229,6 +241,8 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
self.assertEqual(result_container.status,
|
self.assertEqual(result_container.status,
|
||||||
consts.CREATED)
|
consts.CREATED)
|
||||||
|
|
||||||
|
@mock.patch('neutronclient.v2_0.client.Client.create_security_group')
|
||||||
|
@mock.patch('zun.network.neutron.NeutronAPI.expose_ports')
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
'.connect_container_to_network')
|
'.connect_container_to_network')
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
@ -239,7 +253,9 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
self, mock_save,
|
self, mock_save,
|
||||||
mock_get_security_group_ids,
|
mock_get_security_group_ids,
|
||||||
mock_create_or_update_port,
|
mock_create_or_update_port,
|
||||||
mock_connect):
|
mock_connect,
|
||||||
|
mock_expose_ports,
|
||||||
|
mock_create_security_group):
|
||||||
CONF.set_override("docker_remote_api_version", "1.24", "docker")
|
CONF.set_override("docker_remote_api_version", "1.24", "docker")
|
||||||
self.mock_docker.create_host_config = mock.Mock(
|
self.mock_docker.create_host_config = mock.Mock(
|
||||||
return_value={'Id1': 'val1', 'key2': 'val2'})
|
return_value={'Id1': 'val1', 'key2': 'val2'})
|
||||||
@ -259,6 +275,8 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
volumes = []
|
volumes = []
|
||||||
fake_port = {'mac_address': 'fake_mac'}
|
fake_port = {'mac_address': 'fake_mac'}
|
||||||
mock_create_or_update_port.return_value = ([], fake_port)
|
mock_create_or_update_port.return_value = ([], fake_port)
|
||||||
|
mock_create_security_group.return_value = {
|
||||||
|
'security_group': {'id': 'fake-id'}}
|
||||||
result_container = self.driver.create(self.context, mock_container,
|
result_container = self.driver.create(self.context, mock_container,
|
||||||
image, networks, volumes)
|
image, networks, volumes)
|
||||||
host_config = {}
|
host_config = {}
|
||||||
@ -335,11 +353,15 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
|
|
||||||
@mock.patch('zun.container.docker.driver.DockerDriver'
|
@mock.patch('zun.container.docker.driver.DockerDriver'
|
||||||
'._cleanup_network_for_container')
|
'._cleanup_network_for_container')
|
||||||
def test_delete_success(self, mock_cleanup_network_for_container):
|
@mock.patch('zun.container.docker.driver.DockerDriver'
|
||||||
|
'._cleanup_exposed_ports')
|
||||||
|
def test_delete_success(self, mock_cleanup_network_for_container,
|
||||||
|
mock_cleanup_exposed_ports):
|
||||||
self.mock_docker.remove_container = mock.Mock()
|
self.mock_docker.remove_container = mock.Mock()
|
||||||
mock_container = self.mock_default_container
|
mock_container = self.mock_default_container
|
||||||
self.driver.delete(self.context, mock_container, True)
|
self.driver.delete(self.context, mock_container, True)
|
||||||
self.assertTrue(mock_cleanup_network_for_container.called)
|
self.assertTrue(mock_cleanup_network_for_container.called)
|
||||||
|
self.assertTrue(mock_cleanup_exposed_ports.called)
|
||||||
self.mock_docker.remove_container.assert_called_once_with(
|
self.mock_docker.remove_container.assert_called_once_with(
|
||||||
mock_container.container_id, force=True)
|
mock_container.container_id, force=True)
|
||||||
|
|
||||||
@ -714,6 +736,8 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
self.mock_docker.commit.assert_called_once_with(
|
self.mock_docker.commit.assert_called_once_with(
|
||||||
mock_container.container_id, "repo", "tag")
|
mock_container.container_id, "repo", "tag")
|
||||||
|
|
||||||
|
@mock.patch('neutronclient.v2_0.client.Client.create_security_group')
|
||||||
|
@mock.patch('zun.network.neutron.NeutronAPI.expose_ports')
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
'.connect_container_to_network')
|
'.connect_container_to_network')
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
@ -722,7 +746,8 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
@mock.patch('zun.common.utils.get_security_group_ids')
|
@mock.patch('zun.common.utils.get_security_group_ids')
|
||||||
def test_create_sandbox(self, mock_get_security_group_ids,
|
def test_create_sandbox(self, mock_get_security_group_ids,
|
||||||
mock_get_sandbox_name, mock_create_or_update_port,
|
mock_get_sandbox_name, mock_create_or_update_port,
|
||||||
mock_connect):
|
mock_connect, mock_expose_ports,
|
||||||
|
mock_create_security_group):
|
||||||
sandbox_name = 'my_test_sandbox'
|
sandbox_name = 'my_test_sandbox'
|
||||||
mock_get_sandbox_name.return_value = sandbox_name
|
mock_get_sandbox_name.return_value = sandbox_name
|
||||||
self.mock_docker.create_container = mock.Mock(
|
self.mock_docker.create_container = mock.Mock(
|
||||||
@ -747,6 +772,8 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
networking_config={'Id': 'val1', 'key1': 'val2'})
|
networking_config={'Id': 'val1', 'key1': 'val2'})
|
||||||
self.assertEqual(result_sandbox_id, 'val1')
|
self.assertEqual(result_sandbox_id, 'val1')
|
||||||
|
|
||||||
|
@mock.patch('neutronclient.v2_0.client.Client.create_security_group')
|
||||||
|
@mock.patch('zun.network.neutron.NeutronAPI.expose_ports')
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
'.connect_container_to_network')
|
'.connect_container_to_network')
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
@ -756,7 +783,9 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
def test_create_sandbox_with_long_name(self, mock_get_security_group_ids,
|
def test_create_sandbox_with_long_name(self, mock_get_security_group_ids,
|
||||||
mock_get_sandbox_name,
|
mock_get_sandbox_name,
|
||||||
mock_create_or_update_port,
|
mock_create_or_update_port,
|
||||||
mock_connect):
|
mock_connect,
|
||||||
|
mock_expose_ports,
|
||||||
|
mock_create_security_group):
|
||||||
sandbox_name = 'x' * 100
|
sandbox_name = 'x' * 100
|
||||||
mock_get_sandbox_name.return_value = sandbox_name
|
mock_get_sandbox_name.return_value = sandbox_name
|
||||||
self.mock_docker.create_container = mock.Mock(
|
self.mock_docker.create_container = mock.Mock(
|
||||||
@ -780,7 +809,8 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
networking_config={'Id': 'val1', 'key1': 'val2'})
|
networking_config={'Id': 'val1', 'key1': 'val2'})
|
||||||
self.assertEqual(result_sandbox_id, 'val1')
|
self.assertEqual(result_sandbox_id, 'val1')
|
||||||
|
|
||||||
def test_delete_sandbox(self):
|
@mock.patch('neutronclient.v2_0.client.Client.delete_security_group')
|
||||||
|
def test_delete_sandbox(self, mock_delete_security_group):
|
||||||
self.mock_docker.remove_container = mock.Mock()
|
self.mock_docker.remove_container = mock.Mock()
|
||||||
mock_container = mock.MagicMock()
|
mock_container = mock.MagicMock()
|
||||||
mock_container.get_sandbox_id.return_value = 'test_sandbox_id'
|
mock_container.get_sandbox_id.return_value = 'test_sandbox_id'
|
||||||
|
@ -107,6 +107,7 @@ def get_test_container(**kwargs):
|
|||||||
{"retries": "2", "timeout": 3,
|
{"retries": "2", "timeout": 3,
|
||||||
"test": "stat /etc/passwd || exit 1",
|
"test": "stat /etc/passwd || exit 1",
|
||||||
"interval": 3}),
|
"interval": 3}),
|
||||||
|
'exposed_ports': kwargs.get('exposed_ports', {"80/tcp": {}}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,7 +344,7 @@ class TestObject(test_base.TestCase, _TestObject):
|
|||||||
# For more information on object version testing, read
|
# For more information on object version testing, read
|
||||||
# https://docs.openstack.org/zun/latest/
|
# https://docs.openstack.org/zun/latest/
|
||||||
object_data = {
|
object_data = {
|
||||||
'Container': '1.36-ad2bacdaa51afd0047e96003f93ff181',
|
'Container': '1.37-cdc1537de5adf3570b598da1a3728a68',
|
||||||
'VolumeMapping': '1.3-14e3f9fc64e7afd751727c6ad3f32a94',
|
'VolumeMapping': '1.3-14e3f9fc64e7afd751727c6ad3f32a94',
|
||||||
'Image': '1.2-80504fdd797e9dd86128a91680e876ad',
|
'Image': '1.2-80504fdd797e9dd86128a91680e876ad',
|
||||||
'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',
|
'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',
|
||||||
|
Loading…
Reference in New Issue
Block a user