Merge "VMware: fix VM rescue problem with VNC console"

This commit is contained in:
Jenkins 2015-03-15 14:01:38 +00:00 committed by Gerrit Code Review
commit 2235a36bcf
5 changed files with 214 additions and 167 deletions

View File

@ -41,7 +41,6 @@ from nova import block_device
from nova.compute import api as compute_api
from nova.compute import power_state
from nova.compute import task_states
from nova.compute import vm_states
from nova import context
from nova import exception
from nova.image import glance
@ -1441,34 +1440,6 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self.instance)
self.assertFalse(mock_reboot.called)
def destroy_rescued(self, fake_method):
self._rescue()
with contextlib.nested(
mock.patch.object(self.conn._volumeops, "detach_disk_from_vm",
fake_method),
mock.patch.object(vm_util, "power_on_instance"),
) as (fake_detach, fake_power_on):
self.instance['vm_state'] = vm_states.RESCUED
self.conn.destroy(self.context, self.instance, self.network_info)
inst_path = ds_util.DatastorePath(self.ds, self.uuid,
'%s.vmdk' % self.uuid)
self.assertFalse(vmwareapi_fake.get_file(str(inst_path)))
rescue_file_path = ds_util.DatastorePath(
self.ds, '%s-rescue' % self.uuid, '%s-rescue.vmdk' % self.uuid)
self.assertFalse(vmwareapi_fake.get_file(str(rescue_file_path)))
# Unrescue does not power on with destroy
self.assertFalse(fake_power_on.called)
def test_destroy_rescued(self):
def fake_detach_disk_from_vm(*args, **kwargs):
pass
self.destroy_rescued(fake_detach_disk_from_vm)
def test_destroy_rescued_with_exception(self):
def fake_detach_disk_from_vm(*args, **kwargs):
raise exception.NovaException('Here is my fake exception')
self.destroy_rescued(fake_detach_disk_from_vm)
def test_destroy(self):
self._create_vm()
info = self._get_info()
@ -1585,70 +1556,17 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self.conn.rescue(self.context, self.instance, self.network_info,
self.image, 'fake-password')
info = self._get_info(name='1-rescue',
uuid='%s-rescue' % self.uuid)
info = self.conn.get_info({'name': '1',
'uuid': self.uuid,
'node': self.instance_node})
self._check_vm_info(info, power_state.RUNNING)
info = self._get_info()
info = self.conn.get_info({'name': '1-orig',
'uuid': '%s-orig' % self.uuid,
'node': self.instance_node})
self._check_vm_info(info, power_state.SHUTDOWN)
self.assertIsNotNone(vm_util.vm_ref_cache_get('%s-rescue' % self.uuid))
self.assertIsNotNone(vm_util.vm_ref_cache_get(self.uuid))
self.assertEqual(1, self._power_on_called)
def test_rescue(self):
self._rescue()
inst_file_path = ds_util.DatastorePath(self.ds, self.uuid,
'%s.vmdk' % self.uuid)
self.assertTrue(vmwareapi_fake.get_file(str(inst_file_path)))
rescue_file_path = ds_util.DatastorePath(self.ds,
'%s-rescue' % self.uuid,
'%s-rescue.vmdk' % self.uuid)
self.assertTrue(vmwareapi_fake.get_file(str(rescue_file_path)))
def test_rescue_with_config_drive(self):
self.flags(force_config_drive=True)
self._rescue(config_drive=True)
def test_unrescue(self):
# NOTE(dims): driver unrescue ends up eventually in vmops.unrescue
# with power_on=True, the test_destroy_rescued tests the
# vmops.unrescue with power_on=False
self._rescue()
vm_ref = vm_util.get_vm_ref(self.conn._session,
self.instance)
vm_rescue_ref = vm_util.get_vm_ref_from_name(self.conn._session,
'%s-rescue' % self.uuid)
self.poweroff_instance = vm_util.power_off_instance
def fake_power_off_instance(session, instance, vm_ref):
# This is called so that we actually poweroff the simulated vm.
# The reason for this is that there is a validation in destroy
# that the instance is not powered on.
self.poweroff_instance(session, instance, vm_ref)
def fake_detach_disk_from_vm(vm_ref, instance,
device_name, destroy_disk=False):
self.test_device_name = device_name
info = self.conn.get_info(instance)
self._check_vm_info(info, power_state.SHUTDOWN)
with contextlib.nested(
mock.patch.object(vm_util, "power_off_instance",
side_effect=fake_power_off_instance),
mock.patch.object(self.conn._volumeops, "detach_disk_from_vm",
side_effect=fake_detach_disk_from_vm),
mock.patch.object(vm_util, "power_on_instance"),
) as (poweroff, detach, fake_power_on):
self.conn.unrescue(self.instance, None)
poweroff.assert_called_once_with(self.conn._session, mock.ANY,
vm_rescue_ref)
detach.assert_called_once_with(vm_rescue_ref, mock.ANY,
self.test_device_name)
fake_power_on.assert_called_once_with(self.conn._session,
self.instance,
vm_ref=vm_ref)
self.test_vm_ref = None
self.test_device_name = None
def test_get_diagnostics(self):
self._create_vm()
expected = {'memoryReservation': 0, 'suspendInterval': 0,

View File

@ -1199,6 +1199,49 @@ class VMwareVMUtilTestCase(test.NoDBTestCase):
fake_devices)
mock_reconfigure.assert_called_once_with(session, 'fake-ref', mock.ANY)
def test_get_vm_boot_spec(self):
disk = fake.VirtualDisk()
disk.key = 7
fake_factory = fake.FakeFactory()
result = vm_util.get_vm_boot_spec(fake_factory,
disk)
expected = fake_factory.create('ns0:VirtualMachineConfigSpec')
boot_disk = fake_factory.create(
'ns0:VirtualMachineBootOptionsBootableDiskDevice')
boot_disk.deviceKey = disk.key
boot_options = fake_factory.create('ns0:VirtualMachineBootOptions')
boot_options.bootOrder = [boot_disk]
expected.bootOptions = boot_options
self.assertEqual(expected, result)
def _get_devices(self, filename):
devices = fake._create_array_of_type('VirtualDevice')
devices.VirtualDevice = self._vmdk_path_and_adapter_type_devices(
filename)
return devices
def test_find_rescue_device(self):
instance_uuid = uuidutils.generate_uuid()
fake_instance = self.fake_instance_obj({'id': 7, 'name': 'fake!',
'uuid': instance_uuid,
'vcpus': 2, 'memory_mb': 2048})
filename = '[test_datastore] uuid/uuid-rescue.vmdk'
devices = self._get_devices(filename)
device = vm_util.find_rescue_device(devices, fake_instance)
self.assertEqual(filename, device.backing.fileName)
def test_find_rescue_device_not_found(self):
instance_uuid = uuidutils.generate_uuid()
fake_instance = self.fake_instance_obj({'id': 7, 'name': 'fake!',
'uuid': instance_uuid,
'vcpus': 2, 'memory_mb': 2048})
filename = '[test_datastore] uuid/uuid.vmdk'
devices = self._get_devices(filename)
self.assertRaises(exception.NotFound,
vm_util.find_rescue_device,
devices,
fake_instance)
@mock.patch.object(driver.VMwareAPISession, 'vim', stubs.fake_vim_prop)
class VMwareVMUtilGetHostRefTestCase(test.NoDBTestCase):

