diff --git a/compute_hyperv/nova/driver.py b/compute_hyperv/nova/driver.py index 5ee14795..0c6ce5a0 100644 --- a/compute_hyperv/nova/driver.py +++ b/compute_hyperv/nova/driver.py @@ -97,7 +97,7 @@ exception_conversion_map = { class HyperVDriver(driver.ComputeDriver): capabilities = { "has_imagecache": True, - "supports_recreate": False, + "supports_recreate": True, "supports_migrate_to_same_host": False, "supports_attach_interface": True, "supports_device_tagging": True, diff --git a/compute_hyperv/nova/vmops.py b/compute_hyperv/nova/vmops.py index ac95765b..9b14f34c 100644 --- a/compute_hyperv/nova/vmops.py +++ b/compute_hyperv/nova/vmops.py @@ -155,7 +155,12 @@ class VMOps(object): root_iso_path_cached = self._imagecache.get_cached_image(context, instance) root_iso_path = self._pathutils.get_root_vhd_path(instance.name, 'iso') - self._pathutils.copyfile(root_iso_path_cached, root_iso_path) + + if not os.path.exists(root_iso_path): + self._pathutils.copyfile(root_iso_path_cached, root_iso_path) + else: + LOG.info("Root iso '%s' already exists. Reusing it.", + root_iso_path) return root_iso_path @@ -170,6 +175,12 @@ class VMOps(object): root_vhd_path = self._pathutils.get_root_vhd_path(instance.name, format_ext, is_rescue_vhd) + + if os.path.exists(root_vhd_path): + LOG.info("Root vhd '%s' already exists. Reusing it.", + root_vhd_path) + return root_vhd_path + root_vhd_size = instance.flavor.root_gb * units.Gi try: @@ -234,12 +245,16 @@ class VMOps(object): self.create_ephemeral_disk(instance.name, eph) def create_ephemeral_disk(self, instance_name, eph_info): - self._vhdutils.create_dynamic_vhd(eph_info['path'], - eph_info['size'] * units.Gi) + if not os.path.exists(eph_info['path']): + self._vhdutils.create_dynamic_vhd(eph_info['path'], + eph_info['size'] * units.Gi) + else: + LOG.info("Ephemeral '%s' disk already exists. Reusing it.", + eph_info['path']) def get_attached_ephemeral_disks(self, instance_name): vm_image_disks = self._vmutils.get_vm_storage_paths( - instance_name)[0] + instance_name)[0] return [image_path for image_path in vm_image_disks if os.path.basename(image_path).lower().startswith('eph')] @@ -291,11 +306,14 @@ class VMOps(object): if self._vmutils.vm_exists(instance_name): raise exception.InstanceExists(name=instance_name) - # Make sure we're starting with a clean slate. - self._delete_disk_files(instance) - vm_gen = self.get_image_vm_generation(instance.uuid, image_meta) + instance_dir = self._pathutils.get_instance_dir(instance.name, + create_dir=False) + if instance_dir: + LOG.info("Instance directory already exists." + "Reusing existing files.") + self._block_dev_man.validate_and_update_bdi( instance, image_meta, vm_gen, block_device_info) root_device = block_device_info['root_disk'] diff --git a/compute_hyperv/tests/unit/test_vmops.py b/compute_hyperv/tests/unit/test_vmops.py index d28ec433..3ab978d9 100644 --- a/compute_hyperv/tests/unit/test_vmops.py +++ b/compute_hyperv/tests/unit/test_vmops.py @@ -167,13 +167,16 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): mock_create_root_iso.assert_called_once_with(self.context, mock_instance) + @mock.patch('os.path.exists') @mock.patch.object(vmops.imagecache.ImageCache, 'get_cached_image') - def test_create_root_iso(self, mock_get_cached_image): + def _test_create_root_iso(self, mock_get_cached_image, + mock_os_path_exists, iso_already_exists=False): mock_instance = fake_instance.fake_instance_obj(self.context) mock_get_root_vhd_path = self._vmops._pathutils.get_root_vhd_path mock_get_root_vhd_path.return_value = mock.sentinel.ROOT_ISO_PATH mock_get_cached_image.return_value = mock.sentinel.CACHED_ISO_PATH + mock_os_path_exists.return_value = iso_already_exists self._vmops._create_root_iso(self.context, mock_instance) @@ -181,8 +184,17 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): mock_instance) mock_get_root_vhd_path.assert_called_once_with(mock_instance.name, 'iso') - self._vmops._pathutils.copyfile.assert_called_once_with( - mock.sentinel.CACHED_ISO_PATH, mock.sentinel.ROOT_ISO_PATH) + if not iso_already_exists: + self._vmops._pathutils.copyfile.assert_called_once_with( + mock.sentinel.CACHED_ISO_PATH, mock.sentinel.ROOT_ISO_PATH) + else: + self._vmops._pathutils.copyfile.assert_not_called() + + def test_create_root_iso(self): + self._test_create_root_iso() + + def test_create_root_iso_already_existing_image(self): + self._test_create_root_iso(iso_already_exists=True) def _prepare_create_root_device_mocks(self, use_cow_images, vhd_format, vhd_size): @@ -199,15 +211,17 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): return mock_instance + @mock.patch('os.path.exists') @mock.patch('compute_hyperv.nova.imagecache.ImageCache.get_cached_image') def _test_create_root_vhd_exception(self, mock_get_cached_image, - vhd_format): + mock_os_path_exists, vhd_format): mock_instance = self._prepare_create_root_device_mocks( use_cow_images=False, vhd_format=vhd_format, vhd_size=(self.FAKE_SIZE + 1)) fake_vhd_path = self.FAKE_ROOT_PATH % vhd_format mock_get_cached_image.return_value = fake_vhd_path fake_root_path = self._vmops._pathutils.get_root_vhd_path.return_value + mock_os_path_exists.return_value = False self.assertRaises(exception.FlavorDiskSmallerThanImage, self._vmops._create_root_vhd, self.context, @@ -219,13 +233,17 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): self._vmops._pathutils.remove.assert_called_once_with( fake_root_path) + @mock.patch('os.path.exists') @mock.patch('compute_hyperv.nova.imagecache.ImageCache.get_cached_image') - def _test_create_root_vhd_qcow(self, mock_get_cached_image, vhd_format): + def _test_create_root_vhd_qcow(self, mock_get_cached_image, + mock_os_path_exists, vhd_format, + vhd_already_exists=False): mock_instance = self._prepare_create_root_device_mocks( use_cow_images=True, vhd_format=vhd_format, vhd_size=(self.FAKE_SIZE - 1)) fake_vhd_path = self.FAKE_ROOT_PATH % vhd_format mock_get_cached_image.return_value = fake_vhd_path + mock_os_path_exists.return_value = vhd_already_exists fake_root_path = self._vmops._pathutils.get_root_vhd_path.return_value root_vhd_internal_size = mock_instance.flavor.root_gb * units.Gi @@ -237,23 +255,32 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): self.assertEqual(fake_root_path, response) self._vmops._pathutils.get_root_vhd_path.assert_called_with( mock_instance.name, vhd_format, False) + differencing_vhd = self._vmops._vhdutils.create_differencing_vhd - differencing_vhd.assert_called_with(fake_root_path, fake_vhd_path) - self._vmops._vhdutils.get_vhd_info.assert_called_once_with( - fake_vhd_path) - if vhd_format is constants.DISK_FORMAT_VHD: - self.assertFalse(get_size.called) - self.assertFalse(self._vmops._vhdutils.resize_vhd.called) + if not vhd_already_exists: + differencing_vhd.assert_called_with(fake_root_path, fake_vhd_path) + self._vmops._vhdutils.get_vhd_info.assert_called_once_with( + fake_vhd_path) + + if vhd_format is constants.DISK_FORMAT_VHD: + self.assertFalse(get_size.called) + self.assertFalse(self._vmops._vhdutils.resize_vhd.called) + else: + get_size.assert_called_once_with(fake_vhd_path, + root_vhd_internal_size) + self._vmops._vhdutils.resize_vhd.assert_called_once_with( + fake_root_path, root_vhd_internal_size, + is_file_max_size=False) else: - get_size.assert_called_once_with(fake_vhd_path, - root_vhd_internal_size) - self._vmops._vhdutils.resize_vhd.assert_called_once_with( - fake_root_path, root_vhd_internal_size, is_file_max_size=False) + differencing_vhd.assert_not_called() + self._vmops._vhdutils.resize_vhd.assert_not_called() + @mock.patch('os.path.exists') @mock.patch('compute_hyperv.nova.imagecache.ImageCache.get_cached_image') - def _test_create_root_vhd(self, mock_get_cached_image, vhd_format, - is_rescue_vhd=False): + def _test_create_root_vhd(self, mock_get_cached_image, mock_os_path_exists, + vhd_format, is_rescue_vhd=False, + vhd_already_exists=False): mock_instance = self._prepare_create_root_device_mocks( use_cow_images=False, vhd_format=vhd_format, vhd_size=(self.FAKE_SIZE - 1)) @@ -265,6 +292,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): fake_root_path = self._vmops._pathutils.get_root_vhd_path.return_value root_vhd_internal_size = mock_instance.flavor.root_gb * units.Gi get_size = self._vmops._vhdutils.get_internal_vhd_size_by_file_size + mock_os_path_exists.return_value = vhd_already_exists response = self._vmops._create_root_vhd( context=self.context, @@ -278,15 +306,20 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): self._vmops._pathutils.get_root_vhd_path.assert_called_with( mock_instance.name, vhd_format, is_rescue_vhd) - self._vmops._pathutils.copyfile.assert_called_once_with( - fake_vhd_path, fake_root_path) - get_size.assert_called_once_with(fake_vhd_path, root_vhd_internal_size) - if is_rescue_vhd: - self.assertFalse(self._vmops._vhdutils.resize_vhd.called) + if not vhd_already_exists: + self._vmops._pathutils.copyfile.assert_called_once_with( + fake_vhd_path, fake_root_path) + get_size.assert_called_once_with(fake_vhd_path, + root_vhd_internal_size) + + if is_rescue_vhd: + self.assertFalse(self._vmops._vhdutils.resize_vhd.called) + else: + self._vmops._vhdutils.resize_vhd.assert_called_once_with( + fake_root_path, root_vhd_internal_size, + is_file_max_size=False) else: - self._vmops._vhdutils.resize_vhd.assert_called_once_with( - fake_root_path, root_vhd_internal_size, - is_file_max_size=False) + self._vmops._pathutils.copyfile.assert_not_called() def test_create_root_vhd(self): self._test_create_root_vhd(vhd_format=constants.DISK_FORMAT_VHD) @@ -294,12 +327,28 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): def test_create_root_vhdx(self): self._test_create_root_vhd(vhd_format=constants.DISK_FORMAT_VHDX) + def test_create_root_vhd_existing_disk(self): + self._test_create_root_vhd(vhd_format=constants.DISK_FORMAT_VHD, + vhd_already_exists=True) + + def test_create_root_vhdx_existing_disk(self): + self._test_create_root_vhd(vhd_format=constants.DISK_FORMAT_VHDX, + vhd_already_exists=True) + def test_create_root_vhd_use_cow_images_true(self): self._test_create_root_vhd_qcow(vhd_format=constants.DISK_FORMAT_VHD) def test_create_root_vhdx_use_cow_images_true(self): self._test_create_root_vhd_qcow(vhd_format=constants.DISK_FORMAT_VHDX) + def test_create_root_vhd_use_already_existing_cow_images(self): + self._test_create_root_vhd_qcow(vhd_format=constants.DISK_FORMAT_VHD, + vhd_already_exists=True) + + def test_create_root_vhdx_use_already_existing_cow_images(self): + self._test_create_root_vhd_qcow(vhd_format=constants.DISK_FORMAT_VHDX, + vhd_already_exists=True) + def test_create_rescue_vhd(self): self._test_create_root_vhd(vhd_format=constants.DISK_FORMAT_VHD, is_rescue_vhd=True) @@ -488,8 +537,6 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): 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( - mock_instance) mock_validate_and_update_bdi = ( self._vmops._block_dev_man.validate_and_update_bdi) mock_validate_and_update_bdi.assert_called_once_with(