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__)
@validation.validated
class HypervisorsController(wsgi.Controller):
"""The Hypervisors API controller for the OpenStack API."""
@@ -516,6 +517,7 @@ class HypervisorsController(wsgi.Controller):
@wsgi.api_version('2.1', '2.87')
@wsgi.expected_errors(())
@validation.query_schema(schema.statistics_query)
@validation.response_body_schema(schema.statistics_response, '2.1', '2.87')
def statistics(self, req):
"""Prior to microversion 2.88, you could get statistics for the
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.can(hv_policies.BASE_POLICY_NAME % 'statistics', target={})
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_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 = [
func.count().label('count'),
sql.func.sum(
inner_sel.c.vcpus
).label('vcpus'),
inner_sel.c.current_workload
).label('current_workload'),
sql.func.sum(
inner_sel.c.memory_mb
).label('memory_mb'),
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'),
inner_sel.c.disk_available_least
).label('disk_available_least'),
sql.func.sum(
inner_sel.c.free_disk_gb
).label('free_disk_gb'),
sql.func.sum(
inner_sel.c.current_workload
).label('current_workload'),
inner_sel.c.free_ram_mb
).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(
inner_sel.c.running_vms
).label('running_vms'),
sql.func.sum(
inner_sel.c.disk_available_least
).label('disk_available_least'),
inner_sel.c.vcpus
).label('vcpus'),
sql.func.sum(
inner_sel.c.vcpus_used
).label('vcpus_used'),
]
select = sql.select(*agg_cols).select_from(j)
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
fields = ('count', 'vcpus', 'memory_mb', 'local_gb', 'vcpus_used',
'memory_mb_used', 'local_gb_used', 'free_ram_mb', 'free_disk_gb',
'current_workload', 'running_vms', 'disk_available_least')
results = {field: int(results[idx] or 0)
for idx, field in enumerate(fields)}
return results
fields = (
'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')
return {field: int(results[field] or 0) for field in fields}
###################

View File

@@ -69,6 +69,22 @@ class HypervisorsPolicyTest(base.BasePolicyTest):
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(
return_value=None
)
@@ -117,8 +133,7 @@ class HypervisorsPolicyTest(base.BasePolicyTest):
rule_name, self.controller.servers,
self.req, '123')
@mock.patch('nova.compute.api.HostAPI.compute_node_statistics')
def test_statistics_hypervisors_policy(self, mock_statistics):
def test_statistics_hypervisors_policy(self):
rule_name = hv_policies.BASE_POLICY_NAME % 'statistics'
self.common_policy_auth(self.project_admin_authorized_contexts,
rule_name, self.controller.statistics,