Merge "api: Add response body schemas for flavors APIs"

This commit is contained in:
Zuul
2024-12-10 23:43:23 +00:00
committed by Gerrit Code Review
3 changed files with 239 additions and 14 deletions
+26 -11
View File
@@ -41,6 +41,7 @@ class FlavorsController(wsgi.Controller):
# return no response body.
@wsgi.response(202)
@wsgi.expected_errors(404)
@validation.response_body_schema(schema.delete_response)
def delete(self, req, id):
context = req.environ['nova.context']
context.can(fm_policies.POLICY_ROOT % 'delete', target={})
@@ -56,8 +57,11 @@ class FlavorsController(wsgi.Controller):
@wsgi.expected_errors((400, 409))
@validation.schema(schema.create_v20, '2.0', '2.0')
@validation.schema(schema.create, '2.1', '2.54')
@validation.schema(schema.create_v2_55,
flavors_view.FLAVOR_DESCRIPTION_MICROVERSION)
@validation.schema(schema.create_v255, '2.55')
@validation.response_body_schema(schema.create_response, '2.0', '2.54')
@validation.response_body_schema(schema.create_response_v255, '2.55', '2.60') # noqa: E501
@validation.response_body_schema(schema.create_response_v261, '2.61', '2.74') # noqa: E501
@validation.response_body_schema(schema.create_response_v275, '2.75')
def create(self, req, body):
context = req.environ['nova.context']
context.can(fm_policies.POLICY_ROOT % 'create', target={})
@@ -106,10 +110,12 @@ class FlavorsController(wsgi.Controller):
return self._view_builder.show(req, flavor, include_description,
include_extra_specs=include_extra_specs)
@wsgi.Controller.api_version(flavors_view.FLAVOR_DESCRIPTION_MICROVERSION)
@wsgi.Controller.api_version('2.55')
@wsgi.expected_errors((400, 404))
@validation.schema(schema.update_v2_55,
flavors_view.FLAVOR_DESCRIPTION_MICROVERSION)
@validation.schema(schema.update, '2.55')
@validation.response_body_schema(schema.update_response, '2.55', '2.60')
@validation.response_body_schema(schema.update_response_v261, '2.61', '2.74') # noqa: E501
@validation.response_body_schema(schema.update_response_v275, '2.75')
def update(self, req, id, body):
# Validate the policy.
context = req.environ['nova.context']
@@ -131,17 +137,22 @@ class FlavorsController(wsgi.Controller):
return self._view_builder.show(req, flavor, include_description=True,
include_extra_specs=include_extra_specs)
@validation.query_schema(schema.index_query_275, '2.75')
@validation.query_schema(schema.index_query, '2.0', '2.74')
@wsgi.expected_errors(400)
@validation.query_schema(schema.index_query, '2.0', '2.74')
@validation.query_schema(schema.index_query_275, '2.75')
@validation.response_body_schema(schema.index_response, '2.0', '2.54')
@validation.response_body_schema(schema.index_response_v255, '2.55')
def index(self, req):
"""Return all flavors in brief."""
limited_flavors = self._get_flavors(req)
return self._view_builder.index(req, limited_flavors)
@validation.query_schema(schema.index_query_275, '2.75')
@validation.query_schema(schema.index_query, '2.0', '2.74')
@wsgi.expected_errors(400)
@validation.query_schema(schema.index_query, '2.0', '2.74')
@validation.query_schema(schema.index_query_275, '2.75')
@validation.response_body_schema(schema.detail_response, '2.0', '2.54')
@validation.response_body_schema(schema.detail_response_v255, '2.55', '2.60') # noqa: E501
@validation.response_body_schema(schema.detail_response_v261, '2.61')
def detail(self, req):
"""Return all flavors in detail."""
context = req.environ['nova.context']
@@ -156,6 +167,10 @@ class FlavorsController(wsgi.Controller):
@wsgi.expected_errors(404)
@validation.query_schema(schema.show_query)
@validation.response_body_schema(schema.show_response, '2.0', '2.54')
@validation.response_body_schema(schema.show_response_v255, '2.55', '2.60')
@validation.response_body_schema(schema.show_response_v261, '2.61', '2.74')
@validation.response_body_schema(schema.show_response_v275, '2.75')
def show(self, req, id):
"""Return data about the given flavor id."""
context = req.environ['nova.context']
@@ -222,8 +237,8 @@ class FlavorsController(wsgi.Controller):
raise webob.exc.HTTPBadRequest(explanation=msg)
try:
limited_flavors = objects.FlavorList.get_all(context,
filters=filters, sort_key=sort_key, sort_dir=sort_dir,
limited_flavors = objects.FlavorList.get_all(
context, filters=filters, sort_key=sort_key, sort_dir=sort_dir,
limit=limit, marker=marker)
except exception.MarkerNotFound:
msg = _('marker [%s] not found') % marker
+200 -3
View File
@@ -90,12 +90,12 @@ _flavor_description = {
}
create_v2_55 = copy.deepcopy(create)
create_v2_55['properties']['flavor']['properties']['description'] = (
create_v255 = copy.deepcopy(create)
create_v255['properties']['flavor']['properties']['description'] = (
_flavor_description)
update_v2_55 = {
update = {
'type': 'object',
'properties': {
'flavor': {
@@ -145,3 +145,200 @@ show_query = {
'properties': {},
'additionalProperties': True,
}
_flavor_basic = {
'type': 'object',
'properties': {
'id': {'type': 'string'},
'links': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'href': {'type': 'string', 'format': 'uri'},
'rel': {'type': 'string'},
},
'required': ['href', 'rel'],
'additionalProperties': False,
},
},
'name': {'type': 'string'},
},
'required': ['id', 'links', 'name'],
'additionalProperties': False,
}
_flavor_basic_v255 = copy.deepcopy(_flavor_basic)
_flavor_basic_v255['properties']['description'] = {'type': ['string', 'null']}
_flavor_basic_v255['required'].append('description')
_flavor = {
'type': 'object',
'properties': {
'disk': {'type': 'integer'},
'id': {'type': 'string'},
'links': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'href': {'type': 'string', 'format': 'uri'},
'rel': {'type': 'string'},
},
'required': ['href', 'rel'],
},
},
'name': {'type': 'string'},
'os-flavor-access:is_public': {},
'ram': {'type': 'integer'},
'rxtx_factor': {},
'swap': {
'anyOf': [
{'type': 'integer'},
{'const': ''},
],
},
'vcpus': {'type': 'integer'},
'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'},
'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
},
'required': [
'disk',
'id',
'links',
'name',
'os-flavor-access:is_public',
'ram',
'rxtx_factor',
'swap',
'vcpus',
'OS-FLV-DISABLED:disabled',
'OS-FLV-EXT-DATA:ephemeral',
],
'additionalProperties': False,
}
_flavor_v255 = copy.deepcopy(_flavor)
_flavor_v255['properties']['description'] = {'type': ['string', 'null']}
_flavor_v255['required'].append('description')
_flavor_v261 = copy.deepcopy(_flavor_v255)
_flavor_v261['properties']['extra_specs'] = {
'type': 'object',
'patternProperties': {
'^[a-zA-Z0-9-_:. ]{1,255}$': {'type': 'string', 'maxLength': 255},
},
'additionalProperties': False,
}
_flavor_v275 = copy.deepcopy(_flavor_v261)
# we completely overwrite this since the new variant is much simpler
_flavor_v275['properties']['swap'] = {'type': 'integer'}
_flavors_links = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'href': {'type': 'string', 'format': 'uri'},
'rel': {'type': 'string'},
},
'required': ['href', 'rel'],
'additionalProperties': False,
},
}
delete_response = {
'type': 'null',
}
create_response = {
'type': 'object',
'properties': {
'flavor': copy.deepcopy(_flavor),
},
'required': ['flavor'],
'additionalProperties': False,
}
create_response_v255 = copy.deepcopy(create_response)
create_response_v255['properties']['flavor'] = copy.deepcopy(_flavor_v255)
create_response_v261 = copy.deepcopy(create_response_v255)
create_response_v261['properties']['flavor'] = copy.deepcopy(_flavor_v261)
create_response_v275 = copy.deepcopy(create_response_v261)
create_response_v275['properties']['flavor'] = copy.deepcopy(_flavor_v275)
# NOTE(stephenfin): update is only available from 2.55 and the response is
# identical to the create and show response from that point forward
update_response = {
'type': 'object',
'properties': {
'flavor': copy.deepcopy(_flavor_v255),
},
'required': ['flavor'],
'additionalProperties': False,
}
update_response_v261 = copy.deepcopy(update_response)
update_response_v261['properties']['flavor'] = copy.deepcopy(_flavor_v261)
update_response_v275 = copy.deepcopy(update_response_v261)
update_response_v275['properties']['flavor'] = copy.deepcopy(_flavor_v275)
index_response = {
'type': 'object',
'properties': {
'flavors': {
'type': 'array',
'items': _flavor_basic,
},
'flavors_links': _flavors_links,
},
'required': ['flavors'],
'additionalProperties': False,
}
index_response_v255 = copy.deepcopy(index_response)
index_response_v255['properties']['flavors']['items'] = _flavor_basic_v255
detail_response = {
'type': 'object',
'properties': {
'flavors': {
'type': 'array',
'items': _flavor,
},
'flavors_links': _flavors_links,
},
'required': ['flavors'],
'additionalProperties': False,
}
detail_response_v255 = copy.deepcopy(detail_response)
detail_response_v255['properties']['flavors']['items'] = _flavor_v255
detail_response_v261 = copy.deepcopy(detail_response_v255)
detail_response_v261['properties']['flavors']['items'] = _flavor_v261
detail_response_v275 = copy.deepcopy(detail_response_v261)
detail_response_v275['properties']['flavors']['items'] = _flavor_v275
show_response = {
'type': 'object',
'properties': {
'flavor': copy.deepcopy(_flavor),
},
'required': ['flavor'],
'additionalProperties': False,
}
show_response_v255 = copy.deepcopy(show_response)
show_response_v255['properties']['flavor'] = copy.deepcopy(_flavor_v255)
show_response_v261 = copy.deepcopy(show_response_v255)
show_response_v261['properties']['flavor'] = copy.deepcopy(_flavor_v261)
show_response_v275 = copy.deepcopy(show_response_v261)
show_response_v275['properties']['flavor'] = copy.deepcopy(_flavor_v275)
@@ -15,6 +15,7 @@ from unittest import mock
from oslo_utils.fixture import uuidsentinel as uuids
from nova.api.openstack.compute import flavors
from nova import objects
from nova.policies import flavor_manage as fm_policies
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit.policies import base
@@ -69,6 +70,18 @@ class FlavorManagePolicyTest(base.BasePolicyTest):
@mock.patch('nova.objects.Flavor.save')
def test_update_flavor_policy(self, mock_save, mock_get):
rule_name = fm_policies.POLICY_ROOT % 'update'
mock_get.return_value = objects.Flavor(
flavorid=uuids.fake_id,
name='test',
memory_mb=512,
vcpus=2,
root_gb=1,
ephemeral_gb=1,
swap=512,
rxtx_factor=1.0,
is_public=True,
disabled=False,
)
req = fakes.HTTPRequest.blank('', version='2.55')
self.common_policy_auth(self.admin_authorized_contexts,
rule_name, self.controller.update,