Merge "HyperV: Perform proper cleanup after failed instance spawns"
This commit is contained in:
commit
b79492f702
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import ddt
|
||||||
from eventlet import timeout as etimeout
|
from eventlet import timeout as etimeout
|
||||||
import mock
|
import mock
|
||||||
from os_win import constants as os_win_const
|
from os_win import constants as os_win_const
|
||||||
|
@ -40,6 +41,7 @@ from nova.virt.hyperv import volumeops
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||||
"""Unit tests for the Hyper-V VMOps class."""
|
"""Unit tests for the Hyper-V VMOps class."""
|
||||||
|
|
||||||
|
@ -409,17 +411,19 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||||
self.assertRaises(exception.InstanceExists, self._vmops.spawn,
|
self.assertRaises(exception.InstanceExists, self._vmops.spawn,
|
||||||
self.context, mock_instance, mock_image_meta,
|
self.context, mock_instance, mock_image_meta,
|
||||||
[mock.sentinel.FILE], mock.sentinel.PASSWORD,
|
[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:
|
elif fail is os_win_exc.HyperVException:
|
||||||
self.assertRaises(os_win_exc.HyperVException, self._vmops.spawn,
|
self.assertRaises(os_win_exc.HyperVException, self._vmops.spawn,
|
||||||
self.context, mock_instance, mock_image_meta,
|
self.context, mock_instance, mock_image_meta,
|
||||||
[mock.sentinel.FILE], mock.sentinel.PASSWORD,
|
[mock.sentinel.FILE], mock.sentinel.PASSWORD,
|
||||||
mock.sentinel.INFO, block_device_info)
|
mock.sentinel.network_info, block_device_info)
|
||||||
mock_destroy.assert_called_once_with(mock_instance)
|
mock_destroy.assert_called_once_with(mock_instance,
|
||||||
|
mock.sentinel.network_info,
|
||||||
|
block_device_info)
|
||||||
else:
|
else:
|
||||||
self._vmops.spawn(self.context, mock_instance, mock_image_meta,
|
self._vmops.spawn(self.context, mock_instance, mock_image_meta,
|
||||||
[mock.sentinel.FILE], mock.sentinel.PASSWORD,
|
[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(
|
self._vmops._vmutils.vm_exists.assert_called_once_with(
|
||||||
mock_instance.name)
|
mock_instance.name)
|
||||||
mock_delete_disk_files.assert_called_once_with(
|
mock_delete_disk_files.assert_called_once_with(
|
||||||
|
@ -434,11 +438,12 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||||
fake_vm_gen)
|
fake_vm_gen)
|
||||||
mock_create_ephemerals.assert_called_once_with(
|
mock_create_ephemerals.assert_called_once_with(
|
||||||
mock_instance, block_device_info['ephemerals'])
|
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_get_image_vm_gen.assert_called_once_with(mock_instance.uuid,
|
||||||
mock_image_meta)
|
mock_image_meta)
|
||||||
mock_create_instance.assert_called_once_with(
|
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)
|
block_device_info, fake_vm_gen, mock_image_meta)
|
||||||
mock_save_device_metadata.assert_called_once_with(
|
mock_save_device_metadata.assert_called_once_with(
|
||||||
self.context, mock_instance, block_device_info)
|
self.context, mock_instance, block_device_info)
|
||||||
|
@ -447,13 +452,13 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||||
mock_create_config_drive.assert_called_once_with(
|
mock_create_config_drive.assert_called_once_with(
|
||||||
self.context, mock_instance, [mock.sentinel.FILE],
|
self.context, mock_instance, [mock.sentinel.FILE],
|
||||||
mock.sentinel.PASSWORD,
|
mock.sentinel.PASSWORD,
|
||||||
mock.sentinel.INFO)
|
mock.sentinel.network_info)
|
||||||
mock_attach_config_drive.assert_called_once_with(
|
mock_attach_config_drive.assert_called_once_with(
|
||||||
mock_instance, fake_config_drive_path, fake_vm_gen)
|
mock_instance, fake_config_drive_path, fake_vm_gen)
|
||||||
mock_set_boot_order.assert_called_once_with(
|
mock_set_boot_order.assert_called_once_with(
|
||||||
mock_instance.name, fake_vm_gen, block_device_info)
|
mock_instance.name, fake_vm_gen, block_device_info)
|
||||||
mock_power_on.assert_called_once_with(
|
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):
|
def test_spawn(self):
|
||||||
self._test_spawn(exists=False, configdrive_required=True, fail=None)
|
self._test_spawn(exists=False, configdrive_required=True, fail=None)
|
||||||
|
@ -1059,38 +1064,39 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||||
self._vmops._pathutils.get_instance_dir.assert_called_once_with(
|
self._vmops._pathutils.get_instance_dir.assert_called_once_with(
|
||||||
mock_instance.name, create_dir=False, remove_dir=True)
|
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.volumeops.VolumeOps.disconnect_volumes')
|
||||||
@mock.patch('nova.virt.hyperv.vmops.VMOps._delete_disk_files')
|
@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.power_off')
|
||||||
@mock.patch('nova.virt.hyperv.vmops.VMOps.unplug_vifs')
|
@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_delete_disk_files, mock_disconnect_volumes):
|
||||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
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,
|
self._vmops.destroy(instance=mock_instance,
|
||||||
block_device_info=mock.sentinel.FAKE_BD_INFO,
|
block_device_info=mock.sentinel.FAKE_BD_INFO,
|
||||||
network_info=mock.sentinel.fake_network_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(
|
self._vmops._vmutils.vm_exists.assert_called_with(
|
||||||
mock_instance.name)
|
mock_instance.name)
|
||||||
mock_power_off.assert_called_once_with(mock_instance)
|
|
||||||
mock_unplug_vifs.assert_called_once_with(
|
mock_unplug_vifs.assert_called_once_with(
|
||||||
mock_instance, mock.sentinel.fake_network_info)
|
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_disconnect_volumes.assert_called_once_with(
|
||||||
mock.sentinel.FAKE_BD_INFO)
|
mock.sentinel.FAKE_BD_INFO)
|
||||||
mock_delete_disk_files.assert_called_once_with(
|
mock_delete_disk_files.assert_called_once_with(
|
||||||
mock_instance.name)
|
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')
|
@mock.patch('nova.virt.hyperv.vmops.VMOps.power_off')
|
||||||
def test_destroy_exception(self, mock_power_off):
|
def test_destroy_exception(self, mock_power_off):
|
||||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||||
|
@ -1099,7 +1105,9 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||||
self._vmops._vmutils.vm_exists.return_value = True
|
self._vmops._vmutils.vm_exists.return_value = True
|
||||||
|
|
||||||
self.assertRaises(os_win_exc.HyperVException,
|
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):
|
def test_reboot_hard(self):
|
||||||
self._test_reboot(vmops.REBOOT_TYPE_HARD,
|
self._test_reboot(vmops.REBOOT_TYPE_HARD,
|
||||||
|
|
|
@ -301,7 +301,7 @@ class VMOps(object):
|
||||||
self.power_on(instance, network_info=network_info)
|
self.power_on(instance, network_info=network_info)
|
||||||
except Exception:
|
except Exception:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
self.destroy(instance)
|
self.destroy(instance, network_info, block_device_info)
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def wait_vif_plug_events(self, instance, network_info):
|
def wait_vif_plug_events(self, instance, network_info):
|
||||||
|
@ -701,7 +701,7 @@ class VMOps(object):
|
||||||
create_dir=False,
|
create_dir=False,
|
||||||
remove_dir=True)
|
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):
|
destroy_disks=True):
|
||||||
instance_name = instance.name
|
instance_name = instance.name
|
||||||
LOG.info("Got request to destroy instance", instance=instance)
|
LOG.info("Got request to destroy instance", instance=instance)
|
||||||
|
@ -711,12 +711,13 @@ class VMOps(object):
|
||||||
# Stop the VM first.
|
# Stop the VM first.
|
||||||
self._vmutils.stop_vm_jobs(instance_name)
|
self._vmutils.stop_vm_jobs(instance_name)
|
||||||
self.power_off(instance)
|
self.power_off(instance)
|
||||||
self.unplug_vifs(instance, network_info)
|
|
||||||
self._vmutils.destroy_vm(instance_name)
|
self._vmutils.destroy_vm(instance_name)
|
||||||
self._volumeops.disconnect_volumes(block_device_info)
|
|
||||||
else:
|
else:
|
||||||
LOG.debug("Instance not found", instance=instance)
|
LOG.debug("Instance not found", instance=instance)
|
||||||
|
|
||||||
|
self.unplug_vifs(instance, network_info)
|
||||||
|
self._volumeops.disconnect_volumes(block_device_info)
|
||||||
|
|
||||||
if destroy_disks:
|
if destroy_disks:
|
||||||
self._delete_disk_files(instance_name)
|
self._delete_disk_files(instance_name)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
Loading…
Reference in New Issue