api: Add schemas for resource_locks

This is a mostly complete example showing request body, request query
string and response body schemas in action. The only thing not included
yet is descriptions for fields, which is still being worked on.

Change-Id: I14db582eec6db25ea5437675f8207dcf94228b25
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
Partially-implements: bp json-schema-validation
This commit is contained in:
Stephen Finucane 2024-04-25 21:38:25 +01:00
parent 05279a6fe7
commit 9be7243e70
7 changed files with 347 additions and 9 deletions

View File

View File

@ -0,0 +1,240 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from manila.api.validation import parameter_types
from manila.api.validation import response_types
from manila.common import constants
CONF = cfg.CONF
# TODO(stephenfin): Reject additional properties in a future microversion
create_request_body = {
'type': 'object',
'properties': {
'resource_lock': {
'type': 'object',
'properties': {
'resource_id': {
'type': 'string',
'format': 'uuid',
},
'lock_reason': {
'type': ['string', 'null'],
'maxLength': 1023,
},
'resource_type': {
'type': ['string', 'null'],
'enum': constants.RESOURCE_LOCK_RESOURCE_TYPES + (None,),
'default': constants.SHARE_RESOURCE_TYPE,
},
'resource_action': {
'type': ['string', 'null'],
'enum': constants.RESOURCE_LOCK_RESOURCE_ACTIONS + (None,),
'default': constants.RESOURCE_ACTION_DELETE,
},
},
'required': ['resource_id'],
'additionalProperties': False,
},
},
'required': ['resource_lock'],
'additionalProperties': True,
}
update_request_body = {
'type': 'object',
'properties': {
'resource_lock': {
'type': 'object',
'properties': {
'resource_action': {
'type': ['string', 'null'],
'enum': constants.RESOURCE_LOCK_RESOURCE_ACTIONS + (None,),
},
'lock_reason': {
'type': ['string', 'null'],
'maxLength': 1023,
},
},
'additionalProperties': False,
},
},
'required': ['resource_lock'],
'additionalProperties': True,
}
index_request_query = {
'type': 'object',
'properties': {
'limit': parameter_types.multi_params(
parameter_types.non_negative_integer
),
'marker': parameter_types.multi_params({
'type': ['string'],
}),
'offset': parameter_types.multi_params(
parameter_types.non_negative_integer
),
'sort_key': parameter_types.multi_params({
'type': 'string',
'default': 'created_at',
}),
# TODO(stephenfin): Make this an enum of ['asc', 'desc']
'sort_dir': parameter_types.multi_params({
'type': 'string',
'default': 'desc',
}),
'with_count': parameter_types.multi_params(parameter_types.boolean),
'created_since': parameter_types.multi_params({
'type': 'string',
'format': 'date-time',
}),
'created_before': parameter_types.multi_params({
'type': 'string',
'format': 'date-time',
}),
'project_id': parameter_types.multi_params({
'type': ['string', 'null'],
'format': 'uuid',
}),
'user_id': parameter_types.multi_params({
'type': ['string', 'null'],
'format': 'uuid',
}),
'resource_id': parameter_types.multi_params({
'type': ['string', 'null'],
'format': 'uuid',
}),
'resource_action': parameter_types.multi_params({
'type': ['string', 'null'],
'enum': constants.RESOURCE_LOCK_RESOURCE_ACTIONS + (None,),
}),
'resource_type': parameter_types.multi_params({
'type': ['string', 'null'],
'enum': constants.RESOURCE_LOCK_RESOURCE_TYPES + (None,),
}),
'all_projects': parameter_types.multi_params(parameter_types.boolean),
'lock_context': parameter_types.multi_params({
'type': ['string', 'null'],
'maxLength': 10,
}),
'lock_reason': parameter_types.multi_params({
'type': ['string', 'null'],
'maxLength': 1023,
}),
},
# TODO(stephenfin): Exclude additional query string parameters in a future
# microversion
'additionalProperties': True,
}
show_request_query = {
'type': 'object',
'properties': {},
# TODO(stephenfin): Exclude additional query string parameters in a future
# microversion
'additionalProperties': True,
}
_resource_lock_response = {
'type': 'object',
'properties': {
'id': {
'type': 'string',
'format': 'uuid',
},
'user_id': {
'type': 'string',
'format': 'uuid',
},
'project_id': {
'type': 'string',
'format': 'uuid',
},
'lock_context': {
'type': 'string',
},
'resource_type': {
'type': 'string',
'enum': constants.RESOURCE_LOCK_RESOURCE_TYPES,
},
'resource_id': {
'type': 'string',
'format': 'uuid',
},
'resource_action': {
'type': 'string',
'enum': constants.RESOURCE_LOCK_RESOURCE_ACTIONS,
},
'lock_reason': {
'type': ['string', 'null'],
},
'created_at': {
'type': 'string',
'format': 'date-time',
},
'updated_at': {
'type': ['string', 'null'],
'format': 'date-time',
},
'links': response_types.links,
},
}
create_response_body = {
'type': 'object',
'properties': {
'resource_lock': _resource_lock_response,
},
'required': ['resource_lock'],
'additionalProperties': False,
}
index_response_body = {
'type': 'object',
'properties': {
'resource_locks': {
'type': 'array',
'items': _resource_lock_response,
},
'count': {
'type': 'integer',
},
'resource_locks_links': response_types.collection_links,
},
'required': ['resource_locks'],
'additionalProperties': False,
}
show_response_body = {
'type': 'object',
'properties': {
'resource_lock': _resource_lock_response,
},
'required': ['resource_lock'],
'additionalProperties': False,
}
update_response_body = {
'type': 'object',
'properties': {
'resource_lock': _resource_lock_response,
},
'required': ['resource_lock'],
'additionalProperties': False,
}
delete_response_body = {
'type': 'null',
}

