def from_api(cls, api_dict, image_uuid_specified): """Transform the API format of data to the internally used one. Only validate if the source_type field makes sense. """ if not api_dict.get('no_device'): source_type = api_dict.get('source_type') device_uuid = api_dict.get('uuid') destination_type = api_dict.get('destination_type') if source_type not in ('volume', 'image', 'snapshot', 'blank'): raise exception.InvalidBDMFormat( details=_("Invalid source_type field.")) elif source_type == 'blank' and device_uuid: raise exception.InvalidBDMFormat( details=_("Invalid device UUID.")) elif source_type != 'blank': if not device_uuid: raise exception.InvalidBDMFormat( details=_("Missing device UUID.")) api_dict[source_type + '_id'] = device_uuid if source_type == 'image' and destination_type == 'local': boot_index = api_dict.get('boot_index', -1) # if this bdm is generated from --image ,then # source_type = image and destination_type = local is allowed if not (image_uuid_specified and boot_index == 0): raise exception.InvalidBDMFormat( details=_("Mapping image to local is not supported.")) api_dict.pop('uuid', None) return cls(api_dict)
def _validate(self, bdm_dict): """Basic data format validations.""" dict_fields = set(key for key, _ in six.iteritems(bdm_dict)) # Check that there are no bogus fields if not (dict_fields <= (self._fields | self._db_only_fields)): raise exception.InvalidBDMFormat( details=_("Some fields are invalid.")) if bdm_dict.get('no_device'): return # Check that all required fields are there if (self._required_fields and not ((dict_fields & self._required_fields) == self._required_fields)): raise exception.InvalidBDMFormat( details=_("Some required fields are missing")) if 'delete_on_termination' in bdm_dict: bdm_dict['delete_on_termination'] = strutils.bool_from_string( bdm_dict['delete_on_termination']) if bdm_dict.get('device_name') is not None: validate_device_name(bdm_dict['device_name']) validate_and_default_volume_size(bdm_dict) if bdm_dict.get('boot_index'): try: bdm_dict['boot_index'] = int(bdm_dict['boot_index']) except ValueError: raise exception.InvalidBDMFormat( details=_("Boot index is invalid."))
def validate_device_name(value): try: # NOTE (ndipanov): Do not allow empty device names # until assigning default values # is supported by nova.compute utils.check_string_length(value, 'Device name', min_length=1, max_length=255) except exception.InvalidInput: raise exception.InvalidBDMFormat( details=_("Device name empty or too long.")) if ' ' in value: raise exception.InvalidBDMFormat( details=_("Device name contains spaces."))
def from_api(cls, api_dict, image_uuid_specified): """Transform the API format of data to the internally used one. Only validate if the source_type field makes sense. """ if not api_dict.get('no_device'): source_type = api_dict.get('source_type') device_uuid = api_dict.get('uuid') destination_type = api_dict.get('destination_type') volume_type = api_dict.get('volume_type') if source_type == 'blank' and device_uuid: raise exception.InvalidBDMFormat( details=_("Invalid device UUID.")) elif source_type != 'blank': if not device_uuid: raise exception.InvalidBDMFormat( details=_("Missing device UUID.")) api_dict[source_type + '_id'] = device_uuid if source_type == 'image' and destination_type == 'local': # NOTE(mriedem): boot_index can be None so we need to # account for that to avoid a TypeError. boot_index = api_dict.get('boot_index', -1) if boot_index is None: # boot_index=None is equivalent to -1. boot_index = -1 boot_index = int(boot_index) # if this bdm is generated from --image, then # source_type = image and destination_type = local is allowed # if not (image_uuid_specified and boot_index == 0): if not image_uuid_specified: raise exception.InvalidBDMFormat( details=_("Mapping image to local is not supported.")) if destination_type == 'local' and volume_type: raise exception.InvalidBDMFormat( details=_("Specifying a volume_type with destination_type=" "local is not supported.")) # Specifying a volume_type with a pre-existing source volume is # not supported. if source_type == 'volume' and volume_type: raise exception.InvalidBDMFormat( details=_("Specifying volume type to existing volume is " "not supported.")) api_dict.pop('uuid', None) return cls(api_dict)
def validate_and_default_volume_size(bdm): if bdm.get('volume_size'): try: bdm['volume_size'] = utils.validate_integer( bdm['volume_size'], 'volume_size', min_value=0) except exception.InvalidInput as e: raise exception.InvalidBDMFormat( details="Invalid volume_size.")
def validate_and_default_volume_size(bdm): if bdm.get('volume_size'): try: 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."))
def from_api(cls, api_dict): """Transform the API format of data to the internally used one. Only validate if the source_type field makes sense. """ if not api_dict.get('no_device'): source_type = api_dict.get('source_type') device_uuid = api_dict.get('uuid') if source_type not in ('volume', 'image', 'snapshot', 'blank'): raise exception.InvalidBDMFormat( details="Invalid source_type field.") elif source_type != 'blank': if not device_uuid: raise exception.InvalidBDMFormat( details="Missing device UUID.") api_dict[source_type + '_id'] = device_uuid api_dict.pop('uuid', None) return cls(api_dict)
def _get_available_controller_slot(self, controller_type, slot_map): max_slots = (os_win_const.IDE_CONTROLLER_SLOTS_NUMBER if controller_type == constants.CTRL_TYPE_IDE else os_win_const.SCSI_CONTROLLER_SLOTS_NUMBER) for idx, ctrl in enumerate(slot_map[controller_type]): if slot_map[controller_type][idx] >= 1: drive_addr = idx ctrl_disk_addr = max_slots - slot_map[controller_type][idx] slot_map[controller_type][idx] -= 1 return (drive_addr, ctrl_disk_addr) msg = _( "There are no more free slots on controller %s") % controller_type raise exception.InvalidBDMFormat(details=msg)
def validate_and_update_bdi(self, instance, image_meta, vm_gen, block_device_info): slot_map = self._initialize_controller_slot_counter(instance, vm_gen) self._check_and_update_root_device(vm_gen, image_meta, block_device_info, slot_map) self._check_and_update_ephemerals(vm_gen, block_device_info, slot_map) self._check_and_update_volumes(vm_gen, block_device_info, slot_map) if vm_gen == constants.VM_GEN_2 and configdrive.required_by(instance): # for Generation 2 VMs, the configdrive is attached to the SCSI # controller. Check that there is still a slot available for it. if slot_map[constants.CTRL_TYPE_SCSI][0] == 0: msg = _("There are no more free slots on controller %s for " "configdrive.") % constants.CTRL_TYPE_SCSI raise exception.InvalidBDMFormat(details=msg)
def from_legacy(cls, legacy_bdm): copy_over_fields = bdm_legacy_fields & bdm_new_fields copy_over_fields |= (bdm_db_only_fields | bdm_db_inherited_fields) # NOTE (ndipanov): These fields cannot be computed # from legacy bdm, so do not default them # to avoid overwriting meaningful values in the db non_computable_fields = set( ['boot_index', 'disk_bus', 'guest_format', 'device_type']) new_bdm = { fld: val for fld, val in six.iteritems(legacy_bdm) if fld in copy_over_fields } virt_name = legacy_bdm.get('virtual_name') if is_swap_or_ephemeral(virt_name): new_bdm['source_type'] = 'blank' new_bdm['delete_on_termination'] = True new_bdm['destination_type'] = 'local' if virt_name == 'swap': new_bdm['guest_format'] = 'swap' else: new_bdm['guest_format'] = CONF.default_ephemeral_format elif legacy_bdm.get('snapshot_id'): new_bdm['source_type'] = 'snapshot' new_bdm['destination_type'] = 'volume' elif legacy_bdm.get('volume_id'): new_bdm['source_type'] = 'volume' new_bdm['destination_type'] = 'volume' elif legacy_bdm.get('no_device'): # NOTE (ndipanov): Just keep the BDM for now, pass else: raise exception.InvalidBDMFormat( details=_("Unrecognized legacy format.")) return cls(new_bdm, non_computable_fields)
def _validate(*args, **kwargs): raise exception.InvalidBDMFormat(details='Wrong BDM')
class BlockDeviceMappingTestV21(test.TestCase): validation_error = exception.ValidationError def _setup_controller(self): ext_info = extension_info.LoadedExtensionInfo() self.controller = servers_v21.ServersController( extension_info=ext_info) CONF.set_override('extensions_blacklist', 'os-block-device-mapping', 'osapi_v21') self.no_bdm_v2_controller = servers_v21.ServersController( extension_info=ext_info) CONF.set_override('extensions_blacklist', '', 'osapi_v21') def setUp(self): super(BlockDeviceMappingTestV21, self).setUp() fakes.stub_out_nw_api(self) self._setup_controller() fake.stub_out_image_service(self) self.bdm = [{ 'no_device': None, 'source_type': 'volume', 'destination_type': 'volume', 'uuid': 'fake', 'device_name': 'vdb', 'delete_on_termination': False, }] def _get_servers_body(self, no_image=False): body = { 'server': { 'name': 'server_test', 'imageRef': '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6', 'flavorRef': 'http://localhost/123/flavors/3', 'metadata': { 'hello': 'world', 'open': 'stack', }, }, } if no_image: del body['server']['imageRef'] return body def _test_create(self, params, no_image=False, override_controller=None): body = self._get_servers_body(no_image) body['server'].update(params) req = fakes.HTTPRequest.blank('/v2/fake/servers') req.method = 'POST' req.headers['content-type'] = 'application/json' req.body = jsonutils.dump_as_bytes(body) if override_controller: override_controller.create(req, body=body).obj['server'] else: self.controller.create(req, body=body).obj['server'] def test_create_instance_with_block_device_mapping_disabled(self): bdm = [{'device_name': 'foo'}] old_create = compute_api.API.create def create(*args, **kwargs): self.assertNotIn('block_device_mapping', kwargs) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) params = {block_device_mapping.ATTRIBUTE_NAME: bdm} self._test_create(params, override_controller=self.no_bdm_v2_controller) def test_create_instance_with_volumes_enabled_no_image(self): """Test that the create will fail if there is no image and no bdms supplied in the request """ old_create = compute_api.API.create def create(*args, **kwargs): self.assertNotIn('imageRef', kwargs) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) self.assertRaises(exc.HTTPBadRequest, self._test_create, {}, no_image=True) @mock.patch.object(compute_api.API, '_validate_bdm') @mock.patch.object(compute_api.API, '_get_bdm_image_metadata') def test_create_instance_with_bdms_and_no_image(self, mock_bdm_image_metadata, mock_validate_bdm): mock_bdm_image_metadata.return_value = {} mock_validate_bdm.return_value = True old_create = compute_api.API.create def create(*args, **kwargs): self.assertThat( block_device.BlockDeviceDict(self.bdm[0]), matchers.DictMatches(kwargs['block_device_mapping'][0])) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} self._test_create(params, no_image=True) mock_validate_bdm.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY, mock.ANY) mock_bdm_image_metadata.assert_called_once_with( mock.ANY, mock.ANY, False) @mock.patch.object(compute_api.API, '_validate_bdm') @mock.patch.object(compute_api.API, '_get_bdm_image_metadata') def test_create_instance_with_bdms_and_empty_imageRef( self, mock_bdm_image_metadata, mock_validate_bdm): mock_bdm_image_metadata.return_value = {} mock_validate_bdm.return_value = True old_create = compute_api.API.create def create(*args, **kwargs): self.assertThat( block_device.BlockDeviceDict(self.bdm[0]), matchers.DictMatches(kwargs['block_device_mapping'][0])) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) params = { block_device_mapping.ATTRIBUTE_NAME: self.bdm, 'imageRef': '' } self._test_create(params) def test_create_instance_with_imageRef_as_full_url(self): bdm = [{'device_name': 'foo'}] image_href = ('http://localhost/v2/fake/images/' '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6') params = { block_device_mapping.ATTRIBUTE_NAME: bdm, 'imageRef': image_href } self.assertRaises(exception.ValidationError, self._test_create, params) def test_create_instance_with_non_uuid_imageRef(self): bdm = [{'device_name': 'foo'}] params = { block_device_mapping.ATTRIBUTE_NAME: bdm, 'imageRef': '123123abcd' } self.assertRaises(exception.ValidationError, self._test_create, params) def test_create_instance_with_invalid_bdm_in_2nd_dict(self): bdm_1st = { "source_type": "image", "delete_on_termination": True, "boot_index": 0, "uuid": "2ff3a1d3-ed70-4c3f-94ac-941461153bc0", "destination_type": "local" } bdm_2nd = { "source_type": "volume", "uuid": "99d92140-3d0c-4ea5-a49c-f94c38c607f0", "destination_type": "invalid" } bdm = [bdm_1st, bdm_2nd] params = { block_device_mapping.ATTRIBUTE_NAME: bdm, 'imageRef': '2ff3a1d3-ed70-4c3f-94ac-941461153bc0' } self.assertRaises(exception.ValidationError, self._test_create, params) def test_create_instance_with_boot_index_none_ok(self): """Tests creating a server with two block devices. One is the boot device and the other is a non-bootable device. """ # From the docs: # To disable a device from booting, set the boot index to a negative # value or use the default boot index value, which is None. The # simplest usage is, set the boot index of the boot device to 0 and use # the default boot index value, None, for any other devices. bdms = [ # This is the bootable device that would create a 20GB cinder # volume from the given image. { 'source_type': 'image', 'destination_type': 'volume', 'boot_index': 0, 'uuid': '155d900f-4e14-4e4c-a73d-069cbf4541e6', 'volume_size': 20 }, # This is the non-bootable 10GB ext4 ephemeral block device. { 'source_type': 'blank', 'destination_type': 'local', 'boot_index': None, # If 'guest_format' is 'swap' then a swap device is created. 'guest_format': 'ext4' } ] params = {block_device_mapping.ATTRIBUTE_NAME: bdms} self._test_create(params, no_image=True) def test_create_instance_with_boot_index_none_image_local_fails(self): """Tests creating a server with a local image-based block device which has a boot_index of None which is invalid. """ bdms = [{ 'source_type': 'image', 'destination_type': 'local', 'boot_index': None, 'uuid': '155d900f-4e14-4e4c-a73d-069cbf4541e6' }] params = {block_device_mapping.ATTRIBUTE_NAME: bdms} self.assertRaises(exc.HTTPBadRequest, self._test_create, params, no_image=True) def test_create_instance_with_invalid_boot_index(self): bdm = [{ "source_type": "image", "delete_on_termination": True, "boot_index": 'invalid', "uuid": "2ff3a1d3-ed70-4c3f-94ac-941461153bc0", "destination_type": "local" }] params = { block_device_mapping.ATTRIBUTE_NAME: bdm, 'imageRef': '2ff3a1d3-ed70-4c3f-94ac-941461153bc0' } self.assertRaises(exception.ValidationError, self._test_create, params) def test_create_instance_with_device_name_not_string(self): self.bdm[0]['device_name'] = 123 old_create = compute_api.API.create def create(*args, **kwargs): self.assertEqual(kwargs['block_device_mapping'], self.bdm) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} self.assertRaises(self.validation_error, self._test_create, params, no_image=True) @mock.patch.object(compute_api.API, 'create') def test_create_instance_with_bdm_param_not_list(self, mock_create): self.params = {'block_device_mapping': '/dev/vdb'} self.assertRaises(self.validation_error, self._test_create, self.params) def test_create_instance_with_device_name_empty(self): self.bdm[0]['device_name'] = '' old_create = compute_api.API.create def create(*args, **kwargs): self.assertEqual(kwargs['block_device_mapping'], self.bdm) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} self.assertRaises(self.validation_error, self._test_create, params, no_image=True) def test_create_instance_with_device_name_too_long(self): self.bdm[0]['device_name'] = 'a' * 256 old_create = compute_api.API.create def create(*args, **kwargs): self.assertEqual(kwargs['block_device_mapping'], self.bdm) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} self.assertRaises(self.validation_error, self._test_create, params, no_image=True) def test_create_instance_with_space_in_device_name(self): self.bdm[0]['device_name'] = 'v da' old_create = compute_api.API.create def create(*args, **kwargs): self.assertTrue(kwargs['legacy_bdm']) self.assertEqual(kwargs['block_device_mapping'], self.bdm) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} self.assertRaises(self.validation_error, self._test_create, params, no_image=True) def test_create_instance_with_invalid_size(self): self.bdm[0]['volume_size'] = 'hello world' old_create = compute_api.API.create def create(*args, **kwargs): self.assertEqual(kwargs['block_device_mapping'], self.bdm) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} self.assertRaises(self.validation_error, self._test_create, params, no_image=True) def _test_create_instance_with_destination_type_error( self, destination_type): self.bdm[0]['destination_type'] = destination_type params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} self.assertRaises(self.validation_error, self._test_create, params, no_image=True) def test_create_instance_with_destination_type_empty_string(self): self._test_create_instance_with_destination_type_error('') def test_create_instance_with_invalid_destination_type(self): self._test_create_instance_with_destination_type_error('fake') @mock.patch.object(compute_api.API, '_validate_bdm') def test_create_instance_bdm(self, mock_validate_bdm): bdm = [{ 'source_type': 'volume', 'device_name': 'fake_dev', 'uuid': 'fake_vol' }] bdm_expected = [{ 'source_type': 'volume', 'device_name': 'fake_dev', 'volume_id': 'fake_vol' }] old_create = compute_api.API.create def create(*args, **kwargs): self.assertFalse(kwargs['legacy_bdm']) for expected, received in zip(bdm_expected, kwargs['block_device_mapping']): self.assertThat(block_device.BlockDeviceDict(expected), matchers.DictMatches(received)) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) params = {block_device_mapping.ATTRIBUTE_NAME: bdm} self._test_create(params, no_image=True) mock_validate_bdm.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY, mock.ANY) @mock.patch.object(compute_api.API, '_validate_bdm') def test_create_instance_bdm_missing_device_name(self, mock_validate_bdm): del self.bdm[0]['device_name'] old_create = compute_api.API.create def create(*args, **kwargs): self.assertFalse(kwargs['legacy_bdm']) self.assertNotIn(None, kwargs['block_device_mapping'][0]['device_name']) return old_create(*args, **kwargs) self.stub_out('nova.compute.api.API.create', create) params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} self._test_create(params, no_image=True) mock_validate_bdm.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY, mock.ANY) @mock.patch.object( block_device.BlockDeviceDict, '_validate', side_effect=exception.InvalidBDMFormat(details='Wrong BDM')) def test_create_instance_bdm_validation_error(self, mock_validate): params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} self.assertRaises(exc.HTTPBadRequest, self._test_create, params, no_image=True) @mock.patch('nova.compute.api.API._get_bdm_image_metadata') def test_create_instance_non_bootable_volume_fails(self, fake_bdm_meta): params = {block_device_mapping.ATTRIBUTE_NAME: self.bdm} fake_bdm_meta.side_effect = exception.InvalidBDMVolumeNotBootable(id=1) self.assertRaises(exc.HTTPBadRequest, self._test_create, params, no_image=True) def test_create_instance_bdm_api_validation_fails(self): self.validation_fail_test_validate_called = False self.validation_fail_instance_destroy_called = False bdm_exceptions = ((exception.InvalidBDMSnapshot, { 'id': 'fake' }), (exception.InvalidBDMVolume, { 'id': 'fake' }), (exception.InvalidBDMImage, { 'id': 'fake' }), (exception.InvalidBDMBootSequence, {}), (exception.InvalidBDMLocalsLimit, {})) ex_iter = iter(bdm_exceptions) def _validate_bdm(*args, **kwargs): self.validation_fail_test_validate_called = True ex, kargs = next(ex_iter) raise ex(**kargs) def _instance_destroy(*args, **kwargs): self.validation_fail_instance_destroy_called = True self.stub_out('nova.compute.api.API._validate_bdm', _validate_bdm) self.stub_out('nova.objects.Instance.destroy', _instance_destroy) for _unused in range(len(bdm_exceptions)): params = { block_device_mapping.ATTRIBUTE_NAME: [self.bdm[0].copy()] } self.assertRaises(exc.HTTPBadRequest, self._test_create, params) self.assertTrue(self.validation_fail_test_validate_called) self.assertFalse(self.validation_fail_instance_destroy_called) self.validation_fail_test_validate_called = False self.validation_fail_instance_destroy_called = False
def _validate(self, bdm_dict): """Basic data format validations.""" if (not set(key for key, _ in bdm_dict.iteritems()) <= (self._fields | self._db_only_fields)): raise exception.InvalidBDMFormat()