def get_next_device_name(instance, device_name_list, root_device_name=None, device=None): """Validates (or generates) a device name for instance. If device is not set, it will generate a unique device appropriate for the instance. It uses the root_device_name (if provided) and the list of used devices to find valid device names. If the device name is valid but applicable to a different backend (for example /dev/vdc is specified but the backend uses /dev/xvdc), the device name will be converted to the appropriate format. """ req_prefix = None req_letter = None if device: try: req_prefix, req_letter = block_device.match_device(device) except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=device) if not root_device_name: root_device_name = block_device.DEFAULT_ROOT_DEV_NAME try: prefix = block_device.match_device( block_device.prepend_dev(root_device_name))[0] except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=root_device_name) # NOTE(vish): remove this when xenapi is setting default_root_device if driver.is_xenapi(): prefix = '/dev/xvd' if req_prefix != prefix: LOG.debug("Using %(prefix)s instead of %(req_prefix)s", {'prefix': prefix, 'req_prefix': req_prefix}) used_letters = set() for device_path in device_name_list: letter = block_device.get_device_letter(device_path) used_letters.add(letter) # NOTE(vish): remove this when xenapi is properly setting # default_ephemeral_device and default_swap_device if driver.is_xenapi(): flavor = instance.get_flavor() if flavor.ephemeral_gb: used_letters.add('b') if flavor.swap: used_letters.add('c') if not req_letter: req_letter = _get_unused_letter(used_letters) if req_letter in used_letters: raise exception.DevicePathInUse(path=device) return prefix + req_letter
def get_device_name_for_instance(context, instance, bdms, device): """Validates (or generates) a device name for instance. If device is not set, it will generate a unique device appropriate for the instance. It uses the block device mapping table to find valid device names. If the device name is valid but applicable to a different backend (for example /dev/vdc is specified but the backend uses /dev/xvdc), the device name will be converted to the appropriate format. """ req_prefix = None req_letter = None if device: try: req_prefix, req_letter = block_device.match_device(device) except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=device) mappings = block_device.instance_block_mapping(instance, bdms) try: prefix = block_device.match_device(mappings['root'])[0] except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=mappings['root']) # NOTE(vish): remove this when xenapi is setting default_root_device if driver.compute_driver_matches('xenapi.XenAPIDriver'): prefix = '/dev/xvd' if req_prefix != prefix: LOG.debug(_("Using %(prefix)s instead of %(req_prefix)s") % locals()) used_letters = set() for device_path in mappings.itervalues(): letter = block_device.strip_prefix(device_path) # NOTE(vish): delete numbers in case we have something like # /dev/sda1 letter = re.sub("\d+", "", letter) used_letters.add(letter) # NOTE(vish): remove this when xenapi is properly setting # default_ephemeral_device and default_swap_device if driver.compute_driver_matches('xenapi.XenAPIDriver'): instance_type = instance_types.extract_instance_type(instance) if instance_type['ephemeral_gb']: used_letters.add('b') if instance_type['swap']: used_letters.add('c') if not req_letter: req_letter = _get_unused_letter(used_letters) if req_letter in used_letters: raise exception.DevicePathInUse(path=device) device_name = prefix + req_letter return device_name
def get_device_name_for_instance(context, instance, device): """Validates (or generates) a device name for instance. If device is not set, it will generate a unique device appropriate for the instance. It uses the block device mapping table to find valid device names. If the device name is valid but applicable to a different backend (for example /dev/vdc is specified but the backend uses /dev/xvdc), the device name will be converted to the appropriate format. """ req_prefix = None req_letters = None if device: try: req_prefix, req_letters = block_device.match_device(device) except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=device) bdms = db.block_device_mapping_get_all_by_instance(context, instance['uuid']) mappings = block_device.instance_block_mapping(instance, bdms) try: prefix = block_device.match_device(mappings['root'])[0] except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=mappings['root']) if req_prefix != prefix: LOG.debug(_("Using %(prefix)s instead of %(req_prefix)s") % locals()) letters_list = [] for _name, device in mappings.iteritems(): letter = block_device.strip_prefix(device) # NOTE(vish): delete numbers in case we have something like # /dev/sda1 letter = re.sub("\d+", "", letter) letters_list.append(letter) used_letters = set(letters_list) if not req_letters: req_letters = _get_unused_letters(used_letters) if req_letters in used_letters: raise exception.DevicePathInUse(path=device) return prefix + req_letters
def get_next_device_name(instance, device_name_list, root_device_name=None, device=None): """Validates (or generates) a device name for instance. If device is not set, it will generate a unique device appropriate for the instance. It uses the root_device_name (if provided) and the list of used devices to find valid device names. If the device name is valid but applicable to a different backend (for example /dev/vdc is specified but the backend uses /dev/xvdc), the device name will be converted to the appropriate format. """ req_prefix = None req_letter = None if device: try: req_prefix, req_letter = block_device.match_device(device) except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=device) if not root_device_name: root_device_name = block_device.DEFAULT_ROOT_DEV_NAME try: prefix = block_device.match_device(root_device_name)[0] except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=root_device_name) # NOTE(vish): remove this when xenapi is setting default_root_device if driver.compute_driver_matches('xenapi.XenAPIDriver'): prefix = '/dev/xvd' if req_prefix != prefix: LOG.debug(_("Using %(prefix)s instead of %(req_prefix)s"), { 'prefix': prefix, 'req_prefix': req_prefix }) used_letters = set() for device_path in device_name_list: letter = block_device.strip_prefix(device_path) # NOTE(vish): delete numbers in case we have something like # /dev/sda1 letter = re.sub("\d+", "", letter) used_letters.add(letter) # NOTE(vish): remove this when xenapi is properly setting # default_ephemeral_device and default_swap_device if driver.compute_driver_matches('xenapi.XenAPIDriver'): flavor = flavors.extract_flavor(instance) if flavor['ephemeral_gb']: used_letters.add('b') if flavor['swap']: used_letters.add('c') if not req_letter: req_letter = _get_unused_letter(used_letters) if req_letter in used_letters: raise exception.DevicePathInUse(path=device) return prefix + req_letter
class VolumeAttachTestsV21(test.NoDBTestCase): validation_error = exception.ValidationError def setUp(self): super(VolumeAttachTestsV21, self).setUp() self.stub_out( 'nova.objects.BlockDeviceMappingList' '.get_by_instance_uuid', fake_bdm_list_get_by_instance_uuid) self.stubs.Set(compute_api.API, 'get', fake_get_instance) self.stubs.Set(cinder.API, 'get', fake_get_volume) self.context = context.get_admin_context() self.expected_show = { 'volumeAttachment': { 'device': '/dev/fake0', 'serverId': FAKE_UUID, 'id': FAKE_UUID_A, 'volumeId': FAKE_UUID_A } } self.attachments = volumes_v21.VolumeAttachmentController() self.req = fakes.HTTPRequest.blank( '/v2/servers/id/os-volume_attachments/uuid') self.req.body = jsonutils.dump_as_bytes({}) self.req.headers['content-type'] = 'application/json' self.req.environ['nova.context'] = self.context def test_show(self): result = self.attachments.show(self.req, FAKE_UUID, FAKE_UUID_A) self.assertEqual(self.expected_show, result) @mock.patch.object( compute_api.API, 'get', side_effect=exception.InstanceNotFound(instance_id=FAKE_UUID)) def test_show_no_instance(self, mock_mr): self.assertRaises(exc.HTTPNotFound, self.attachments.show, self.req, FAKE_UUID, FAKE_UUID_A) @mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid', return_value=None) def test_show_no_bdms(self, mock_mr): self.assertRaises(exc.HTTPNotFound, self.attachments.show, self.req, FAKE_UUID, FAKE_UUID_A) def test_show_bdms_no_mountpoint(self): FAKE_UUID_NOTEXIST = '00000000-aaaa-aaaa-aaaa-aaaaaaaaaaaa' self.assertRaises(exc.HTTPNotFound, self.attachments.show, self.req, FAKE_UUID, FAKE_UUID_NOTEXIST) def test_detach(self): self.stubs.Set(compute_api.API, 'detach_volume', fake_detach_volume) result = self.attachments.delete(self.req, FAKE_UUID, FAKE_UUID_A) # NOTE: on v2.1, http status code is set as wsgi_code of API # method instead of status_int in a response object. if isinstance(self.attachments, volumes_v21.VolumeAttachmentController): status_int = self.attachments.delete.wsgi_code else: status_int = result.status_int self.assertEqual(202, status_int) @mock.patch.object(common, 'get_instance') def test_detach_vol_shelved_not_supported(self, mock_get_instance): inst = fake_instance.fake_instance_obj(self.context, **{'uuid': FAKE_UUID}) inst.vm_state = vm_states.SHELVED mock_get_instance.return_value = inst req = fakes.HTTPRequest.blank( '/v2/servers/id/os-volume_attachments/uuid', version='2.19') req.method = 'DELETE' req.headers['content-type'] = 'application/json' req.environ['nova.context'] = self.context self.assertRaises(webob.exc.HTTPConflict, self.attachments.delete, req, FAKE_UUID, FAKE_UUID_A) @mock.patch.object(compute_api.API, 'detach_volume') @mock.patch.object(common, 'get_instance') def test_detach_vol_shelved_supported(self, mock_get_instance, mock_detach): inst = fake_instance.fake_instance_obj(self.context, **{'uuid': FAKE_UUID}) inst.vm_state = vm_states.SHELVED mock_get_instance.return_value = inst req = fakes.HTTPRequest.blank( '/v2/servers/id/os-volume_attachments/uuid', version='2.20') req.method = 'DELETE' req.headers['content-type'] = 'application/json' req.environ['nova.context'] = self.context self.attachments.delete(req, FAKE_UUID, FAKE_UUID_A) self.assertTrue(mock_detach.called) def test_detach_vol_not_found(self): self.stubs.Set(compute_api.API, 'detach_volume', fake_detach_volume) self.assertRaises(exc.HTTPNotFound, self.attachments.delete, self.req, FAKE_UUID, FAKE_UUID_C) @mock.patch('nova.objects.BlockDeviceMapping.is_root', new_callable=mock.PropertyMock) def test_detach_vol_root(self, mock_isroot): mock_isroot.return_value = True self.assertRaises(exc.HTTPForbidden, self.attachments.delete, self.req, FAKE_UUID, FAKE_UUID_A) def test_detach_volume_from_locked_server(self): def fake_detach_volume_from_locked_server(self, context, instance, volume): raise exception.InstanceIsLocked(instance_uuid=instance['uuid']) self.stubs.Set(compute_api.API, 'detach_volume', fake_detach_volume_from_locked_server) self.assertRaises(webob.exc.HTTPConflict, self.attachments.delete, self.req, FAKE_UUID, FAKE_UUID_A) def test_attach_volume(self): self.stubs.Set(compute_api.API, 'attach_volume', fake_attach_volume) body = { 'volumeAttachment': { 'volumeId': FAKE_UUID_A, 'device': '/dev/fake' } } result = self.attachments.create(self.req, FAKE_UUID, body=body) self.assertEqual('00000000-aaaa-aaaa-aaaa-000000000000', result['volumeAttachment']['id']) @mock.patch.object(common, 'get_instance') def test_attach_vol_shelved_not_supported(self, mock_get_instance): body = { 'volumeAttachment': { 'volumeId': FAKE_UUID_A, 'device': '/dev/fake' } } inst = fake_instance.fake_instance_obj(self.context, **{'uuid': FAKE_UUID}) inst.vm_state = vm_states.SHELVED mock_get_instance.return_value = inst self.assertRaises(webob.exc.HTTPConflict, self.attachments.create, self.req, FAKE_UUID, body=body) @mock.patch.object(compute_api.API, 'attach_volume', return_value='/dev/myfake') @mock.patch.object(common, 'get_instance') def test_attach_vol_shelved_supported(self, mock_get_instance, mock_attach): body = { 'volumeAttachment': { 'volumeId': FAKE_UUID_A, 'device': '/dev/fake' } } inst = fake_instance.fake_instance_obj(self.context, **{'uuid': FAKE_UUID}) inst.vm_state = vm_states.SHELVED mock_get_instance.return_value = inst req = fakes.HTTPRequest.blank('/v2/servers/id/os-volume_attachments', version='2.20') req.method = 'POST' req.body = jsonutils.dump_as_bytes({}) req.headers['content-type'] = 'application/json' req.environ['nova.context'] = self.context result = self.attachments.create(req, FAKE_UUID, body=body) self.assertEqual('00000000-aaaa-aaaa-aaaa-000000000000', result['volumeAttachment']['id']) self.assertEqual('/dev/myfake', result['volumeAttachment']['device']) @mock.patch.object(compute_api.API, 'attach_volume', return_value='/dev/myfake') def test_attach_volume_with_auto_device(self, mock_attach): body = {'volumeAttachment': {'volumeId': FAKE_UUID_A, 'device': None}} result = self.attachments.create(self.req, FAKE_UUID, body=body) self.assertEqual('00000000-aaaa-aaaa-aaaa-000000000000', result['volumeAttachment']['id']) self.assertEqual('/dev/myfake', result['volumeAttachment']['device']) def test_attach_volume_to_locked_server(self): def fake_attach_volume_to_locked_server(self, context, instance, volume_id, device=None): raise exception.InstanceIsLocked(instance_uuid=instance['uuid']) self.stubs.Set(compute_api.API, 'attach_volume', fake_attach_volume_to_locked_server) body = { 'volumeAttachment': { 'volumeId': FAKE_UUID_A, 'device': '/dev/fake' } } self.assertRaises(webob.exc.HTTPConflict, self.attachments.create, self.req, FAKE_UUID, body=body) def test_attach_volume_bad_id(self): self.stubs.Set(compute_api.API, 'attach_volume', fake_attach_volume) body = { 'volumeAttachment': { 'device': None, 'volumeId': 'TESTVOLUME', } } self.assertRaises(self.validation_error, self.attachments.create, self.req, FAKE_UUID, body=body) @mock.patch.object(compute_api.API, 'attach_volume', side_effect=exception.DevicePathInUse(path='/dev/sda')) def test_attach_volume_device_in_use(self, mock_attach): body = { 'volumeAttachment': { 'device': '/dev/sda', 'volumeId': FAKE_UUID_A, } } self.assertRaises(webob.exc.HTTPConflict, self.attachments.create, self.req, FAKE_UUID, body=body) def test_attach_volume_without_volumeId(self): self.stubs.Set(compute_api.API, 'attach_volume', fake_attach_volume) body = {'volumeAttachment': {'device': None}} self.assertRaises(self.validation_error, self.attachments.create, self.req, FAKE_UUID, body=body) def test_attach_volume_with_extra_arg(self): body = { 'volumeAttachment': { 'volumeId': FAKE_UUID_A, 'device': '/dev/fake', 'extra': 'extra_arg' } } self.assertRaises(self.validation_error, self.attachments.create, self.req, FAKE_UUID, body=body) @mock.patch.object(compute_api.API, 'attach_volume') def test_attach_volume_with_invalid_input(self, mock_attach): mock_attach.side_effect = exception.InvalidInput( reason='Invalid volume') body = { 'volumeAttachment': { 'volumeId': FAKE_UUID_A, 'device': '/dev/fake' } } req = fakes.HTTPRequest.blank('/v2/servers/id/os-volume_attachments') req.method = 'POST' req.body = jsonutils.dump_as_bytes({}) req.headers['content-type'] = 'application/json' req.environ['nova.context'] = self.context self.assertRaises(exc.HTTPBadRequest, self.attachments.create, req, FAKE_UUID, body=body) def _test_swap(self, attachments, uuid=FAKE_UUID_A, fake_func=None, body=None): fake_func = fake_func or fake_swap_volume self.stubs.Set(compute_api.API, 'swap_volume', fake_func) body = body or {'volumeAttachment': {'volumeId': FAKE_UUID_B}} return attachments.update(self.req, FAKE_UUID, uuid, body=body) def test_swap_volume_for_locked_server(self): def fake_swap_volume_for_locked_server(self, context, instance, old_volume, new_volume): raise exception.InstanceIsLocked(instance_uuid=instance['uuid']) self.assertRaises(webob.exc.HTTPConflict, self._test_swap, self.attachments, fake_func=fake_swap_volume_for_locked_server) def test_swap_volume(self): result = self._test_swap(self.attachments) # NOTE: on v2.1, http status code is set as wsgi_code of API # method instead of status_int in a response object. if isinstance(self.attachments, volumes_v21.VolumeAttachmentController): status_int = self.attachments.update.wsgi_code else: status_int = result.status_int self.assertEqual(202, status_int) def test_swap_volume_with_nonexistent_uri(self): self.assertRaises(exc.HTTPNotFound, self._test_swap, self.attachments, uuid=FAKE_UUID_C) @mock.patch.object(cinder.API, 'get') def test_swap_volume_with_nonexistent_dest_in_body(self, mock_update): mock_update.side_effect = [ None, exception.VolumeNotFound(volume_id=FAKE_UUID_C) ] body = {'volumeAttachment': {'volumeId': FAKE_UUID_C}} self.assertRaises(exc.HTTPBadRequest, self._test_swap, self.attachments, body=body) def test_swap_volume_without_volumeId(self): body = {'volumeAttachment': {'device': '/dev/fake'}} self.assertRaises(self.validation_error, self._test_swap, self.attachments, body=body) def test_swap_volume_with_extra_arg(self): body = { 'volumeAttachment': { 'volumeId': FAKE_UUID_A, 'device': '/dev/fake' } } self.assertRaises(self.validation_error, self._test_swap, self.attachments, body=body)