Merge "V3 jsonschema validation: Volumes"
This commit is contained in:
commit
b52d26a361
|
@ -12,42 +12,22 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.schemas import scheduler_hints
|
||||
from cinder.api import validation
|
||||
|
||||
|
||||
class SchedulerHintsController(wsgi.Controller):
|
||||
|
||||
@validation.schema(scheduler_hints.create)
|
||||
def _extract_scheduler_hints(self, req, body):
|
||||
hints = {}
|
||||
attr = '%s:scheduler_hints' % Scheduler_hints.alias
|
||||
if body.get(attr) is not None:
|
||||
hints.update(body.get(attr))
|
||||
|
||||
return hints
|
||||
|
||||
@wsgi.extends
|
||||
def create(self, req, body):
|
||||
attr = '%s:scheduler_hints' % Scheduler_hints.alias
|
||||
def create(req, body):
|
||||
attr = 'OS-SCH-HNT:scheduler_hints'
|
||||
if body.get(attr) is not None:
|
||||
scheduler_hints_body = dict.fromkeys((attr,), body.get(attr))
|
||||
hints = self._extract_scheduler_hints(req, body=scheduler_hints_body)
|
||||
|
||||
if 'volume' in body:
|
||||
body['volume']['scheduler_hints'] = hints
|
||||
yield
|
||||
@validation.schema(scheduler_hints.create)
|
||||
def _validate_scheduler_hints(req=None, body=None):
|
||||
# TODO(pooja_jadhav): The scheduler hints schema validation
|
||||
# should be moved to v3 volume schema directly and this module
|
||||
# should be deleted at the time of deletion of v2 version code.
|
||||
pass
|
||||
|
||||
|
||||
class Scheduler_hints(extensions.ExtensionDescriptor):
|
||||
"""Pass arbitrary key/value pairs to the scheduler."""
|
||||
|
||||
name = "SchedulerHints"
|
||||
alias = "OS-SCH-HNT"
|
||||
updated = "2013-04-18T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = SchedulerHintsController()
|
||||
ext = extensions.ControllerExtension(self, 'volumes', controller)
|
||||
return [ext]
|
||||
_validate_scheduler_hints(req=req, body=scheduler_hints_body)
|
||||
body['volume']['scheduler_hints'] = scheduler_hints_body.get(attr)
|
||||
return body
|
||||
|
|
|
@ -31,7 +31,7 @@ import cinder.policy
|
|||
CONF = cfg.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
FILES_TO_SKIP = ['resource_common_manage.py']
|
||||
FILES_TO_SKIP = ['resource_common_manage.py', 'scheduler_hints.py']
|
||||
|
||||
|
||||
class ExtensionDescriptor(object):
|
||||
|
|
|
@ -143,6 +143,8 @@ BACKUP_AZ = '3.51'
|
|||
|
||||
SUPPORT_VOLUME_TYPE_FILTER = '3.52'
|
||||
|
||||
SUPPORT_VOLUME_SCHEMA_CHANGES = '3.53'
|
||||
|
||||
|
||||
def get_mv_header(version):
|
||||
"""Gets a formatted HTTP microversion header.
|
||||
|
|
|
@ -118,6 +118,13 @@ REST_API_VERSION_HISTORY = """
|
|||
* 3.52 - ``RESKEY:availability_zones`` is a reserved spec key for AZ
|
||||
volume type, and filter volume type by ``extra_specs`` is
|
||||
supported now.
|
||||
* 3.53 - Add schema validation support for request body using jsonschema
|
||||
for V2/V3 volume APIs.
|
||||
1. Modified create volume API to accept only parameters which are
|
||||
documented in the api-ref otherwise it will return 400 error.
|
||||
2. Update volume API expects user to pass at least one valid
|
||||
parameter in the request body in order to update the volume.
|
||||
Also, additional parameters will not be allowed.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
|
@ -125,9 +132,9 @@ REST_API_VERSION_HISTORY = """
|
|||
# minimum version of the API supported.
|
||||
# Explicitly using /v2 endpoints will still work
|
||||
_MIN_API_VERSION = "3.0"
|
||||
_MAX_API_VERSION = "3.52"
|
||||
_MAX_API_VERSION = "3.53"
|
||||
_LEGACY_API_VERSION2 = "2.0"
|
||||
UPDATED = "2017-09-19T20:18:14Z"
|
||||
UPDATED = "2018-06-29T05:34:49Z"
|
||||
|
||||
|
||||
# NOTE(cyeoh): min and max versions declared as functions so we can
|
||||
|
|
|
@ -409,3 +409,27 @@ Add support for cross AZ backups.
|
|||
----
|
||||
``RESKEY:availability_zones`` is a reserved spec key for AZ volume type,
|
||||
and filter volume type by ``extra_specs`` is supported now.
|
||||
|
||||
3.53
|
||||
----
|
||||
Schema validation support has been added using jsonschema for V2/V3
|
||||
volume APIs.
|
||||
|
||||
- Create volume API
|
||||
Before 3.53, create volume API used to accept any invalid parameters in the
|
||||
request body like the ones below were passed by python-cinderclient.
|
||||
|
||||
1. user_id
|
||||
2. project_id
|
||||
3. status
|
||||
4. attach_status
|
||||
|
||||
But in 3.53, this behavior is updated. If user passes any invalid
|
||||
parameters to the API which are not documented in api-ref, then
|
||||
it will raise badRequest error.
|
||||
|
||||
- Update volume API
|
||||
Before 3.53, even if user doesn't pass any valid parameters in the request body,
|
||||
the volume was updated.
|
||||
But in 3.53, user will need to pass at least one valid parameter in the request
|
||||
body otherwise it will return 400 error.
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
# 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 Volumes API.
|
||||
|
||||
"""
|
||||
import copy
|
||||
|
||||
from cinder.api.validation import parameter_types
|
||||
|
||||
|
||||
create = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'volume': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {'type': ['string', 'null'],
|
||||
'format': 'name_non_mandatory_remove_white_spaces'},
|
||||
'description': {
|
||||
'type': ['string', 'null'],
|
||||
'format': 'description_non_mandatory_remove_white_spaces'},
|
||||
'display_name': {
|
||||
'type': ['string', 'null'],
|
||||
'format': 'name_non_mandatory_remove_white_spaces'},
|
||||
'display_description': {
|
||||
'type': ['string', 'null'],
|
||||
'format':
|
||||
'description_non_mandatory_remove_white_spaces'},
|
||||
# volume_type accepts 'id' as well as 'name' so do lazy schema
|
||||
# validation for it.
|
||||
'volume_type': parameter_types.name_allow_zero_min_length,
|
||||
'metadata': parameter_types.metadata_allows_null,
|
||||
'snapshot_id': parameter_types.optional_uuid,
|
||||
'source_volid': parameter_types.optional_uuid,
|
||||
'consistencygroup_id': parameter_types.optional_uuid,
|
||||
'size': parameter_types.volume_size,
|
||||
'availability_zone': parameter_types.availability_zone,
|
||||
'multiattach': parameter_types.optional_boolean,
|
||||
'image_id': {'type': ['string', 'null'], 'minLength': 0,
|
||||
'maxLength': 255},
|
||||
'imageRef': {'type': ['string', 'null'], 'minLength': 0,
|
||||
'maxLength': 255},
|
||||
},
|
||||
'required': ['size'],
|
||||
'additionalProperties': True,
|
||||
},
|
||||
'OS-SCH-HNT:scheduler_hints': {
|
||||
'type': ['object', 'null']
|
||||
},
|
||||
},
|
||||
'required': ['volume'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
|
||||
create_volume_v313 = copy.deepcopy(create)
|
||||
create_volume_v313['properties']['volume']['properties'][
|
||||
'group_id'] = {'type': ['string', 'null'], 'minLength': 0,
|
||||
'maxLength': 255}
|
||||
|
||||
create_volume_v347 = copy.deepcopy(create_volume_v313)
|
||||
create_volume_v347['properties']['volume']['properties'][
|
||||
'backup_id'] = parameter_types.optional_uuid
|
||||
|
||||
create_volume_v353 = copy.deepcopy(create_volume_v347)
|
||||
create_volume_v353['properties']['volume']['additionalProperties'] = False
|
||||
|
||||
|
||||
update = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'volume': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
# The 'name' and 'description' are required to be compatible
|
||||
# with v2.
|
||||
'name': {
|
||||
'type': ['string', 'null'],
|
||||
'format': 'name_non_mandatory_remove_white_spaces'},
|
||||
'description': {
|
||||
'type': ['string', 'null'],
|
||||
'format':
|
||||
'description_non_mandatory_remove_white_spaces'},
|
||||
'display_name': {
|
||||
'type': ['string', 'null'],
|
||||
'format': 'name_non_mandatory_remove_white_spaces'},
|
||||
'display_description': {
|
||||
'type': ['string', 'null'],
|
||||
'format':
|
||||
'description_non_mandatory_remove_white_spaces'},
|
||||
'metadata': parameter_types.extra_specs,
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['volume'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
|
||||
update_volume_v353 = copy.deepcopy(update)
|
||||
update_volume_v353['properties']['volume']['anyOf'] = [
|
||||
{'required': ['name']},
|
||||
{'required': ['description']},
|
||||
{'required': ['display_name']},
|
||||
{'required': ['display_description']},
|
||||
{'required': ['metadata']}]
|
|
@ -25,8 +25,11 @@ import webob
|
|||
from webob import exc
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api.contrib import scheduler_hints
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.schemas import volumes
|
||||
from cinder.api.v2.views import volumes as volume_views
|
||||
from cinder.api import validation
|
||||
from cinder import exception
|
||||
from cinder import group as group_api
|
||||
from cinder.i18n import _
|
||||
|
@ -172,24 +175,23 @@ class VolumeController(wsgi.Controller):
|
|||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
@wsgi.response(http_client.ACCEPTED)
|
||||
@validation.schema(volumes.create, '2.0')
|
||||
def create(self, req, body):
|
||||
"""Creates a new volume."""
|
||||
self.assert_valid_body(body, 'volume')
|
||||
|
||||
LOG.debug('Create volume request body: %s', body)
|
||||
context = req.environ['cinder.context']
|
||||
|
||||
# NOTE (pooja_jadhav) To fix bug 1774155, scheduler hints is not
|
||||
# loaded as a standard extension. If user passes
|
||||
# OS-SCH-HNT:scheduler_hints in the request body, then it will be
|
||||
# validated in the create method and this method will add
|
||||
# scheduler_hints in body['volume'].
|
||||
body = scheduler_hints.create(req, body)
|
||||
volume = body['volume']
|
||||
|
||||
# Check up front for legacy replication parameters to quick fail
|
||||
source_replica = volume.get('source_replica')
|
||||
if source_replica:
|
||||
msg = _("Creating a volume from a replica source was part of the "
|
||||
"replication v1 implementation which is no longer "
|
||||
"available.")
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
kwargs = {}
|
||||
self.validate_name_and_description(volume)
|
||||
self.validate_name_and_description(volume, check_length=False)
|
||||
|
||||
# NOTE(thingee): v2 API allows name instead of display_name
|
||||
if 'name' in volume:
|
||||
|
@ -213,9 +215,6 @@ class VolumeController(wsgi.Controller):
|
|||
|
||||
snapshot_id = volume.get('snapshot_id')
|
||||
if snapshot_id is not None:
|
||||
if not uuidutils.is_uuid_like(snapshot_id):
|
||||
msg = _("Snapshot ID must be in UUID form.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
# Not found exception will be handled at the wsgi level
|
||||
kwargs['snapshot'] = self.volume_api.get_snapshot(context,
|
||||
snapshot_id)
|
||||
|
@ -224,10 +223,6 @@ class VolumeController(wsgi.Controller):
|
|||
|
||||
source_volid = volume.get('source_volid')
|
||||
if source_volid is not None:
|
||||
if not uuidutils.is_uuid_like(source_volid):
|
||||
msg = _("Source volume ID '%s' must be a "
|
||||
"valid UUID.") % source_volid
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
# Not found exception will be handled at the wsgi level
|
||||
kwargs['source_volume'] = \
|
||||
self.volume_api.get_volume(context,
|
||||
|
@ -239,10 +234,6 @@ class VolumeController(wsgi.Controller):
|
|||
kwargs['consistencygroup'] = None
|
||||
consistencygroup_id = volume.get('consistencygroup_id')
|
||||
if consistencygroup_id is not None:
|
||||
if not uuidutils.is_uuid_like(consistencygroup_id):
|
||||
msg = _("Consistency group ID '%s' must be a "
|
||||
"valid UUID.") % consistencygroup_id
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
# Not found exception will be handled at the wsgi level
|
||||
kwargs['group'] = self.group_api.get(context, consistencygroup_id)
|
||||
|
||||
|
@ -285,34 +276,14 @@ class VolumeController(wsgi.Controller):
|
|||
"""Return volume search options allowed by non-admin."""
|
||||
return CONF.query_volume_filters
|
||||
|
||||
@validation.schema(volumes.update, '2.0', '3.52')
|
||||
@validation.schema(volumes.update_volume_v353, '3.53')
|
||||
def update(self, req, id, body):
|
||||
"""Update a volume."""
|
||||
context = req.environ['cinder.context']
|
||||
update_dict = body['volume']
|
||||
|
||||
if not body:
|
||||
msg = _("Missing request body")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if 'volume' not in body:
|
||||
msg = _("Missing required element '%s' in request body") % 'volume'
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
volume = body['volume']
|
||||
update_dict = {}
|
||||
|
||||
valid_update_keys = (
|
||||
'name',
|
||||
'description',
|
||||
'display_name',
|
||||
'display_description',
|
||||
'metadata',
|
||||
)
|
||||
|
||||
for key in valid_update_keys:
|
||||
if key in volume:
|
||||
update_dict[key] = volume[key]
|
||||
|
||||
self.validate_name_and_description(update_dict)
|
||||
self.validate_name_and_description(update_dict, check_length=False)
|
||||
|
||||
# NOTE(thingee): v2 API allows name instead of display_name
|
||||
if 'name' in update_dict:
|
||||
|
|
|
@ -15,17 +15,19 @@
|
|||
|
||||
from oslo_log import log as logging
|
||||
from oslo_log import versionutils
|
||||
from oslo_utils import uuidutils
|
||||
import six
|
||||
from six.moves import http_client
|
||||
import webob
|
||||
from webob import exc
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api.contrib import scheduler_hints
|
||||
from cinder.api import microversions as mv
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.schemas import volumes
|
||||
from cinder.api.v2 import volumes as volumes_v2
|
||||
from cinder.api.v3.views import volumes as volume_views_v3
|
||||
from cinder.api import validation
|
||||
from cinder.backup import api as backup_api
|
||||
from cinder import exception
|
||||
from cinder import group as group_api
|
||||
|
@ -224,6 +226,10 @@ class VolumeController(volumes_v2.VolumeController):
|
|||
return image_snapshot
|
||||
|
||||
@wsgi.response(http_client.ACCEPTED)
|
||||
@validation.schema(volumes.create, '3.0', '3.12')
|
||||
@validation.schema(volumes.create_volume_v313, '3.13', '3.46')
|
||||
@validation.schema(volumes.create_volume_v347, '3.47', '3.52')
|
||||
@validation.schema(volumes.create_volume_v353, '3.53')
|
||||
def create(self, req, body):
|
||||
"""Creates a new volume.
|
||||
|
||||
|
@ -232,39 +238,21 @@ class VolumeController(volumes_v2.VolumeController):
|
|||
:returns: dict -- the new volume dictionary
|
||||
:raises HTTPNotFound, HTTPBadRequest:
|
||||
"""
|
||||
self.assert_valid_body(body, 'volume')
|
||||
|
||||
LOG.debug('Create volume request body: %s', body)
|
||||
context = req.environ['cinder.context']
|
||||
|
||||
req_version = req.api_version_request
|
||||
# Remove group_id from body if max version is less than GROUP_VOLUME.
|
||||
if req_version.matches(None, mv.get_prior_version(mv.GROUP_VOLUME)):
|
||||
# NOTE(xyang): The group_id is from a group created with a
|
||||
# group_type. So with this group_id, we've got a group_type
|
||||
# for this volume. Also if group_id is passed in, that means
|
||||
# we already know which backend is hosting the group and the
|
||||
# volume will be created on the same backend as well. So it
|
||||
# won't go through the scheduler again if a group_id is
|
||||
# passed in.
|
||||
try:
|
||||
body.get('volume', {}).pop('group_id', None)
|
||||
except AttributeError:
|
||||
msg = (_("Invalid body provided for creating volume. "
|
||||
"Request API version: %s.") % req_version)
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
# NOTE (pooja_jadhav) To fix bug 1774155, scheduler hints is not
|
||||
# loaded as a standard extension. If user passes
|
||||
# OS-SCH-HNT:scheduler_hints in the request body, then it will be
|
||||
# validated in the create method and this method will add
|
||||
# scheduler_hints in body['volume'].
|
||||
body = scheduler_hints.create(req, body)
|
||||
|
||||
volume = body['volume']
|
||||
kwargs = {}
|
||||
self.validate_name_and_description(volume)
|
||||
|
||||
# Check up front for legacy replication parameters to quick fail
|
||||
source_replica = volume.get('source_replica')
|
||||
if source_replica:
|
||||
msg = _("Creating a volume from a replica source was part of the "
|
||||
"replication v1 implementation which is no longer "
|
||||
"available.")
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
self.validate_name_and_description(volume, check_length=False)
|
||||
|
||||
# NOTE(thingee): v2 API allows name instead of display_name
|
||||
if 'name' in volume:
|
||||
|
@ -288,9 +276,6 @@ class VolumeController(volumes_v2.VolumeController):
|
|||
|
||||
snapshot_id = volume.get('snapshot_id')
|
||||
if snapshot_id is not None:
|
||||
if not uuidutils.is_uuid_like(snapshot_id):
|
||||
msg = _("Snapshot ID must be in UUID form.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
# Not found exception will be handled at the wsgi level
|
||||
kwargs['snapshot'] = self.volume_api.get_snapshot(context,
|
||||
snapshot_id)
|
||||
|
@ -299,10 +284,6 @@ class VolumeController(volumes_v2.VolumeController):
|
|||
|
||||
source_volid = volume.get('source_volid')
|
||||
if source_volid is not None:
|
||||
if not uuidutils.is_uuid_like(source_volid):
|
||||
msg = _("Source volume ID '%s' must be a "
|
||||
"valid UUID.") % source_volid
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
# Not found exception will be handled at the wsgi level
|
||||
kwargs['source_volume'] = (
|
||||
self.volume_api.get_volume(context,
|
||||
|
@ -314,10 +295,6 @@ class VolumeController(volumes_v2.VolumeController):
|
|||
kwargs['consistencygroup'] = None
|
||||
consistencygroup_id = volume.get('consistencygroup_id')
|
||||
if consistencygroup_id is not None:
|
||||
if not uuidutils.is_uuid_like(consistencygroup_id):
|
||||
msg = _("Consistency group ID '%s' must be a "
|
||||
"valid UUID.") % consistencygroup_id
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
# Not found exception will be handled at the wsgi level
|
||||
kwargs['group'] = self.group_api.get(context, consistencygroup_id)
|
||||
|
||||
|
@ -338,18 +315,10 @@ class VolumeController(volumes_v2.VolumeController):
|
|||
else:
|
||||
kwargs['image_id'] = image_uuid
|
||||
|
||||
# Add backup if min version is greater than or equal
|
||||
# to VOLUME_CREATE_FROM_BACKUP.
|
||||
if req_version.matches(mv.VOLUME_CREATE_FROM_BACKUP, None):
|
||||
backup_id = volume.get('backup_id')
|
||||
if backup_id:
|
||||
if not uuidutils.is_uuid_like(backup_id):
|
||||
msg = _("Backup ID must be in UUID form.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
kwargs['backup'] = self.backup_api.get(context,
|
||||
backup_id=backup_id)
|
||||
else:
|
||||
kwargs['backup'] = None
|
||||
backup_id = volume.get('backup_id')
|
||||
if backup_id:
|
||||
kwargs['backup'] = self.backup_api.get(context,
|
||||
backup_id=backup_id)
|
||||
|
||||
size = volume.get('size', None)
|
||||
if size is None and kwargs['snapshot'] is not None:
|
||||
|
|
|
@ -208,7 +208,8 @@ nullable_string = {
|
|||
volume_size = {
|
||||
'type': ['integer', 'string'],
|
||||
'pattern': '^[0-9]+$',
|
||||
'minimum': 1
|
||||
'minimum': 1,
|
||||
'maximum': constants.DB_MAX_INT
|
||||
}
|
||||
|
||||
|
||||
|
@ -262,3 +263,11 @@ key_size = {'type': ['string', 'integer', 'null'],
|
|||
'minimum': 0,
|
||||
'maximum': constants.DB_MAX_INT,
|
||||
'format': 'key_size'}
|
||||
|
||||
|
||||
availability_zone = {
|
||||
'type': ['string', 'null'], 'minLength': 1, 'maxLength': 255
|
||||
}
|
||||
|
||||
|
||||
optional_boolean = {'oneOf': [{'type': 'null'}, boolean]}
|
||||
|
|
|
@ -219,6 +219,24 @@ def _validate_disabled_reason(param_value):
|
|||
return True
|
||||
|
||||
|
||||
@jsonschema.FormatChecker.cls_checks(
|
||||
'name_non_mandatory_remove_white_spaces')
|
||||
def _validate_name_non_mandatory_remove_white_spaces(param_value):
|
||||
_validate_string_length(param_value, 'name',
|
||||
mandatory=False, min_length=0, max_length=255,
|
||||
remove_whitespaces=True)
|
||||
return True
|
||||
|
||||
|
||||
@jsonschema.FormatChecker.cls_checks(
|
||||
'description_non_mandatory_remove_white_spaces')
|
||||
def _validate_description_non_mandatory_remove_white_spaces(param_value):
|
||||
_validate_string_length(param_value, 'description',
|
||||
mandatory=False, min_length=0, max_length=255,
|
||||
remove_whitespaces=True)
|
||||
return True
|
||||
|
||||
|
||||
@jsonschema.FormatChecker.cls_checks('quota_set')
|
||||
def _validate_quota_set(quota_set):
|
||||
bad_keys = []
|
||||
|
|
|
@ -137,7 +137,7 @@ class VolumeMetaDataTest(test.TestCase):
|
|||
"metadata": {}}
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
self.volume_controller.create(req, body)
|
||||
self.volume_controller.create(req, body=body)
|
||||
|
||||
def test_index(self):
|
||||
req = fakes.HTTPRequest.blank(self.url)
|
||||
|
|
|
@ -74,7 +74,7 @@ class VolumeApiTest(test.TestCase):
|
|||
vol = self._vol_in_request_body()
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
ex = self._expected_vol_from_controller()
|
||||
self.assertEqual(ex, res_dict)
|
||||
self.assertTrue(mock_validate.called)
|
||||
|
@ -100,19 +100,19 @@ class VolumeApiTest(test.TestCase):
|
|||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
# Raise 404 when type name isn't valid
|
||||
self.assertRaises(exception.VolumeTypeNotFoundByName,
|
||||
self.controller.create, req, body)
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
# Use correct volume type name
|
||||
vol.update(dict(volume_type=CONF.default_volume_type))
|
||||
vol = self._vol_in_request_body(volume_type=CONF.default_volume_type)
|
||||
body.update(dict(volume=vol))
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
volume_id = res_dict['volume']['id']
|
||||
self.assertEqual(1, len(res_dict))
|
||||
|
||||
# Use correct volume type id
|
||||
vol.update(dict(volume_type=db_vol_type['id']))
|
||||
vol = self._vol_in_request_body(volume_type=db_vol_type['id'])
|
||||
body.update(dict(volume=vol))
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
volume_id = res_dict['volume']['id']
|
||||
self.assertEqual(1, len(res_dict))
|
||||
|
||||
|
@ -241,7 +241,7 @@ class VolumeApiTest(test.TestCase):
|
|||
vol = self._vol_in_request_body(snapshot_id=snapshot_id)
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
|
||||
ex = self._expected_vol_from_controller(snapshot_id=snapshot_id)
|
||||
self.assertEqual(ex, res_dict)
|
||||
|
@ -268,7 +268,7 @@ class VolumeApiTest(test.TestCase):
|
|||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
# Raise 404 when snapshot cannot be found.
|
||||
self.assertRaises(exception.SnapshotNotFound, self.controller.create,
|
||||
req, body)
|
||||
req, body=body)
|
||||
context = req.environ['cinder.context']
|
||||
get_snapshot.assert_called_once_with(self.controller.volume_api,
|
||||
context, snapshot_id)
|
||||
|
@ -282,8 +282,8 @@ class VolumeApiTest(test.TestCase):
|
|||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
# Raise 400 when snapshot has not uuid type.
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
|
||||
req, body)
|
||||
self.assertRaises(exception.ValidationError, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
@mock.patch.object(db.sqlalchemy.api, '_volume_type_get_full',
|
||||
autospec=True)
|
||||
|
@ -299,7 +299,7 @@ class VolumeApiTest(test.TestCase):
|
|||
vol = self._vol_in_request_body(source_volid=source_volid)
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
|
||||
ex = self._expected_vol_from_controller(source_volid=source_volid)
|
||||
self.assertEqual(ex, res_dict)
|
||||
|
@ -329,7 +329,7 @@ class VolumeApiTest(test.TestCase):
|
|||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
# Raise 404 when source volume cannot be found.
|
||||
self.assertRaises(exception.VolumeNotFound, self.controller.create,
|
||||
req, body)
|
||||
req, body=body)
|
||||
|
||||
context = req.environ['cinder.context']
|
||||
get_volume.assert_called_once_with(self.controller.volume_api,
|
||||
|
@ -345,8 +345,8 @@ class VolumeApiTest(test.TestCase):
|
|||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
# Raise 400 for resource requested with invalid uuids.
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
|
||||
req, body)
|
||||
self.assertRaises(exception.ValidationError, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
@mock.patch.object(groupAPI.API, 'get', autospec=True)
|
||||
def test_volume_creation_fails_with_invalid_consistency_group(self,
|
||||
|
@ -361,7 +361,7 @@ class VolumeApiTest(test.TestCase):
|
|||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
# Raise 404 when consistency group is not found.
|
||||
self.assertRaises(exception.GroupNotFound,
|
||||
self.controller.create, req, body)
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
context = req.environ['cinder.context']
|
||||
get_cg.assert_called_once_with(self.controller.group_api,
|
||||
|
@ -371,10 +371,10 @@ class VolumeApiTest(test.TestCase):
|
|||
vol = self._vol_in_request_body(size="")
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
def test_volume_creation_fails_with_bad_availability_zone(self):
|
||||
vol = self._vol_in_request_body(availability_zone="zonen:hostn")
|
||||
|
@ -382,7 +382,7 @@ class VolumeApiTest(test.TestCase):
|
|||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
self.assertRaises(exception.InvalidAvailabilityZone,
|
||||
self.controller.create,
|
||||
req, body)
|
||||
req, body=body)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
|
@ -399,7 +399,7 @@ class VolumeApiTest(test.TestCase):
|
|||
ex = self._expected_vol_from_controller(availability_zone="nova")
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
self.assertEqual(ex, res_dict)
|
||||
self.assertTrue(mock_validate.called)
|
||||
|
||||
|
@ -410,10 +410,10 @@ class VolumeApiTest(test.TestCase):
|
|||
image_ref=1234)
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
def test_volume_create_with_image_ref_not_uuid_format(self):
|
||||
self.mock_object(volume_api.API, "create", v2_fakes.fake_volume_create)
|
||||
|
@ -428,7 +428,7 @@ class VolumeApiTest(test.TestCase):
|
|||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
def test_volume_create_with_image_ref_with_empty_string(self):
|
||||
self.mock_object(volume_api.API, "create", v2_fakes.fake_volume_create)
|
||||
|
@ -443,7 +443,7 @@ class VolumeApiTest(test.TestCase):
|
|||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
|
@ -460,7 +460,7 @@ class VolumeApiTest(test.TestCase):
|
|||
ex = self._expected_vol_from_controller(availability_zone="nova")
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
self.assertEqual(ex, res_dict)
|
||||
self.assertTrue(mock_validate.called)
|
||||
|
||||
|
@ -471,10 +471,10 @@ class VolumeApiTest(test.TestCase):
|
|||
image_id=1234)
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
def test_volume_create_with_image_id_not_uuid_format(self):
|
||||
self.mock_object(volume_api.API, "create", v2_fakes.fake_volume_create)
|
||||
|
@ -489,7 +489,7 @@ class VolumeApiTest(test.TestCase):
|
|||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
def test_volume_create_with_image_id_with_empty_string(self):
|
||||
self.mock_object(volume_api.API, "create", v2_fakes.fake_volume_create)
|
||||
|
@ -504,7 +504,7 @@ class VolumeApiTest(test.TestCase):
|
|||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
|
@ -524,9 +524,8 @@ class VolumeApiTest(test.TestCase):
|
|||
ex = self._expected_vol_from_controller(availability_zone="nova")
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
self.assertEqual(ex, res_dict)
|
||||
self.assertTrue(mock_validate.called)
|
||||
|
||||
def test_volume_create_with_image_name_has_multiple(self):
|
||||
self.mock_object(db, 'volume_get', v2_fakes.fake_volume_get_db)
|
||||
|
@ -544,7 +543,7 @@ class VolumeApiTest(test.TestCase):
|
|||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
def test_volume_create_with_image_name_no_match(self):
|
||||
self.mock_object(db, 'volume_get', v2_fakes.fake_volume_get_db)
|
||||
|
@ -562,17 +561,17 @@ class VolumeApiTest(test.TestCase):
|
|||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
def test_volume_create_with_invalid_multiattach(self):
|
||||
vol = self._vol_in_request_body(multiattach="InvalidBool")
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
@mock.patch.object(volume_api.API, 'create', autospec=True)
|
||||
@mock.patch.object(volume_api.API, 'get', autospec=True)
|
||||
|
@ -591,7 +590,7 @@ class VolumeApiTest(test.TestCase):
|
|||
ex = self._expected_vol_from_controller(multiattach=True)
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
|
||||
self.assertEqual(ex, res_dict)
|
||||
|
||||
|
@ -605,36 +604,36 @@ class VolumeApiTest(test.TestCase):
|
|||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
|
||||
if len(list(value.keys())[0]) == 0 or list(value.values())[0] is None:
|
||||
exc = exception.InvalidVolumeMetadata
|
||||
else:
|
||||
exc = exception.InvalidVolumeMetadataSize
|
||||
self.assertRaises(exc,
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
def test_volume_update(self, mock_validate):
|
||||
@ddt.data({"name": "Updated Test Name",
|
||||
"description": "Updated Test Description"},
|
||||
{"name": " test name ",
|
||||
"description": " test description "})
|
||||
def test_volume_update(self, body):
|
||||
self.mock_object(volume_api.API, 'get', v2_fakes.fake_volume_api_get)
|
||||
self.mock_object(volume_api.API, "update", v2_fakes.fake_volume_update)
|
||||
self.mock_object(db.sqlalchemy.api, '_volume_type_get_full',
|
||||
v2_fakes.fake_volume_type_get)
|
||||
|
||||
updates = {
|
||||
"name": "Updated Test Name",
|
||||
"name": body['name'],
|
||||
"description": body['description']
|
||||
}
|
||||
body = {"volume": updates}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/%s' % fake.VOLUME_ID)
|
||||
self.assertEqual(0, len(self.notifier.notifications))
|
||||
res_dict = self.controller.update(req, fake.VOLUME_ID, body)
|
||||
name = updates["name"].strip()
|
||||
description = updates["description"].strip()
|
||||
expected = self._expected_vol_from_controller(
|
||||
availability_zone=v2_fakes.DEFAULT_AZ, name="Updated Test Name",
|
||||
availability_zone=v2_fakes.DEFAULT_AZ, name=name,
|
||||
description=description,
|
||||
metadata={'attached_mode': 'rw', 'readonly': 'False'})
|
||||
res_dict = self.controller.update(req, fake.VOLUME_ID, body=body)
|
||||
self.assertEqual(expected, res_dict)
|
||||
self.assertEqual(2, len(self.notifier.notifications))
|
||||
self.assertTrue(mock_validate.called)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
|
@ -651,7 +650,7 @@ class VolumeApiTest(test.TestCase):
|
|||
body = {"volume": updates}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/%s' % fake.VOLUME_ID)
|
||||
self.assertEqual(0, len(self.notifier.notifications))
|
||||
res_dict = self.controller.update(req, fake.VOLUME_ID, body)
|
||||
res_dict = self.controller.update(req, fake.VOLUME_ID, body=body)
|
||||
expected = self._expected_vol_from_controller(
|
||||
availability_zone=v2_fakes.DEFAULT_AZ, name="Updated Test Name",
|
||||
description="Updated Test Description",
|
||||
|
@ -678,7 +677,7 @@ class VolumeApiTest(test.TestCase):
|
|||
body = {"volume": updates}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/%s' % fake.VOLUME_ID)
|
||||
self.assertEqual(0, len(self.notifier.notifications))
|
||||
res_dict = self.controller.update(req, fake.VOLUME_ID, body)
|
||||
res_dict = self.controller.update(req, fake.VOLUME_ID, body=body)
|
||||
expected = self._expected_vol_from_controller(
|
||||
availability_zone=v2_fakes.DEFAULT_AZ,
|
||||
name="New Name", description="New Description",
|
||||
|
@ -701,7 +700,7 @@ class VolumeApiTest(test.TestCase):
|
|||
body = {"volume": updates}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/%s' % fake.VOLUME_ID)
|
||||
self.assertEqual(0, len(self.notifier.notifications))
|
||||
res_dict = self.controller.update(req, fake.VOLUME_ID, body)
|
||||
res_dict = self.controller.update(req, fake.VOLUME_ID, body=body)
|
||||
expected = self._expected_vol_from_controller(
|
||||
availability_zone=v2_fakes.DEFAULT_AZ,
|
||||
metadata={'attached_mode': 'rw', 'readonly': 'False',
|
||||
|
@ -742,7 +741,7 @@ class VolumeApiTest(test.TestCase):
|
|||
self.assertEqual(0, len(self.notifier.notifications))
|
||||
admin_ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
||||
req.environ['cinder.context'] = admin_ctx
|
||||
res_dict = self.controller.update(req, fake.VOLUME_ID, body)
|
||||
res_dict = self.controller.update(req, fake.VOLUME_ID, body=body)
|
||||
expected = self._expected_vol_from_controller(
|
||||
availability_zone=v2_fakes.DEFAULT_AZ, volume_type=None,
|
||||
status='in-use', name='Updated Test Name',
|
||||
|
@ -776,29 +775,36 @@ class VolumeApiTest(test.TestCase):
|
|||
body = {"volume": updates}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/%s' % fake.VOLUME_ID)
|
||||
|
||||
if len(list(value.keys())[0]) == 0 or list(value.values())[0] is None:
|
||||
exc = exception.InvalidVolumeMetadata
|
||||
else:
|
||||
exc = webob.exc.HTTPRequestEntityTooLarge
|
||||
self.assertRaises(exc,
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.update,
|
||||
req, fake.VOLUME_ID, body)
|
||||
req, fake.VOLUME_ID, body=body)
|
||||
|
||||
def test_update_empty_body(self):
|
||||
body = {}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/%s' % fake.VOLUME_ID)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.update,
|
||||
req, fake.VOLUME_ID, body)
|
||||
req, fake.VOLUME_ID, body=body)
|
||||
|
||||
def test_update_invalid_body(self):
|
||||
body = {
|
||||
'name': 'missing top level volume key'
|
||||
}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/%s' % fake.VOLUME_ID)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.update,
|
||||
req, fake.VOLUME_ID, body)
|
||||
req, fake.VOLUME_ID, body=body)
|
||||
|
||||
@ddt.data({'name': 'a' * 256},
|
||||
{'description': 'a' * 256},
|
||||
{'display_name': 'a' * 256},
|
||||
{'display_description': 'a' * 256})
|
||||
def test_update_exceeds_length_name_description(self, vol):
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/%s' % fake.VOLUME_ID)
|
||||
body = {'volume': vol}
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.controller.update,
|
||||
req, fake.VOLUME_ID, body=body)
|
||||
|
||||
def test_update_not_found(self):
|
||||
self.mock_object(volume_api.API, "get",
|
||||
|
@ -810,7 +816,7 @@ class VolumeApiTest(test.TestCase):
|
|||
req = fakes.HTTPRequest.blank('/v2/volumes/%s' % fake.VOLUME_ID)
|
||||
self.assertRaises(exception.VolumeNotFound,
|
||||
self.controller.update,
|
||||
req, fake.VOLUME_ID, body)
|
||||
req, fake.VOLUME_ID, body=body)
|
||||
|
||||
def test_volume_list_summary(self):
|
||||
self.mock_object(volume_api.API, 'get_all',
|
||||
|
@ -1476,8 +1482,8 @@ class VolumeApiTest(test.TestCase):
|
|||
req = fakes.HTTPRequest.blank('/v2/%s/volumes' % fake.PROJECT_ID)
|
||||
req.method = 'POST'
|
||||
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create, req, body)
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
def test_create_no_body(self):
|
||||
self._create_volume_bad_request(body=None)
|
||||
|
|
|
@ -163,7 +163,7 @@ class VolumeMetaDataTest(test.TestCase):
|
|||
"metadata": {}}
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
self.volume_controller.create(req, body)
|
||||
self.volume_controller.create(req, body=body)
|
||||
|
||||
def test_index(self):
|
||||
req = fakes.HTTPRequest.blank(self.url, version=mv.ETAGS)
|
||||
|
|
|
@ -16,7 +16,9 @@ import ddt
|
|||
import iso8601
|
||||
|
||||
import mock
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import strutils
|
||||
from six.moves import http_client
|
||||
import webob
|
||||
|
||||
from cinder.api import extensions
|
||||
|
@ -284,7 +286,7 @@ class VolumeApiTest(test.TestCase):
|
|||
req = fakes.HTTPRequest.blank('/v3/volumes')
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_NOVA_IMAGE)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_NOVA_IMAGE)
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
self.assertEqual(ex, res_dict)
|
||||
context = req.environ['cinder.context']
|
||||
get_snapshot.assert_called_once_with(self.controller.volume_api,
|
||||
|
@ -314,7 +316,7 @@ class VolumeApiTest(test.TestCase):
|
|||
availability_zone="nova")
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v3/volumes')
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
|
||||
req = self._fake_volumes_summary_request()
|
||||
res_dict = self.controller.summary(req)
|
||||
|
@ -498,25 +500,34 @@ class VolumeApiTest(test.TestCase):
|
|||
|
||||
return volume
|
||||
|
||||
@ddt.data(mv.GROUP_VOLUME, mv.get_prior_version(mv.GROUP_VOLUME))
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
def test_volume_create(self, max_ver, mock_validate):
|
||||
@ddt.data((mv.GROUP_VOLUME,
|
||||
{'display_name': ' test name ',
|
||||
'display_description': ' test desc ',
|
||||
'size': 1}),
|
||||
(mv.get_prior_version(mv.GROUP_VOLUME),
|
||||
{'name': ' test name ',
|
||||
'description': ' test desc ',
|
||||
'size': 1}))
|
||||
@ddt.unpack
|
||||
def test_volume_create(self, max_ver, volume_body):
|
||||
self.mock_object(volume_api.API, 'get', v2_fakes.fake_volume_get)
|
||||
self.mock_object(volume_api.API, "create",
|
||||
v2_fakes.fake_volume_api_create)
|
||||
self.mock_object(db.sqlalchemy.api, '_volume_type_get_full',
|
||||
v2_fakes.fake_volume_type_get)
|
||||
|
||||
vol = self._vol_in_request_body()
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v3/volumes')
|
||||
req.api_version_request = mv.get_api_version(max_ver)
|
||||
res_dict = self.controller.create(req, body)
|
||||
|
||||
body = {'volume': volume_body}
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
ex = self._expected_vol_from_controller(
|
||||
req_version=req.api_version_request)
|
||||
self.assertEqual(ex, res_dict)
|
||||
self.assertTrue(mock_validate.called)
|
||||
req_version=req.api_version_request, name='test name',
|
||||
description='test desc')
|
||||
self.assertEqual(ex['volume']['name'],
|
||||
res_dict['volume']['name'])
|
||||
self.assertEqual(ex['volume']['description'],
|
||||
res_dict['volume']['description'])
|
||||
|
||||
@ddt.data(mv.GROUP_SNAPSHOTS, mv.get_prior_version(mv.GROUP_SNAPSHOTS))
|
||||
@mock.patch.object(group_api.API, 'get')
|
||||
|
@ -542,7 +553,7 @@ class VolumeApiTest(test.TestCase):
|
|||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v3/volumes')
|
||||
req.api_version_request = mv.get_api_version(max_ver)
|
||||
res_dict = self.controller.create(req, body)
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
ex = self._expected_vol_from_controller(
|
||||
snapshot_id=snapshot_id,
|
||||
req_version=req.api_version_request)
|
||||
|
@ -574,11 +585,14 @@ class VolumeApiTest(test.TestCase):
|
|||
volume_type_get.side_effect = v2_fakes.fake_volume_type_get
|
||||
|
||||
backup_id = fake.BACKUP_ID
|
||||
vol = self._vol_in_request_body(backup_id=backup_id)
|
||||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v3/volumes')
|
||||
req.api_version_request = mv.get_api_version(max_ver)
|
||||
res_dict = self.controller.create(req, body)
|
||||
if max_ver == mv.VOLUME_CREATE_FROM_BACKUP:
|
||||
vol = self._vol_in_request_body(backup_id=backup_id)
|
||||
else:
|
||||
vol = self._vol_in_request_body()
|
||||
body = {"volume": vol}
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
ex = self._expected_vol_from_controller(
|
||||
req_version=req.api_version_request)
|
||||
self.assertEqual(ex, res_dict)
|
||||
|
@ -597,6 +611,63 @@ class VolumeApiTest(test.TestCase):
|
|||
v2_fakes.DEFAULT_VOL_DESCRIPTION,
|
||||
**kwargs)
|
||||
|
||||
def test_volume_creation_with_scheduler_hints(self):
|
||||
vol = self._vol_in_request_body(availability_zone=None)
|
||||
vol.pop('group_id')
|
||||
body = {"volume": vol,
|
||||
"OS-SCH-HNT:scheduler_hints": {
|
||||
'different_host': [fake.UUID1, fake.UUID2]}}
|
||||
req = webob.Request.blank('/v3/%s/volumes' % 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.ctxt))
|
||||
res_dict = jsonutils.loads(res.body)
|
||||
self.assertEqual(http_client.ACCEPTED, res.status_int)
|
||||
self.assertIn('id', res_dict['volume'])
|
||||
|
||||
@ddt.data('fake_host', '', 1234, ' ')
|
||||
def test_volume_creation_invalid_scheduler_hints(self, invalid_hints):
|
||||
vol = self._vol_in_request_body()
|
||||
vol.pop('group_id')
|
||||
body = {"volume": vol,
|
||||
"OS-SCH-HNT:scheduler_hints": {
|
||||
'different_host': invalid_hints}}
|
||||
req = fakes.HTTPRequest.blank('/v3/volumes')
|
||||
self.assertRaises(exception.ValidationError, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
@ddt.data({'size': 'a'},
|
||||
{'size': ''},
|
||||
{'size': 0},
|
||||
{'size': 2 ** 31},
|
||||
{'size': None},
|
||||
{})
|
||||
def test_volume_creation_fails_with_invalid_parameters(
|
||||
self, vol_body):
|
||||
body = {"volume": vol_body}
|
||||
req = fakes.HTTPRequest.blank('/v3/volumes')
|
||||
self.assertRaises(exception.ValidationError, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
def test_volume_creation_fails_with_additional_properties(self):
|
||||
body = {"volume": {"size": 1, "user_id": fake.USER_ID,
|
||||
"project_id": fake.PROJECT_ID}}
|
||||
req = fakes.HTTPRequest.blank('/v3/volumes')
|
||||
req.api_version_request = mv.get_api_version(
|
||||
mv.SUPPORT_VOLUME_SCHEMA_CHANGES)
|
||||
self.assertRaises(exception.ValidationError, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
def test_volume_update_without_vol_data(self):
|
||||
body = {"volume": {}}
|
||||
req = fakes.HTTPRequest.blank('/v3/volumes/%s' % fake.VOLUME_ID)
|
||||
req.api_version_request = mv.get_api_version(
|
||||
mv.SUPPORT_VOLUME_SCHEMA_CHANGES)
|
||||
self.assertRaises(exception.ValidationError, self.controller.update,
|
||||
req, fake.VOLUME_ID, body=body)
|
||||
|
||||
@ddt.data({'s': 'ea895e29-8485-4930-bbb8-c5616a309c0e'},
|
||||
['ea895e29-8485-4930-bbb8-c5616a309c0e'],
|
||||
42)
|
||||
|
@ -606,8 +677,8 @@ class VolumeApiTest(test.TestCase):
|
|||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v3/volumes')
|
||||
# Raise 400 when snapshot has not uuid type.
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
|
||||
req, body)
|
||||
self.assertRaises(exception.ValidationError, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
@ddt.data({'source_volid': 1},
|
||||
{'source_volid': []},
|
||||
|
@ -619,8 +690,8 @@ class VolumeApiTest(test.TestCase):
|
|||
body = {"volume": vol}
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes')
|
||||
# Raise 400 for resource requested with invalid uuids.
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
|
||||
req, body)
|
||||
self.assertRaises(exception.ValidationError, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
@ddt.data(mv.get_prior_version(mv.RESOURCE_FILTER), mv.RESOURCE_FILTER,
|
||||
mv.LIKE_FILTER)
|
||||
|
|
Loading…
Reference in New Issue