diff --git a/nova/tests/unit/virt/vmwareapi/test_driver_api.py b/nova/tests/unit/virt/vmwareapi/test_driver_api.py index 91fa3a9557..b19e194c5d 100644 --- a/nova/tests/unit/virt/vmwareapi/test_driver_api.py +++ b/nova/tests/unit/virt/vmwareapi/test_driver_api.py @@ -680,6 +680,25 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): connection_info, self.instance, 'fake-name') mock_destroy.assert_called_once_with(self.instance, True) + @mock.patch.object(vmops.VMwareVMOps, 'power_off', + side_effect=vexc.ManagedObjectNotFoundException()) + @mock.patch.object(vmops.VMwareVMOps, 'destroy') + def test_destroy_with_attached_volumes_missing(self, + mock_destroy, + mock_power_off): + self._create_vm() + connection_info = {'data': 'fake-data', 'serial': 'volume-fake-id'} + bdm = [{'connection_info': connection_info, + 'disk_bus': 'fake-bus', + 'device_name': 'fake-name', + 'mount_device': '/dev/sdb'}] + bdi = {'block_device_mapping': bdm, 'root_device_name': '/dev/sda'} + self.assertNotEqual(vm_states.STOPPED, self.instance.vm_state) + self.conn.destroy(self.context, self.instance, self.network_info, + block_device_info=bdi) + mock_power_off.assert_called_once_with(self.instance) + mock_destroy.assert_called_once_with(self.instance, True) + @mock.patch.object(driver.VMwareVCDriver, 'detach_volume', side_effect=exception.StorageError(reason='oh man')) @mock.patch.object(vmops.VMwareVMOps, 'destroy') diff --git a/nova/virt/vmwareapi/driver.py b/nova/virt/vmwareapi/driver.py index fd7616e726..cb99efe1c3 100644 --- a/nova/virt/vmwareapi/driver.py +++ b/nova/virt/vmwareapi/driver.py @@ -27,6 +27,7 @@ from oslo_log import versionutils from oslo_serialization import jsonutils from oslo_utils import excutils from oslo_vmware import api +from oslo_vmware import exceptions as vexc from oslo_vmware import pbm from oslo_vmware import vim from oslo_vmware import vim_util @@ -573,6 +574,31 @@ class VMwareVCDriver(driver.ComputeDriver): """Reboot VM instance.""" self._vmops.reboot(instance, network_info, reboot_type) + def _detach_instance_volumes(self, instance, block_device_info): + # We need to detach attached volumes + block_device_mapping = driver.block_device_info_get_mapping( + block_device_info) + if block_device_mapping: + # Certain disk types, for example 'IDE' do not support hot + # plugging. Hence we need to power off the instance and update + # the instance state. + self._vmops.power_off(instance) + # TODO(garyk): update the volumeops to read the state form the + # VM instead of relying on a instance flag + instance.vm_state = vm_states.STOPPED + for disk in block_device_mapping: + connection_info = disk['connection_info'] + try: + self.detach_volume(connection_info, instance, + disk.get('device_name')) + except Exception as e: + with excutils.save_and_reraise_exception(): + LOG.error(_LE("Failed to detach %(device_name)s. " + "Exception: %(exc)s"), + {'device_name': disk.get('device_name'), + 'exc': e}, + instance=instance) + def destroy(self, context, instance, network_info, block_device_info=None, destroy_disks=True, migrate_data=None): """Destroy VM instance.""" @@ -590,29 +616,12 @@ class VMwareVCDriver(driver.ComputeDriver): # We need to detach attached volumes if block_device_info is not None: - block_device_mapping = driver.block_device_info_get_mapping( - block_device_info) - if block_device_mapping: - # Certain disk types, for example 'IDE' do not support hot - # plugging. Hence we need to power off the instance and update - # the instance state. - self._vmops.power_off(instance) - # TODO(garyk): update the volumeops to read the state form the - # VM instead of relying on a instance flag - instance.vm_state = vm_states.STOPPED - for disk in block_device_mapping: - connection_info = disk['connection_info'] - try: - self.detach_volume(connection_info, instance, - disk.get('device_name')) - except Exception as e: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Failed to detach %(device_name)s. " - "Exception: %(exc)s"), - {'device_name': disk.get('device_name'), - 'exc': e}, - instance=instance) - + try: + self._detach_instance_volumes(instance, block_device_info) + except vexc.ManagedObjectNotFoundException: + LOG.warning(_LW('Instance does not exists. Proceeding to ' + 'delete instance properties on datastore'), + instance=instance) self._vmops.destroy(instance, destroy_disks) def pause(self, instance):