HyperV: Perform proper cleanup after failed instance spawns
This change ensures that vif ports as well as volume connections are properly removed after an instance fails to spawn. In order to avoid having similar issues in the future, the 'block_device_info' and 'network_info' arguments become mandatory for the VMOps.destroy method. Change-Id: I8d255658c4e45df855379738b120f0543b11027f Closes-Bug: #1714285
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
import os
|
||||
|
||||
import ddt
|
||||
from eventlet import timeout as etimeout
|
||||
import mock
|
||||
from os_win import constants as os_win_const
|
||||
@@ -40,6 +41,7 @@ from nova.virt.hyperv import volumeops
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
"""Unit tests for the Hyper-V VMOps class."""
|
||||
|
||||
@@ -413,17 +415,19 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self.assertRaises(exception.InstanceExists, self._vmops.spawn,
|
||||
self.context, mock_instance, mock_image_meta,
|
||||
[mock.sentinel.FILE], mock.sentinel.PASSWORD,
|
||||
mock.sentinel.INFO, block_device_info)
|
||||
mock.sentinel.network_info, block_device_info)
|
||||
elif fail is os_win_exc.HyperVException:
|
||||
self.assertRaises(os_win_exc.HyperVException, self._vmops.spawn,
|
||||
self.context, mock_instance, mock_image_meta,
|
||||
[mock.sentinel.FILE], mock.sentinel.PASSWORD,
|
||||
mock.sentinel.INFO, block_device_info)
|
||||
mock_destroy.assert_called_once_with(mock_instance)
|
||||
mock.sentinel.network_info, block_device_info)
|
||||
mock_destroy.assert_called_once_with(mock_instance,
|
||||
mock.sentinel.network_info,
|
||||
block_device_info)
|
||||
else:
|
||||
self._vmops.spawn(self.context, mock_instance, mock_image_meta,
|
||||
[mock.sentinel.FILE], mock.sentinel.PASSWORD,
|
||||
mock.sentinel.INFO, block_device_info)
|
||||
mock.sentinel.network_info, block_device_info)
|
||||
self._vmops._vmutils.vm_exists.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
mock_delete_disk_files.assert_called_once_with(
|
||||
@@ -438,11 +442,12 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
fake_vm_gen)
|
||||
mock_create_ephemerals.assert_called_once_with(
|
||||
mock_instance, block_device_info['ephemerals'])
|
||||
mock_get_neutron_events.assert_called_once_with(mock.sentinel.INFO)
|
||||
mock_get_neutron_events.assert_called_once_with(
|
||||
mock.sentinel.network_info)
|
||||
mock_get_image_vm_gen.assert_called_once_with(mock_instance.uuid,
|
||||
mock_image_meta)
|
||||
mock_create_instance.assert_called_once_with(
|
||||
mock_instance, mock.sentinel.INFO, root_device_info,
|
||||
mock_instance, mock.sentinel.network_info, root_device_info,
|
||||
block_device_info, fake_vm_gen, mock_image_meta)
|
||||
mock_save_device_metadata.assert_called_once_with(
|
||||
self.context, mock_instance, block_device_info)
|
||||
@@ -451,13 +456,13 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_create_config_drive.assert_called_once_with(
|
||||
self.context, mock_instance, [mock.sentinel.FILE],
|
||||
mock.sentinel.PASSWORD,
|
||||
mock.sentinel.INFO)
|
||||
mock.sentinel.network_info)
|
||||
mock_attach_config_drive.assert_called_once_with(
|
||||
mock_instance, fake_config_drive_path, fake_vm_gen)
|
||||
mock_set_boot_order.assert_called_once_with(
|
||||
mock_instance.name, fake_vm_gen, block_device_info)
|
||||
mock_power_on.assert_called_once_with(
|
||||
mock_instance, network_info=mock.sentinel.INFO)
|
||||
mock_instance, network_info=mock.sentinel.network_info)
|
||||
|
||||
def test_spawn(self):
|
||||
self._test_spawn(exists=False, configdrive_required=True, fail=None)
|
||||
@@ -1063,38 +1068,39 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self._vmops._pathutils.get_instance_dir.assert_called_once_with(
|
||||
mock_instance.name, create_dir=False, remove_dir=True)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch('nova.virt.hyperv.volumeops.VolumeOps.disconnect_volumes')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps._delete_disk_files')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.power_off')
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.unplug_vifs')
|
||||
def test_destroy(self, mock_unplug_vifs, mock_power_off,
|
||||
def test_destroy(self, vm_exists, mock_unplug_vifs, mock_power_off,
|
||||
mock_delete_disk_files, mock_disconnect_volumes):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
self._vmops._vmutils.vm_exists.return_value = True
|
||||
self._vmops._vmutils.vm_exists.return_value = vm_exists
|
||||
|
||||
self._vmops.destroy(instance=mock_instance,
|
||||
block_device_info=mock.sentinel.FAKE_BD_INFO,
|
||||
network_info=mock.sentinel.fake_network_info)
|
||||
|
||||
if vm_exists:
|
||||
self._vmops._vmutils.stop_vm_jobs.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
mock_power_off.assert_called_once_with(mock_instance)
|
||||
self._vmops._vmutils.destroy_vm.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
else:
|
||||
self.assertFalse(mock_power_off.called)
|
||||
self.assertFalse(self._vmops._vmutils.destroy_vm.called)
|
||||
|
||||
self._vmops._vmutils.vm_exists.assert_called_with(
|
||||
mock_instance.name)
|
||||
mock_power_off.assert_called_once_with(mock_instance)
|
||||
mock_unplug_vifs.assert_called_once_with(
|
||||
mock_instance, mock.sentinel.fake_network_info)
|
||||
self._vmops._vmutils.destroy_vm.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
mock_disconnect_volumes.assert_called_once_with(
|
||||
mock.sentinel.FAKE_BD_INFO)
|
||||
mock_delete_disk_files.assert_called_once_with(
|
||||
mock_instance.name)
|
||||
|
||||
def test_destroy_inexistent_instance(self):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
self._vmops._vmutils.vm_exists.return_value = False
|
||||
|
||||
self._vmops.destroy(instance=mock_instance)
|
||||
self.assertFalse(self._vmops._vmutils.destroy_vm.called)
|
||||
|
||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.power_off')
|
||||
def test_destroy_exception(self, mock_power_off):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
@@ -1103,7 +1109,9 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self._vmops._vmutils.vm_exists.return_value = True
|
||||
|
||||
self.assertRaises(os_win_exc.HyperVException,
|
||||
self._vmops.destroy, mock_instance)
|
||||
self._vmops.destroy, mock_instance,
|
||||
mock.sentinel.network_info,
|
||||
mock.sentinel.block_device_info)
|
||||
|
||||
def test_reboot_hard(self):
|
||||
self._test_reboot(vmops.REBOOT_TYPE_HARD,
|
||||
|
||||
@@ -305,7 +305,7 @@ class VMOps(object):
|
||||
self.power_on(instance, network_info=network_info)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.destroy(instance)
|
||||
self.destroy(instance, network_info, block_device_info)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def wait_vif_plug_events(self, instance, network_info):
|
||||
@@ -705,7 +705,7 @@ class VMOps(object):
|
||||
create_dir=False,
|
||||
remove_dir=True)
|
||||
|
||||
def destroy(self, instance, network_info=None, block_device_info=None,
|
||||
def destroy(self, instance, network_info, block_device_info,
|
||||
destroy_disks=True):
|
||||
instance_name = instance.name
|
||||
LOG.info("Got request to destroy instance", instance=instance)
|
||||
@@ -715,12 +715,13 @@ class VMOps(object):
|
||||
# Stop the VM first.
|
||||
self._vmutils.stop_vm_jobs(instance_name)
|
||||
self.power_off(instance)
|
||||
self.unplug_vifs(instance, network_info)
|
||||
self._vmutils.destroy_vm(instance_name)
|
||||
self._volumeops.disconnect_volumes(block_device_info)
|
||||
else:
|
||||
LOG.debug("Instance not found", instance=instance)
|
||||
|
||||
self.unplug_vifs(instance, network_info)
|
||||
self._volumeops.disconnect_volumes(block_device_info)
|
||||
|
||||
if destroy_disks:
|
||||
self._delete_disk_files(instance_name)
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user