V3 jsonschema validation: volume_image_metadata
This patch adds jsonschema validation for below volume image metadata API's: * Set image metadata for a volume POST /v3/{project_id}/volumes/{volume_id}/action * Remove image metadata from a volume POST /v3/{project_id}/volumes/{volume_id}/action Note: In this patch it will raise BadRequest(400) if user passes long key and long value(greater than 255 characters) for metadata. Earlier it was raising HTTPRequestEntityTooLarge(413) on master. Partial-Implements: bp json-schema-validation Change-Id: I226391f60c0577d83c9beb94bfe1bc08ab22b708
This commit is contained in:
parent
9d894b5232
commit
a3f573a3f0
|
@ -21,6 +21,8 @@ from oslo_log import log as logging
|
|||
from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.schemas import volume_image_metadata
|
||||
from cinder.api import validation
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.policies import volume_metadata as policy
|
||||
|
@ -83,14 +85,11 @@ class VolumeImageMetadataController(wsgi.Controller):
|
|||
self._add_image_metadata(context, volumes)
|
||||
|
||||
@wsgi.action("os-set_image_metadata")
|
||||
@validation.schema(volume_image_metadata.set_image_metadata)
|
||||
def create(self, req, id, body):
|
||||
context = req.environ['cinder.context']
|
||||
if context.authorize(policy.IMAGE_METADATA_POLICY):
|
||||
try:
|
||||
metadata = body['os-set_image_metadata']['metadata']
|
||||
except (KeyError, TypeError):
|
||||
msg = _("Malformed request body.")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
metadata = body['os-set_image_metadata']['metadata']
|
||||
new_metadata = self._update_volume_image_metadata(context,
|
||||
id,
|
||||
metadata,
|
||||
|
@ -125,27 +124,20 @@ class VolumeImageMetadataController(wsgi.Controller):
|
|||
return {'metadata': self._get_image_metadata(context, id)[1]}
|
||||
|
||||
@wsgi.action("os-unset_image_metadata")
|
||||
@validation.schema(volume_image_metadata.unset_image_metadata)
|
||||
def delete(self, req, id, body):
|
||||
"""Deletes an existing image metadata."""
|
||||
context = req.environ['cinder.context']
|
||||
if context.authorize(policy.IMAGE_METADATA_POLICY):
|
||||
try:
|
||||
key = body['os-unset_image_metadata']['key']
|
||||
except (KeyError, TypeError):
|
||||
msg = _("Malformed request body.")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
key = body['os-unset_image_metadata']['key']
|
||||
|
||||
if key:
|
||||
vol, metadata = self._get_image_metadata(context, id)
|
||||
if key not in metadata:
|
||||
raise exception.GlanceMetadataNotFound(id=id)
|
||||
vol, metadata = self._get_image_metadata(context, id)
|
||||
if key not in metadata:
|
||||
raise exception.GlanceMetadataNotFound(id=id)
|
||||
|
||||
self.volume_api.delete_volume_metadata(
|
||||
context, vol, key,
|
||||
meta_type=common.METADATA_TYPES.image)
|
||||
else:
|
||||
msg = _("The key cannot be None.")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
self.volume_api.delete_volume_metadata(
|
||||
context, vol, key,
|
||||
meta_type=common.METADATA_TYPES.image)
|
||||
|
||||
return webob.Response(status_int=http_client.OK)
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# Copyright (C) 2018 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 volume image metadata API.
|
||||
|
||||
"""
|
||||
|
||||
from cinder.api.validation import parameter_types
|
||||
|
||||
|
||||
set_image_metadata = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'os-set_image_metadata': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'metadata': parameter_types.extra_specs,
|
||||
},
|
||||
'required': ['metadata'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['os-set_image_metadata'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
|
||||
unset_image_metadata = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'os-unset_image_metadata': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'key': {'type': 'string',
|
||||
'minLength': 1,
|
||||
'maxLength': 255},
|
||||
},
|
||||
'required': ['key'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['os-unset_image_metadata'],
|
||||
'additionalProperties': False,
|
||||
}
|
|
@ -238,7 +238,8 @@ class VolumeImageMetadataTest(test.TestCase):
|
|||
req.body = jsonutils.dump_as_bytes(body)
|
||||
|
||||
self.assertRaises(exception.PolicyNotAuthorized,
|
||||
self.controller.create, req, fake.VOLUME_ID, None)
|
||||
self.controller.create, req, fake.VOLUME_ID,
|
||||
body=body)
|
||||
|
||||
def test_create_with_keys_case_insensitive(self):
|
||||
# If the keys in uppercase_and_lowercase, should return the one
|
||||
|
@ -277,8 +278,9 @@ class VolumeImageMetadataTest(test.TestCase):
|
|||
req.method = 'POST'
|
||||
req.headers["content-type"] = "application/json"
|
||||
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create, req, fake.VOLUME_ID, None)
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create, req, fake.VOLUME_ID,
|
||||
body=None)
|
||||
|
||||
def test_create_nonexistent_volume(self):
|
||||
self.mock_object(volume.api.API, 'get', return_volume_nonexistent)
|
||||
|
@ -292,7 +294,8 @@ class VolumeImageMetadataTest(test.TestCase):
|
|||
}
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
self.assertRaises(exception.VolumeNotFound,
|
||||
self.controller.create, req, fake.VOLUME_ID, body)
|
||||
self.controller.create, req, fake.VOLUME_ID,
|
||||
body=body)
|
||||
|
||||
def test_invalid_metadata_items_on_create(self):
|
||||
self.mock_object(db, 'volume_metadata_update',
|
||||
|
@ -308,24 +311,27 @@ class VolumeImageMetadataTest(test.TestCase):
|
|||
|
||||
# Test for long key
|
||||
req.body = jsonutils.dump_as_bytes(data)
|
||||
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
|
||||
self.controller.create, req, fake.VOLUME_ID, data)
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create, req, fake.VOLUME_ID,
|
||||
body=data)
|
||||
|
||||
# Test for long value
|
||||
data = {"os-set_image_metadata": {
|
||||
"metadata": {"key": "v" * 260}}
|
||||
}
|
||||
req.body = jsonutils.dump_as_bytes(data)
|
||||
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
|
||||
self.controller.create, req, fake.VOLUME_ID, data)
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create, req, fake.VOLUME_ID,
|
||||
body=data)
|
||||
|
||||
# Test for empty key.
|
||||
data = {"os-set_image_metadata": {
|
||||
"metadata": {"": "value1"}}
|
||||
}
|
||||
req.body = jsonutils.dump_as_bytes(data)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create, req, fake.VOLUME_ID, data)
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create, req, fake.VOLUME_ID,
|
||||
body=data)
|
||||
|
||||
def test_delete(self):
|
||||
self.mock_object(db, 'volume_metadata_delete',
|
||||
|
@ -361,8 +367,9 @@ class VolumeImageMetadataTest(test.TestCase):
|
|||
}
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
|
||||
self.assertRaises(exception.PolicyNotAuthorized,
|
||||
self.controller.delete, req, fake.VOLUME_ID, None)
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.delete, req, fake.VOLUME_ID,
|
||||
body=None)
|
||||
|
||||
def test_delete_meta_not_found(self):
|
||||
data = {"os-unset_image_metadata": {
|
||||
|
@ -375,7 +382,8 @@ class VolumeImageMetadataTest(test.TestCase):
|
|||
req.headers["content-type"] = "application/json"
|
||||
|
||||
self.assertRaises(exception.GlanceMetadataNotFound,
|
||||
self.controller.delete, req, fake.VOLUME_ID, data)
|
||||
self.controller.delete, req, fake.VOLUME_ID,
|
||||
body=data)
|
||||
|
||||
def test_delete_nonexistent_volume(self):
|
||||
self.mock_object(db, 'volume_metadata_delete',
|
||||
|
@ -391,7 +399,18 @@ class VolumeImageMetadataTest(test.TestCase):
|
|||
req.headers["content-type"] = "application/json"
|
||||
|
||||
self.assertRaises(exception.GlanceMetadataNotFound,
|
||||
self.controller.delete, req, fake.VOLUME_ID, body)
|
||||
self.controller.delete, req, fake.VOLUME_ID,
|
||||
body=body)
|
||||
|
||||
def test_delete_empty_body(self):
|
||||
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' % (
|
||||
fake.PROJECT_ID, fake.VOLUME_ID))
|
||||
req.method = 'POST'
|
||||
req.headers["content-type"] = "application/json"
|
||||
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.delete, req, fake.VOLUME_ID,
|
||||
body=None)
|
||||
|
||||
def test_show_image_metadata(self):
|
||||
body = {"os-show_image_metadata": None}
|
||||
|
|
|
@ -1156,7 +1156,6 @@ class API(base.Base):
|
|||
'%s status.') % volume['status']
|
||||
LOG.info(msg, resource=volume)
|
||||
raise exception.InvalidVolume(reason=msg)
|
||||
utils.check_metadata_properties(metadata)
|
||||
return self.db.volume_metadata_update(context, volume['id'],
|
||||
metadata, delete, meta_type)
|
||||
|
||||
|
|
Loading…
Reference in New Issue