View File

@ -29,6 +29,8 @@ from webob import exc
from manila.api import common
from manila.api.openstack import wsgi
from manila.api.schemas import resource_locks as schema
from manila.api import validation
from manila.api.views import resource_locks as resource_locks_view
from manila.common import constants
from manila import exception
@ -39,6 +41,7 @@ from manila import utils
RESOURCE_LOCKS_MIN_API_VERSION = '2.81'
@validation.validated
class ResourceLocksController(wsgi.Controller):
"""The Resource Locks API controller for the OpenStack API."""
@ -92,6 +95,8 @@ class ResourceLocksController(wsgi.Controller):
@wsgi.Controller.api_version(RESOURCE_LOCKS_MIN_API_VERSION)
@wsgi.Controller.authorize('get_all')
@validation.request_query_schema(schema.index_request_query)
@validation.response_body_schema(schema.index_response_body)
def index(self, req):
"""Returns a list of locks, transformed through view builder."""
context = req.environ['manila.context']
@ -131,6 +136,8 @@ class ResourceLocksController(wsgi.Controller):
@wsgi.Controller.api_version(RESOURCE_LOCKS_MIN_API_VERSION)
@wsgi.Controller.authorize('get')
@validation.request_query_schema(schema.show_request_query)
@validation.response_body_schema(schema.show_response_body)
def show(self, req, id):
"""Return an existing resource lock by ID."""
context = req.environ['manila.context']
@ -143,6 +150,7 @@ class ResourceLocksController(wsgi.Controller):
@wsgi.Controller.api_version(RESOURCE_LOCKS_MIN_API_VERSION)
@wsgi.Controller.authorize
@wsgi.action("delete")
@validation.response_body_schema(schema.delete_response_body)
def delete(self, req, id):
"""Delete an existing resource lock."""
context = req.environ['manila.context']
@ -154,6 +162,8 @@ class ResourceLocksController(wsgi.Controller):
@wsgi.Controller.api_version(RESOURCE_LOCKS_MIN_API_VERSION)
@wsgi.Controller.authorize
@validation.request_body_schema(schema.create_request_body)
@validation.response_body_schema(schema.create_response_body)
def create(self, req, body):
"""Create a resource lock."""
context = req.environ['manila.context']
@ -180,6 +190,8 @@ class ResourceLocksController(wsgi.Controller):
@wsgi.Controller.api_version(RESOURCE_LOCKS_MIN_API_VERSION)
@wsgi.Controller.authorize
@validation.request_body_schema(schema.update_request_body)
@validation.response_body_schema(schema.update_response_body)
def update(self, req, id, body):
"""Update an existing resource lock."""
context = req.environ['manila.context']

