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

We split this one up due to its size, which itself is mainly due to the
amount of aliasing that went on in early versions as well as the amount
of changes that have been made over the years.

This focuses on the statistics view. We also reorder the output fields
in the view alphabetically just to make reviewing the schema slightly
easier.

Change-Id: I950a7e2286d451b37b2f7cbd02c4a0a82ac64361
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane
2024-11-05 10:34:39 +00:00
parent f5d9e5cb2f
commit c918fcc587
4 changed files with 89 additions and 33 deletions

View File

@@ -37,6 +37,7 @@ from nova import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@validation.validated
class HypervisorsController(wsgi.Controller): class HypervisorsController(wsgi.Controller):
"""The Hypervisors API controller for the OpenStack API.""" """The Hypervisors API controller for the OpenStack API."""
@@ -516,6 +517,7 @@ class HypervisorsController(wsgi.Controller):
@wsgi.api_version('2.1', '2.87') @wsgi.api_version('2.1', '2.87')
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.statistics_query) @validation.query_schema(schema.statistics_query)
@validation.response_body_schema(schema.statistics_response, '2.1', '2.87')
def statistics(self, req): def statistics(self, req):
"""Prior to microversion 2.88, you could get statistics for the """Prior to microversion 2.88, you could get statistics for the
hypervisor. Most of these are now accessible from placement and the few hypervisor. Most of these are now accessible from placement and the few
@@ -524,4 +526,4 @@ class HypervisorsController(wsgi.Controller):
context = req.environ['nova.context'] context = req.environ['nova.context']
context.can(hv_policies.BASE_POLICY_NAME % 'statistics', target={}) context.can(hv_policies.BASE_POLICY_NAME % 'statistics', target={})
stats = self.host_api.compute_node_statistics(context) stats = self.host_api.compute_node_statistics(context)
return dict(hypervisor_statistics=stats) return {'hypervisor_statistics': stats}

View File

@@ -305,3 +305,43 @@ uptime_response_v253['properties']['hypervisor']['properties'].update({
'uptime': {'type': ['string', 'null']} 'uptime': {'type': ['string', 'null']}
}) })
uptime_response_v253['properties']['hypervisor']['required'].append('uptime') uptime_response_v253['properties']['hypervisor']['required'].append('uptime')
statistics_response = {
'type': 'object',
'properties': {
'hypervisor_statistics': {
'type': 'object',
'properties': {
'count': {'type': 'integer'},
'current_workload': {'type': 'integer'},
'disk_available_least': {'type': 'integer'},
'free_disk_gb': {'type': 'integer'},
'free_ram_mb': {'type': 'integer'},
'local_gb': {'type': 'integer'},
'local_gb_used': {'type': 'integer'},
'memory_mb': {'type': 'integer'},
'memory_mb_used': {'type': 'integer'},
'running_vms': {'type': 'integer'},
'vcpus': {'type': 'integer'},
'vcpus_used': {'type': 'integer'},
},
'required': [
'count',
'current_workload',
'disk_available_least',
'free_disk_gb',
'free_ram_mb',
'local_gb',
'local_gb_used',
'memory_mb',
'memory_mb_used',
'running_vms',
'vcpus',
'vcpus_used',
],
'additionalProperties': False,
},
},
'required': ['hypervisor_statistics'],
'additionalProperties': False,
}

View File

