From 9026db874634a78b8cdd3abb45a75a37f6ededfe Mon Sep 17 00:00:00 2001 From: Lucian Petrut Date: Thu, 10 Apr 2014 04:16:07 +0300 Subject: [PATCH] Fixes cinder volume from snapshot on Windows When creating a volume from a snapshot on Windows, a shadow copy volume is exported as an iSCSI disk. The issue is that this export is readonly and cannot be mounted to instances. As this export cannot be modified, to make the new volume usable, the according image must be moved to a new path and imported . Closes-Bug: #1306032 Change-Id: I3a936d30fdd7875059dc56c2681f453757c2605f --- cinder/tests/windows/test_windows.py | 2 +- cinder/volume/drivers/windows/windows.py | 12 +---- .../volume/drivers/windows/windows_utils.py | 52 +++++++++++++++++-- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/cinder/tests/windows/test_windows.py b/cinder/tests/windows/test_windows.py index bd73e3ab1..dfae30c34 100644 --- a/cinder/tests/windows/test_windows.py +++ b/cinder/tests/windows/test_windows.py @@ -146,7 +146,7 @@ class TestWindowsDriver(test.TestCase): self.mox.StubOutWithMock(windows_utils.WindowsUtils, 'create_volume_from_snapshot') windows_utils.WindowsUtils.\ - create_volume_from_snapshot(volume['name'], snapshot['name']) + create_volume_from_snapshot(volume, snapshot['name']) self.mox.ReplayAll() diff --git a/cinder/volume/drivers/windows/windows.py b/cinder/volume/drivers/windows/windows.py index f88a9bc78..e4d8e2048 100644 --- a/cinder/volume/drivers/windows/windows.py +++ b/cinder/volume/drivers/windows/windows.py @@ -100,14 +100,7 @@ class WindowsDriver(driver.ISCSIDriver): self.utils.create_volume(vhd_path, vol_name, vol_size) def local_path(self, volume, format=None): - base_vhd_folder = self.configuration.windows_iscsi_lun_path - if not os.path.exists(base_vhd_folder): - LOG.debug('Creating folder %s ', base_vhd_folder) - os.makedirs(base_vhd_folder) - if not format: - format = self.utils.get_supported_format() - return os.path.join(base_vhd_folder, str(volume['name']) + "." + - format) + return self.utils.local_path(volume, format) def delete_volume(self, volume): """Driver entry point for destroying existing volumes.""" @@ -127,8 +120,7 @@ class WindowsDriver(driver.ISCSIDriver): def create_volume_from_snapshot(self, volume, snapshot): """Driver entry point for exporting snapshots as volumes.""" snapshot_name = snapshot['name'] - vol_name = volume['name'] - self.utils.create_volume_from_snapshot(vol_name, snapshot_name) + self.utils.create_volume_from_snapshot(volume, snapshot_name) def delete_snapshot(self, snapshot): """Driver entry point for deleting a snapshot.""" diff --git a/cinder/volume/drivers/windows/windows_utils.py b/cinder/volume/drivers/windows/windows_utils.py index 6b30561ee..0d2fbbac0 100644 --- a/cinder/volume/drivers/windows/windows_utils.py +++ b/cinder/volume/drivers/windows/windows_utils.py @@ -16,9 +16,12 @@ Utility class for Windows Storage Server 2012 volume related operations. """ +import ctypes import os import time +from oslo.config import cfg + from cinder import exception from cinder.openstack.common import log as logging from cinder.volume.drivers.windows import constants @@ -27,6 +30,10 @@ from cinder.volume.drivers.windows import constants if os.name == 'nt': import wmi + from ctypes import wintypes + +CONF = cfg.CONF + LOG = logging.getLogger(__name__) @@ -132,13 +139,17 @@ class WindowsUtils(object): LOG.error(err_msg) raise exception.VolumeBackendAPIException(data=err_msg) - def create_volume(self, vhd_path, vol_name, vol_size): + def create_volume(self, vhd_path, vol_name, vol_size=None): """Creates a volume.""" try: cl = self._conn_wmi.__getattr__("WT_Disk") + if vol_size: + size_mb = vol_size * 1024 + else: + size_mb = None cl.NewWTDisk(DevicePath=vhd_path, Description=vol_name, - SizeInMB=vol_size * 1024) + SizeInMB=size_mb) except wmi.x_wmi as exc: err_msg = (_( 'create_volume: error when creating the volume name: ' @@ -203,14 +214,24 @@ class WindowsUtils(object): LOG.error(err_msg) raise exception.VolumeBackendAPIException(data=err_msg) - def create_volume_from_snapshot(self, vol_name, snap_name): + def create_volume_from_snapshot(self, volume, snap_name): """Driver entry point for exporting snapshots as volumes.""" try: + vol_name = volume['name'] + vol_path = self.local_path(volume) + wt_snapshot = self._conn_wmi.WT_Snapshot(Description=snap_name)[0] disk_id = wt_snapshot.Export()[0] + # This export is read-only, so it needs to be copied + # to another disk. wt_disk = self._conn_wmi.WT_Disk(WTD=disk_id)[0] - wt_disk.Description = vol_name + wt_disk.Description = '%s-temp' % vol_name wt_disk.put() + src_path = wt_disk.DevicePath + + self.copy(src_path, vol_path) + self.create_volume(vol_path, vol_name) + wt_disk.Delete_() except wmi.x_wmi as exc: err_msg = (_( 'create_volume_from_snapshot: error when creating the volume ' @@ -321,6 +342,16 @@ class WindowsUtils(object): LOG.error(err_msg) raise exception.VolumeBackendAPIException(data=err_msg) + def local_path(self, volume, format=None): + base_vhd_folder = CONF.windows_iscsi_lun_path + if not os.path.exists(base_vhd_folder): + LOG.debug('Creating folder: %s' % base_vhd_folder) + os.makedirs(base_vhd_folder) + if not format: + format = self.get_supported_format() + return os.path.join(base_vhd_folder, str(volume['name']) + "." + + format) + def check_min_windows_version(self, major, minor, build=0): version_str = self.get_windows_version() return map(int, version_str.split('.')) >= [major, minor, build] @@ -347,6 +378,19 @@ class WindowsUtils(object): raise exception.VolumeBackendAPIException( _('Operation failed with return value: %s') % ret_val) + def copy(self, src, dest): + # With large files this is 2x-3x faster than shutil.copy(src, dest), + # especially with UNC targets. + kernel32 = ctypes.windll.kernel32 + kernel32.CopyFileW.restype = wintypes.BOOL + + retcode = kernel32.CopyFileW(ctypes.c_wchar_p(src), + ctypes.c_wchar_p(dest), + wintypes.BOOL(True)) + if not retcode: + raise IOError(_('The file copy from %(src)s to %(dest)s failed.') + % {'src': src, 'dest': dest}) + def _wait_for_job(self, job_path): """Poll WMI job state and wait for completion.""" job = self._get_wmi_obj(job_path) -- 2.45.2