V3 jsonschema validation: Group Snapshots

This patch adds jsonschema validation for below group snapshots API's

* POST /v3/{project_id}/group_snapshots
* POST /v3/{project_id}/group_snapshots/{group_snapshot_id}/action

Made changes to unit tests to pass body as keyword argument as wsgi
calls action method [1] and passes body as keyword argument.

[1] https://github.com/openstack/cinder/blob/master/cinder/api/openstack/wsgi.py#L997

Change-Id: Ie3b8ffb209b30edf2a26a935aab840441b43adfa
Partial-Implements: bp json-schema-validation
This commit is contained in:
pooja jadhav 2017-11-06 13:50:14 +05:30
parent 9a31b4c8f5
commit 65d57cf4b1
6 changed files with 110 additions and 39 deletions

View File

@ -0,0 +1,61 @@
# Copyright (C) 2017 NTT DATA
# All Rights Reserved.
#
# 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.
"""
Schema for V3 Group Snapshots API.
"""
from cinder.api.validation import parameter_types
name_optional = parameter_types.name
name_optional['minLength'] = 0
create = {
'type': 'object',
'properties': {
'type': 'object',
'group_snapshot': {
'type': 'object',
'properties': {
'group_id': parameter_types.uuid,
'name': name_optional,
'description': parameter_types.description,
},
'required': ['group_id'],
'additionalProperties': False,
},
},
'required': ['group_snapshot'],
'additionalProperties': False,
}
reset_status = {
'type': 'object',
'properties': {
'type': 'object',
'reset_status': {
'type': 'object',
'properties': {
'status': parameter_types.group_snapshot_status,
},
'required': ['status'],
'additionalProperties': False,
},
},
'required': ['reset_status'],
'additionalProperties': False,
}

View File

