From aea0140c18a01d1baeb760bd2a0933ad1dbf2fa9 Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Thu, 24 Jul 2014 15:04:24 -0700 Subject: [PATCH] VMware: ephemeral disk support This patch ensures that the driver honors the following for ephemeral disks: 1. the flavor definition 2. the block device mappings The driver will now create an ephemeral disk(s) if necessary. The ephemeral disks will be of a 'thin' disk type. Closes-bug: #1369254 This is part of blueprint vmware-ephemeral-disk-support DocImpact - Indicate that ephemeral disks are now supported by the driver Change-Id: Icb1796e58126a1e03613946b0f5a917d6eb4708e --- .../unit/virt/vmwareapi/test_driver_api.py | 42 ++++++++- .../tests/unit/virt/vmwareapi/test_vm_util.py | 4 + nova/tests/unit/virt/vmwareapi/test_vmops.py | 88 ++++++++++++++++++- nova/virt/vmwareapi/constants.py | 1 + nova/virt/vmwareapi/vm_util.py | 4 + nova/virt/vmwareapi/vmops.py | 40 +++++++++ 6 files changed, 172 insertions(+), 7 deletions(-) diff --git a/nova/tests/unit/virt/vmwareapi/test_driver_api.py b/nova/tests/unit/virt/vmwareapi/test_driver_api.py index 979e265d1f2b..9028bb4e0216 100644 --- a/nova/tests/unit/virt/vmwareapi/test_driver_api.py +++ b/nova/tests/unit/virt/vmwareapi/test_driver_api.py @@ -385,12 +385,15 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): 'flavorid': '1', 'vcpu_weight': None, 'id': 2} def _create_instance(self, node=None, set_image_ref=True, - uuid=None, instance_type='m1.large'): + uuid=None, instance_type='m1.large', + ephemeral=None): if not node: node = self.node_name if not uuid: uuid = uuidutils.generate_uuid() self.type_data = self._get_instance_type_by_name(instance_type) + if ephemeral is not None: + self.type_data['ephemeral_gb'] = ephemeral values = {'name': 'fake_name', 'id': 1, 'uuid': uuid, @@ -416,17 +419,19 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): self.context, **values) def _create_vm(self, node=None, num_instances=1, uuid=None, - instance_type='m1.large', powered_on=True): + instance_type='m1.large', powered_on=True, + ephemeral=None, bdi=None): """Create and spawn the VM.""" if not node: node = self.node_name self._create_instance(node=node, uuid=uuid, - instance_type=instance_type) + instance_type=instance_type, + ephemeral=ephemeral) self.assertIsNone(vm_util.vm_ref_cache_get(self.uuid)) self.conn.spawn(self.context, self.instance, self.image, injected_files=[], admin_password=None, network_info=self.network_info, - block_device_info=None) + block_device_info=bdi) self._check_vm_record(num_instances=num_instances, powered_on=powered_on) self.assertIsNotNone(vm_util.vm_ref_cache_get(self.uuid)) @@ -644,6 +649,35 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): self._create_vm() self.assertEqual(self.iso_index, 2) + def test_ephemeral_disk_attach(self): + self._create_vm(ephemeral=50) + path = ds_util.DatastorePath(self.ds, self.uuid, + 'ephemeral_0.vmdk') + self.assertTrue(vmwareapi_fake.get_file(str(path))) + + def test_ephemeral_disk_attach_from_bdi(self): + ephemerals = [{'device_type': 'disk', + 'disk_bus': 'lsiLogic', + 'size': 25}, + {'device_type': 'disk', + 'disk_bus': 'lsiLogic', + 'size': 25}] + bdi = {'ephemerals': ephemerals} + self._create_vm(bdi=bdi, ephemeral=50) + path = ds_util.DatastorePath(self.ds, self.uuid, + 'ephemeral_0.vmdk') + self.assertTrue(vmwareapi_fake.get_file(str(path))) + path = ds_util.DatastorePath(self.ds, self.uuid, + 'ephemeral_1.vmdk') + self.assertTrue(vmwareapi_fake.get_file(str(path))) + + def test_ephemeral_disk_attach_from_bdii_with_no_ephs(self): + bdi = {'ephemerals': []} + self._create_vm(bdi=bdi, ephemeral=50) + path = ds_util.DatastorePath(self.ds, self.uuid, + 'ephemeral_0.vmdk') + self.assertTrue(vmwareapi_fake.get_file(str(path))) + def test_cdrom_attach_with_config_drive(self): self.flags(force_config_drive=True) diff --git a/nova/tests/unit/virt/vmwareapi/test_vm_util.py b/nova/tests/unit/virt/vmwareapi/test_vm_util.py index a84a27bd802f..82328ee13336 100644 --- a/nova/tests/unit/virt/vmwareapi/test_vm_util.py +++ b/nova/tests/unit/virt/vmwareapi/test_vm_util.py @@ -1089,6 +1089,10 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): 'fake_policy') self.assertIsNone(profile_spec) + def test_get_ephemeral_name(self): + filename = vm_util.get_ephemeral_name(0) + self.assertEqual('ephemeral_0.vmdk', filename) + @mock.patch.object(driver.VMwareAPISession, 'vim', stubs.fake_vim_prop) class VMwareVMUtilGetHostRefTestCase(test.NoDBTestCase): diff --git a/nova/tests/unit/virt/vmwareapi/test_vmops.py b/nova/tests/unit/virt/vmwareapi/test_vmops.py index b37bb1dac41a..592f29782df4 100644 --- a/nova/tests/unit/virt/vmwareapi/test_vmops.py +++ b/nova/tests/unit/virt/vmwareapi/test_vmops.py @@ -739,7 +739,7 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): def test_use_iso_image_without_root_disk(self): self._test_use_iso_image(with_root_disk=False) - def _verify_spawn_method_calls(self, mock_call_method): + def _verify_spawn_method_calls(self, mock_call_method, extras=None): # TODO(vui): More explicit assertions of spawn() behavior # are waiting on additional refactoring pertaining to image # handling/manipulation. Till then, we continue to assert on the @@ -753,6 +753,8 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): 'SearchDatastore_Task', 'ExtendVirtualDisk_Task', ] + if extras: + expected_methods.extend(extras) recorded_methods = [c[1][1] for c in mock_call_method.mock_calls] self.assertEqual(expected_methods, recorded_methods) @@ -873,7 +875,8 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): else: self.assertFalse(mock_power_on_instance.called) - if block_device_info: + if (block_device_info and + 'block_device_mapping' in block_device_info): root_disk = block_device_info['block_device_mapping'][0] mock_attach = self._vmops._volumeops.attach_root_volume mock_attach.assert_called_once_with( @@ -897,7 +900,10 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): upload_file_name, cookies='Fake-CookieJar') self.assertTrue(len(_wait_for_task.mock_calls) > 0) - self._verify_spawn_method_calls(_call_method) + extras = None + if block_device_info and 'ephemerals' in block_device_info: + extras = ['CreateVirtualDisk_Task'] + self._verify_spawn_method_calls(_call_method, extras) dc_ref = 'fake_dc_ref' source_file = unicode('[fake_ds] vmware_base/%s/%s.vmdk' % @@ -990,6 +996,82 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): self._test_spawn(block_device_info=block_device_info, config_drive=True) + def test_spawn_with_block_device_info_ephemerals(self): + ephemerals = [{'device_type': 'disk', + 'disk_bus': 'virtio', + 'device_name': '/dev/vdb', + 'size': 1}] + block_device_info = {'ephemerals': ephemerals} + self._test_spawn(block_device_info=block_device_info) + + def _get_fake_vi(self): + image_info = images.VMwareImage( + image_id=self._image_id, + file_size=7, + linked_clone=False) + vi = vmops.VirtualMachineInstanceConfigInfo( + self._instance, 'fake_uuid', image_info, + self._ds, self._dc_info, mock.Mock()) + return vi + + @mock.patch.object(vm_util, 'create_virtual_disk') + def test_create_and_attach_ephemeral_disk(self, mock_create): + vi = self._get_fake_vi() + self._vmops._volumeops = mock.Mock() + mock_attach_disk_to_vm = self._vmops._volumeops.attach_disk_to_vm + + self._vmops._create_and_attach_ephemeral_disk(self._instance, + 'fake-vm-ref', + vi, 1, + 'fake-adapter-type', + 'fake-filename') + path = str(ds_util.DatastorePath(vi.datastore.name, 'fake_uuid', + 'fake-filename')) + mock_create.assert_called_once_with( + self._session, self._dc_info.ref, 'fake-adapter-type', + 'thin', path, 1) + mock_attach_disk_to_vm.assert_called_once_with( + 'fake-vm-ref', self._instance, 'fake-adapter-type', + 'thin', path, 1, False) + + def test_create_ephemeral_with_bdi(self): + ephemerals = [{'device_type': 'disk', + 'disk_bus': 'virtio', + 'device_name': '/dev/vdb', + 'size': 1}] + block_device_info = {'ephemerals': ephemerals} + vi = self._get_fake_vi() + with mock.patch.object( + self._vmops, '_create_and_attach_ephemeral_disk') as mock_caa: + self._vmops._create_ephemeral(block_device_info, + self._instance, + 'fake-vm-ref', + vi) + mock_caa.assert_called_once_with(self._instance, 'fake-vm-ref', + vi, 1 * units.Mi, 'virtio', + 'ephemeral_0.vmdk') + + def _test_create_ephemeral_from_instance(self, bdi): + vi = self._get_fake_vi() + with mock.patch.object( + self._vmops, '_create_and_attach_ephemeral_disk') as mock_caa: + self._vmops._create_ephemeral(bdi, + self._instance, + 'fake-vm-ref', + vi) + mock_caa.assert_called_once_with(self._instance, 'fake-vm-ref', + vi, 1 * units.Mi, 'lsiLogic', + 'ephemeral_0.vmdk') + + def test_create_ephemeral_with_bdi_but_no_ephemerals(self): + block_device_info = {'ephemerals': []} + self._instance.ephemeral_gb = 1 + self._test_create_ephemeral_from_instance(block_device_info) + + def test_create_ephemeral_with_no_bdi(self): + self._instance.ephemeral_gb = 1 + self._test_create_ephemeral_from_instance(None) + def test_build_virtual_machine(self): image_id = nova.tests.unit.image.fake.get_valid_image_id() image = images.VMwareImage(image_id=image_id) diff --git a/nova/virt/vmwareapi/constants.py b/nova/virt/vmwareapi/constants.py index 800b0a8f438e..f5c34d7101cf 100644 --- a/nova/virt/vmwareapi/constants.py +++ b/nova/virt/vmwareapi/constants.py @@ -22,6 +22,7 @@ DISK_FORMAT_ISO = 'iso' DISK_FORMAT_VMDK = 'vmdk' DISK_FORMATS_ALL = [DISK_FORMAT_ISO, DISK_FORMAT_VMDK] +DISK_TYPE_THIN = 'thin' DISK_TYPE_SPARSE = 'sparse' DISK_TYPE_THIN = 'thin' DISK_TYPE_PREALLOCATED = 'preallocated' diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py index 6fd0c5fbb593..ecf1af7d1437 100644 --- a/nova/virt/vmwareapi/vm_util.py +++ b/nova/virt/vmwareapi/vm_util.py @@ -1481,3 +1481,7 @@ def power_off_instance(session, instance, vm_ref=None): LOG.debug("Powered off the VM", instance=instance) except vexc.InvalidPowerStateException: LOG.debug("VM already powered off", instance=instance) + + +def get_ephemeral_name(id): + return 'ephemeral_%d.vmdk' % id diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index 1619b50946b8..ca2b4df12d2e 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -477,6 +477,43 @@ class VMwareVMOps(object): LOG.debug("Cleaning up location %s", str(tmp_dir_loc)) self._delete_datastore_file(str(tmp_dir_loc), vi.dc_info.ref) + def _create_and_attach_ephemeral_disk(self, instance, vm_ref, vi, size, + adapter_type, filename): + path = str(ds_util.DatastorePath(vi.datastore.name, instance.uuid, + filename)) + disk_type = constants.DISK_TYPE_THIN + vm_util.create_virtual_disk( + self._session, vi.dc_info.ref, + adapter_type, + disk_type, + path, + size) + + self._volumeops.attach_disk_to_vm( + vm_ref, vi.instance, + adapter_type, disk_type, + path, size, False) + + def _create_ephemeral(self, bdi, instance, vm_ref, vi): + ephemerals = None + if bdi is not None: + ephemerals = driver.block_device_info_get_ephemerals(bdi) + for idx, eph in enumerate(ephemerals): + size = eph['size'] * units.Mi + adapter_type = eph.get('disk_bus', vi.ii.adapter_type) + filename = vm_util.get_ephemeral_name(idx) + self._create_and_attach_ephemeral_disk(instance, vm_ref, vi, + size, adapter_type, + filename) + # There may be block devices defined but no ephemerals. In this case + # we need to allocate a ephemeral disk if required + if not ephemerals and instance.ephemeral_gb: + size = instance.ephemeral_gb * units.Mi + filename = vm_util.get_ephemeral_name(0) + self._create_and_attach_ephemeral_disk(instance, vm_ref, vi, + size, vi.ii.adapter_type, + filename) + def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info, block_device_info=None, instance_name=None, power_on=True): @@ -551,6 +588,9 @@ class VMwareVMOps(object): else: self._use_disk_image_as_full_clone(vm_ref, vi) + # Create ephemeral disks + self._create_ephemeral(block_device_info, instance, vm_ref, vi) + if configdrive.required_by(instance): self._configure_config_drive( instance, vm_ref, vi.dc_info, vi.datastore,