From 53c7f6bb9cd13b238445768dd92ef73d2f5602aa Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Sat, 16 Jun 2018 22:10:47 +0000 Subject: [PATCH] Introduce 'fixed_ips' to network_list Modify the response of network_list API. Add a 'fixed_ips' field that contains the list of IP addresses and their subnet IDs. This change is primarily for aligning the format with nova and neutron so that API consumers can use the same parsing code. The first use case is from Heat. Heat would be able to use the same code for parsing nova's interface_list and zun's network_list. Change-Id: Id1e652d944be852697f18958899e395918ab0885 --- api-ref/source/containers.inc | 1 + api-ref/source/parameters.yaml | 11 ++++- .../samples/container-network-list-resp.json | 10 +++-- zun/api/controllers/v1/containers.py | 40 +++++++++++++++++-- zun/api/controllers/versions.py | 3 +- zun/api/rest_api_version_history.rst | 21 ++++++++++ zun/tests/unit/api/base.py | 2 +- zun/tests/unit/api/controllers/test_root.py | 4 +- .../api/controllers/v1/test_containers.py | 31 +++++++++++--- 9 files changed, 103 insertions(+), 20 deletions(-) diff --git a/api-ref/source/containers.inc b/api-ref/source/containers.inc index 57d2c7fb4..b47e2548a 100644 --- a/api-ref/source/containers.inc +++ b/api-ref/source/containers.inc @@ -1276,6 +1276,7 @@ Response - port_id: port_id - version: version - ip_address: ip_address + - fixed_ips: fixed_ips Response Example ---------------- diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 209098002..34e9e02bd 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -427,6 +427,13 @@ exec_url: The URL to start an exec instance. in: body type: dict +fixed_ips: + description: | + A list of fixed IP addresses with subnet IDs and other detailed + information. + in: body + required: true + type: string forced_down: description: | Whether or not this service was forced down manually by an @@ -608,7 +615,7 @@ interactive-request: type: boolean ip_address: description: | - The IP address of the port. + The IP address. in: body required: true type: string @@ -834,7 +841,7 @@ status_reason: type: string subnet_id: description: | - The UUID of subnet of a container. + The UUID of subnet. in: body required: true type: string diff --git a/api-ref/source/samples/container-network-list-resp.json b/api-ref/source/samples/container-network-list-resp.json index 47c1671dc..dabe60584 100644 --- a/api-ref/source/samples/container-network-list-resp.json +++ b/api-ref/source/samples/container-network-list-resp.json @@ -1,11 +1,13 @@ { "networks": [ { - "subnet_id": "ae8d7cce-859e-432f-8a33-d7d8834ccd14", "port_id": "5be06e49-70dc-4984-94a2-1b946bb136fb", - "version": 4, - "ip_address": "30.30.30.10", - "net_id": "7e6b5e1b-9b44-4f55-b4e3-16a1ead98161" + "net_id": "7e6b5e1b-9b44-4f55-b4e3-16a1ead98161", + "fixed_ips": { + "ip_address": "30.30.30.10", + "version": 4, + "subnet_id": "ae8d7cce-859e-432f-8a33-d7d8834ccd14" + } } ] } diff --git a/zun/api/controllers/v1/containers.py b/zun/api/controllers/v1/containers.py index 4f9582b60..91c38831a 100644 --- a/zun/api/controllers/v1/containers.py +++ b/zun/api/controllers/v1/containers.py @@ -1039,7 +1039,32 @@ class ContainersController(base.Controller): requested_networks = utils.build_requested_networks(context, [kwargs]) compute_api.network_attach(context, container, requested_networks[0]) - @base.Controller.api_version("1.13") + @base.Controller.api_version("1.13", "1.17") + @pecan.expose('json') + @exception.wrap_pecan_controller_exception + def network_list(self, container_ident): + """Retrieve a list of networks of the container. + + :param container_ident: UUID or Name of a container. + """ + container = utils.get_container(container_ident) + container_networks = self._get_container_networks_legacy(container) + return {'networks': container_networks} + + def _get_container_networks_legacy(self, container): + container_networks = [] + for net_id, net_infos in container.addresses.items(): + for net_info in net_infos: + container_networks.append({ + 'net_id': net_id, + 'subnet_id': net_info.get("subnet_id"), + 'port_id': net_info.get("port"), + 'version': net_info.get("version"), + 'ip_address': net_info.get("addr") + }) + return container_networks + + @base.Controller.api_version("1.18") # noqa @pecan.expose('json') @exception.wrap_pecan_controller_exception def network_list(self, container_ident): @@ -1054,12 +1079,19 @@ class ContainersController(base.Controller): def _get_container_networks(self, container): container_networks = [] for net_id, net_infos in container.addresses.items(): + addresses = {} for net_info in net_infos: - container_networks.append({ - 'net_id': net_id, + port_id = net_info["port"] + addresses.setdefault(port_id, []) + addresses[port_id].append({ 'subnet_id': net_info.get("subnet_id"), - 'port_id': net_info.get("port"), 'version': net_info.get("version"), 'ip_address': net_info.get("addr") }) + for port_id, fixed_ips in addresses.items(): + container_networks.append({ + 'net_id': net_id, + 'port_id': port_id, + 'fixed_ips': fixed_ips, + }) return container_networks diff --git a/zun/api/controllers/versions.py b/zun/api/controllers/versions.py index d4e8c7d02..1ae9c6aeb 100644 --- a/zun/api/controllers/versions.py +++ b/zun/api/controllers/versions.py @@ -50,10 +50,11 @@ REST_API_VERSION_HISTORY = """REST API Version History: * 1.15 - Remove add_security_group and remove_security_group * 1.16 - Modify restart_policy to capsule spec content * 1.17 - Add support for detaching ports + * 1.18 - Modify the response of network list """ BASE_VER = '1.1' -CURRENT_MAX_VER = '1.17' +CURRENT_MAX_VER = '1.18' class Version(object): diff --git a/zun/api/rest_api_version_history.rst b/zun/api/rest_api_version_history.rst index 669c1391f..0409c71d2 100644 --- a/zun/api/rest_api_version_history.rst +++ b/zun/api/rest_api_version_history.rst @@ -140,3 +140,24 @@ user documentation. Add parameter ``port`` to the network_detach API. This allow users to detach a container from a neutron port. + +1.18 +---- + + Modify the response of network_list + (GET /v1/containers/{container_ident}/network_list) API. The normal response + will be something like:: + + { + "networks": [ + { + "port_id": "5be06e49-70dc-4984-94a2-1b946bb136fb", + "net_id": "7e6b5e1b-9b44-4f55-b4e3-16a1ead98161", + "fixed_ips" [ + "ip_address": "30.30.30.10", + "version": 4, + "subnet_id": "ae8d7cce-859e-432f-8a33-d7d8834ccd14" + ] + } + ] + } diff --git a/zun/tests/unit/api/base.py b/zun/tests/unit/api/base.py index 86532ab88..6bd2cc0c3 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.14" +CURRENT_VERSION = "container 1.18" 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 9d15cc953..fbfbf9321 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.17', + 'max_version': '1.18', '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.17', + 'max_version': '1.18', '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 4ccc905d7..5809d6ca7 100644 --- a/zun/tests/unit/api/controllers/v1/test_containers.py +++ b/zun/tests/unit/api/controllers/v1/test_containers.py @@ -1749,6 +1749,7 @@ class TestContainerController(api_base.FunctionalTest): def test_add_security_group_by_uuid(self, mock_get_resource, mock_find_resourceid, mock_add_security_group): + headers = {"OpenStack-API-Version": "container 1.14"} test_container = utils.get_test_container() test_container_obj = objects.Container(self.context, **test_container) mock_get_resource.return_value = test_container_obj @@ -1758,7 +1759,7 @@ class TestContainerController(api_base.FunctionalTest): url = '/v1/containers/%s/%s?name=%s' % (container_name, 'add_security_group', security_group_id_to_add) - response = self.post(url) + response = self.post(url, headers=headers) self.assertEqual(202, response.status_int) self.assertEqual('application/json', response.content_type) mock_find_resourceid.assert_called_once_with( @@ -1772,6 +1773,7 @@ class TestContainerController(api_base.FunctionalTest): def test_add_security_group_not_found(self, mock_get_resource, mock_find_resourceid, mock_add_security_group): + headers = {"OpenStack-API-Version": "container 1.14"} test_container = utils.get_test_container() test_container_obj = objects.Container(self.context, **test_container) mock_get_resource.return_value = test_container_obj @@ -1781,7 +1783,7 @@ class TestContainerController(api_base.FunctionalTest): url = '/v1/containers/%s/%s?name=%s' % (container_name, 'add_security_group', security_group_to_add) - response = self.post(url, expect_errors=True) + response = self.post(url, expect_errors=True, headers=headers) self.assertEqual(400, response.status_int) self.assertEqual('application/json', response.content_type) self.assertEqual( @@ -1794,6 +1796,7 @@ class TestContainerController(api_base.FunctionalTest): def test_add_security_group_not_unique_match(self, mock_get_resource, mock_find_resourceid, mock_add_security_group): + headers = {"OpenStack-API-Version": "container 1.14"} test_container = utils.get_test_container() test_container_obj = objects.Container(self.context, **test_container) mock_get_resource.return_value = test_container_obj @@ -1803,7 +1806,7 @@ class TestContainerController(api_base.FunctionalTest): url = '/v1/containers/%s/%s?name=%s' % (container_name, 'add_security_group', security_group_to_add) - response = self.post(url, expect_errors=True) + response = self.post(url, expect_errors=True, headers=headers) self.assertEqual(409, response.status_int) self.assertEqual('application/json', response.content_type) self.assertEqual( @@ -1935,8 +1938,23 @@ class TestContainerController(api_base.FunctionalTest): mock_container_get_by_uuid.assert_called_once_with( mock.ANY, test_container['uuid']) - self.assertEqual(test_container['addresses']['private'][0]['port'], - response.json['networks'][0]['port_id']) + self._assert_networks(test_container['addresses'], + response.json['networks']) + + def _assert_networks(self, addresses, networks): + self.assertEqual(len(addresses), len(networks)) + for network in networks: + address_list = addresses[network['net_id']] + self.assertEqual(len(address_list), len(network['fixed_ips'])) + for address in address_list: + matched = 0 + for fixed_ip in network['fixed_ips']: + if (address['addr'] == fixed_ip['ip_address'] and + address['version'] == fixed_ip['version'] and + address['subnet_id'] == fixed_ip['subnet_id'] and + address['port'] == network['port_id']): + matched += 1 + self.assertEqual(1, matched) @mock.patch('zun.compute.api.API.remove_security_group') @mock.patch('zun.network.neutron.NeutronAPI.find_resourceid_by_name_or_id') @@ -1944,6 +1962,7 @@ class TestContainerController(api_base.FunctionalTest): def test_remove_security_group_by_uuid(self, mock_get_resource, mock_find_resourceid, mock_remove_security_group): + headers = {"OpenStack-API-Version": "container 1.14"} test_container = utils.get_test_container( security_groups=['affb9021-964d-4b1b-80a8-9b9db60497e4']) test_container_obj = objects.Container(self.context, **test_container) @@ -1955,7 +1974,7 @@ class TestContainerController(api_base.FunctionalTest): url = '/v1/containers/%s/%s?name=%s' % (container_name, 'remove_security_group', security_group_id_to_remove) - response = self.post(url) + response = self.post(url, headers=headers) self.assertEqual(202, response.status_int) self.assertEqual('application/json', response.content_type) mock_find_resourceid.assert_called_once_with(