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:
Neha Alhat 2018-01-03 17:08:22 +05:30
parent 9d894b5232
commit a3f573a3f0
4 changed files with 102 additions and 35 deletions

View File

@ -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)

View File

@ -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,
}

View File

@ -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}

View File

@ -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)