From e5711229c7374a76faea295e81a6f91105488064 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Thu, 8 Mar 2018 04:07:10 +0000 Subject: [PATCH] Support network_attach with more parameters In before, the network_attach API supports only the 'network' parameter. This patch adds three more parameter 'port' and 'fixed_ip'. Change-Id: Ic7d3367f00b8cb034ee6d7d27962168e8cdc0a67 Closes-Bug: #1746654 --- api-ref/source/containers.inc | 4 +- api-ref/source/parameters.yaml | 24 +++++- zun/api/controllers/v1/schemas/containers.py | 32 +++++++- zun/common/utils.py | 12 ++- zun/network/kuryr_network.py | 3 +- .../api/controllers/v1/test_containers.py | 75 +++++++++++++++++++ .../container/docker/test_docker_driver.py | 9 +-- 7 files changed, 141 insertions(+), 18 deletions(-) diff --git a/api-ref/source/containers.inc b/api-ref/source/containers.inc index a81485ac5..b69d369c4 100644 --- a/api-ref/source/containers.inc +++ b/api-ref/source/containers.inc @@ -1026,7 +1026,9 @@ Response Codes .. rest_parameters:: parameters.yaml - container_ident: container_ident - - network: network + - network: network-query + - port: port-query + - fixed_ip: fixed_ip-query Response -------- diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 7e0c8bf6f..209098002 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -56,6 +56,13 @@ exec_run: in: query required: false type: boolean +fixed_ip-query: + description: | + Fixed IP addresses. If you request a specific fixed IP address without + a ``network``, the request returns a Bad Request (400) response code. + in: query + required: false + type: string force: description: | Specify to delete container forcefully. @@ -70,16 +77,31 @@ height: type: string network: description: | - Network to be detached of the container. + The ID or name of the network to be detached from the container. in: query required: true type: string +network-query: + description: | + The ID or name of the network to be attached to the container. + in: query + required: false + type: string new_name: description: | The new name for the container. in: query required: true type: string +port-query: + description: | + The ID or name of the port for which you want to bind to the container. + The ``network`` and ``port`` parameters are mutually exclusive. + If you do not specify the ``port`` parameter, Zun will allocate a port + and bind the port to the container. + in: query + required: false + type: string ps_args: description: | The arguments of ps command. diff --git a/zun/api/controllers/v1/schemas/containers.py b/zun/api/controllers/v1/schemas/containers.py index 41c4cc8a8..5cf49aa85 100644 --- a/zun/api/controllers/v1/schemas/containers.py +++ b/zun/api/controllers/v1/schemas/containers.py @@ -184,4 +184,34 @@ network_detach = { 'additionalProperties': False } -network_attach = copy.deepcopy(network_detach) +network_attach = { + 'type': 'object', + 'properties': { + 'network': { + 'type': ['string'], + 'minLength': 1, + 'maxLength': 255, + }, + 'fixed_ip': { + 'type': ['string'], + 'oneOf': [ + {'format': 'ipv4'}, + {'format': 'ipv6'} + ] + }, + 'port': { + 'type': ['string'], + 'maxLength': 255, + 'minLength': 1, + } + }, + 'additionalProperties': False, + 'oneOf': [ + { + 'required': ['network'] + }, + { + 'required': ['port'] + } + ] +} diff --git a/zun/common/utils.py b/zun/common/utils.py index 67611fec6..6a3e17d14 100644 --- a/zun/common/utils.py +++ b/zun/common/utils.py @@ -500,8 +500,7 @@ def build_requested_networks(context, nets): 'router:external': network.get('router:external'), 'shared': network.get('shared'), - 'v4-fixed-ip': '', - 'v6-fixed-ip': '', + 'fixed_ip': '', 'preserve_on_delete': True}) elif net.get('network'): network = neutron_api.get_neutron_network(net['network']) @@ -510,9 +509,9 @@ def build_requested_networks(context, nets): 'router:external': network.get('router:external'), 'shared': network.get('shared'), - 'v4-fixed-ip': - net.get('v4-fixed-ip', ''), - 'v6-fixed-ip': + 'fixed_ip': + net.get('fixed_ip') or + net.get('v4-fixed-ip', '') or net.get('v6-fixed-ip', ''), 'preserve_on_delete': False}) if not requested_networks: @@ -521,8 +520,7 @@ def build_requested_networks(context, nets): neutron_net = neutron_api.get_available_network() requested_networks.append({'network': neutron_net['id'], 'port': '', - 'v4-fixed-ip': '', - 'v6-fixed-ip': '', + 'fixed_ip': '', 'preserve_on_delete': False}) check_external_network_attach(context, requested_networks) diff --git a/zun/network/kuryr_network.py b/zun/network/kuryr_network.py index faea537fe..ce4b7e40f 100644 --- a/zun/network/kuryr_network.py +++ b/zun/network/kuryr_network.py @@ -229,8 +229,7 @@ class KuryrNetwork(network.Network): 'tenant_id': self.context.project_id, 'device_id': container.uuid, } - ip_addr = requested_network.get("v4-fixed-ip") or requested_network.\ - get("v6-fixed-ip") + ip_addr = requested_network.get("fixed_ip") if ip_addr: port_dict['fixed_ips'] = [{'ip_address': ip_addr}] if security_groups is not None: diff --git a/zun/tests/unit/api/controllers/v1/test_containers.py b/zun/tests/unit/api/controllers/v1/test_containers.py index bc7498e17..acf968fa2 100644 --- a/zun/tests/unit/api/controllers/v1/test_containers.py +++ b/zun/tests/unit/api/controllers/v1/test_containers.py @@ -1811,6 +1811,81 @@ class TestContainerController(api_base.FunctionalTest): "use an ID to be more specific." % security_group_to_add, response.json['errors'][0]['detail']) + @patch('zun.network.neutron.NeutronAPI.get_neutron_network') + @patch('zun.compute.api.API.network_attach') + @patch('zun.objects.Container.get_by_uuid') + def test_network_attach(self, mock_by_uuid, mock_attach, mock_get_network): + built_requested_network = { + 'network': 'fake-net-id', + 'port': '', + 'router:external': False, + 'shared': False, + 'fixed_ip': '', + 'preserve_on_delete': False} + query = 'network=private' + self._test_network_attach(mock_by_uuid, mock_attach, mock_get_network, + query, built_requested_network) + mock_get_network.assert_called_once_with('private') + + @patch('zun.network.neutron.NeutronAPI.get_neutron_network') + @patch('zun.compute.api.API.network_attach') + @patch('zun.objects.Container.get_by_uuid') + def test_network_attach_with_fixed_ip(self, mock_by_uuid, mock_attach, + mock_get_network): + built_requested_network = { + 'network': 'fake-net-id', + 'port': '', + 'router:external': False, + 'shared': False, + 'fixed_ip': '10.0.0.3', + 'preserve_on_delete': False} + query = 'network=private&fixed_ip=10.0.0.3' + self._test_network_attach(mock_by_uuid, mock_attach, mock_get_network, + query, built_requested_network) + mock_get_network.assert_called_once_with('private') + + @patch('zun.network.neutron.NeutronAPI.get_neutron_port') + @patch('zun.network.neutron.NeutronAPI.ensure_neutron_port_usable') + @patch('zun.network.neutron.NeutronAPI.get_neutron_network') + @patch('zun.compute.api.API.network_attach') + @patch('zun.objects.Container.get_by_uuid') + def test_network_attach_with_port(self, mock_by_uuid, mock_attach, + mock_get_network, + mock_ensure, mock_get_port): + mock_get_port.return_value = { + 'id': 'fake-port-id', + 'network_id': 'fake-net-id', + } + built_requested_network = { + 'network': 'fake-net-id', + 'port': 'fake-port-id', + 'router:external': False, + 'shared': False, + 'fixed_ip': '', + 'preserve_on_delete': True} + query = 'port=fake-port' + self._test_network_attach(mock_by_uuid, mock_attach, mock_get_network, + query, built_requested_network) + mock_get_port.assert_called_once_with('fake-port') + mock_get_network.assert_called_once_with('fake-net-id') + + def _test_network_attach(self, mock_by_uuid, mock_attach, mock_get_network, + query, built_requested_network): + test_container = utils.get_test_container() + test_container_obj = objects.Container(self.context, **test_container) + mock_by_uuid.return_value = test_container_obj + container_uuid = test_container.get('uuid') + fake_network = {'id': 'fake-net-id', + 'router:external': False, + 'shared': False} + mock_get_network.return_value = fake_network + url = '/v1/containers/%s/%s?%s' % (container_uuid, 'network_attach', + query) + response = self.post(url) + self.assertEqual(200, response.status_int) + mock_attach.assert_called_once_with(mock.ANY, test_container_obj, + built_requested_network) + @patch('zun.network.neutron.NeutronAPI.get_neutron_network') @patch('zun.compute.api.API.network_detach') @patch('zun.objects.Container.get_by_uuid') diff --git a/zun/tests/unit/container/docker/test_docker_driver.py b/zun/tests/unit/container/docker/test_docker_driver.py index 4f17a93ad..38691a250 100644 --- a/zun/tests/unit/container/docker/test_docker_driver.py +++ b/zun/tests/unit/container/docker/test_docker_driver.py @@ -798,8 +798,7 @@ class TestDockerDriver(base.DriverTestCase): mock_list.return_value = {'network': 'network'} requested_network = {'network': 'network', 'port': '', - 'v4-fixed-ip': '', - 'v6-fixed-ip': '', + 'fixed_ip': '', 'preserve_on_delete': False} self.driver.network_attach(self.context, mock_container, requested_network) @@ -814,8 +813,7 @@ class TestDockerDriver(base.DriverTestCase): mock_container.addresses = {'already-attached-net': []} requested_network = {'network': 'already-attached-net', 'port': '', - 'v4-fixed-ip': '', - 'v6-fixed-ip': '', + 'fixed_ip': '', 'preserve_on_delete': False} self.assertRaises(exception.ZunException, self.driver.network_attach, @@ -837,8 +835,7 @@ class TestDockerDriver(base.DriverTestCase): mock_get_sec_group_id.return_value = test_sec_group_id requested_network = {'network': 'network', 'port': '', - 'v4-fixed-ip': '', - 'v6-fixed-ip': '', + 'fixed_ip': '', 'preserve_on_delete': False} self.driver.network_attach(self.context, mock_container, requested_network)