From 44dc51f8dff9b901a408fff73176403dc363cc29 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Sun, 13 May 2018 21:52:48 +0000 Subject: [PATCH] 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: ``{"/": {}}``. 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 --- api-ref/source/containers.inc | 1 + api-ref/source/parameters.yaml | 12 +++++ zun/api/controllers/v1/containers.py | 6 +++ zun/api/controllers/v1/schemas/containers.py | 10 ++++- .../controllers/v1/schemas/parameter_types.py | 4 ++ zun/api/controllers/versions.py | 3 +- zun/api/rest_api_version_history.rst | 14 ++++++ zun/common/utils.py | 30 +++++++++++++ zun/container/docker/driver.py | 28 ++++++++++++ ...4de8e7d3_add_exposed_ports_to_container.py | 37 ++++++++++++++++ zun/db/sqlalchemy/models.py | 1 + zun/network/kuryr_network.py | 12 ----- zun/network/neutron.py | 28 ++++++++++++ zun/objects/container.py | 4 +- zun/tests/unit/api/base.py | 2 +- zun/tests/unit/api/controllers/test_root.py | 4 +- .../api/controllers/v1/test_containers.py | 23 ++++++++++ .../container/docker/test_docker_driver.py | 44 ++++++++++++++++--- zun/tests/unit/db/utils.py | 1 + zun/tests/unit/objects/test_objects.py | 2 +- 20 files changed, 240 insertions(+), 26 deletions(-) create mode 100644 zun/db/sqlalchemy/alembic/versions/02134de8e7d3_add_exposed_ports_to_container.py diff --git a/api-ref/source/containers.inc b/api-ref/source/containers.inc index 522f88b64..d88785787 100644 --- a/api-ref/source/containers.inc +++ b/api-ref/source/containers.inc @@ -60,6 +60,7 @@ Request - mounts: mounts - privileged: privileged-request - healthcheck: healthcheck-request + - exposed_ports: exposed_ports Request Example ---------------- diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index e0809b7ce..e82c7122c 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -482,6 +482,18 @@ exec_url: The URL to start an exec instance. in: body 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 + ``{"/: {}"}``, 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: description: | A list of fixed IP addresses with subnet IDs and other detailed diff --git a/zun/api/controllers/v1/containers.py b/zun/api/controllers/v1/containers.py index b0b54c86f..496d77cd8 100644 --- a/zun/api/controllers/v1/containers.py +++ b/zun/api/controllers/v1/containers.py @@ -398,6 +398,12 @@ class ContainersController(base.Controller): if container_dict.get('restart_policy'): 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 extra_spec = {} extra_spec['hints'] = container_dict.get('hints', None) diff --git a/zun/api/controllers/v1/schemas/containers.py b/zun/api/controllers/v1/schemas/containers.py index 94f820353..d823946a0 100644 --- a/zun/api/controllers/v1/schemas/containers.py +++ b/zun/api/controllers/v1/schemas/containers.py @@ -39,6 +39,7 @@ _legacy_container_properties = { 'auto_heal': parameter_types.boolean, 'privileged': parameter_types.boolean, 'healthcheck': parameter_types.healthcheck, + 'exposed_ports': parameter_types.exposed_ports, } legacy_container_create = { @@ -54,7 +55,14 @@ _container_properties['command'] = parameter_types.command_list container_create = { 'type': 'object', 'properties': _container_properties, - 'required': ['image'], + 'allOf': [ + { + 'required': ['image'], + }, + { + 'not': {'required': ['security_groups', 'exposed_ports']} + } + ], 'additionalProperties': False } diff --git a/zun/api/controllers/v1/schemas/parameter_types.py b/zun/api/controllers/v1/schemas/parameter_types.py index e354f051b..091f69a4a 100644 --- a/zun/api/controllers/v1/schemas/parameter_types.py +++ b/zun/api/controllers/v1/schemas/parameter_types.py @@ -196,6 +196,10 @@ healthcheck = { } } +exposed_ports = { + 'type': ['object', 'null'] +} + mounts = { 'type': ['array', 'null'], 'items': { diff --git a/zun/api/controllers/versions.py b/zun/api/controllers/versions.py index dfc4ec9fa..7006d36b1 100644 --- a/zun/api/controllers/versions.py +++ b/zun/api/controllers/versions.py @@ -56,10 +56,11 @@ REST_API_VERSION_HISTORY = """REST API Version History: * 1.21 - Add support privileged * 1.22 - Add healthcheck to container create * 1.23 - Add attribute 'type' to parameter 'mounts' + * 1.24 - Add exposed_ports to container """ BASE_VER = '1.1' -CURRENT_MAX_VER = '1.23' +CURRENT_MAX_VER = '1.24' class Version(object): diff --git a/zun/api/rest_api_version_history.rst b/zun/api/rest_api_version_history.rst index 5c6d26aeb..c75eb39f6 100644 --- a/zun/api/rest_api_version_history.rst +++ b/zun/api/rest_api_version_history.rst @@ -188,3 +188,17 @@ user documentation. Add support for file injection when creating a container. 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": { "/: {}" } + + 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. diff --git a/zun/common/utils.py b/zun/common/utils.py index fe286c18b..e80b2511c 100644 --- a/zun/common/utils.py +++ b/zun/common/utils.py @@ -487,6 +487,36 @@ def check_for_restart_policy(container_dict): 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): """Build requested networks by calling neutron client diff --git a/zun/container/docker/driver.py b/zun/container/docker/driver.py index 357c76e67..0545736e7 100644 --- a/zun/container/docker/driver.py +++ b/zun/container/docker/driver.py @@ -17,6 +17,7 @@ import functools import types from docker import errors +from neutronclient.common import exceptions as n_exc from oslo_log import log as logging from oslo_utils import timeutils from oslo_utils import uuidutils @@ -285,6 +286,7 @@ class DockerDriver(driver.ContainerDriver): # host_config['pid_mode'] = 'container:%s' % sandbox_id host_config['ipc_mode'] = 'container:%s' % sandbox_id else: + self._process_exposed_ports(network_api.neutron_api, container) self._process_networking_config( context, container, requested_networks, host_config, kwargs, docker) @@ -343,6 +345,16 @@ class DockerDriver(driver.ContainerDriver): def get_host_default_base_size(self): 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, requested_networks, host_config, container_kwargs, docker): @@ -380,6 +392,9 @@ class DockerDriver(driver.ContainerDriver): self._get_or_create_docker_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): binds = {} for volume in requested_volumes: @@ -419,6 +434,8 @@ class DockerDriver(driver.ContainerDriver): network_api = zun_network.api(context=context, docker_api=docker) self._cleanup_network_for_container(container, network_api) + self._cleanup_exposed_ports(network_api.neutron_api, + container) if container.container_id: docker.remove_container(container.container_id, force=force) @@ -438,6 +455,15 @@ class DockerDriver(driver.ContainerDriver): network_api.disconnect_container_from_network( 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): with docker_utils.docker_client() as docker: docker_containers = [c['Id'] @@ -975,6 +1001,7 @@ class DockerDriver(driver.ContainerDriver): 'hostname': name[:63], 'volumes': volumes, } + self._process_exposed_ports(network_api.neutron_api, container) self._process_networking_config( context, container, requested_networks, host_config, kwargs, docker) @@ -1042,6 +1069,7 @@ class DockerDriver(driver.ContainerDriver): with docker_utils.docker_client() as docker: network_api = zun_network.api(context=context, docker_api=docker) self._cleanup_network_for_container(container, network_api) + self._cleanup_exposed_ports(network_api.neutron_api, container) try: docker.remove_container(sandbox_id, force=True) except errors.APIError as api_error: diff --git a/zun/db/sqlalchemy/alembic/versions/02134de8e7d3_add_exposed_ports_to_container.py b/zun/db/sqlalchemy/alembic/versions/02134de8e7d3_add_exposed_ports_to_container.py new file mode 100644 index 000000000..32ca5f3ac --- /dev/null +++ b/zun/db/sqlalchemy/alembic/versions/02134de8e7d3_add_exposed_ports_to_container.py @@ -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)) diff --git a/zun/db/sqlalchemy/models.py b/zun/db/sqlalchemy/models.py index 5fe6e834b..c5c50db53 100644 --- a/zun/db/sqlalchemy/models.py +++ b/zun/db/sqlalchemy/models.py @@ -172,6 +172,7 @@ class Container(Base): started_at = Column(DateTime) privileged = Column(Boolean, default=False) healthcheck = Column(JSONEncodedDict) + exposed_ports = Column(JSONEncodedDict) class VolumeMapping(Base): diff --git a/zun/network/kuryr_network.py b/zun/network/kuryr_network.py index e119d77c7..84bbbf6c7 100644 --- a/zun/network/kuryr_network.py +++ b/zun/network/kuryr_network.py @@ -189,18 +189,6 @@ class KuryrNetwork(network.Network): if requested_network.get('port'): neutron_port_id = requested_network.get('port') 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 port_req_body = {'port': {'device_id': container.uuid}} self.neutron_api.update_port(neutron_port_id, port_req_body) diff --git a/zun/network/neutron.py b/zun/network/neutron.py index d397d63bb..709025d84 100644 --- a/zun/network/neutron.py +++ b/zun/network/neutron.py @@ -11,7 +11,9 @@ # under the License. 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 oslo_log import log as logging from oslo_utils import uuidutils from zun.common import clients @@ -19,6 +21,9 @@ from zun.common import exception from zun.common.i18n import _ +LOG = logging.getLogger(__name__) + + class NeutronAPI(object): def __init__(self, context): @@ -98,3 +103,26 @@ class NeutronAPI(object): binding_vif_type = port.get('binding:vif_type') if binding_vif_type == 'binding_failed': 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) diff --git a/zun/objects/container.py b/zun/objects/container.py index 46e699a5b..fef25f9c0 100644 --- a/zun/objects/container.py +++ b/zun/objects/container.py @@ -67,7 +67,8 @@ class Container(base.ZunPersistentObject, base.ZunObject): # Version 1.34: Add privileged to container # Version 1.35: Add 'healthcheck' attribute # Version 1.36: Add 'get_count' method - VERSION = '1.36' + # Version 1.37: Add 'exposed_ports' attribute + VERSION = '1.37' fields = { 'id': fields.IntegerField(), @@ -107,6 +108,7 @@ class Container(base.ZunPersistentObject, base.ZunObject): 'auto_heal': fields.BooleanField(nullable=True), 'capsule_id': fields.IntegerField(nullable=True), 'started_at': fields.DateTimeField(tzinfo_aware=False, nullable=True), + 'exposed_ports': z_fields.JsonField(nullable=True), 'exec_instances': fields.ListOfObjectsField('ExecInstance', nullable=True), 'privileged': fields.BooleanField(nullable=True), diff --git a/zun/tests/unit/api/base.py b/zun/tests/unit/api/base.py index 0cb9eb0b2..58280a93c 100644 --- a/zun/tests/unit/api/base.py +++ b/zun/tests/unit/api/base.py @@ -26,7 +26,7 @@ from zun.tests.unit.db import base PATH_PREFIX = '/v1' -CURRENT_VERSION = "container 1.23" +CURRENT_VERSION = "container 1.24" class FunctionalTest(base.DbTestCase): diff --git a/zun/tests/unit/api/controllers/test_root.py b/zun/tests/unit/api/controllers/test_root.py index a31eb1a44..eee845587 100644 --- a/zun/tests/unit/api/controllers/test_root.py +++ b/zun/tests/unit/api/controllers/test_root.py @@ -28,7 +28,7 @@ class TestRootController(api_base.FunctionalTest): 'default_version': {'id': 'v1', 'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}], - 'max_version': '1.23', + 'max_version': '1.24', 'min_version': '1.1', 'status': 'CURRENT'}, 'description': 'Zun is an OpenStack project which ' @@ -37,7 +37,7 @@ class TestRootController(api_base.FunctionalTest): 'versions': [{'id': 'v1', 'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}], - 'max_version': '1.23', + 'max_version': '1.24', 'min_version': '1.1', 'status': 'CURRENT'}]} diff --git a/zun/tests/unit/api/controllers/v1/test_containers.py b/zun/tests/unit/api/controllers/v1/test_containers.py index 717416d72..b39d83c30 100644 --- a/zun/tests/unit/api/controllers/v1/test_containers.py +++ b/zun/tests/unit/api/controllers/v1/test_containers.py @@ -87,6 +87,24 @@ class TestContainerController(api_base.FunctionalTest): self.post('/v1/containers?run=true', params=params, 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): params = ('{"name": "MyDocker", "image": "ubuntu",' '"command": "env", "memory": "512",' @@ -282,6 +300,7 @@ class TestContainerController(api_base.FunctionalTest): '"runtime": "runc", "hostname": "testhost",' '"disk": 20, "restart_policy": {"Name": "no"},' '"nets": [{"network": "testpublicnet"}],' + '"exposed_ports": {"80/tcp": {}, "22": {}},' '"mounts": [{"source": "s", "destination": "d"}]}') response = self.post('/v1/containers/', params=params, @@ -311,6 +330,10 @@ class TestContainerController(api_base.FunctionalTest): mock_container_create.call_args[1]['requested_volumes'] self.assertEqual(1, len(requested_volumes)) 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 def side_effect(*args, **kwargs): diff --git a/zun/tests/unit/container/docker/test_docker_driver.py b/zun/tests/unit/container/docker/test_docker_driver.py index f95001b6d..9b714a393 100644 --- a/zun/tests/unit/container/docker/test_docker_driver.py +++ b/zun/tests/unit/container/docker/test_docker_driver.py @@ -94,6 +94,8 @@ class TestDockerDriver(base.DriverTestCase): self.driver.images(repo='test') 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' '.connect_container_to_network') @mock.patch('zun.network.kuryr_network.KuryrNetwork' @@ -104,7 +106,9 @@ class TestDockerDriver(base.DriverTestCase): self, mock_save, mock_get_security_group_ids, mock_create_or_update_port, - mock_connect): + mock_connect, + mock_expose_ports, + mock_create_security_group): self.mock_docker.create_host_config = mock.Mock( return_value={'Id1': 'val1', 'key2': 'val2'}) self.mock_docker.create_container = mock.Mock( @@ -122,6 +126,8 @@ class TestDockerDriver(base.DriverTestCase): volumes = [] fake_port = {'mac_address': 'fake_mac'} 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 self.driver._host.sp_disk_quota = True self.driver._host.storage_driver = 'overlay2' @@ -161,6 +167,8 @@ class TestDockerDriver(base.DriverTestCase): self.assertEqual(result_container.status, 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' '.connect_container_to_network') @mock.patch('zun.network.kuryr_network.KuryrNetwork' @@ -171,7 +179,9 @@ class TestDockerDriver(base.DriverTestCase): self, mock_save, mock_get_security_group_ids, mock_create_or_update_port, - mock_connect): + mock_connect, + mock_expose_ports, + mock_create_security_group): self.mock_docker.create_host_config = mock.Mock( return_value={'Id1': 'val1', 'key2': 'val2'}) self.mock_docker.create_container = mock.Mock( @@ -189,6 +199,8 @@ class TestDockerDriver(base.DriverTestCase): volumes = [] fake_port = {'mac_address': 'fake_mac'} 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 self.driver._host.sp_disk_quota = True self.driver._host.storage_driver = 'devicemapper' @@ -229,6 +241,8 @@ class TestDockerDriver(base.DriverTestCase): self.assertEqual(result_container.status, 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' '.connect_container_to_network') @mock.patch('zun.network.kuryr_network.KuryrNetwork' @@ -239,7 +253,9 @@ class TestDockerDriver(base.DriverTestCase): self, mock_save, mock_get_security_group_ids, 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") self.mock_docker.create_host_config = mock.Mock( return_value={'Id1': 'val1', 'key2': 'val2'}) @@ -259,6 +275,8 @@ class TestDockerDriver(base.DriverTestCase): volumes = [] fake_port = {'mac_address': 'fake_mac'} 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, image, networks, volumes) host_config = {} @@ -335,11 +353,15 @@ class TestDockerDriver(base.DriverTestCase): @mock.patch('zun.container.docker.driver.DockerDriver' '._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() mock_container = self.mock_default_container self.driver.delete(self.context, mock_container, True) self.assertTrue(mock_cleanup_network_for_container.called) + self.assertTrue(mock_cleanup_exposed_ports.called) self.mock_docker.remove_container.assert_called_once_with( mock_container.container_id, force=True) @@ -714,6 +736,8 @@ class TestDockerDriver(base.DriverTestCase): self.mock_docker.commit.assert_called_once_with( 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' '.connect_container_to_network') @mock.patch('zun.network.kuryr_network.KuryrNetwork' @@ -722,7 +746,8 @@ class TestDockerDriver(base.DriverTestCase): @mock.patch('zun.common.utils.get_security_group_ids') def test_create_sandbox(self, mock_get_security_group_ids, 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' mock_get_sandbox_name.return_value = sandbox_name self.mock_docker.create_container = mock.Mock( @@ -747,6 +772,8 @@ class TestDockerDriver(base.DriverTestCase): networking_config={'Id': 'val1', 'key1': 'val2'}) 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' '.connect_container_to_network') @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, mock_get_sandbox_name, mock_create_or_update_port, - mock_connect): + mock_connect, + mock_expose_ports, + mock_create_security_group): sandbox_name = 'x' * 100 mock_get_sandbox_name.return_value = sandbox_name self.mock_docker.create_container = mock.Mock( @@ -780,7 +809,8 @@ class TestDockerDriver(base.DriverTestCase): networking_config={'Id': 'val1', 'key1': 'val2'}) 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() mock_container = mock.MagicMock() mock_container.get_sandbox_id.return_value = 'test_sandbox_id' diff --git a/zun/tests/unit/db/utils.py b/zun/tests/unit/db/utils.py index df209139b..aa26020dc 100644 --- a/zun/tests/unit/db/utils.py +++ b/zun/tests/unit/db/utils.py @@ -107,6 +107,7 @@ def get_test_container(**kwargs): {"retries": "2", "timeout": 3, "test": "stat /etc/passwd || exit 1", "interval": 3}), + 'exposed_ports': kwargs.get('exposed_ports', {"80/tcp": {}}), } diff --git a/zun/tests/unit/objects/test_objects.py b/zun/tests/unit/objects/test_objects.py index 3ccf0a417..c399f3520 100644 --- a/zun/tests/unit/objects/test_objects.py +++ b/zun/tests/unit/objects/test_objects.py @@ -344,7 +344,7 @@ class TestObject(test_base.TestCase, _TestObject): # For more information on object version testing, read # https://docs.openstack.org/zun/latest/ object_data = { - 'Container': '1.36-ad2bacdaa51afd0047e96003f93ff181', + 'Container': '1.37-cdc1537de5adf3570b598da1a3728a68', 'VolumeMapping': '1.3-14e3f9fc64e7afd751727c6ad3f32a94', 'Image': '1.2-80504fdd797e9dd86128a91680e876ad', 'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',