diff --git a/nova/block_device.py b/nova/block_device.py index 0f15632e2845..d6999b3b19dc 100644 --- a/nova/block_device.py +++ b/nova/block_device.py @@ -186,6 +186,9 @@ class BlockDeviceDict(dict): 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( @@ -411,8 +414,9 @@ def new_format_is_swap(bdm): def new_format_is_ephemeral(bdm): - if (bdm.get('source_type') == 'blank' and not - new_format_is_swap(bdm)): + if (bdm.get('source_type') == 'blank' and + bdm.get('destination_type') == 'local' and + bdm.get('guest_format') != 'swap'): return True return False diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 171f4c3770cf..600fdb78c10c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1694,8 +1694,9 @@ class ComputeManager(manager.Manager): root_bdm.save() def _is_mapping(bdm): - return (bdm.source_type in ('image', 'volume', 'snapshot') and - driver_block_device.is_implemented(bdm)) + return (bdm.source_type in ('image', 'volume', 'snapshot', 'blank') + and bdm.destination_type == 'volume' + and driver_block_device.is_implemented(bdm)) ephemerals = filter(block_device.new_format_is_ephemeral, block_devices) @@ -1731,6 +1732,11 @@ class ComputeManager(manager.Manager): driver_block_device.convert_images(bdms), context, instance, self.volume_api, self.driver, self._await_block_device_map_created, + do_check_attach=do_check_attach) + + driver_block_device.attach_block_devices( + driver_block_device.convert_blanks(bdms), + context, instance, self.volume_api, + self.driver, self._await_block_device_map_created, do_check_attach=do_check_attach)) } diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index 8991a7ab6067..d20002fb0e92 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -1094,6 +1094,80 @@ class ComputeVolumeTestCase(BaseTestCase): self.context, instance, bdms) self.assertTrue(mock_create.called) + @mock.patch.object(nova.virt.block_device, 'get_swap') + @mock.patch.object(nova.virt.block_device, 'convert_blanks') + @mock.patch.object(nova.virt.block_device, 'convert_images') + @mock.patch.object(nova.virt.block_device, 'convert_snapshots') + @mock.patch.object(nova.virt.block_device, 'convert_volumes') + @mock.patch.object(nova.virt.block_device, 'convert_ephemerals') + @mock.patch.object(nova.virt.block_device, 'convert_swap') + @mock.patch.object(nova.virt.block_device, 'attach_block_devices') + def test_prep_block_device_with_blanks(self, attach_block_devices, + convert_swap, convert_ephemerals, + convert_volumes, convert_snapshots, + convert_images, convert_blanks, + get_swap): + instance = self._create_fake_instance() + instance['root_device_name'] = '/dev/vda' + root_volume = objects.BlockDeviceMapping( + **fake_block_device.FakeDbBlockDeviceDict({ + 'instance_uuid': 'fake-instance', + 'source_type': 'image', + 'destination_type': 'volume', + 'image_id': 'fake-image-id-1', + 'volume_size': 1, + 'boot_index': 0})) + blank_volume1 = objects.BlockDeviceMapping( + **fake_block_device.FakeDbBlockDeviceDict({ + 'instance_uuid': 'fake-instance', + 'source_type': 'blank', + 'destination_type': 'volume', + 'volume_size': 1, + 'boot_index': 1})) + blank_volume2 = objects.BlockDeviceMapping( + **fake_block_device.FakeDbBlockDeviceDict({ + 'instance_uuid': 'fake-instance', + 'source_type': 'blank', + 'destination_type': 'volume', + 'volume_size': 1, + 'boot_index': 2})) + bdms = [blank_volume1, blank_volume2, root_volume] + + def fake_attach_block_devices(bdm, *args, **kwargs): + return bdm + + convert_swap.return_value = [] + convert_ephemerals.return_value = [] + convert_volumes.return_value = [blank_volume1, blank_volume2] + convert_snapshots.return_value = [] + convert_images.return_value = [root_volume] + convert_blanks.return_value = [] + attach_block_devices.side_effect = fake_attach_block_devices + get_swap.return_value = [] + + expected_block_device_info = { + 'root_device_name': '/dev/vda', + 'swap': [], + 'ephemerals': [], + 'block_device_mapping': bdms + } + + manager = compute_manager.ComputeManager() + manager.use_legacy_block_device_info = False + block_device_info = manager._prep_block_device(self.context, instance, + bdms) + + convert_swap.assert_called_once_with(bdms) + convert_ephemerals.assert_called_once_with(bdms) + convert_volumes.assert_called_once_with(bdms) + convert_snapshots.assert_called_once_with(bdms) + convert_images.assert_called_once_with(bdms) + convert_blanks.assert_called_once_with(bdms) + + self.assertEqual(expected_block_device_info, block_device_info) + self.assertEqual(4, attach_block_devices.call_count) + get_swap.assert_called_once_with([]) + class ComputeTestCase(BaseTestCase): def test_wrap_instance_fault(self): @@ -6800,6 +6874,64 @@ class ComputeTestCase(BaseTestCase): instance, {}, bdms) + def test_default_block_device_names_with_blank_volumes(self): + instance = self._create_fake_instance() + image_meta = {} + root_volume = objects.BlockDeviceMapping( + **fake_block_device.FakeDbBlockDeviceDict({ + 'id': 1, 'instance_uuid': 'fake-instance', + 'source_type': 'volume', + 'destination_type': 'volume', + 'image_id': 'fake-image-id-1', + 'boot_index': 0})) + blank_volume1 = objects.BlockDeviceMapping( + **fake_block_device.FakeDbBlockDeviceDict({ + 'id': 2, 'instance_uuid': 'fake-instance', + 'source_type': 'blank', + 'destination_type': 'volume', + 'boot_index': -1})) + blank_volume2 = objects.BlockDeviceMapping( + **fake_block_device.FakeDbBlockDeviceDict({ + 'id': 3, 'instance_uuid': 'fake-instance', + 'source_type': 'blank', + 'destination_type': 'volume', + 'boot_index': -1})) + ephemeral = objects.BlockDeviceMapping( + **fake_block_device.FakeDbBlockDeviceDict({ + 'id': 4, 'instance_uuid': 'fake-instance', + 'source_type': 'blank', + 'destination_type': 'local'})) + swap = objects.BlockDeviceMapping( + **fake_block_device.FakeDbBlockDeviceDict({ + 'id': 5, 'instance_uuid': 'fake-instance', + 'source_type': 'blank', + 'destination_type': 'local', + 'guest_format': 'swap' + })) + bdms = block_device_obj.block_device_make_list( + self.context, [root_volume, blank_volume1, blank_volume2, + ephemeral, swap]) + + with contextlib.nested( + mock.patch.object(self.compute, '_default_root_device_name', + return_value='/dev/vda'), + mock.patch.object(self.compute, '_instance_update'), + mock.patch.object(objects.BlockDeviceMapping, 'save'), + mock.patch.object(self.compute, + '_default_device_names_for_instance') + ) as (default_root_device, instance_update, object_save, + default_device_names): + self.compute._default_block_device_names(self.context, instance, + image_meta, bdms) + default_root_device.assert_called_once_with(instance, image_meta, + bdms[0]) + instance_update.assert_called_once_with( + self.context, instance['uuid'], root_device_name='/dev/vda') + self.assertTrue(object_save.called) + default_device_names.assert_called_once_with(instance, + '/dev/vda', [bdms[-2]], [bdms[-1]], + [bdm for bdm in bdms[:-2]]) + def test_reserve_block_device_name(self): instance = self._create_fake_instance_obj( params={'root_device_name': '/dev/vda'}) diff --git a/nova/tests/compute/test_compute_utils.py b/nova/tests/compute/test_compute_utils.py index a9d13b00bb70..307eb1108522 100644 --- a/nova/tests/compute/test_compute_utils.py +++ b/nova/tests/compute/test_compute_utils.py @@ -279,6 +279,12 @@ class DefaultDeviceNamesForInstanceTestCase(test.NoDBTestCase): 'source_type': 'snapshot', 'destination_type': 'volume', 'snapshot_id': 'fake-snapshot-id-1', + 'boot_index': -1}), + fake_block_device.FakeDbBlockDeviceDict( + {'id': 5, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/vde', + 'source_type': 'blank', + 'destination_type': 'volume', 'boot_index': -1})]) self.flavor = {'swap': 4} self.instance = {'uuid': 'fake_instance', 'ephemeral_gb': 2} @@ -326,11 +332,14 @@ class DefaultDeviceNamesForInstanceTestCase(test.NoDBTestCase): for original, new in zip(original_bdm, self.block_device_mapping): self.assertEqual(original.device_name, new.device_name) - # Asser it defaults the missing one as expected + # Assert it defaults the missing one as expected self.block_device_mapping[1]['device_name'] = None + self.block_device_mapping[2]['device_name'] = None self._test_default_device_names([], [], self.block_device_mapping) - self.assertEqual(self.block_device_mapping[1]['device_name'], - '/dev/vdb') + self.assertEqual('/dev/vdb', + self.block_device_mapping[1]['device_name']) + self.assertEqual('/dev/vdc', + self.block_device_mapping[2]['device_name']) def test_with_ephemerals(self): # Test ephemeral gets assigned @@ -340,10 +349,13 @@ class DefaultDeviceNamesForInstanceTestCase(test.NoDBTestCase): self.assertEqual(self.ephemerals[0]['device_name'], '/dev/vdb') self.block_device_mapping[1]['device_name'] = None + self.block_device_mapping[2]['device_name'] = None self._test_default_device_names(self.ephemerals, [], self.block_device_mapping) - self.assertEqual(self.block_device_mapping[1]['device_name'], - '/dev/vdc') + self.assertEqual('/dev/vdc', + self.block_device_mapping[1]['device_name']) + self.assertEqual('/dev/vdd', + self.block_device_mapping[2]['device_name']) def test_with_swap(self): # Test swap only @@ -354,11 +366,14 @@ class DefaultDeviceNamesForInstanceTestCase(test.NoDBTestCase): # Test swap and block_device_mapping self.swap[0]['device_name'] = None self.block_device_mapping[1]['device_name'] = None + self.block_device_mapping[2]['device_name'] = None self._test_default_device_names([], self.swap, self.block_device_mapping) self.assertEqual(self.swap[0]['device_name'], '/dev/vdb') - self.assertEqual(self.block_device_mapping[1]['device_name'], - '/dev/vdc') + self.assertEqual('/dev/vdc', + self.block_device_mapping[1]['device_name']) + self.assertEqual('/dev/vdd', + self.block_device_mapping[2]['device_name']) def test_all_together(self): # Test swap missing @@ -379,12 +394,15 @@ class DefaultDeviceNamesForInstanceTestCase(test.NoDBTestCase): self.swap[0]['device_name'] = None self.ephemerals[0]['device_name'] = None self.block_device_mapping[1]['device_name'] = None + self.block_device_mapping[2]['device_name'] = None self._test_default_device_names(self.ephemerals, self.swap, self.block_device_mapping) self.assertEqual(self.ephemerals[0]['device_name'], '/dev/vdb') self.assertEqual(self.swap[0]['device_name'], '/dev/vdc') - self.assertEqual(self.block_device_mapping[1]['device_name'], - '/dev/vdd') + self.assertEqual('/dev/vdd', + self.block_device_mapping[1]['device_name']) + self.assertEqual('/dev/vde', + self.block_device_mapping[2]['device_name']) class UsageInfoTestCase(test.TestCase): diff --git a/nova/tests/test_block_device.py b/nova/tests/test_block_device.py index bd3928b13804..3b817ac3383c 100644 --- a/nova/tests/test_block_device.py +++ b/nova/tests/test_block_device.py @@ -524,6 +524,16 @@ class TestBlockDeviceDict(test.NoDBTestCase): block_device.BlockDeviceDict.from_api(api), matchers.IsSubDictOf(new)) + def test_from_api_invalid_blank_id(self): + api_dict = {'id': 1, + 'source_type': 'blank', + 'destination_type': 'volume', + 'uuid': 'fake-volume-id-1', + 'delete_on_termination': True, + 'boot_index': -1} + self.assertRaises(exception.InvalidBDMFormat, + block_device.BlockDeviceDict.from_api, api_dict) + def test_legacy(self): for legacy, new in zip(self.legacy_mapping, self.new_mapping): self.assertThat( diff --git a/nova/tests/virt/libvirt/test_blockinfo.py b/nova/tests/virt/libvirt/test_blockinfo.py index fe36bb5beb90..b5a3cd3740ab 100644 --- a/nova/tests/virt/libvirt/test_blockinfo.py +++ b/nova/tests/virt/libvirt/test_blockinfo.py @@ -893,6 +893,15 @@ class DefaultDeviceNamesTestCase(test.TestCase): 'disk_bus': 'virtio', 'destination_type': 'volume', 'snapshot_id': 'fake-snapshot-id-1', + 'boot_index': -1})), + objects.BlockDeviceMapping(self.context, + **fake_block_device.FakeDbBlockDeviceDict( + {'id': 5, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/vde', + 'source_type': 'blank', + 'device_type': 'disk', + 'disk_bus': 'virtio', + 'destination_type': 'volume', 'boot_index': -1}))] def tearDown(self): @@ -915,11 +924,14 @@ class DefaultDeviceNamesTestCase(test.TestCase): original_bdm, self.block_device_mapping): self.assertEqual(original.device_name, defaulted.device_name) - # Asser it defaults the missing one as expected + # Assert it defaults the missing one as expected self.block_device_mapping[1]['device_name'] = None + self.block_device_mapping[2]['device_name'] = None self._test_default_device_names([], [], self.block_device_mapping) self.assertEqual('/dev/vdd', self.block_device_mapping[1]['device_name']) + self.assertEqual('/dev/vde', + self.block_device_mapping[2]['device_name']) def test_with_ephemerals(self): # Test ephemeral gets assigned @@ -929,10 +941,13 @@ class DefaultDeviceNamesTestCase(test.TestCase): self.assertEqual('/dev/vdb', self.ephemerals[0]['device_name']) self.block_device_mapping[1]['device_name'] = None + self.block_device_mapping[2]['device_name'] = None self._test_default_device_names(self.ephemerals, [], self.block_device_mapping) self.assertEqual('/dev/vdd', self.block_device_mapping[1]['device_name']) + self.assertEqual('/dev/vde', + self.block_device_mapping[2]['device_name']) def test_with_swap(self): # Test swap only @@ -943,11 +958,14 @@ class DefaultDeviceNamesTestCase(test.TestCase): # Test swap and block_device_mapping self.swap[0]['device_name'] = None self.block_device_mapping[1]['device_name'] = None + self.block_device_mapping[2]['device_name'] = None self._test_default_device_names([], self.swap, self.block_device_mapping) self.assertEqual('/dev/vdc', self.swap[0]['device_name']) self.assertEqual('/dev/vdd', self.block_device_mapping[1]['device_name']) + self.assertEqual('/dev/vde', + self.block_device_mapping[2]['device_name']) def test_all_together(self): # Test swap missing @@ -968,9 +986,12 @@ class DefaultDeviceNamesTestCase(test.TestCase): self.swap[0]['device_name'] = None self.ephemerals[0]['device_name'] = None self.block_device_mapping[1]['device_name'] = None + self.block_device_mapping[2]['device_name'] = None self._test_default_device_names(self.ephemerals, self.swap, self.block_device_mapping) self.assertEqual('/dev/vdb', self.ephemerals[0]['device_name']) self.assertEqual('/dev/vdc', self.swap[0]['device_name']) self.assertEqual('/dev/vdd', self.block_device_mapping[1]['device_name']) + self.assertEqual('/dev/vde', + self.block_device_mapping[2]['device_name']) diff --git a/nova/tests/virt/test_block_device.py b/nova/tests/virt/test_block_device.py index af727b5d55eb..7008991f36b0 100644 --- a/nova/tests/virt/test_block_device.py +++ b/nova/tests/virt/test_block_device.py @@ -12,12 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. +import contextlib + import mock from nova import block_device from nova import context from nova.openstack.common import jsonutils from nova import test +from nova.tests import fake_instance from nova.tests import matchers from nova.virt import block_device as driver_block_device from nova.virt import driver @@ -31,7 +34,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): 'ephemeral': driver_block_device.DriverEphemeralBlockDevice, 'volume': driver_block_device.DriverVolumeBlockDevice, 'snapshot': driver_block_device.DriverSnapshotBlockDevice, - 'image': driver_block_device.DriverImageBlockDevice + 'image': driver_block_device.DriverImageBlockDevice, + 'blank': driver_block_device.DriverBlankBlockDevice } swap_bdm = block_device.BlockDeviceDict( @@ -163,6 +167,34 @@ class TestDriverBlockDevice(test.NoDBTestCase): 'connection_info': {"fake": "connection_info"}, 'delete_on_termination': True} + blank_bdm = block_device.BlockDeviceDict( + {'id': 6, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/sda2', + 'delete_on_termination': True, + 'volume_size': 3, + 'disk_bus': 'scsi', + 'device_type': 'disk', + 'source_type': 'blank', + 'destination_type': 'volume', + 'connection_info': '{"fake": "connection_info"}', + 'snapshot_id': 'fake-snapshot-id-1', + 'volume_id': 'fake-volume-id-2', + 'boot_index': -1}) + + blank_driver_bdm = { + 'mount_device': '/dev/sda2', + 'connection_info': {"fake": "connection_info"}, + 'delete_on_termination': True, + 'disk_bus': 'scsi', + 'device_type': 'disk', + 'guest_format': None, + 'boot_index': -1} + + blank_legacy_driver_bdm = { + 'mount_device': '/dev/sda2', + 'connection_info': {"fake": "connection_info"}, + 'delete_on_termination': True} + def setUp(self): super(TestDriverBlockDevice, self).setUp() self.volume_api = self.mox.CreateMock(cinder.API) @@ -275,6 +307,15 @@ class TestDriverBlockDevice(test.NoDBTestCase): self.assertRaises(driver_block_device._InvalidType, self.driver_classes['image'], bdm) + def test_driver_blank_block_device(self): + self._test_driver_device('blank') + + test_bdm = self.driver_classes['blank']( + self.blank_bdm) + self.assertEqual(6, test_bdm._bdm_obj.id) + self.assertEqual('fake-volume-id-2', test_bdm.volume_id) + self.assertEqual(3, test_bdm.volume_size) + def _test_volume_attach(self, driver_bdm, bdm_dict, fake_volume, check_attach=True, fail_check_attach=False, driver_attach=False, @@ -546,6 +587,33 @@ class TestDriverBlockDevice(test.NoDBTestCase): self.virt_driver) self.assertEqual(test_bdm.volume_id, 'fake-volume-id-2') + def test_blank_attach_volume(self): + no_blank_volume = self.blank_bdm.copy() + no_blank_volume['volume_id'] = None + test_bdm = self.driver_classes['blank'](no_blank_volume) + instance = fake_instance.fake_instance_obj(mock.sentinel.ctx, + **{'uuid': 'fake-uuid'}) + volume_class = self.driver_classes['volume'] + volume = {'id': 'fake-volume-id-2', + 'display_name': 'fake-uuid-blank-vol'} + + with contextlib.nested( + mock.patch.object(self.volume_api, 'create', return_value=volume), + mock.patch.object(volume_class, 'attach') + ) as (vol_create, vol_attach): + test_bdm.attach(self.context, instance, self.volume_api, + self.virt_driver) + + vol_create.assert_called_once_with(self.context, + test_bdm.volume_size, + 'fake-uuid-blank-vol', + '') + vol_attach.assert_called_once_with(self.context, instance, + self.volume_api, + self.virt_driver, + do_check_attach=True) + self.assertEqual('fake-volume-id-2', test_bdm.volume_id) + def test_convert_block_devices(self): converted = driver_block_device._convert_block_devices( self.driver_classes['volume'], diff --git a/nova/virt/block_device.py b/nova/virt/block_device.py index 0022d2331100..3cdfc56b7c1d 100644 --- a/nova/virt/block_device.py +++ b/nova/virt/block_device.py @@ -324,6 +324,26 @@ class DriverImageBlockDevice(DriverVolumeBlockDevice): do_check_attach=do_check_attach) +class DriverBlankBlockDevice(DriverVolumeBlockDevice): + + _valid_source = 'blank' + _proxy_as_attr = set(['volume_size', 'volume_id', 'image_id']) + + def attach(self, context, instance, volume_api, + virt_driver, wait_func=None, do_check_attach=True): + if not self.volume_id: + vol_name = instance.uuid + '-blank-vol' + vol = volume_api.create(context, self.volume_size, vol_name, '') + if wait_func: + wait_func(context, vol['id']) + + self.volume_id = vol['id'] + + super(DriverBlankBlockDevice, self).attach( + context, instance, volume_api, virt_driver, + do_check_attach=do_check_attach) + + def _convert_block_devices(device_type, block_device_mapping): def _is_transformable(bdm): try: @@ -355,6 +375,9 @@ convert_snapshots = functools.partial(_convert_block_devices, convert_images = functools.partial(_convert_block_devices, DriverImageBlockDevice) +convert_blanks = functools.partial(_convert_block_devices, + DriverBlankBlockDevice) + def attach_block_devices(block_device_mapping, *attach_args, **attach_kwargs): def _log_and_attach(bdm): @@ -417,7 +440,7 @@ def get_swap(transformed_list): _IMPLEMENTED_CLASSES = (DriverSwapBlockDevice, DriverEphemeralBlockDevice, DriverVolumeBlockDevice, DriverSnapshotBlockDevice, - DriverImageBlockDevice) + DriverImageBlockDevice, DriverBlankBlockDevice) def is_implemented(bdm): diff --git a/nova/virt/libvirt/blockinfo.py b/nova/virt/libvirt/blockinfo.py index d83bee8ecbca..401e83eb7b62 100644 --- a/nova/virt/libvirt/blockinfo.py +++ b/nova/virt/libvirt/blockinfo.py @@ -442,6 +442,8 @@ def default_device_names(virt_type, context, instance, root_device_name, driver_block_device.convert_volumes( block_device_mapping) + driver_block_device.convert_snapshots( + block_device_mapping) + + driver_block_device.convert_blanks( block_device_mapping)) }