From 31b162632005ad56c190391b912f7b4dbb9741b6 Mon Sep 17 00:00:00 2001 From: pooja jadhav Date: Wed, 20 Dec 2017 11:50:40 +0530 Subject: [PATCH] V3 jsonschema validation: Quota classes This patch adds jsonschema validation for below quota classes API's * PUT /v3/{admin_project_id}/os-quota-class-sets/{quota_class_name} APIImpact : quota class update API will raise BadRequest(400) for quota classes keys other than those accepted by quotas and group_quotas resources. Change-Id: Id7ac6ec88a4b0ea83131d929a9d607f2c4e3429b Partial-Implements: bp json-schema-validation --- cinder/api/contrib/quota_classes.py | 20 +++++------ cinder/api/schemas/quota_classes.py | 32 ++++++++++++++++++ cinder/api/validation/parameter_types.py | 17 ++++++++++ cinder/api/validation/validators.py | 14 ++++++++ .../unit/api/contrib/test_quotas_classes.py | 33 ++++++++----------- 5 files changed, 86 insertions(+), 30 deletions(-) create mode 100644 cinder/api/schemas/quota_classes.py diff --git a/cinder/api/contrib/quota_classes.py b/cinder/api/contrib/quota_classes.py index 097d1b81815..0d98c57cb81 100644 --- a/cinder/api/contrib/quota_classes.py +++ b/cinder/api/contrib/quota_classes.py @@ -17,11 +17,12 @@ import webob from cinder.api import extensions from cinder.api.openstack import wsgi +from cinder.api.schemas import quota_classes as quota_class +from cinder.api import validation from cinder import db from cinder import exception from cinder.policies import quota_class as policy from cinder import quota -from cinder import utils QUOTAS = quota.QUOTAS @@ -50,6 +51,7 @@ class QuotaClassSetsController(wsgi.Controller): return self._format_quota_set(id, quota_set) + @validation.schema(quota_class.update_quota_class) def update(self, req, id, body): context = req.environ['cinder.context'] context.authorize(policy.MANAGE_POLICY) @@ -57,18 +59,14 @@ class QuotaClassSetsController(wsgi.Controller): min_length=1, max_length=255) quota_class = id - self.assert_valid_body(body, 'quota_class_set') for key, value in body['quota_class_set'].items(): - if key in QUOTAS or key in GROUP_QUOTAS: - try: - value = utils.validate_integer(value, key, min_value=-1, - max_value=db.MAX_INT) - db.quota_class_update(context, quota_class, key, value) - except exception.QuotaClassNotFound: - db.quota_class_create(context, quota_class, key, value) - except exception.AdminRequired: - raise webob.exc.HTTPForbidden() + try: + db.quota_class_update(context, quota_class, key, value) + except exception.QuotaClassNotFound: + db.quota_class_create(context, quota_class, key, value) + except exception.AdminRequired: + raise webob.exc.HTTPForbidden() quota_set = QUOTAS.get_class_quotas(context, quota_class) group_quota_set = GROUP_QUOTAS.get_class_quotas(context, quota_class) diff --git a/cinder/api/schemas/quota_classes.py b/cinder/api/schemas/quota_classes.py new file mode 100644 index 00000000000..e03f2a3fa2d --- /dev/null +++ b/cinder/api/schemas/quota_classes.py @@ -0,0 +1,32 @@ +# 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 Quota classes API. + +""" + +from cinder.api.validation import parameter_types + + +update_quota_class = { + 'type': 'object', + 'properties': { + 'type': 'object', + 'quota_class_set': parameter_types.quota_class_set + }, + 'required': ['quota_class_set'], + 'additionalProperties': False, +} diff --git a/cinder/api/validation/parameter_types.py b/cinder/api/validation/parameter_types.py index 1695ed58a35..47af7f17f2e 100644 --- a/cinder/api/validation/parameter_types.py +++ b/cinder/api/validation/parameter_types.py @@ -24,6 +24,8 @@ import unicodedata import six +from cinder.common import constants + def _is_printable(char): """determine if a unicode code point is printable. @@ -232,3 +234,18 @@ service_id = { optional_uuid = {'oneOf': [{'type': 'null'}, {'type': 'string', 'format': 'uuid'}]} + + +quota_class_set = { + 'type': 'object', + 'format': 'quota_class_set', + 'patternProperties': { + '^[a-zA-Z0-9-_:. ]{1,255}$': { + 'type': ['integer', 'string'], + 'pattern': '^[0-9]*$', 'minimum': -1, 'minLength': 1, + 'maximum': constants.DB_MAX_INT + } + }, + 'additionalProperties': False + +} diff --git a/cinder/api/validation/validators.py b/cinder/api/validation/validators.py index c8e0034899b..9a61c121fe2 100644 --- a/cinder/api/validation/validators.py +++ b/cinder/api/validation/validators.py @@ -239,6 +239,20 @@ def _validate_quota_set(quota_set): return True +@jsonschema.FormatChecker.cls_checks('quota_class_set') +def _validate_quota_class_set(instance): + bad_keys = [] + for key in instance: + if key not in QUOTAS and key not in GROUP_QUOTAS: + bad_keys.append(key) + + if len(bad_keys) > 0: + msg = _("Bad key(s) in quota class set: %s") % ", ".join(bad_keys) + raise exception.InvalidInput(reason=msg) + + return True + + class FormatChecker(jsonschema.FormatChecker): """A FormatChecker can output the message from cause exception diff --git a/cinder/tests/unit/api/contrib/test_quotas_classes.py b/cinder/tests/unit/api/contrib/test_quotas_classes.py index d0b15870227..0df6290ec54 100644 --- a/cinder/tests/unit/api/contrib/test_quotas_classes.py +++ b/cinder/tests/unit/api/contrib/test_quotas_classes.py @@ -20,9 +20,6 @@ Tests for cinder.api.contrib.quota_classes.py import mock -import webob.exc - - from cinder.api.contrib import quota_classes from cinder import context from cinder import exception @@ -110,48 +107,46 @@ class QuotaClassSetsControllerTest(test.TestCase): volume_types.create(self.ctxt, 'fake_type') body = make_body(gigabytes=2000, snapshots=15, volumes=5, tenant_id=None) - result = self.controller.update(self.req, fake.PROJECT_ID, body) + result = self.controller.update(self.req, fake.PROJECT_ID, body=body) self.assertDictEqual(body, result) @mock.patch('cinder.api.openstack.wsgi.Controller.validate_string_length') - @mock.patch('cinder.utils.validate_integer') - def test_update_limit(self, mock_validate_integer, mock_validate): - mock_validate_integer.return_value = 5 + def test_update_limit(self, mock_validate): volume_types.create(self.ctxt, 'fake_type') - body = make_body(volumes=5) - result = self.controller.update(self.req, fake.PROJECT_ID, body) + body = make_body(volumes=5, tenant_id=None) + result = self.controller.update(self.req, fake.PROJECT_ID, body=body) self.assertEqual(5, result['quota_class_set']['volumes']) self.assertTrue(mock_validate.called) - self.assertTrue(mock_validate_integer.called) def test_update_wrong_key(self): volume_types.create(self.ctxt, 'fake_type') - body = {'quota_class_set': {'bad': 'bad'}} - result = self.controller.update(self.req, fake.PROJECT_ID, body) - self.assertDictEqual(make_body(tenant_id=None), result) + body = {'quota_class_set': {'bad': 100}} + self.assertRaises(exception.InvalidInput, self.controller.update, + self.req, fake.PROJECT_ID, body=body) def test_update_invalid_key_value(self): body = {'quota_class_set': {'gigabytes': "should_be_int"}} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, fake.PROJECT_ID, body) + self.assertRaises(exception.ValidationError, self.controller.update, + self.req, fake.PROJECT_ID, body=body) def test_update_bad_quota_limit(self): body = {'quota_class_set': {'gigabytes': -1000}} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, fake.PROJECT_ID, body) + self.assertRaises(exception.ValidationError, self.controller.update, + self.req, fake.PROJECT_ID, body=body) def test_update_no_admin(self): self.req.environ['cinder.context'].is_admin = False + volume_types.create(self.ctxt, 'fake_type') self.assertRaises(exception.PolicyNotAuthorized, self.controller.update, self.req, fake.PROJECT_ID, - make_body(tenant_id=None)) + body=make_body(tenant_id=None)) def test_update_with_more_volume_types(self): volume_types.create(self.ctxt, 'fake_type_1') volume_types.create(self.ctxt, 'fake_type_2') body = {'quota_class_set': {'gigabytes_fake_type_1': 1111, 'volumes_fake_type_2': 2222}} - result = self.controller.update(self.req, fake.PROJECT_ID, body) + result = self.controller.update(self.req, fake.PROJECT_ID, body=body) self.assertDictEqual(make_response_body(ctxt=self.ctxt, quota_class=fake.PROJECT_ID, request_body=body,