diff --git a/nova/tests/unit/virt/vmwareapi/test_configdrive.py b/nova/tests/unit/virt/vmwareapi/test_configdrive.py index b07f5a8d3186..e11bcd7026ee 100644 --- a/nova/tests/unit/virt/vmwareapi/test_configdrive.py +++ b/nova/tests/unit/virt/vmwareapi/test_configdrive.py @@ -82,6 +82,9 @@ class ConfigDriveTestCase(test.NoDBTestCase): } self.test_instance = fake_instance.fake_instance_obj(self.context, **instance_values) + self.test_instance.flavor = objects.Flavor(vcpus=4, memory_mb=8192, + ephemeral_gb=0, swap=0, + extra_specs={}) (image_service, image_id) = glance.get_remote_image_service(context, image_ref) diff --git a/nova/tests/unit/virt/vmwareapi/test_vm_util.py b/nova/tests/unit/virt/vmwareapi/test_vm_util.py index cf6a68642c1e..18da1f9ec7cb 100644 --- a/nova/tests/unit/virt/vmwareapi/test_vm_util.py +++ b/nova/tests/unit/virt/vmwareapi/test_vm_util.py @@ -1411,6 +1411,37 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): expected.extraConfig.append(extra_config) self.assertEqual(expected, result) + def test_get_swap(self): + vm_ref = 'fake-vm-ref' + + # Root disk + controller_key = 1000 + root_disk = fake.VirtualDisk() + root_disk.controllerKey = controller_key + disk_backing = fake.VirtualDiskFlatVer2BackingInfo() + disk_backing.fileName = '[test_datastore] uuid/uuid.vmdk' + root_disk.capacityInBytes = 1048576 + root_disk.backing = disk_backing + + # Swap disk + swap_disk = fake.VirtualDisk() + swap_disk.controllerKey = controller_key + disk_backing = fake.VirtualDiskFlatVer2BackingInfo() + disk_backing.fileName = "swap" + swap_disk.capacityInBytes = 1024 + swap_disk.backing = disk_backing + devices = [root_disk, swap_disk] + + session = fake.FakeSession() + with mock.patch.object(session, '_call_method', + return_value=devices) as mock_call: + device = vm_util.get_swap(session, vm_ref) + + mock_call.assert_called_once_with(mock.ANY, + "get_dynamic_property", vm_ref, "VirtualMachine", + "config.hardware.device") + self.assertEqual(swap_disk, device) + @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 d6c43a415047..4047a894b91a 100644 --- a/nova/tests/unit/virt/vmwareapi/test_vmops.py +++ b/nova/tests/unit/virt/vmwareapi/test_vmops.py @@ -495,13 +495,14 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): def _test_finish_migration(self, power_on=True, resize_instance=False): with contextlib.nested( - mock.patch.object(self._vmops, '_resize_create_ephemerals'), + mock.patch.object(self._vmops, + '_resize_create_ephemerals_and_swap'), mock.patch.object(self._vmops, "_update_instance_progress"), mock.patch.object(vm_util, "power_on_instance"), mock.patch.object(vm_util, "get_vm_ref", return_value='fake-ref') - ) as (fake_resize_create_ephemerals, fake_update_instance_progress, - fake_power_on, fake_get_vm_ref): + ) as (fake_resize_create_ephemerals_and_swap, + fake_update_instance_progress, fake_power_on, fake_get_vm_ref): self._vmops.finish_migration(context=self._context, migration=None, instance=self._instance, @@ -511,9 +512,8 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): resize_instance=resize_instance, image_meta=None, power_on=power_on) - fake_resize_create_ephemerals.called_once_with('fake-ref', - self._instance, - None) + fake_resize_create_ephemerals_and_swap.called_once_with( + 'fake-ref', self._instance, None) if power_on: fake_power_on.assert_called_once_with(self._session, self._instance, @@ -538,8 +538,8 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): self._test_finish_migration(power_on=True, resize_instance=True) @mock.patch.object(vmops.VMwareVMOps, '_get_extra_specs') - @mock.patch.object(vmops.VMwareVMOps, '_resize_create_ephemerals') - @mock.patch.object(vmops.VMwareVMOps, '_remove_ephemerals') + @mock.patch.object(vmops.VMwareVMOps, '_resize_create_ephemerals_and_swap') + @mock.patch.object(vmops.VMwareVMOps, '_remove_ephemerals_and_swap') @mock.patch.object(ds_util, 'disk_delete') @mock.patch.object(ds_util, 'disk_move') @mock.patch.object(ds_util, 'file_exists', @@ -558,8 +558,8 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): fake_get_browser, fake_original_exists, fake_disk_move, fake_disk_delete, - fake_remove_ephemerals, - fake_resize_create_ephemerals, + fake_remove_ephemerals_and_swap, + fake_resize_create_ephemerals_and_swap, fake_get_extra_specs, power_on): """Tests the finish_revert_migration method on vmops.""" @@ -641,10 +641,9 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): mock_attach_disk.assert_called_once_with( 'fake-ref', self._instance, 'fake-adapter', 'fake-disk', '[fake] uuid/root.vmdk') - fake_remove_ephemerals.called_once_with('fake-ref') - fake_resize_create_ephemerals.called_once_with('fake-ref', - self._instance, - None) + fake_remove_ephemerals_and_swap.called_once_with('fake-ref') + fake_resize_create_ephemerals_and_swap.called_once_with( + 'fake-ref', self._instance, None) if power_on: fake_power_on.assert_called_once_with(self._session, self._instance) @@ -728,6 +727,21 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): 'fake-ref', self._instance, 'fake-adapter', 'fake-disk', '[fake] uuid/root.vmdk') + @mock.patch.object(vm_util, 'detach_devices_from_vm') + @mock.patch.object(vm_util, 'get_swap') + @mock.patch.object(vm_util, 'get_ephemerals') + def test_remove_ephemerals_and_swap(self, get_ephemerals, get_swap, + detach_devices): + get_ephemerals.return_value = [mock.sentinel.ephemeral0, + mock.sentinel.ephemeral1] + get_swap.return_value = mock.sentinel.swap + devices = [mock.sentinel.ephemeral0, mock.sentinel.ephemeral1, + mock.sentinel.swap] + + self._vmops._remove_ephemerals_and_swap(mock.sentinel.vm_ref) + detach_devices.assert_called_once_with(self._vmops._session, + mock.sentinel.vm_ref, devices) + @mock.patch.object(ds_util, 'disk_delete') @mock.patch.object(ds_util, 'file_exists', return_value=True) @@ -784,7 +798,7 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): self._test_migrate_disk_and_power_off, flavor_root_gb=self._instance.root_gb - 1) - @mock.patch.object(vmops.VMwareVMOps, "_remove_ephemerals") + @mock.patch.object(vmops.VMwareVMOps, "_remove_ephemerals_and_swap") @mock.patch.object(vm_util, 'get_vmdk_info') @mock.patch.object(vmops.VMwareVMOps, "_resize_disk") @mock.patch.object(vmops.VMwareVMOps, "_resize_vm") @@ -794,7 +808,7 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): def _test_migrate_disk_and_power_off(self, fake_get_vm_ref, fake_progress, fake_power_off, fake_resize_vm, fake_resize_disk, fake_get_vmdk_info, - fake_remove_ephemerals, + fake_remove_ephemerals_and_swap, flavor_root_gb): vmdk = vm_util.VmdkInfo('[fake] uuid/root.vmdk', 'fake-adapter', @@ -1405,7 +1419,8 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): cookies='Fake-CookieJar') self.assertTrue(len(_wait_for_task.mock_calls) > 0) extras = None - if block_device_info and 'ephemerals' in block_device_info: + if block_device_info and ('ephemerals' in block_device_info or + 'swap' in block_device_info): extras = ['CreateVirtualDisk_Task'] self._verify_spawn_method_calls(_call_method, extras) @@ -1512,6 +1527,99 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): 'size': 1}] self._spawn_with_block_device_info_ephemerals(ephemerals) + def test_spawn_with_block_device_info_swap(self): + block_device_info = {'swap': {'disk_bus': None, + 'swap_size': 512, + 'device_name': '/dev/sdb'}} + self._test_spawn(block_device_info=block_device_info) + + @mock.patch('nova.virt.vmwareapi.vm_util.power_on_instance') + @mock.patch.object(vmops.VMwareVMOps, '_create_and_attach_thin_disk') + @mock.patch.object(vmops.VMwareVMOps, '_use_disk_image_as_linked_clone') + @mock.patch.object(vmops.VMwareVMOps, '_fetch_image_if_missing') + @mock.patch( + 'nova.virt.vmwareapi.imagecache.ImageCacheManager.enlist_image') + @mock.patch.object(vmops.VMwareVMOps, 'build_virtual_machine') + @mock.patch.object(vmops.VMwareVMOps, '_get_vm_config_info') + @mock.patch.object(vmops.VMwareVMOps, '_get_extra_specs') + @mock.patch.object(nova.virt.vmwareapi.images.VMwareImage, + 'from_image') + def test_spawn_with_ephemerals_and_swap(self, from_image, + get_extra_specs, + get_vm_config_info, + build_virtual_machine, + enlist_image, + fetch_image, + use_disk_image, + create_and_attach_thin_disk, + power_on_instance): + self._instance.flavor = objects.Flavor(vcpus=1, memory_mb=512, + name="m1.tiny", root_gb=1, + ephemeral_gb=1, swap=512, + extra_specs={}) + extra_specs = self._vmops._get_extra_specs(self._instance.flavor) + ephemerals = [{'device_type': 'disk', + 'disk_bus': None, + 'device_name': '/dev/vdb', + 'size': 1}, + {'device_type': 'disk', + 'disk_bus': None, + 'device_name': '/dev/vdc', + 'size': 1}] + swap = {'disk_bus': None, 'swap_size': 512, 'device_name': '/dev/vdd'} + bdi = {'block_device_mapping': [], 'root_device_name': '/dev/sda', + 'ephemerals': ephemerals, 'swap': swap} + metadata = self._vmops._get_instance_metadata(self._context, + self._instance) + self.flags(enabled=False, group='vnc') + self.flags(flat_injected=False) + + image_size = (self._instance.root_gb) * units.Gi / 2 + image_info = images.VMwareImage( + image_id=self._image_id, + file_size=image_size) + vi = get_vm_config_info.return_value + from_image.return_value = image_info + build_virtual_machine.return_value = 'fake-vm-ref' + + self._vmops.spawn(self._context, self._instance, {}, + injected_files=None, admin_password=None, + network_info=[], block_device_info=bdi) + + from_image.assert_called_once_with(self._instance.image_ref, {}) + get_vm_config_info.assert_called_once_with(self._instance, + image_info, extra_specs) + build_virtual_machine.assert_called_once_with(self._instance, + image_info, vi.dc_info, vi.datastore, [], extra_specs, metadata) + enlist_image.assert_called_once_with(image_info.image_id, + vi.datastore, vi.dc_info.ref) + fetch_image.assert_called_once_with(self._context, vi) + use_disk_image.assert_called_once_with('fake-vm-ref', vi) + + # _create_and_attach_thin_disk should be called for each ephemeral + # and swap disk + eph0_path = str(ds_obj.DatastorePath(vi.datastore.name, 'fake_uuid', + 'ephemeral_0.vmdk')) + eph1_path = str(ds_obj.DatastorePath(vi.datastore.name, 'fake_uuid', + 'ephemeral_1.vmdk')) + swap_path = str(ds_obj.DatastorePath(vi.datastore.name, 'fake_uuid', + 'swap.vmdk')) + create_and_attach_thin_disk.assert_has_calls([ + mock.call(self._instance, 'fake-vm-ref', vi.dc_info, + ephemerals[0]['size'] * units.Mi, vi.ii.adapter_type, + eph0_path), + mock.call(self._instance, 'fake-vm-ref', vi.dc_info, + ephemerals[1]['size'] * units.Mi, vi.ii.adapter_type, + eph1_path), + mock.call(self._instance, 'fake-vm-ref', vi.dc_info, + swap['swap_size'] * units.Ki, vi.ii.adapter_type, + swap_path) + ]) + + power_on_instance.assert_called_once_with(self._session, + self._instance, + vm_ref='fake-vm-ref') + def _get_fake_vi(self): image_info = images.VMwareImage( image_id=self._image_id, @@ -1523,18 +1631,18 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): return vi @mock.patch.object(vm_util, 'create_virtual_disk') - def test_create_and_attach_ephemeral_disk(self, mock_create): + def test_create_and_attach_thin_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 path = str(ds_obj.DatastorePath(vi.datastore.name, 'fake_uuid', - 'fake-filename')) - self._vmops._create_and_attach_ephemeral_disk(self._instance, - 'fake-vm-ref', - vi.dc_info, 1, - 'fake-adapter-type', - path) + 'fake-filename')) + self._vmops._create_and_attach_thin_disk(self._instance, + 'fake-vm-ref', + vi.dc_info, 1, + 'fake-adapter-type', + path) mock_create.assert_called_once_with( self._session, self._dc_info.ref, 'fake-adapter-type', 'thin', path, 1) @@ -1550,7 +1658,7 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): 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_and_attach_thin_disk') as mock_caa: self._vmops._create_ephemeral(block_device_info, self._instance, 'fake-vm-ref', @@ -1565,7 +1673,7 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): 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_and_attach_thin_disk') as mock_caa: self._vmops._create_ephemeral(bdi, self._instance, 'fake-vm-ref', @@ -1586,6 +1694,35 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): self._instance.ephemeral_gb = 1 self._test_create_ephemeral_from_instance(None) + def _test_create_swap_from_instance(self, bdi): + vi = self._get_fake_vi() + flavor = objects.Flavor(vcpus=1, memory_mb=1024, ephemeral_gb=1, + swap=1024, extra_specs={}) + self._instance.flavor = flavor + with mock.patch.object( + self._vmops, '_create_and_attach_thin_disk' + ) as create_and_attach: + self._vmops._create_swap(bdi, self._instance, 'fake-vm-ref', + vi.dc_info, vi.datastore, 'fake_uuid', + 'lsiLogic') + size = flavor.swap * units.Ki + if bdi is not None: + swap = bdi.get('swap', {}) + size = swap.get('swap_size', 0) * units.Ki + path = str(ds_obj.DatastorePath(vi.datastore.name, 'fake_uuid', + 'swap.vmdk')) + create_and_attach.assert_called_once_with(self._instance, + 'fake-vm-ref', vi.dc_info, size, 'lsiLogic', path) + + def test_create_swap_with_bdi(self): + block_device_info = {'swap': {'disk_bus': None, + 'swap_size': 512, + 'device_name': '/dev/sdb'}} + self._test_create_swap_from_instance(block_device_info) + + def test_create_swap_with_no_bdi(self): + self._test_create_swap_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/vm_util.py b/nova/virt/vmwareapi/vm_util.py index c6b12f49f431..81c01f2d8b63 100644 --- a/nova/virt/vmwareapi/vm_util.py +++ b/nova/virt/vmwareapi/vm_util.py @@ -1571,3 +1571,19 @@ def get_ephemerals(session, vm_ref): if 'ephemeral' in device.backing.fileName: devices.append(device) return devices + + +def get_swap(session, vm_ref): + hardware_devices = session._call_method(vim_util, + "get_dynamic_property", vm_ref, "VirtualMachine", + "config.hardware.device") + + if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice": + hardware_devices = hardware_devices.VirtualDevice + + for device in hardware_devices: + if (device.__class__.__name__ == "VirtualDisk" and + device.backing.__class__.__name__ == + "VirtualDiskFlatVer2BackingInfo" and + 'swap' in device.backing.fileName): + return device diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index f85fbc12f370..d285bc4fa272 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -557,8 +557,8 @@ class VMwareVMOps(object): instance=vi.instance) self._delete_datastore_file(str(tmp_dir_loc), vi.dc_info.ref) - def _create_and_attach_ephemeral_disk(self, instance, vm_ref, dc_info, - size, adapter_type, path): + def _create_and_attach_thin_disk(self, instance, vm_ref, dc_info, size, + adapter_type, path): disk_type = constants.DISK_TYPE_THIN vm_util.create_virtual_disk( self._session, dc_info.ref, @@ -583,19 +583,42 @@ class VMwareVMOps(object): filename = vm_util.get_ephemeral_name(idx) path = str(ds_obj.DatastorePath(datastore.name, folder, filename)) - self._create_and_attach_ephemeral_disk(instance, vm_ref, - dc_info, size, - at, path) + self._create_and_attach_thin_disk(instance, vm_ref, dc_info, + size, at, path) + # 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) path = str(ds_obj.DatastorePath(datastore.name, folder, - filename)) - self._create_and_attach_ephemeral_disk(instance, vm_ref, - dc_info, size, - adapter_type, path) + filename)) + self._create_and_attach_thin_disk(instance, vm_ref, dc_info, size, + adapter_type, path) + + def _create_swap(self, bdi, instance, vm_ref, dc_info, datastore, + folder, adapter_type): + swap = None + filename = "swap.vmdk" + path = str(ds_obj.DatastorePath(datastore.name, folder, filename)) + if bdi is not None: + swap = driver.block_device_info_get_swap(bdi) + if driver.swap_is_usable(swap): + size = swap['swap_size'] * units.Ki + self._create_and_attach_thin_disk(instance, vm_ref, dc_info, + size, adapter_type, path) + else: + # driver.block_device_info_get_swap returns + # {'device_name': None, 'swap_size': 0} if swap is None + # in block_device_info. If block_device_info does not contain + # a swap device, we need to reset swap to None, so we can + # extract the swap_size from the instance's flavor. + swap = None + + size = instance.flavor.swap * units.Ki + if not swap and size > 0: + self._create_and_attach_thin_disk(instance, vm_ref, dc_info, size, + adapter_type, path) def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info, block_device_info=None): @@ -678,6 +701,8 @@ class VMwareVMOps(object): self._create_ephemeral(block_device_info, instance, vm_ref, vi.dc_info, vi.datastore, instance.uuid, vi.ii.adapter_type) + self._create_swap(block_device_info, instance, vm_ref, vi.dc_info, + vi.datastore, instance.uuid, vi.ii.adapter_type) if configdrive.required_by(instance): self._configure_config_drive( @@ -1176,12 +1201,17 @@ class VMwareVMOps(object): vmdk.adapter_type, vmdk.disk_type, vmdk.path) - def _remove_ephemerals(self, vm_ref): + def _remove_ephemerals_and_swap(self, vm_ref): devices = vm_util.get_ephemerals(self._session, vm_ref) + swap = vm_util.get_swap(self._session, vm_ref) + if swap is not None: + devices.append(swap) + if devices: vm_util.detach_devices_from_vm(self._session, vm_ref, devices) - def _resize_create_ephemerals(self, vm_ref, instance, block_device_info): + def _resize_create_ephemerals_and_swap(self, vm_ref, instance, + block_device_info): vmdk = vm_util.get_vmdk_info(self._session, vm_ref, uuid=instance.uuid) ds_ref = vmdk.device.backing.datastore @@ -1190,6 +1220,8 @@ class VMwareVMOps(object): folder = ds_obj.DatastorePath.parse(vmdk.path).dirname self._create_ephemeral(block_device_info, instance, vm_ref, dc_info, datastore, folder, vmdk.adapter_type) + self._create_swap(block_device_info, instance, vm_ref, dc_info, + datastore, folder, vmdk.adapter_type) def migrate_disk_and_power_off(self, context, instance, dest, flavor): @@ -1235,8 +1267,8 @@ class VMwareVMOps(object): step=3, total_steps=RESIZE_TOTAL_STEPS) - # 4. Purge ephemeral disks - self._remove_ephemerals(vm_ref) + # 4. Purge ephemeral and swap disks + self._remove_ephemerals_and_swap(vm_ref) self._update_instance_progress(context, instance, step=4, total_steps=RESIZE_TOTAL_STEPS) @@ -1298,8 +1330,9 @@ class VMwareVMOps(object): vmdk.adapter_type, vmdk.disk_type, vmdk.path) # Reconfigure ephemerals - self._remove_ephemerals(vm_ref) - self._resize_create_ephemerals(vm_ref, instance, block_device_info) + self._remove_ephemerals_and_swap(vm_ref) + self._resize_create_ephemerals_and_swap(vm_ref, instance, + block_device_info) if power_on: vm_util.power_on_instance(self._session, instance) @@ -1310,7 +1343,8 @@ class VMwareVMOps(object): vm_ref = vm_util.get_vm_ref(self._session, instance) # 5. Update ephemerals if necessary - self._resize_create_ephemerals(vm_ref, instance, block_device_info) + self._resize_create_ephemerals_and_swap(vm_ref, instance, + block_device_info) self._update_instance_progress(context, instance, step=5,