diff --git a/nova/api/openstack/compute/plugins/v3/block_device_mapping.py b/nova/api/openstack/compute/plugins/v3/block_device_mapping.py index b0794a63cb15..c28851e265e1 100644 --- a/nova/api/openstack/compute/plugins/v3/block_device_mapping.py +++ b/nova/api/openstack/compute/plugins/v3/block_device_mapping.py @@ -17,6 +17,8 @@ from webob import exc +from nova.api.openstack.compute.schemas.v3 import block_device_mapping as \ + schema_block_device_mapping from nova.api.openstack import extensions from nova import block_device from nova import exception @@ -53,19 +55,17 @@ class BlockDeviceMapping(extensions.V3APIExtensionBase): 'is not allowed in the same request.') raise exc.HTTPBadRequest(explanation=expl) - if not isinstance(bdm, list): - msg = _('block_device_mapping_v2 must be a list') - raise exc.HTTPBadRequest(explanation=msg) - try: block_device_mapping = [ block_device.BlockDeviceDict.from_api(bdm_dict) for bdm_dict in bdm] - except (exception.InvalidBDMFormat, - exception.InvalidBDMVolumeNotBootable) as e: + except exception.InvalidBDMFormat as e: raise exc.HTTPBadRequest(explanation=e.format_message()) if block_device_mapping: create_kwargs['block_device_mapping'] = block_device_mapping # Unset the legacy_bdm flag if we got a block device mapping. create_kwargs['legacy_bdm'] = False + + def get_server_create_schema(self): + return schema_block_device_mapping.server_create diff --git a/nova/api/openstack/compute/schemas/v3/block_device_mapping.py b/nova/api/openstack/compute/schemas/v3/block_device_mapping.py new file mode 100644 index 000000000000..ad4719cfb0c3 --- /dev/null +++ b/nova/api/openstack/compute/schemas/v3/block_device_mapping.py @@ -0,0 +1,86 @@ +# Copyright 2014 NEC Corporation. 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. + +from nova.api.validation import parameter_types + +server_create = { + 'block_device_mapping_v2': { + 'type': 'array', + 'items': [{ + 'type': 'object', + 'properties': { + 'virtual_name': { + 'type': 'string', 'maxLength': 255, + }, + # defined in nova/block_device.py:from_api() + # NOTE: Client can specify the Id with the combination of + # source_type and uuid, or a single attribute like volume_id/ + # image_id/snapshot_id. + 'source_type': { + 'type': 'string', + 'enum': ['volume', 'image', 'snapshot', 'blank'], + }, + 'uuid': { + 'type': 'string', 'minLength': 1, 'maxLength': 255, + 'pattern': '^[a-zA-Z0-9._-]*$', + }, + 'volume_id': parameter_types.volume_id, + 'image_id': parameter_types.image_id, + 'snapshot_id': parameter_types.image_id, + + 'volume_size': parameter_types.non_negative_integer, + # Defined as varchar(255) in column "destination_type" in table + # "block_device_mapping" + 'destination_type': { + 'type': 'string', 'maxLength': 255, + }, + # Defined as varchar(255) in column "guest_format" in table + # "block_device_mapping" + 'guest_format': { + 'type': 'string', 'maxLength': 255, + }, + # Defined as varchar(255) in column "device_type" in table + # "block_device_mapping" + 'device_type': { + 'type': 'string', 'maxLength': 255, + }, + # Defined as varchar(255) in column "disk_bus" in table + # "block_device_mapping" + 'disk_bus': { + 'type': 'string', 'maxLength': 255, + }, + # Defined as integer in nova/block_device.py:from_api() + 'boot_index': { + 'type': ['integer', 'string'], + 'pattern': '^-?[0-9]+$', + }, + # Do not allow empty device names and number values and + # containing spaces(defined in nova/block_device.py:from_api()) + 'device_name': { + 'type': 'string', 'minLength': 1, 'maxLength': 255, + 'pattern': '^[a-zA-Z0-9._-r/]*$', + }, + # Defined as boolean in nova/block_device.py:from_api() + 'delete_on_termination': parameter_types.boolean, + 'no_device': {}, + # Defined as mediumtext in column "connection_info" in table + # "block_device_mapping" + 'connection_info': { + 'type': 'string', 'maxLength': 16777215 + }, + }, + 'additionalProperties': False + }] + } +} diff --git a/nova/api/validation/parameter_types.py b/nova/api/validation/parameter_types.py index 0e1d1c2c1791..fb568377ae87 100644 --- a/nova/api/validation/parameter_types.py +++ b/nova/api/validation/parameter_types.py @@ -94,6 +94,11 @@ server_id = { } +image_id = { + 'type': 'string', 'format': 'uuid' +} + + volume_id = { 'type': 'string', 'format': 'uuid' } diff --git a/nova/block_device.py b/nova/block_device.py index 8800e1a4e4e9..13b9eec4f899 100644 --- a/nova/block_device.py +++ b/nova/block_device.py @@ -384,6 +384,9 @@ def validate_and_default_volume_size(bdm): bdm['volume_size'] = utils.validate_integer( bdm['volume_size'], 'volume_size', min_value=0) except exception.InvalidInput: + # NOTE: We can remove this validation code after removing + # Nova v2.0 API code because v2.1 API validates this case + # already at its REST API layer. raise exception.InvalidBDMFormat( details=_("Invalid volume_size.")) diff --git a/nova/tests/unit/api/openstack/compute/contrib/test_block_device_mapping.py b/nova/tests/unit/api/openstack/compute/contrib/test_block_device_mapping.py index a76deb74d8b6..0cb2f70dc747 100644 --- a/nova/tests/unit/api/openstack/compute/contrib/test_block_device_mapping.py +++ b/nova/tests/unit/api/openstack/compute/contrib/test_block_device_mapping.py @@ -37,6 +37,7 @@ CONF = cfg.CONF class BlockDeviceMappingTestV21(test.TestCase): + validation_error = exception.ValidationError def _setup_controller(self): ext_info = plugins.LoadedExtensionInfo() @@ -158,7 +159,7 @@ class BlockDeviceMappingTestV21(test.TestCase): self.stubs.Set(compute_api.API, 'create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} - self.assertRaises(exc.HTTPBadRequest, + self.assertRaises(self.validation_error, self._test_create, params, no_image=True) @mock.patch.object(compute_api.API, 'create') @@ -179,7 +180,7 @@ class BlockDeviceMappingTestV21(test.TestCase): self.stubs.Set(compute_api.API, 'create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} - self.assertRaises(exc.HTTPBadRequest, + self.assertRaises(self.validation_error, self._test_create, params, no_image=True) def test_create_instance_with_device_name_too_long(self): @@ -194,7 +195,7 @@ class BlockDeviceMappingTestV21(test.TestCase): self.stubs.Set(compute_api.API, 'create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} - self.assertRaises(exc.HTTPBadRequest, + self.assertRaises(self.validation_error, self._test_create, params, no_image=True) def test_create_instance_with_space_in_device_name(self): @@ -210,7 +211,7 @@ class BlockDeviceMappingTestV21(test.TestCase): self.stubs.Set(compute_api.API, 'create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} - self.assertRaises(exc.HTTPBadRequest, + self.assertRaises(self.validation_error, self._test_create, params, no_image=True) def test_create_instance_with_invalid_size(self): @@ -225,7 +226,7 @@ class BlockDeviceMappingTestV21(test.TestCase): self.stubs.Set(compute_api.API, 'create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} - self.assertRaises(exc.HTTPBadRequest, + self.assertRaises(self.validation_error, self._test_create, params, no_image=True) def test_create_instance_bdm(self): @@ -332,6 +333,7 @@ class BlockDeviceMappingTestV21(test.TestCase): class BlockDeviceMappingTestV2(BlockDeviceMappingTestV21): + validation_error = exc.HTTPBadRequest def _setup_controller(self): self.ext_mgr = extensions.ExtensionManager()