Merge "api: Add response body schemas for hypervisors APIs (2/3)"

This commit is contained in:
Zuul
2025-06-11 08:17:05 +00:00
committed by Gerrit Code Review
3 changed files with 222 additions and 6 deletions
+11
View File
@@ -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
@@ -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')
+43 -6
View File
@@ -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):