diff --git a/nova/api/openstack/compute/hypervisors.py b/nova/api/openstack/compute/hypervisors.py index 4c32061b41..3b2201f088 100644 --- a/nova/api/openstack/compute/hypervisors.py +++ b/nova/api/openstack/compute/hypervisors.py @@ -255,6 +255,11 @@ class HypervisorsController(wsgi.Controller): @validation.query_schema(schema.index_query, '2.1', '2.32') @validation.query_schema(schema.index_query_v233, '2.33', '2.52') @validation.query_schema(schema.index_query_v253, '2.53') + @validation.response_body_schema(schema.detail_response, '2.1', '2.26') + @validation.response_body_schema(schema.detail_response_v227, '2.27', '2.32') # noqa: E501 + @validation.response_body_schema(schema.detail_response_v233, '2.33', '2.52') # noqa: E501 + @validation.response_body_schema(schema.detail_response_v253, '2.53', '2.87') # noqa: E501 + @validation.response_body_schema(schema.detail_response_v288, '2.88') def detail(self, req): """List hypervisors with extra details. @@ -307,6 +312,10 @@ class HypervisorsController(wsgi.Controller): @wsgi.expected_errors((400, 404), '2.53') @validation.query_schema(schema.show_query, '2.1', '2.52') @validation.query_schema(schema.show_query_v253, '2.53') + @validation.response_body_schema(schema.show_response, '2.1', '2.26') + @validation.response_body_schema(schema.show_response_v227, '2.27', '2.52') + @validation.response_body_schema(schema.show_response_v253, '2.53', '2.87') + @validation.response_body_schema(schema.show_response_v288, '2.88') def show(self, req, id): """Show a hypervisor. @@ -365,6 +374,8 @@ class HypervisorsController(wsgi.Controller): @wsgi.api_version('2.1', '2.87') @wsgi.expected_errors((400, 404, 501)) @validation.query_schema(schema.uptime_query) + @validation.response_body_schema(schema.uptime_response, '2.1', '2.52') + @validation.response_body_schema(schema.uptime_response_v253, '2.53', '2.87') # noqa: E501 def uptime(self, req, id): """Prior to microversion 2.88, you could retrieve a special version of the hypervisor detail view that included uptime. Starting in 2.88, this diff --git a/nova/api/openstack/compute/schemas/hypervisors.py b/nova/api/openstack/compute/schemas/hypervisors.py index ec8ee37e50..daa30fdce8 100644 --- a/nova/api/openstack/compute/schemas/hypervisors.py +++ b/nova/api/openstack/compute/schemas/hypervisors.py @@ -104,6 +104,95 @@ _hypervisor_response_v253['properties'].update({ 'servers': copy.deepcopy(_servers_response), }) +_hypervisor_detail_response = copy.deepcopy(_hypervisor_response) +_hypervisor_detail_response['properties'].update({ + 'cpu_info': {'type': 'string'}, + 'current_workload': {'type': 'integer'}, + 'disk_available_least': {'type': 'integer'}, + 'free_disk_gb': {'type': 'integer'}, + 'free_ram_mb': {'type': 'integer'}, + 'host_ip': {'type': 'string'}, + 'hypervisor_type': {'type': 'string'}, + 'hypervisor_version': {'type': ['string', 'integer']}, + 'local_gb': {'type': 'integer'}, + 'local_gb_used': {'type': 'integer'}, + 'memory_mb': {'type': 'integer'}, + 'memory_mb_used': {'type': 'integer'}, + 'running_vms': {'type': 'integer'}, + 'service': { + 'type': 'object', + 'properties': { + 'disabled_reason': {'type': ['null', 'string']}, + 'host': {'type': 'string'}, + 'id': {'type': 'integer'}, + }, + 'required': ['disabled_reason', 'host', 'id'], + }, + 'vcpus': {'type': 'integer'}, + 'vcpus_used': {'type': 'integer'}, +}) +_hypervisor_detail_response['required'].extend([ + 'cpu_info', + 'current_workload', + 'free_disk_gb', + 'free_ram_mb', + 'local_gb', + 'local_gb_used', + 'memory_mb', + 'memory_mb_used', + 'running_vms', + 'service', + 'vcpus', + 'vcpus_used', +]) + +_hypervisor_detail_response_v227 = copy.deepcopy(_hypervisor_detail_response) +_hypervisor_detail_response_v227['properties'].update({ + 'cpu_info': { + # NOTE(stephenfin): This is virt-driver specific hence no schema + 'type': 'object', + 'properties': {}, + 'required': [], + 'additionalProperties': True, + }, +}) + +_hypervisor_detail_response_v253 = copy.deepcopy( + _hypervisor_detail_response_v227 +) +_hypervisor_detail_response_v253['properties'].update( + _hypervisor_response_v253['properties'] +) +_hypervisor_detail_response_v253['properties']['service'][ + 'properties' +].update({ + 'id': {'type': 'string', 'format': 'uuid'}, +}) + +_hypervisor_detail_response_v288 = copy.deepcopy( + _hypervisor_detail_response_v253 +) +for field in { + 'cpu_info', + 'current_workload', + 'free_disk_gb', + 'free_ram_mb', + 'local_gb', + 'local_gb_used', + 'memory_mb', + 'memory_mb_used', + 'running_vms', + 'vcpus', + 'vcpus_used', +}: + del _hypervisor_detail_response_v288['properties'][field] + _hypervisor_detail_response_v288['required'].remove(field) + +_hypervisor_detail_response_v288['properties'].update({ + 'uptime': {'type': ['string', 'null']} +}) +_hypervisor_detail_response_v288['required'].append('uptime') + index_response = { 'type': 'object', 'properties': { @@ -137,3 +226,82 @@ servers_response = copy.deepcopy(index_response) servers_response['properties']['hypervisors']['items']['properties'].update({ 'servers': copy.deepcopy(_servers_response), }) + +detail_response = copy.deepcopy(index_response) +detail_response['properties']['hypervisors'][ + 'items' +] = _hypervisor_detail_response + +# v2.27 changes the 'cpu_info' field from a stringified object to a real object +detail_response_v227 = copy.deepcopy(detail_response) +detail_response_v227['properties']['hypervisors'][ + 'items' +] = _hypervisor_detail_response_v227 + +# v2.33 adds the hypervisors_links field +detail_response_v233 = copy.deepcopy(detail_response_v227) +detail_response_v233['properties'].update({ + 'hypervisors_links': response_types.collection_links, +}) + +# v2.53 adds the 'servers' field but only if a user requests it via the +# 'with_servers' query arg. It also changes the 'id' field to a UUID. Note that +# v2.75 makes the 'servers' property always present even if empty, but that's +# not something we can capture with jsonschema so we don't try +detail_response_v253 = copy.deepcopy(detail_response_v233) +detail_response_v253['properties']['hypervisors'][ + 'items' +] = _hypervisor_detail_response_v253 + +# v2.88 drops a whole lot of fields that were now duplicated in placement. It +# also adds the uptime field into the response rather than a separate API +detail_response_v288 = copy.deepcopy(detail_response_v253) +detail_response_v288['properties']['hypervisors'][ + 'items' +] = _hypervisor_detail_response_v288 + +show_response = { + 'type': 'object', + 'properties': { + 'hypervisor': copy.deepcopy(_hypervisor_detail_response), + }, + 'required': ['hypervisor'], + 'additionalProperties': False, +} + +show_response_v227 = copy.deepcopy(show_response) +show_response_v227['properties']['hypervisor'] = copy.deepcopy( + _hypervisor_detail_response_v227 +) + +show_response_v253 = copy.deepcopy(show_response_v227) +show_response_v253['properties']['hypervisor'] = copy.deepcopy( + _hypervisor_detail_response_v253 +) + +show_response_v288 = copy.deepcopy(show_response_v253) +show_response_v288['properties']['hypervisor'] = copy.deepcopy( + _hypervisor_detail_response_v288 +) + +uptime_response = { + 'type': 'object', + 'properties': { + 'hypervisor': copy.deepcopy(_hypervisor_response), + }, + 'required': ['hypervisor'], + 'additionalProperties': False, +} +uptime_response['properties']['hypervisor']['properties'].update({ + 'uptime': {'type': ['string', 'null']} +}) +uptime_response['properties']['hypervisor']['required'].append('uptime') + +uptime_response_v253 = copy.deepcopy(uptime_response) +uptime_response_v253['properties']['hypervisor'] = copy.deepcopy( + _hypervisor_response_v253 +) +uptime_response_v253['properties']['hypervisor']['properties'].update({ + 'uptime': {'type': ['string', 'null']} +}) +uptime_response_v253['properties']['hypervisor']['required'].append('uptime') diff --git a/nova/tests/unit/policies/test_hypervisors.py b/nova/tests/unit/policies/test_hypervisors.py index dd17ebe2fe..e4d4764855 100644 --- a/nova/tests/unit/policies/test_hypervisors.py +++ b/nova/tests/unit/policies/test_hypervisors.py @@ -10,9 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime from unittest import mock from nova.api.openstack.compute import hypervisors +from nova import objects from nova.policies import base as base_policy from nova.policies import hypervisors as hv_policies from nova.tests.unit.api.openstack import fakes @@ -33,8 +35,43 @@ class HypervisorsPolicyTest(base.BasePolicyTest): self.req = fakes.HTTPRequest.blank('') self.controller._get_compute_nodes_by_name_pattern = mock.MagicMock() self.controller.host_api.compute_node_get_all = mock.MagicMock() - self.controller.host_api.service_get_by_compute_host = mock.MagicMock() - self.controller.host_api.compute_node_get = mock.MagicMock() + self.controller.host_api.service_get_by_compute_host = mock.MagicMock( + return_value=objects.Service( + id=123, + binary='nova-compute', + created_at=datetime.datetime(2012, 9, 18, 2, 46, 27), + disabled=False, + disabled_reason=None, + forced_down=False, + host='foo', + last_seen_up=None, + ) + ) + self.controller.host_api.compute_node_get = mock.MagicMock( + return_value=objects.ComputeNode( + id=123, + cpu_info='{}', + current_workload=1, + disk_available_least=10, + free_disk_gb=50, + free_ram_mb=512, + host='foo', + host_ip='1.2.3.4', + hypervisor_hostname='foo', + hypervisor_type='libvirt', + hypervisor_version='123', + local_gb=1000, + local_gb_used=10, + memory_mb=8192, + memory_mb_used=1024, + running_vms=1, + vcpus=16, + vcpus_used=8, + ), + ) + self.controller.host_api.get_host_uptime = mock.MagicMock( + return_value=None + ) # With legacy rule and scope check disabled by default, system admin, # legacy admin, and project admin will be able to perform hypervisors @@ -59,26 +96,26 @@ class HypervisorsPolicyTest(base.BasePolicyTest): rule_name = hv_policies.BASE_POLICY_NAME % 'show' self.common_policy_auth(self.project_admin_authorized_contexts, rule_name, self.controller.show, - self.req, 11111) + self.req, '123') @mock.patch('nova.compute.api.HostAPI.get_host_uptime') def test_uptime_hypervisors_policy(self, mock_uptime): rule_name = hv_policies.BASE_POLICY_NAME % 'uptime' self.common_policy_auth(self.project_admin_authorized_contexts, rule_name, self.controller.uptime, - self.req, 11111) + self.req, '123') def test_search_hypervisors_policy(self): rule_name = hv_policies.BASE_POLICY_NAME % 'search' self.common_policy_auth(self.project_admin_authorized_contexts, rule_name, self.controller.search, - self.req, 11111) + self.req, '123') def test_servers_hypervisors_policy(self): rule_name = hv_policies.BASE_POLICY_NAME % 'servers' self.common_policy_auth(self.project_admin_authorized_contexts, rule_name, self.controller.servers, - self.req, 11111) + self.req, '123') @mock.patch('nova.compute.api.HostAPI.compute_node_statistics') def test_statistics_hypervisors_policy(self, mock_statistics):