From f7a03e5834fbd38a0797a0910fd4a0abb07d4f3d Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 27 Aug 2024 23:44:19 +0100 Subject: [PATCH] api: Add response body schemas for hosts APIs Change-Id: I9b045e9d8d344f2cd8caf7fcbd45f0d8b610da04 Signed-off-by: Stephen Finucane --- nova/api/openstack/compute/hosts.py | 21 ++++-- nova/api/openstack/compute/schemas/hosts.py | 83 +++++++++++++++++++++ nova/tests/unit/policies/test_hosts.py | 20 +++-- 3 files changed, 110 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/compute/hosts.py b/nova/api/openstack/compute/hosts.py index 559cac4d8d..dfeae2bf7a 100644 --- a/nova/api/openstack/compute/hosts.py +++ b/nova/api/openstack/compute/hosts.py @@ -19,7 +19,7 @@ from oslo_log import log as logging import webob.exc from nova.api.openstack import common -from nova.api.openstack.compute.schemas import hosts +from nova.api.openstack.compute.schemas import hosts as schema from nova.api.openstack import wsgi from nova.api import validation from nova.compute import api as compute @@ -31,6 +31,7 @@ from nova.policies import hosts as hosts_policies LOG = logging.getLogger(__name__) +@validation.validated class HostController(wsgi.Controller): """The Hosts API controller for the OpenStack API.""" @@ -39,8 +40,9 @@ class HostController(wsgi.Controller): self.api = compute.HostAPI() @wsgi.api_version("2.1", "2.42") - @validation.query_schema(hosts.index_query) @wsgi.expected_errors(()) + @validation.query_schema(schema.index_query) + @validation.response_body_schema(schema.index_response) def index(self, req): """Returns a dict in the format @@ -90,7 +92,8 @@ class HostController(wsgi.Controller): @wsgi.api_version("2.1", "2.42") @wsgi.expected_errors((400, 404, 501)) - @validation.schema(hosts.update) + @validation.schema(schema.update) + @validation.response_body_schema(schema.update_response) def update(self, req, id, body): """Return booleanized version of body dict. @@ -182,7 +185,8 @@ class HostController(wsgi.Controller): @wsgi.api_version("2.1", "2.42") @wsgi.expected_errors((400, 404, 501)) - @validation.query_schema(hosts.startup_query) + @validation.query_schema(schema.startup_query) + @validation.response_body_schema(schema.startup_response) def startup(self, req, id): context = req.environ['nova.context'] context.can(hosts_policies.POLICY_NAME % 'start', @@ -191,7 +195,8 @@ class HostController(wsgi.Controller): @wsgi.api_version("2.1", "2.42") @wsgi.expected_errors((400, 404, 501)) - @validation.query_schema(hosts.shutdown_query) + @validation.query_schema(schema.shutdown_query) + @validation.response_body_schema(schema.shutdown_response) def shutdown(self, req, id): context = req.environ['nova.context'] context.can(hosts_policies.POLICY_NAME % 'shutdown', @@ -200,7 +205,8 @@ class HostController(wsgi.Controller): @wsgi.api_version("2.1", "2.42") @wsgi.expected_errors((400, 404, 501)) - @validation.query_schema(hosts.reboot_query) + @validation.query_schema(schema.reboot_query) + @validation.response_body_schema(schema.reboot_response) def reboot(self, req, id): context = req.environ['nova.context'] context.can(hosts_policies.POLICY_NAME % 'reboot', @@ -258,7 +264,8 @@ class HostController(wsgi.Controller): @wsgi.api_version("2.1", "2.42") @wsgi.expected_errors(404) - @validation.query_schema(hosts.show_query) + @validation.query_schema(schema.show_query) + @validation.response_body_schema(schema.show_response) def show(self, req, id): """Shows the physical/usage resource given by hosts. diff --git a/nova/api/openstack/compute/schemas/hosts.py b/nova/api/openstack/compute/schemas/hosts.py index 532ce248af..b88d451e75 100644 --- a/nova/api/openstack/compute/schemas/hosts.py +++ b/nova/api/openstack/compute/schemas/hosts.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + from nova.api.validation import parameter_types update = { @@ -55,3 +57,84 @@ startup_query = {} shutdown_query = {} reboot_query = {} show_query = {} + +index_response = { + 'type': 'object', + 'properties': { + 'hosts': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'host_name': {'type': 'string'}, + # TODO(stephenfin): This should be an enum + 'service': {'type': 'string'}, + 'zone': {'type': 'string'}, + }, + 'required': ['host_name', 'service', 'zone'], + 'additionalProperties': False, + }, + }, + }, + 'required': ['hosts'], + 'additionalProperties': False, +} + +show_response = { + 'type': 'object', + 'properties': { + 'host': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'resource': { + 'type': 'object', + 'properties': { + 'cpu': {'type': 'integer'}, + 'disk_gb': {'type': 'integer'}, + 'host': {'type': 'string'}, + 'memory_mb': {'type': 'integer'}, + 'project': {'type': 'string'}, + }, + 'required': [ + 'cpu', 'disk_gb', 'host', 'memory_mb', 'project' + ], + 'additionalProperties': False, + }, + }, + 'required': ['resource'], + 'additionalProperties': False, + }, + }, + }, + 'required': ['host'], + 'additionalProperties': False, +} + +update_response = { + 'type': 'object', + 'properties': { + 'host': {'type': 'string'}, + 'maintenance_mode': {'enum': ['on_maintenance', 'off_maintenance']}, + 'status': {'enum': ['enabled', 'disabled']}, + }, + 'required': ['host'], + 'additionalProperties': False, +} + +_power_action_response = { + 'type': 'object', + 'properties': { + 'host': {'type': 'string'}, + # NOTE(stephenfin): This is virt driver specific and the API is + # deprecated, so this is left empty + 'power_action': {}, + }, + 'required': ['host', 'power_action'], + 'additionalProperties': False, +} + +startup_response = copy.deepcopy(_power_action_response) +shutdown_response = copy.deepcopy(_power_action_response) +reboot_response = copy.deepcopy(_power_action_response) diff --git a/nova/tests/unit/policies/test_hosts.py b/nova/tests/unit/policies/test_hosts.py index c904077fe2..11ca5f60a5 100644 --- a/nova/tests/unit/policies/test_hosts.py +++ b/nova/tests/unit/policies/test_hosts.py @@ -13,6 +13,7 @@ from unittest import mock from nova.api.openstack.compute import hosts +from nova import objects from nova.policies import base as base_policy from nova.policies import hosts as policies from nova.tests.unit.api.openstack import fakes @@ -48,14 +49,18 @@ class HostsPolicyTest(base.BasePolicyTest): @mock.patch('nova.context.set_target_cell') @mock.patch('nova.objects.HostMapping.get_by_host') - @mock.patch('nova.objects.ComputeNode.' - 'get_first_node_by_host_for_old_compat') + @mock.patch('nova.objects.ComputeNode.get_first_node_by_host_for_old_compat') # noqa: E501 @mock.patch('nova.compute.api.HostAPI.instance_get_all_by_host') def test_show_host_policy(self, mock_get, mock_node, mock_map, mock_set): + mock_get.return_value = [] + mock_node.return_value = objects.ComputeNode( + vcpus=16, memory_mb=8192, local_gb=1000, + vcpus_used=4, memory_mb_used=1024, local_gb_used=10, + ) rule_name = policies.POLICY_NAME % 'show' self.common_policy_auth(self.project_admin_authorized_contexts, rule_name, self.controller.show, - self.req, 11111) + self.req, 'hostname') @mock.patch('nova.compute.api.HostAPI.set_host_enabled') def test_update_host_policy(self, mock_set_host_enabled): @@ -63,28 +68,29 @@ class HostsPolicyTest(base.BasePolicyTest): rule_name = policies.POLICY_NAME % 'update' self.common_policy_auth(self.project_admin_authorized_contexts, rule_name, self.controller.update, - self.req, 11111, body={'status': 'enable'}) + self.req, 'hostname', + body={'status': 'enable'}) @mock.patch('nova.compute.api.HostAPI.host_power_action') def test_reboot_host_policy(self, mock_action): rule_name = policies.POLICY_NAME % 'reboot' self.common_policy_auth(self.project_admin_authorized_contexts, rule_name, self.controller.reboot, - self.req, 11111) + self.req, 'hostname') @mock.patch('nova.compute.api.HostAPI.host_power_action') def test_shutdown_host_policy(self, mock_action): rule_name = policies.POLICY_NAME % 'shutdown' self.common_policy_auth(self.project_admin_authorized_contexts, rule_name, self.controller.shutdown, - self.req, 11111) + self.req, 'hostname') @mock.patch('nova.compute.api.HostAPI.host_power_action') def test_startup_host_policy(self, mock_action): rule_name = policies.POLICY_NAME % 'start' self.common_policy_auth(self.project_admin_authorized_contexts, rule_name, self.controller.startup, - self.req, 11111) + self.req, 'hostname') class HostsNoLegacyNoScopePolicyTest(HostsPolicyTest):