Merge "api: Add response body schemas for flavors APIs"
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user