From ce8faa8d74f609dc8fb45b81ecadeb39776e3178 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 14 Nov 2024 15:57:47 +0000 Subject: [PATCH] api: Add response body schemas for networks API Yet another proxy API documented, albeit very loosely. We also remove a conditional that can never be reached: we will always have a network from neutron by time we attempt to show it. If we didn't, we'd have exited early due to an exception. Change-Id: I008975b3eabf5f3552ebad7e5bbe847b9c7eaa16 Signed-off-by: Stephen Finucane --- nova/api/openstack/compute/networks.py | 8 +- .../api/openstack/compute/schemas/networks.py | 77 +++++++++++++++++++ nova/tests/unit/policies/test_networks.py | 2 + 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/compute/networks.py b/nova/api/openstack/compute/networks.py index e3a4c27ff5..1ddfefc1ec 100644 --- a/nova/api/openstack/compute/networks.py +++ b/nova/api/openstack/compute/networks.py @@ -37,9 +37,6 @@ _removal_reason_api = _removal_reason % 'API' def network_dict(context, network): - if not network: - return {} - fields = ('id', 'cidr', 'netmask', 'gateway', 'broadcast', 'dns1', 'dns2', 'cidr_v6', 'gateway_v6', 'label', 'netmask_v6') admin_fields = ('created_at', 'updated_at', 'deleted_at', 'deleted', @@ -70,16 +67,18 @@ def network_dict(context, network): return result +@validation.validated class NetworkController(wsgi.Controller): def __init__(self, network_api=None): super(NetworkController, self).__init__() - # TODO(stephenfin): 'network_api' is only being passed for use by tests + # NOTE(stephenfin): 'network_api' is only being passed for use by tests self.network_api = network_api or neutron.API() @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.expected_errors(()) @validation.query_schema(schema.index_query) + @validation.response_body_schema(schema.index_response) def index(self, req): context = req.environ['nova.context'] context.can(net_policies.POLICY_ROOT % 'list', @@ -91,6 +90,7 @@ class NetworkController(wsgi.Controller): @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.expected_errors(404) @validation.query_schema(schema.show_query) + @validation.response_body_schema(schema.show_response) def show(self, req, id): context = req.environ['nova.context'] context.can(net_policies.POLICY_ROOT % 'show', diff --git a/nova/api/openstack/compute/schemas/networks.py b/nova/api/openstack/compute/schemas/networks.py index bfaca68d72..c7f9cf9b1e 100644 --- a/nova/api/openstack/compute/schemas/networks.py +++ b/nova/api/openstack/compute/schemas/networks.py @@ -20,6 +20,83 @@ disassociate = {} index_query = {} show_query = {} +# NOTE(stephenfin): This is a *very* loose schema since this is a deprecated +# API and only the id and label fields are populated with non-null values these +# days. +_network_response = { + 'type': 'object', + 'properties': { + 'bridge': {'type': ['string', 'null']}, + 'bridge_interface': {'type': ['string', 'null']}, + 'broadcast': {'type': ['string', 'null']}, + 'cidr': {'type': ['string', 'null']}, + 'cidr_v6': {'type': ['string', 'null']}, + 'created_at': {'type': ['string', 'null']}, + 'deleted': {'type': ['string', 'null']}, + 'deleted_at': {'type': ['string', 'null']}, + 'dhcp_server': {'type': ['string', 'null']}, + 'dhcp_start': {'type': ['string', 'null']}, + 'dns1': {'type': ['string', 'null']}, + 'dns2': {'type': ['string', 'null']}, + 'enable_dhcp': {'type': ['string', 'null']}, + 'gateway': {'type': ['string', 'null']}, + 'gateway_v6': {'type': ['string', 'null']}, + 'host': {'type': ['string', 'null']}, + 'id': {'type': 'string', 'format': 'string'}, + 'injected': {'type': ['string', 'null']}, + 'label': {'type': 'string'}, + 'multi_host': {'type': ['string', 'null']}, + 'mtu': {'type': ['integer', 'null']}, + 'netmask': {'type': ['string', 'null']}, + 'netmask_v6': {'type': ['string', 'null']}, + 'priority': {'type': ['string', 'null']}, + 'project_id': {'type': ['string', 'null']}, + 'rxtx_base': {'type': ['string', 'null']}, + 'share_address': {'type': ['string', 'null']}, + 'updated_at': {'type': ['string', 'null']}, + 'vlan': {'type': ['string', 'null']}, + 'vpn_private_address': {'type': ['string', 'null']}, + 'vpn_public_address': {'type': ['string', 'null']}, + 'vpn_public_port': {'type': ['integer', 'null']}, + }, + 'required': [ + # admin fields are optional, but the rest will always be shown + 'broadcast', + 'cidr', + 'cidr_v6', + 'dns1', + 'dns2', + 'gateway', + 'gateway_v6', + 'id', + 'label', + 'netmask', + 'netmask_v6', + ], + 'additionalProperties': False, +} + +index_response = { + 'type': 'object', + 'properties': { + 'networks': { + 'type': 'array', + 'items': _network_response, + }, + }, + 'required': ['networks'], + 'additionalProperties': False, +} + +show_response = { + 'type': 'object', + 'properties': { + 'network': _network_response, + }, + 'required': ['network'], + 'additionalProperties': False, +} + disassociate_response = {} delete_response = {} create_response = {} diff --git a/nova/tests/unit/policies/test_networks.py b/nova/tests/unit/policies/test_networks.py index 9c3e0b735a..53fa5a03bd 100644 --- a/nova/tests/unit/policies/test_networks.py +++ b/nova/tests/unit/policies/test_networks.py @@ -51,6 +51,7 @@ class NetworksPolicyTest(base.BasePolicyTest): @mock.patch('nova.network.neutron.API.get_all') def test_list_networks_policy(self, mock_get): + mock_get.return_value = [] rule_name = "os_compute_api:os-networks:list" self.common_policy_auth(self.project_reader_authorized_contexts, rule_name, self.controller.index, @@ -58,6 +59,7 @@ class NetworksPolicyTest(base.BasePolicyTest): @mock.patch('nova.network.neutron.API.get') def test_show_network_policy(self, mock_get): + mock_get.return_value = {'id': uuids.network_id, 'name': 'foo'} rule_name = "os_compute_api:os-networks:show" self.common_policy_auth(self.project_reader_authorized_contexts, rule_name, self.controller.show,