@@ -949,51 +949,50 @@ def compute_node_statistics(context):
agg_cols = [ agg_cols = [
func.count().label('count'), func.count().label('count'),
sql.func.sum( sql.func.sum(
inner_sel.c.vcpus inner_sel.c.current_workload
).label('vcpus'), ).label('current_workload'),
sql.func.sum( sql.func.sum(
inner_sel.c.memory_mb inner_sel.c.disk_available_least
).label('memory_mb'), ).label('disk_available_least'),
sql.func.sum(
inner_sel.c.local_gb
).label('local_gb'),
sql.func.sum(
inner_sel.c.vcpus_used
).label('vcpus_used'),
sql.func.sum(
inner_sel.c.memory_mb_used
).label('memory_mb_used'),
sql.func.sum(
inner_sel.c.local_gb_used
).label('local_gb_used'),
sql.func.sum(
inner_sel.c.free_ram_mb
).label('free_ram_mb'),
sql.func.sum( sql.func.sum(
inner_sel.c.free_disk_gb inner_sel.c.free_disk_gb
).label('free_disk_gb'), ).label('free_disk_gb'),
sql.func.sum( sql.func.sum(
inner_sel.c.current_workload inner_sel.c.free_ram_mb
).label('current_workload'), ).label('free_ram_mb'),
sql.func.sum(
inner_sel.c.local_gb
).label('local_gb'),
sql.func.sum(
inner_sel.c.local_gb_used
).label('local_gb_used'),
sql.func.sum(
inner_sel.c.memory_mb
).label('memory_mb'),
sql.func.sum(
inner_sel.c.memory_mb_used
).label('memory_mb_used'),
sql.func.sum( sql.func.sum(
inner_sel.c.running_vms inner_sel.c.running_vms
).label('running_vms'), ).label('running_vms'),
sql.func.sum( sql.func.sum(
inner_sel.c.disk_available_least inner_sel.c.vcpus
).label('disk_available_least'), ).label('vcpus'),
sql.func.sum(
inner_sel.c.vcpus_used
).label('vcpus_used'),
] ]
select = sql.select(*agg_cols).select_from(j) select = sql.select(*agg_cols).select_from(j)
with engine.connect() as conn, conn.begin(): with engine.connect() as conn, conn.begin():
results = conn.execute(select).fetchone() results = conn.execute(select).mappings().fetchone()
# Build a dict of the info--making no assumptions about result # Build a dict of the info--making no assumptions about result
fields = ('count', 'vcpus', 'memory_mb', 'local_gb', 'vcpus_used', fields = (
'memory_mb_used', 'local_gb_used', 'free_ram_mb', 'free_disk_gb', 'count', 'current_workload', 'disk_available_least', 'free_disk_gb',
'current_workload', 'running_vms', 'disk_available_least') 'free_ram_mb', 'local_gb', 'local_gb_used', 'memory_mb',
results = {field: int(results[idx] or 0) 'memory_mb_used', 'running_vms', 'vcpus', 'vcpus_used')
for idx, field in enumerate(fields)} return {field: int(results[field] or 0) for field in fields}
return results
################### ###################

View File

@@ -69,6 +69,22 @@ class HypervisorsPolicyTest(base.BasePolicyTest):
vcpus_used=8, vcpus_used=8,
), ),
) )
self.controller.host_api.compute_node_statistics = mock.MagicMock(
return_value={
'count': 0,
'current_workload': 0,
'disk_available_least': 0,
'free_disk_gb': 0,
'free_ram_mb': 0,
'local_gb': 0,
'local_gb_used': 0,
'memory_mb': 0,
'memory_mb_used': 0,
'running_vms': 0,
'vcpus': 0,
'vcpus_used': 0,
}
)
self.controller.host_api.get_host_uptime = mock.MagicMock( self.controller.host_api.get_host_uptime = mock.MagicMock(
return_value=None return_value=None
) )
@@ -117,8 +133,7 @@ class HypervisorsPolicyTest(base.BasePolicyTest):
rule_name, self.controller.servers, rule_name, self.controller.servers,
self.req, '123') self.req, '123')
@mock.patch('nova.compute.api.HostAPI.compute_node_statistics') def test_statistics_hypervisors_policy(self):
def test_statistics_hypervisors_policy(self, mock_statistics):
rule_name = hv_policies.BASE_POLICY_NAME % 'statistics' rule_name = hv_policies.BASE_POLICY_NAME % 'statistics'
self.common_policy_auth(self.project_admin_authorized_contexts, self.common_policy_auth(self.project_admin_authorized_contexts,
rule_name, self.controller.statistics, rule_name, self.controller.statistics,