Merge "V3 jsonschema validation: Quota classes"
This commit is contained in:
commit
dee77b32dd
@ -17,11 +17,12 @@ import webob
|
|||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
from cinder.api.openstack import wsgi
|
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 db
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.policies import quota_class as policy
|
from cinder.policies import quota_class as policy
|
||||||
from cinder import quota
|
from cinder import quota
|
||||||
from cinder import utils
|
|
||||||
|
|
||||||
|
|
||||||
QUOTAS = quota.QUOTAS
|
QUOTAS = quota.QUOTAS
|
||||||
@ -50,6 +51,7 @@ class QuotaClassSetsController(wsgi.Controller):
|
|||||||
|
|
||||||
return self._format_quota_set(id, quota_set)
|
return self._format_quota_set(id, quota_set)
|
||||||
|
|
||||||
|
@validation.schema(quota_class.update_quota_class)
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
context.authorize(policy.MANAGE_POLICY)
|
context.authorize(policy.MANAGE_POLICY)
|
||||||
@ -57,13 +59,9 @@ class QuotaClassSetsController(wsgi.Controller):
|
|||||||
min_length=1, max_length=255)
|
min_length=1, max_length=255)
|
||||||
|
|
||||||
quota_class = id
|
quota_class = id
|
||||||
self.assert_valid_body(body, 'quota_class_set')
|
|
||||||
|
|
||||||
for key, value in body['quota_class_set'].items():
|
for key, value in body['quota_class_set'].items():
|
||||||
if key in QUOTAS or key in GROUP_QUOTAS:
|
|
||||||
try:
|
try:
|
||||||
value = utils.validate_integer(value, key, min_value=-1,
|
|
||||||
max_value=db.MAX_INT)
|
|
||||||
db.quota_class_update(context, quota_class, key, value)
|
db.quota_class_update(context, quota_class, key, value)
|
||||||
except exception.QuotaClassNotFound:
|
except exception.QuotaClassNotFound:
|
||||||
db.quota_class_create(context, quota_class, key, value)
|
db.quota_class_create(context, quota_class, key, value)
|
||||||
|
32
cinder/api/schemas/quota_classes.py
Normal file
32
cinder/api/schemas/quota_classes.py
Normal file
@ -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,
|
||||||
|
}
|
@ -24,6 +24,8 @@ import unicodedata
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from cinder.common import constants
|
||||||
|
|
||||||
|
|
||||||
def _is_printable(char):
|
def _is_printable(char):
|
||||||
"""determine if a unicode code point is printable.
|
"""determine if a unicode code point is printable.
|
||||||
@ -232,3 +234,18 @@ service_id = {
|
|||||||
|
|
||||||
optional_uuid = {'oneOf': [{'type': 'null'},
|
optional_uuid = {'oneOf': [{'type': 'null'},
|
||||||
{'type': 'string', 'format': 'uuid'}]}
|
{'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
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -239,6 +239,20 @@ def _validate_quota_set(quota_set):
|
|||||||
return True
|
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):
|
class FormatChecker(jsonschema.FormatChecker):
|
||||||
"""A FormatChecker can output the message from cause exception
|
"""A FormatChecker can output the message from cause exception
|
||||||
|
|
||||||
|
@ -20,9 +20,6 @@ Tests for cinder.api.contrib.quota_classes.py
|
|||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
import webob.exc
|
|
||||||
|
|
||||||
|
|
||||||
from cinder.api.contrib import quota_classes
|
from cinder.api.contrib import quota_classes
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
@ -110,48 +107,46 @@ class QuotaClassSetsControllerTest(test.TestCase):
|
|||||||
volume_types.create(self.ctxt, 'fake_type')
|
volume_types.create(self.ctxt, 'fake_type')
|
||||||
body = make_body(gigabytes=2000, snapshots=15,
|
body = make_body(gigabytes=2000, snapshots=15,
|
||||||
volumes=5, tenant_id=None)
|
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)
|
self.assertDictEqual(body, result)
|
||||||
|
|
||||||
@mock.patch('cinder.api.openstack.wsgi.Controller.validate_string_length')
|
@mock.patch('cinder.api.openstack.wsgi.Controller.validate_string_length')
|
||||||
@mock.patch('cinder.utils.validate_integer')
|
def test_update_limit(self, mock_validate):
|
||||||
def test_update_limit(self, mock_validate_integer, mock_validate):
|
|
||||||
mock_validate_integer.return_value = 5
|
|
||||||
volume_types.create(self.ctxt, 'fake_type')
|
volume_types.create(self.ctxt, 'fake_type')
|
||||||
body = make_body(volumes=5)
|
body = make_body(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.assertEqual(5, result['quota_class_set']['volumes'])
|
self.assertEqual(5, result['quota_class_set']['volumes'])
|
||||||
self.assertTrue(mock_validate.called)
|
self.assertTrue(mock_validate.called)
|
||||||
self.assertTrue(mock_validate_integer.called)
|
|
||||||
|
|
||||||
def test_update_wrong_key(self):
|
def test_update_wrong_key(self):
|
||||||
volume_types.create(self.ctxt, 'fake_type')
|
volume_types.create(self.ctxt, 'fake_type')
|
||||||
body = {'quota_class_set': {'bad': 'bad'}}
|
body = {'quota_class_set': {'bad': 100}}
|
||||||
result = self.controller.update(self.req, fake.PROJECT_ID, body)
|
self.assertRaises(exception.InvalidInput, self.controller.update,
|
||||||
self.assertDictEqual(make_body(tenant_id=None), result)
|
self.req, fake.PROJECT_ID, body=body)
|
||||||
|
|
||||||
def test_update_invalid_key_value(self):
|
def test_update_invalid_key_value(self):
|
||||||
body = {'quota_class_set': {'gigabytes': "should_be_int"}}
|
body = {'quota_class_set': {'gigabytes': "should_be_int"}}
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
|
self.assertRaises(exception.ValidationError, self.controller.update,
|
||||||
self.req, fake.PROJECT_ID, body)
|
self.req, fake.PROJECT_ID, body=body)
|
||||||
|
|
||||||
def test_update_bad_quota_limit(self):
|
def test_update_bad_quota_limit(self):
|
||||||
body = {'quota_class_set': {'gigabytes': -1000}}
|
body = {'quota_class_set': {'gigabytes': -1000}}
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
|
self.assertRaises(exception.ValidationError, self.controller.update,
|
||||||
self.req, fake.PROJECT_ID, body)
|
self.req, fake.PROJECT_ID, body=body)
|
||||||
|
|
||||||
def test_update_no_admin(self):
|
def test_update_no_admin(self):
|
||||||
self.req.environ['cinder.context'].is_admin = False
|
self.req.environ['cinder.context'].is_admin = False
|
||||||
|
volume_types.create(self.ctxt, 'fake_type')
|
||||||
self.assertRaises(exception.PolicyNotAuthorized,
|
self.assertRaises(exception.PolicyNotAuthorized,
|
||||||
self.controller.update, self.req, fake.PROJECT_ID,
|
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):
|
def test_update_with_more_volume_types(self):
|
||||||
volume_types.create(self.ctxt, 'fake_type_1')
|
volume_types.create(self.ctxt, 'fake_type_1')
|
||||||
volume_types.create(self.ctxt, 'fake_type_2')
|
volume_types.create(self.ctxt, 'fake_type_2')
|
||||||
body = {'quota_class_set': {'gigabytes_fake_type_1': 1111,
|
body = {'quota_class_set': {'gigabytes_fake_type_1': 1111,
|
||||||
'volumes_fake_type_2': 2222}}
|
'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,
|
self.assertDictEqual(make_response_body(ctxt=self.ctxt,
|
||||||
quota_class=fake.PROJECT_ID,
|
quota_class=fake.PROJECT_ID,
|
||||||
request_body=body,
|
request_body=body,
|
||||||
|
Loading…
Reference in New Issue
Block a user