View File

@ -351,6 +351,67 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
def test_get_datacenter_ref_and_name_with_no_datastore(self):
self._test_get_datacenter_ref_and_name()
@mock.patch.object(vm_util, 'power_off_instance')
@mock.patch.object(ds_util, 'disk_copy')
@mock.patch.object(vm_util, 'get_vm_ref', return_value='fake-ref')
@mock.patch.object(vm_util, 'get_values_from_object_properties')
@mock.patch.object(vm_util, 'find_rescue_device')
@mock.patch.object(vm_util, 'get_vm_boot_spec')
@mock.patch.object(vm_util, 'reconfigure_vm')
@mock.patch.object(vm_util, 'power_on_instance')
@mock.patch.object(ds_util, 'get_datastore_by_ref')
def test_rescue(self, mock_get_ds_by_ref, mock_power_on, mock_reconfigure,
mock_get_boot_spec, mock_find_rescue,
mock_get_values, mock_get_vm_ref, mock_disk_copy,
mock_power_off):
_volumeops = mock.Mock()
self._vmops._volumeops = _volumeops
ds = ds_util.Datastore('fake-ref', 'ds1')
mock_get_ds_by_ref.return_value = ds
mock_find_rescue.return_value = 'fake-rescue-device'
mock_get_boot_spec.return_value = 'fake-boot-spec'
device = vmwareapi_fake.DataObject()
backing = vmwareapi_fake.DataObject()
backing.datastore = ds.ref
device.backing = backing
vmdk = vm_util.VmdkInfo('[fake] uuid/root.vmdk',
'fake-adapter',
'fake-disk',
'fake-capacity',
device)
with contextlib.nested(
mock.patch.object(self._vmops, 'get_datacenter_ref_and_name'),
mock.patch.object(vm_util, 'get_vmdk_info',
return_value=vmdk)
) as (_get_dc_ref_and_name, fake_vmdk_info):
dc_info = mock.Mock()
_get_dc_ref_and_name.return_value = dc_info
self._vmops.rescue(self._context, self._instance, None, None)
mock_power_off.assert_called_once_with(self._session,
self._instance,
'fake-ref')
uuid = self._instance.image_ref
cache_path = ds.build_path('vmware_base', uuid, uuid + '.vmdk')
rescue_path = ds.build_path('fake_uuid', uuid + '-rescue.vmdk')
mock_disk_copy.assert_called_once_with(self._session, dc_info.ref,
cache_path, rescue_path)
_volumeops.attach_disk_to_vm.assert_called_once_with('fake-ref',
self._instance, mock.ANY, mock.ANY, rescue_path)
mock_get_boot_spec.assert_called_once_with(mock.ANY,
'fake-rescue-device')
mock_reconfigure.assert_called_once_with(self._session,
'fake-ref',
'fake-boot-spec')
mock_power_on.assert_called_once_with(self._session,
self._instance,
vm_ref='fake-ref')
def test_unrescue_power_on(self):
self._test_unrescue(True)
@ -358,58 +419,38 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
self._test_unrescue(False)
def _test_unrescue(self, power_on):
self._vmops._volumeops = mock.Mock()
vm_rescue_ref = mock.Mock()
_volumeops = mock.Mock()
self._vmops._volumeops = _volumeops
vm_ref = mock.Mock()
args_list = [(vm_rescue_ref, 'VirtualMachine',
'config.hardware.device')]
def fake_call_method(module, method, *args, **kwargs):
expected_args = args_list.pop(0)
expected_args = (vm_ref, 'VirtualMachine',
'config.hardware.device')
self.assertEqual('get_dynamic_property', method)
self.assertEqual(expected_args, args)
vmdk = vm_util.VmdkInfo(mock.sentinel.PATH,
mock.sentinel.ADAPTER_TYPE,
mock.sentinel.DISK_TYPE,
mock.sentinel.CAPACITY,
mock.sentinel.DEVICE)
with contextlib.nested(
mock.patch.object(vm_util, 'get_vmdk_info',
return_value=vmdk),
mock.patch.object(vm_util, 'get_vmdk_volume_disk'),
mock.patch.object(vm_util, 'power_on_instance'),
mock.patch.object(vm_util, 'find_rescue_device'),
mock.patch.object(vm_util, 'get_vm_ref', return_value=vm_ref),
mock.patch.object(vm_util, 'get_vm_ref_from_name',
return_value=vm_rescue_ref),
mock.patch.object(self._session, '_call_method',
fake_call_method),
mock.patch.object(vm_util, 'power_off_instance'),
mock.patch.object(self._vmops, '_destroy_instance'),
) as (_get_vmdk_info, _get_vmdk_volume_disk,
_power_on_instance, _get_vm_ref, _get_vm_ref_from_name,
_call_method, _power_off, _destroy_instance):
mock.patch.object(vm_util, 'power_off_instance')
) as (_power_on_instance, _find_rescue, _get_vm_ref,
_call_method, _power_off):
self._vmops.unrescue(self._instance, power_on=power_on)
_get_vmdk_info.assert_called_once_with(self._session,
vm_ref, 'fake_uuid')
_get_vmdk_volume_disk.assert_called_once_with(
None, path=mock.sentinel.PATH)
if power_on:
_power_on_instance.assert_called_once_with(self._session,
self._instance,
vm_ref=vm_ref)
self._instance, vm_ref=vm_ref)
else:
self.assertFalse(_power_on_instance.called)
_get_vm_ref.assert_called_once_with(self._session,
self._instance)
_get_vm_ref_from_name.assert_called_once_with(self._session,
'fake_uuid-rescue')
_power_off.assert_called_once_with(self._session, self._instance,
vm_rescue_ref)
_destroy_instance.assert_called_once_with(self._instance,
instance_name='fake_uuid-rescue')
vm_ref)
_volumeops.detach_disk_from_vm.assert_called_once_with(
vm_ref, self._instance, mock.ANY, destroy_disk=True)
def _test_finish_migration(self, power_on=True, resize_instance=False):
with contextlib.nested(

View File

@ -239,6 +239,22 @@ def get_vm_create_spec(client_factory, instance, name, data_store_name,
return config_spec
def get_vm_boot_spec(client_factory, device):
"""Returns updated boot settings for the instance.
The boot order for the instance will be changed to have the
input device as the boot disk.
"""
config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
boot_disk = client_factory.create(
'ns0:VirtualMachineBootOptionsBootableDiskDevice')
boot_disk.deviceKey = device.key
boot_options = client_factory.create('ns0:VirtualMachineBootOptions')
boot_options.bootOrder = [boot_disk]
config_spec.bootOptions = boot_options
return config_spec
def get_vm_resize_spec(client_factory, vcpus, memory_mb, extra_specs):
"""Provides updates for a VM spec."""
resize_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
@ -1446,6 +1462,26 @@ def power_off_instance(session, instance, vm_ref=None):
LOG.debug("VM already powered off", instance=instance)
def find_rescue_device(hardware_devices, instance):
"""Returns the rescue device.
The method will raise an exception if the rescue device does not
exist. The resuce device has suffix '-rescue.vmdk'.
:param hardware_devices: the hardware devices for the instance
:param instance: nova.objects.instance.Instance object
:return: the rescue disk device object
"""
for device in hardware_devices.VirtualDevice:
if (device.__class__.__name__ == "VirtualDisk" and
device.backing.__class__.__name__ ==
'VirtualDiskFlatVer2BackingInfo' and
device.backing.fileName.endswith('-rescue.vmdk')):
return device
msg = _('Rescue device does not exist for instance %s') % instance.uuid
raise exception.NotFound(msg)
def get_ephemeral_name(id):
return 'ephemeral_%d.vmdk' % id

View File

@ -37,7 +37,6 @@ from nova.api.metadata import base as instance_metadata
from nova import compute
from nova.compute import power_state
from nova.compute import task_states
from nova.compute import vm_states
from nova.console import type as ctype
from nova import context as nova_context
from nova import exception
@ -163,8 +162,6 @@ class VMwareVMOps(object):
self._datastore_regex = datastore_regex
self._base_folder = self._get_base_folder()
self._tmp_folder = 'vmware_temp'
self._rescue_suffix = '-rescue'
self._migrate_suffix = '-orig'
self._datastore_dc_mapping = {}
self._datastore_browser_mapping = {}
self._imagecache = imagecache.ImageCacheManager(self._session,
@ -968,16 +965,6 @@ class VMwareVMOps(object):
# If there is a rescue VM then we need to destroy that one too.
LOG.debug("Destroying instance", instance=instance)
if instance.vm_state == vm_states.RESCUED:
LOG.debug("Rescue VM configured", instance=instance)
try:
self.unrescue(instance, power_on=False)
LOG.debug("Rescue VM destroyed", instance=instance)
except Exception:
rescue_name = instance.uuid + self._rescue_suffix
self._destroy_instance(instance,
destroy_disks=destroy_disks,
instance_name=rescue_name)
self._destroy_instance(instance, destroy_disks=destroy_disks)
LOG.debug("Instance destroyed", instance=instance)
@ -1027,50 +1014,72 @@ class VMwareVMOps(object):
reason = _("instance is not in a suspended state")
raise exception.InstanceResumeFailure(reason=reason)
def _get_rescue_device(self, instance, vm_ref):
hardware_devices = self._session._call_method(vim_util,
"get_dynamic_property", vm_ref,
"VirtualMachine", "config.hardware.device")
return vm_util.find_rescue_device(hardware_devices,
instance)
def rescue(self, context, instance, network_info, image_meta):
"""Rescue the specified instance.
- shutdown the instance VM.
- spawn a rescue VM (the vm name-label will be instance-N-rescue).
Attach the image that the instance was created from and boot from it.
"""
vm_ref = vm_util.get_vm_ref(self._session, instance)
vm_util.power_off_instance(self._session, instance, vm_ref)
instance_name = instance.uuid + self._rescue_suffix
self.spawn(context, instance, image_meta,
None, None, network_info,
instance_name=instance_name,
power_on=False)
# Get the root disk vmdk object
vmdk = vm_util.get_vmdk_info(self._session, vm_ref,
uuid=instance.uuid)
ds_ref = vmdk.device.backing.datastore
datastore = ds_util.get_datastore_by_ref(self._session, ds_ref)
dc_info = self.get_datacenter_ref_and_name(datastore.ref)
# Attach vmdk to the rescue VM
vmdk = vm_util.get_vmdk_info(self._session, vm_ref, instance.uuid)
rescue_vm_ref = vm_util.get_vm_ref_from_name(self._session,
instance_name)
self._volumeops.attach_disk_to_vm(rescue_vm_ref,
instance,
vmdk.adapter_type,
vmdk.disk_type,
vmdk.path)
vm_util.power_on_instance(self._session, instance,
vm_ref=rescue_vm_ref)
# Get the image details of the instance
image_info = images.VMwareImage.from_image(instance.image_ref,
image_meta)
vi = VirtualMachineInstanceConfigInfo(instance,
None,
image_info,
datastore,
dc_info,
self._imagecache)
vm_util.power_off_instance(self._session, instance, vm_ref)
# Get the rescue disk path
rescue_disk_path = datastore.build_path(instance.uuid,
"%s-rescue.%s" % (image_info.image_id, image_info.file_type))
# Copy the cached image to the be the rescue disk. This will be used
# as the rescue disk for the instance.
ds_util.disk_copy(self._session, dc_info.ref,
vi.cache_image_path, rescue_disk_path)
# Attach the rescue disk to the instance
self._volumeops.attach_disk_to_vm(vm_ref, instance, vmdk.adapter_type,
vmdk.disk_type, rescue_disk_path)
# Get the rescue device and configure the boot order to
# boot from this device
rescue_device = self._get_rescue_device(instance, vm_ref)
factory = self._session.vim.client.factory
boot_spec = vm_util.get_vm_boot_spec(factory, rescue_device)
# Update the VM with the new boot order and power on
vm_util.reconfigure_vm(self._session, vm_ref, boot_spec)
vm_util.power_on_instance(self._session, instance, vm_ref=vm_ref)
def unrescue(self, instance, power_on=True):
"""Unrescue the specified instance."""
# Get the original vmdk_path
vm_ref = vm_util.get_vm_ref(self._session, instance)
vmdk = vm_util.get_vmdk_info(self._session, vm_ref, instance.uuid)
instance_name = instance.uuid + self._rescue_suffix
# detach the original instance disk from the rescue disk
vm_rescue_ref = vm_util.get_vm_ref_from_name(self._session,
instance_name)
hardware_devices = self._session._call_method(vim_util,
"get_dynamic_property", vm_rescue_ref,
"VirtualMachine", "config.hardware.device")
device = vm_util.get_vmdk_volume_disk(hardware_devices, path=vmdk.path)
vm_util.power_off_instance(self._session, instance, vm_rescue_ref)
self._volumeops.detach_disk_from_vm(vm_rescue_ref, instance, device)
self._destroy_instance(instance, instance_name=instance_name)
# Get the rescue device and detach it from the instance.
try:
rescue_device = self._get_rescue_device(instance, vm_ref)
except exception.NotFound:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Unable to access the rescue disk'),
instance=instance)
vm_util.power_off_instance(self._session, instance, vm_ref)
self._volumeops.detach_disk_from_vm(vm_ref, instance, rescue_device,
destroy_disk=True)
if power_on:
vm_util.power_on_instance(self._session, instance, vm_ref=vm_ref)