@ -24,7 +24,9 @@ from webob import exc
from cinder.api import common
from cinder.api import microversions as mv
from cinder.api.openstack import wsgi
from cinder.api.schemas import group_snapshots as snapshot
from cinder.api.v3.views import group_snapshots as group_snapshot_views
from cinder.api import validation
from cinder import exception
from cinder import group as group_api
from cinder.i18n import _
@ -146,20 +148,15 @@ class GroupSnapshotsController(wsgi.Controller):
@wsgi.Controller.api_version(mv.GROUP_SNAPSHOTS)
@wsgi.response(http_client.ACCEPTED)
@validation.schema(snapshot.create)
def create(self, req, body):
"""Create a new group_snapshot."""
LOG.debug('Creating new group_snapshot %s', body)
self.assert_valid_body(body, 'group_snapshot')
context = req.environ['cinder.context']
group_snapshot = body['group_snapshot']
self.validate_name_and_description(group_snapshot)
try:
group_id = group_snapshot['group_id']
except KeyError:
msg = _("'group_id' must be specified")
raise exc.HTTPBadRequest(explanation=msg)
group_id = group_snapshot['group_id']
group = self.group_snapshot_api.get(context, group_id)
self._check_default_cgsnapshot_type(group.group_type_id)
@ -184,6 +181,7 @@ class GroupSnapshotsController(wsgi.Controller):
@wsgi.Controller.api_version(mv.GROUP_SNAPSHOT_RESET_STATUS)
@wsgi.action("reset_status")
@validation.schema(snapshot.reset_status)
def reset_status(self, req, id, body):
return self._reset_status(req, id, body)
@ -191,10 +189,7 @@ class GroupSnapshotsController(wsgi.Controller):
"""Reset status on group snapshots"""
context = req.environ['cinder.context']
try:
status = body['reset_status']['status'].lower()
except (TypeError, KeyError):
raise exc.HTTPBadRequest(explanation=_("Must specify 'status'"))
status = body['reset_status']['status'].lower()
LOG.debug("Updating group '%(id)s' with "
"'%(update)s'", {'id': id,

View File

@ -150,3 +150,7 @@ extra_specs = {
},
'additionalProperties': False
}
group_snapshot_status = {
'type': 'string', 'format': 'group_snapshot_status'
}

View File

@ -28,6 +28,7 @@ import six
from cinder import exception
from cinder.i18n import _
from cinder.objects import fields as c_fields
def _soft_validate_additional_properties(
@ -96,10 +97,10 @@ def _validate_datetime_format(param_value):
@jsonschema.FormatChecker.cls_checks('name', exception.InvalidName)
def _validate_name(param_value):
if not param_value:
msg = "The 'name' can not be None."
msg = _("The 'name' can not be None.")
raise exception.InvalidName(reason=msg)
elif len(param_value.strip()) == 0:
msg = "The 'name' can not be empty."
msg = _("The 'name' can not be empty.")
raise exception.InvalidName(reason=msg)
return True
@ -109,6 +110,20 @@ def _validate_uuid_format(instance):
return uuidutils.is_uuid_like(instance)
@jsonschema.FormatChecker.cls_checks('group_snapshot_status')
def _validate_status(param_value):
if len(param_value.strip()) == 0:
msg = _("The 'status' can not be empty.")
raise exception.InvalidGroupSnapshotStatus(reason=msg)
elif param_value.lower() not in c_fields.GroupSnapshotStatus.ALL:
msg = _("Group snapshot status: %(status)s is invalid, "
"valid statuses are: "
"%(valid)s.") % {'status': param_value,
'valid': c_fields.GroupSnapshotStatus.ALL}
raise exception.InvalidGroupSnapshotStatus(reason=msg)
return True
class FormatChecker(jsonschema.FormatChecker):
"""A FormatChecker can output the message from cause exception

View File

@ -925,12 +925,6 @@ class API(base.Base):
"""Reset status of group snapshot"""
context.authorize(gsnap_action_policy.RESET_STATUS)
if status not in c_fields.GroupSnapshotStatus.ALL:
msg = _("Group snapshot status: %(status)s is invalid, "
"valid statuses are: "
"%(valid)s.") % {'status': status,
'valid': c_fields.GroupSnapshotStatus.ALL}
raise exception.InvalidGroupSnapshotStatus(reason=msg)
field = {'updated_at': timeutils.utcnow(),
'status': status}
gsnapshot.update(field)

View File

@ -320,12 +320,9 @@ class GroupSnapshotsAPITestCase(test.TestCase):
self.assertNotIn('description',
res_dict['group_snapshots'][2 - index].keys())
@mock.patch(
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
@mock.patch('cinder.db.volume_type_get')
@mock.patch('cinder.quota.VolumeTypeQuotaEngine.reserve')
def test_create_group_snapshot_json(self, mock_quota, mock_vol_type,
mock_validate):
def test_create_group_snapshot_json(self, mock_quota, mock_vol_type):
body = {"group_snapshot": {"name": "group_snapshot1",
"description":
"Group Snapshot 1",
@ -333,20 +330,17 @@ class GroupSnapshotsAPITestCase(test.TestCase):
req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' %
fake.PROJECT_ID,
version=mv.GROUP_SNAPSHOTS)
res_dict = self.controller.create(req, body)
res_dict = self.controller.create(req, body=body)
self.assertEqual(1, len(res_dict))
self.assertIn('id', res_dict['group_snapshot'])
self.assertTrue(mock_validate.called)
group_snapshot = objects.GroupSnapshot.get_by_id(
context.get_admin_context(), res_dict['group_snapshot']['id'])
group_snapshot.destroy()
@mock.patch(
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
@mock.patch('cinder.db.volume_type_get')
def test_create_group_snapshot_when_volume_in_error_status(
self, mock_vol_type, mock_validate):
self, mock_vol_type):
group = utils.create_group(
self.context,
group_type_id=fake.GROUP_TYPE_ID,
@ -364,8 +358,7 @@ class GroupSnapshotsAPITestCase(test.TestCase):
fake.PROJECT_ID,
version=mv.GROUP_SNAPSHOTS)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
req, body)
self.assertTrue(mock_validate.called)
req, body=body)
group.destroy()
db.volume_destroy(context.get_admin_context(),
@ -376,8 +369,17 @@ class GroupSnapshotsAPITestCase(test.TestCase):
req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' %
fake.PROJECT_ID,
version=mv.GROUP_SNAPSHOTS)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
req, None)
self.assertRaises(exception.ValidationError, self.controller.create,
req, body=None)
def test_create_group_snapshot_with_empty_body(self):
# empty body in the request
req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' %
fake.PROJECT_ID,
version=mv.GROUP_SNAPSHOTS)
body = {"group_snapshot": {}}
self.assertRaises(exception.ValidationError, self.controller.create,
req, body=body)
@mock.patch.object(group_api.API, 'create_group_snapshot',
side_effect=exception.InvalidGroupSnapshot(
@ -391,7 +393,7 @@ class GroupSnapshotsAPITestCase(test.TestCase):
fake.PROJECT_ID,
version=mv.GROUP_SNAPSHOTS)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
req, body)
req, body=body)
@mock.patch.object(group_api.API, 'create_group_snapshot',
side_effect=exception.GroupSnapshotNotFound(
@ -406,7 +408,7 @@ class GroupSnapshotsAPITestCase(test.TestCase):
version=mv.GROUP_SNAPSHOTS)
self.assertRaises(exception.GroupSnapshotNotFound,
self.controller.create,
req, body)
req, body=body)
def test_create_group_snapshot_from_empty_group(self):
empty_group = utils.create_group(
@ -422,7 +424,7 @@ class GroupSnapshotsAPITestCase(test.TestCase):
version=mv.GROUP_SNAPSHOTS)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
req, body)
req, body=body)
empty_group.destroy()
def test_delete_group_snapshot_available(self):
@ -507,7 +509,7 @@ class GroupSnapshotsAPITestCase(test.TestCase):
}}
self.assertRaises(exceptions,
self.controller.reset_status,
req, group_snapshot_id, body)
req, group_snapshot_id, body=body)
def test_reset_group_snapshot_status_invalid_status(self):
group_snapshot = utils.create_group_snapshot(
@ -520,9 +522,9 @@ class GroupSnapshotsAPITestCase(test.TestCase):
body = {"reset_status": {
"status": "invalid_test_status"
}}
self.assertRaises(webob.exc.HTTPBadRequest,
self.assertRaises(exception.InvalidGroupSnapshotStatus,
self.controller.reset_status,
req, group_snapshot.id, body)
req, group_snapshot.id, body=body)
group_snapshot.destroy()
def test_reset_group_snapshot_status(self):
@ -537,7 +539,7 @@ class GroupSnapshotsAPITestCase(test.TestCase):
"status": fields.GroupSnapshotStatus.AVAILABLE
}}
response = self.controller.reset_status(req, group_snapshot.id,
body)
body=body)
g_snapshot = objects.GroupSnapshot.get_by_id(self.context,
group_snapshot.id)