V3 jsonschema validation: volume_transfer

This patch adds jsonschema validation for below volume transfer API's
* POST /v3/{project_id}/os-volume-transfer
* POST  /v3/{project_id}/os-volume-transfer/{transfer_id}/accept

Modified existing unit test cases which were mistakenly passed
'transer_id' in request body which is supposed to pass in path.

Partial-Implements: bp json-schema-validation

Change-Id: Idcbfd37d49584ed6cb438cfdc0e7403504977525
This commit is contained in:
Neha Alhat 2018-01-02 17:37:18 +05:30
parent 9586112848
commit 43ae14cc17
3 changed files with 126 additions and 48 deletions

View File

@ -21,9 +21,10 @@ from webob import exc
from cinder.api import common
from cinder.api import extensions
from cinder.api.openstack import wsgi
from cinder.api.schemas import volume_transfer
from cinder.api import validation
from cinder.api.views import transfers as transfer_view
from cinder import exception
from cinder.i18n import _
from cinder import transfer as transferAPI
LOG = logging.getLogger(__name__)
@ -74,25 +75,18 @@ class VolumeTransferController(wsgi.Controller):
return transfers
@wsgi.response(http_client.ACCEPTED)
@validation.schema(volume_transfer.create)
def create(self, req, body):
"""Create a new volume transfer."""
LOG.debug('Creating new volume transfer %s', body)
self.assert_valid_body(body, 'transfer')
context = req.environ['cinder.context']
transfer = body['transfer']
try:
volume_id = transfer['volume_id']
except KeyError:
msg = _("Incorrect request body format")
raise exc.HTTPBadRequest(explanation=msg)
volume_id = transfer['volume_id']
name = transfer.get('name', None)
if name is not None:
self.validate_string_length(name, 'Transfer name',
min_length=1, max_length=255,
remove_whitespaces=True)
name = name.strip()
LOG.info("Creating transfer of volume %s",
@ -109,20 +103,15 @@ class VolumeTransferController(wsgi.Controller):
return transfer
@wsgi.response(http_client.ACCEPTED)
@validation.schema(volume_transfer.accept)
def accept(self, req, id, body):
"""Accept a new volume transfer."""
transfer_id = id
LOG.debug('Accepting volume transfer %s', transfer_id)
self.assert_valid_body(body, 'accept')
context = req.environ['cinder.context']
accept = body['accept']
try:
auth_key = accept['auth_key']
except KeyError:
msg = _("Incorrect request body format")
raise exc.HTTPBadRequest(explanation=msg)
auth_key = accept['auth_key']
LOG.info("Accepting transfer %s", transfer_id)

View File

@ -0,0 +1,58 @@
# 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 transfer API.
"""
from cinder.api.validation import parameter_types
create = {
'type': 'object',
'properties': {
'transfer': {
'type': 'object',
'properties': {
'volume_id': parameter_types.uuid,
'name': {'oneOf': [{'type': 'string',
'format':
"name_skip_leading_trailing_spaces"},
{'type': 'null'}]}
},
'required': ['volume_id'],
'additionalProperties': False,
},
},
'required': ['transfer'],
'additionalProperties': False,
}
accept = {
'type': 'object',
'properties': {
'accept': {
'type': 'object',
'properties': {
'auth_key': {'type': ['string', 'integer']},
},
'required': ['auth_key'],
'additionalProperties': False,
},
},
'required': ['accept'],
'additionalProperties': False,
}

View File

@ -17,8 +17,6 @@
Tests for volume transfer code.
"""
import mock
from oslo_serialization import jsonutils
from six.moves import http_client
import webob
@ -186,9 +184,7 @@ class VolumeTransferAPITestCase(test.TestCase):
db.transfer_destroy(context.get_admin_context(), transfer1['id'])
db.volume_destroy(context.get_admin_context(), volume_id_1)
@mock.patch(
'cinder.api.openstack.wsgi.Controller.validate_string_length')
def test_create_transfer_json(self, mock_validate):
def test_create_transfer_json(self):
volume_id = self._create_volume(status='available', size=5)
body = {"transfer": {"name": "transfer1",
"volume_id": volume_id}}
@ -209,7 +205,6 @@ class VolumeTransferAPITestCase(test.TestCase):
self.assertIn('created_at', res_dict['transfer'])
self.assertIn('name', res_dict['transfer'])
self.assertIn('volume_id', res_dict['transfer'])
self.assertTrue(mock_validate.called)
db.volume_destroy(context.get_admin_context(), volume_id)
@ -227,9 +222,6 @@ class VolumeTransferAPITestCase(test.TestCase):
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertEqual(http_client.BAD_REQUEST,
res_dict['badRequest']['code'])
self.assertEqual("Missing required element 'transfer' in "
"request body.",
res_dict['badRequest']['message'])
def test_create_transfer_with_body_KeyError(self):
body = {"transfer": {"name": "transfer1"}}
@ -245,10 +237,8 @@ class VolumeTransferAPITestCase(test.TestCase):
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertEqual(http_client.BAD_REQUEST,
res_dict['badRequest']['code'])
self.assertEqual('Incorrect request body format',
res_dict['badRequest']['message'])
def test_create_transfer_with_VolumeNotFound(self):
def test_create_transfer_with_invalid_volume_id_value(self):
body = {"transfer": {"name": "transfer1",
"volume_id": 1234}}
@ -261,11 +251,9 @@ class VolumeTransferAPITestCase(test.TestCase):
fake_auth_context=self.user_ctxt))
res_dict = jsonutils.loads(res.body)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
self.assertEqual(http_client.NOT_FOUND,
res_dict['itemNotFound']['code'])
self.assertEqual('Volume 1234 could not be found.',
res_dict['itemNotFound']['message'])
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertEqual(http_client.BAD_REQUEST,
res_dict['badRequest']['code'])
def test_create_transfer_with_InvalidVolume(self):
volume_id = self._create_volume(status='attached')
@ -288,6 +276,26 @@ class VolumeTransferAPITestCase(test.TestCase):
db.volume_destroy(context.get_admin_context(), volume_id)
def test_create_transfer_with_leading_trailing_spaces_for_name(self):
volume_id = self._create_volume(status='available', size=5)
body = {"transfer": {"name": " transfer1 ",
"volume_id": volume_id}}
req = webob.Request.blank('/v2/%s/os-volume-transfer' %
fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
res_dict = jsonutils.loads(res.body)
self.assertEqual(http_client.ACCEPTED, res.status_int)
self.assertEqual(body['transfer']['name'].strip(),
res_dict['transfer']['name'])
db.volume_destroy(context.get_admin_context(), volume_id)
def test_delete_transfer_awaiting_transfer(self):
volume_id = self._create_volume()
transfer = self._create_transfer(volume_id)
@ -340,8 +348,7 @@ class VolumeTransferAPITestCase(test.TestCase):
transfer = self._create_transfer(volume_id)
svc = self.start_service('volume', host='fake_host')
body = {"accept": {"id": transfer['id'],
"auth_key": transfer['auth_key']}}
body = {"accept": {"auth_key": transfer['auth_key']}}
req = webob.Request.blank('/v2/%s/os-volume-transfer/%s/accept' % (
fake.PROJECT_ID, transfer['id']))
req.method = 'POST'
@ -374,9 +381,8 @@ class VolumeTransferAPITestCase(test.TestCase):
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertEqual(http_client.BAD_REQUEST,
res_dict['badRequest']['code'])
self.assertEqual("Missing required element 'accept' in request body.",
res_dict['badRequest']['message'])
db.transfer_destroy(context.get_admin_context(), transfer['id'])
db.volume_destroy(context.get_admin_context(), volume_id)
def test_accept_transfer_with_body_KeyError(self):
@ -398,15 +404,15 @@ class VolumeTransferAPITestCase(test.TestCase):
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertEqual(http_client.BAD_REQUEST,
res_dict['badRequest']['code'])
self.assertEqual("Missing required element 'accept' in request body.",
res_dict['badRequest']['message'])
db.transfer_destroy(context.get_admin_context(), transfer['id'])
db.volume_destroy(context.get_admin_context(), volume_id)
def test_accept_transfer_invalid_id_auth_key(self):
volume_id = self._create_volume()
transfer = self._create_transfer(volume_id)
body = {"accept": {"id": transfer['id'],
"auth_key": 1}}
body = {"accept": {"auth_key": 1}}
req = webob.Request.blank('/v2/%s/os-volume-transfer/%s/accept' % (
fake.PROJECT_ID, transfer['id']))
req.method = 'POST'
@ -430,8 +436,7 @@ class VolumeTransferAPITestCase(test.TestCase):
volume_id = self._create_volume()
transfer = self._create_transfer(volume_id)
body = {"accept": {"id": transfer['id'],
"auth_key": 1}}
body = {"accept": {"auth_key": 1}}
req = webob.Request.blank('/v2/%s/os-volume-transfer/%s/accept' % (
fake.PROJECT_ID, fake.WILL_NOT_BE_FOUND_ID))
req.method = 'POST'
@ -467,8 +472,7 @@ class VolumeTransferAPITestCase(test.TestCase):
volume_id = self._create_volume()
transfer = self._create_transfer(volume_id)
body = {"accept": {"id": transfer['id'],
"auth_key": transfer['auth_key']}}
body = {"accept": {"auth_key": transfer['auth_key']}}
req = webob.Request.blank('/v2/%s/os-volume-transfer/%s/accept' % (
fake.PROJECT_ID, transfer['id']))
@ -486,6 +490,9 @@ class VolumeTransferAPITestCase(test.TestCase):
'2G has been consumed.',
res_dict['overLimit']['message'])
db.transfer_destroy(context.get_admin_context(), transfer['id'])
db.volume_destroy(context.get_admin_context(), volume_id)
def test_accept_transfer_with_VolumeLimitExceeded(self):
def fake_transfer_api_accept_throwing_VolumeLimitExceeded(cls,
@ -500,8 +507,7 @@ class VolumeTransferAPITestCase(test.TestCase):
volume_id = self._create_volume()
transfer = self._create_transfer(volume_id)
body = {"accept": {"id": transfer['id'],
"auth_key": transfer['auth_key']}}
body = {"accept": {"auth_key": transfer['auth_key']}}
req = webob.Request.blank('/v2/%s/os-volume-transfer/%s/accept' % (
fake.PROJECT_ID, transfer['id']))
@ -517,3 +523,28 @@ class VolumeTransferAPITestCase(test.TestCase):
self.assertEqual("VolumeLimitExceeded: Maximum number of volumes "
"allowed (1) exceeded for quota 'volumes'.",
res_dict['overLimit']['message'])
db.transfer_destroy(context.get_admin_context(), transfer['id'])
db.volume_destroy(context.get_admin_context(), volume_id)
def test_accept_transfer_with_auth_key_null(self):
volume_id = self._create_volume(size=5)
transfer = self._create_transfer(volume_id)
body = {"accept": {"auth_key": None}}
req = webob.Request.blank('/v2/%s/os-volume-transfer/%s/accept' % (
fake.PROJECT_ID, transfer['id']))
req.body = jsonutils.dump_as_bytes(body)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.headers['Accept'] = 'application/json'
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
res_dict = jsonutils.loads(res.body)
self.assertEqual(http_client.BAD_REQUEST,
res_dict['badRequest']['code'])
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
db.transfer_destroy(context.get_admin_context(), transfer['id'])
db.volume_destroy(context.get_admin_context(), volume_id)