diff --git a/nova/tests/virt/vmwareapi/test_vmwareapi.py b/nova/tests/virt/vmwareapi/test_vmwareapi.py index c2de9ec2153a..25e9bcdfb870 100755 --- a/nova/tests/virt/vmwareapi/test_vmwareapi.py +++ b/nova/tests/virt/vmwareapi/test_vmwareapi.py @@ -34,6 +34,7 @@ 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 db from nova import exception @@ -793,6 +794,31 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): 'node': self.instance_node}) self._check_vm_info(info, power_state.RUNNING) + def destroy_rescued(self, fake_method): + self._rescue() + with ( + mock.patch.object(self.conn._volumeops, "detach_disk_from_vm", + fake_method) + ): + self.instance['vm_state'] = vm_states.RESCUED + self.conn.destroy(self.instance, self.network_info) + inst_path = '[%s] %s/%s.vmdk' % (self.ds, self.uuid, self.uuid) + self.assertFalse(vmwareapi_fake.get_file(inst_path)) + rescue_file_path = '[%s] %s-rescue/%s-rescue.vmdk' % (self.ds, + self.uuid, + self.uuid) + self.assertFalse(vmwareapi_fake.get_file(rescue_file_path)) + + 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.conn.get_info({'uuid': self.uuid, diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index 397fa9137795..c900b587d1c3 100755 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -35,6 +35,7 @@ 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 import context as nova_context from nova import exception from nova.openstack.common import excutils @@ -904,14 +905,9 @@ class VMwareVMOps(object): except Exception as exc: LOG.exception(exc, instance=instance) - def destroy(self, instance, network_info, destroy_disks=True, - instance_name=None): - """ - Destroy a VM instance. Steps followed are: - 1. Power off the VM, if it is in poweredOn state. - 2. Un-register a VM. - 3. Delete the contents of the folder holding the VM related data. - """ + def _destroy_instance(self, instance, network_info, destroy_disks=True, + instance_name=None): + # Destroy a VM instance # Get the instance name. In some cases this may differ from the 'uuid', # for example when the spawn of a rescue instance takes place. if not instance_name: @@ -948,8 +944,9 @@ class VMwareVMOps(object): "UnregisterVM", vm_ref) LOG.debug(_("Unregistered the VM"), instance=instance) except Exception as excep: - LOG.warn(_("In vmwareapi:vmops:destroy, got this exception" - " while un-registering the VM: %s") % str(excep)) + LOG.warn(_("In vmwareapi:vmops:_destroy_instance, got this " + "exception while un-registering the VM: %s"), + excep) # Delete the folder holding the VM related content on # the datastore. if destroy_disks: @@ -977,13 +974,37 @@ class VMwareVMOps(object): {'datastore_name': datastore_name}, instance=instance) except Exception as excep: - LOG.warn(_("In vmwareapi:vmops:destroy, " - "got this exception while deleting" - " the VM contents from the disk: %s") - % str(excep)) + LOG.warn(_("In vmwareapi:vmops:_destroy_instance, " + "got this exception while deleting " + "the VM contents from the disk: %s"), + excep) except Exception as exc: LOG.exception(exc, instance=instance) + def destroy(self, instance, network_info, destroy_disks=True): + """Destroy a VM instance. + + Steps followed for each VM are: + 1. Power off, if it is in poweredOn state. + 2. Un-register. + 3. Delete the contents of the folder holding the VM related data. + """ + # 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, network_info, + destroy_disks=destroy_disks, + instance_name=rescue_name) + self._destroy_instance(instance, network_info, + destroy_disks=destroy_disks) + LOG.debug(_("Instance destroyed"), instance=instance) + def pause(self, instance): msg = _("pause not supported for vmwareapi") raise NotImplementedError(msg) @@ -1065,7 +1086,7 @@ class VMwareVMOps(object): controller_key=controller_key, unit_number=unit_number) - def unrescue(self, instance): + 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) @@ -1087,8 +1108,9 @@ class VMwareVMOps(object): device = vm_util.get_vmdk_volume_disk(hardware_devices, path=vmdk_path) self._power_off_vm_ref(vm_rescue_ref) self._volumeops.detach_disk_from_vm(vm_rescue_ref, r_instance, device) - self.destroy(r_instance, None, instance_name=instance_name) - self._power_on(instance) + self._destroy_instance(r_instance, None, instance_name=instance_name) + if power_on: + self._power_on(instance) def _power_off_vm_ref(self, vm_ref): """Power off the specifed vm.