View File

@ -10,7 +10,22 @@
# License for the specific language governing permissions and limitations
# under the License.
"""Common parameter types for validating API requests/responses."""
"""Common parameter types for validating API requests."""
from manila.common import constants
def single_param(schema):
"""Macro function to support query params that allow only one value."""
ret = multi_params(schema)
ret['maxItems'] = 1
return ret
def multi_params(schema):
"""Macro function to support query params that allow multiple values."""
return {'type': 'array', 'items': schema}
boolean = {
'type': ['boolean', 'string'],
@ -43,3 +58,19 @@ boolean = {
'f',
],
}
positive_integer = {
'type': ['integer', 'string'],
'pattern': '^[0-9]*$',
'minimum': 1,
'maximum': constants.DB_MAX_INT,
'minLength': 1,
}
non_negative_integer = {
'type': ['integer', 'string'],
'pattern': '^[0-9]*$',
'minimum': 0,
'maximum': constants.DB_MAX_INT,
'minLength': 1,
}

View File

@ -0,0 +1,53 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Common field types for validating API responses."""
links = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'rel': {
'type': 'string',
'enum': ['self', 'bookmark'],
},
'href': {
'type': 'string',
'format': 'uri',
},
},
'required': ['rel', 'href'],
'additionalProperties': False,
},
}
collection_links = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'rel': {
'const': 'next',
},
'href': {
'type': 'string',
'format': 'uri',
},
},
'required': ['rel', 'href'],
'additionalProperties': False,
},
# there should be one and only one link object
'minItems': 1,
'maxItems': 1,
}

View File

@ -272,8 +272,8 @@ RESOURCE_LOCK_RESOURCE_ACTIONS = (
)
RESOURCE_LOCK_ACTIONS_MAPPING = {
"share": [RESOURCE_ACTION_DELETE],
"access_rule": [RESOURCE_ACTION_DELETE, RESOURCE_ACTION_SHOW],
SHARE_RESOURCE_TYPE: [RESOURCE_ACTION_DELETE],
SHARE_ACCESS_RESOURCE_TYPE: [RESOURCE_ACTION_DELETE, RESOURCE_ACTION_SHOW],
}
DISALLOWED_STATUS_WHEN_LOCKING_SHARES = (

View File

@ -209,7 +209,7 @@ class ResourceLockApiTest(test.TestCase):
url, version=resource_locks.RESOURCE_LOCKS_MIN_API_VERSION)
req.environ['manila.context'] = self.ctxt
self.assertRaises(webob.exc.HTTPBadRequest,
self.assertRaises(exception.ValidationError,
self.controller.index,
req)
@ -226,7 +226,7 @@ class ResourceLockApiTest(test.TestCase):
url, version=resource_locks.RESOURCE_LOCKS_MIN_API_VERSION)
req.environ['manila.context'] = self.ctxt
self.assertRaises(webob.exc.HTTPBadRequest,
self.assertRaises(exception.ValidationError,
self.controller.index,
req)
@ -343,7 +343,7 @@ class ResourceLockApiTest(test.TestCase):
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create,
self.req,
body)
body=body)
def test_create_visibility_already_locked(self):
self.mock_object(self.controller, '_check_body')
@ -364,7 +364,7 @@ class ResourceLockApiTest(test.TestCase):
self.assertRaises(webob.exc.HTTPConflict,
self.controller.create,
self.req,
body)
body=body)
def test_create(self):
self.mock_object(self.controller, '_check_body')
@ -382,7 +382,9 @@ class ResourceLockApiTest(test.TestCase):
'create',
mock.Mock(return_value=expected_lock))
actual_lock = self.controller.create(self.req, body)['resource_lock']
actual_lock = self.controller.create(
self.req, body=body
)['resource_lock']
self.controller.resource_locks_api.create.assert_called_once_with(
utils.IsAMatcher(context.RequestContext),
@ -415,7 +417,7 @@ class ResourceLockApiTest(test.TestCase):
actual_lock = self.controller.update(
self.req,
'04512dae-18c2-45b5-bbab-50b775ba6f1d',
body
body=body
)['resource_lock']
self.controller.resource_locks_api.update.assert_called_once_with(