From 2e4837971ff512342fb47d3b43a57b67d0ab36d3 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Sat, 19 Jul 2014 21:35:17 +0000 Subject: [PATCH] Remove Hyper-V dependency in the Windows Cinder Volume Driver Currently, the Windows Cinder Volume driver depends on the Hyper-V feature in order to be able to access the WMI namespaces that are used for some of the volume related operations. Those WMI calls must be urgently replaced with Win32 calls in order to provide CI tests performed on Windows Storage Server edition. Change-Id: Ib4b8bccee3c7ee86e6cc44e1b16d20d455e61612 Closes-Bug: #1346496 --- cinder/tests/windows/test_vhdutils.py | 194 ++++++++++++++---- cinder/tests/windows/test_vhdutilsv2.py | 70 ------- cinder/tests/windows/test_windows.py | 9 +- cinder/tests/windows/test_windows_utils.py | 89 -------- cinder/volume/drivers/windows/constants.py | 4 - cinder/volume/drivers/windows/utilsfactory.py | 37 ---- cinder/volume/drivers/windows/vhdutils.py | 181 ++++++++++++++-- cinder/volume/drivers/windows/vhdutilsv2.py | 64 ------ cinder/volume/drivers/windows/windows.py | 8 +- .../volume/drivers/windows/windows_utils.py | 52 ----- 10 files changed, 326 insertions(+), 382 deletions(-) delete mode 100644 cinder/tests/windows/test_vhdutilsv2.py delete mode 100644 cinder/tests/windows/test_windows_utils.py delete mode 100644 cinder/volume/drivers/windows/utilsfactory.py delete mode 100644 cinder/volume/drivers/windows/vhdutilsv2.py diff --git a/cinder/tests/windows/test_vhdutils.py b/cinder/tests/windows/test_vhdutils.py index 21af78165..2caa03f9e 100644 --- a/cinder/tests/windows/test_vhdutils.py +++ b/cinder/tests/windows/test_vhdutils.py @@ -14,57 +14,171 @@ import mock +from cinder import exception from cinder import test +from cinder.volume.drivers.windows import constants from cinder.volume.drivers.windows import vhdutils -from cinder.volume.drivers.windows import windows_utils class VHDUtilsTestCase(test.TestCase): _FAKE_FORMAT = 2 - _FAKE_TYPE = 3 + _FAKE_TYPE = constants.VHD_TYPE_DYNAMIC _FAKE_JOB_PATH = 'fake_job_path' _FAKE_VHD_PATH = r'C:\fake\vhd.vhd' - _FAKE_DESTINATION_PATH = r'C:\fake\destination.vhd' + _FAKE_DEST_PATH = r'C:\fake\destination.vhdx' _FAKE_RET_VAL = 0 _FAKE_VHD_SIZE = 1024 + _FAKE_DEVICE_ID = 'fake_device_id' def setUp(self): super(VHDUtilsTestCase, self).setUp() - windows_utils.WindowsUtils.__init__ = lambda x: None - vhdutils.VHDUtils.__init__ = lambda x: None - self.wutils = windows_utils.WindowsUtils() - self.wutils.check_ret_val = mock.MagicMock() - self.vhdutils = vhdutils.VHDUtils() - self.vhdutils._conn = mock.MagicMock() - self.vhdutils.utils = self.wutils - self.mock_img_svc = ( - self.vhdutils._conn.Msvm_ImageManagementService()[0]) - self.vhdutils._get_resize_method = mock.Mock( - return_value=self.mock_img_svc.ExpandVirtualHardDisk) - - def test_convert_vhd(self): - self.mock_img_svc.ConvertVirtualHardDisk.return_value = ( - self._FAKE_JOB_PATH, self._FAKE_RET_VAL) - - self.vhdutils.convert_vhd(self._FAKE_VHD_PATH, - self._FAKE_DESTINATION_PATH, - self._FAKE_TYPE) - - self.mock_img_svc.ConvertVirtualHardDisk.assert_called_once() - self.wutils.check_ret_val.assert_called_once_with( - self._FAKE_RET_VAL, self._FAKE_JOB_PATH) - - def test_resize_vhd(self): - self.mock_img_svc.ExpandVirtualHardDisk.return_value = ( - self._FAKE_JOB_PATH, self._FAKE_RET_VAL) - - self.vhdutils.resize_vhd(self._FAKE_VHD_PATH, - self._FAKE_VHD_SIZE) - - self.mock_img_svc.ExpandVirtualHardDisk.assert_called_once() - self.wutils.check_ret_val.assert_called_once_with(self._FAKE_RET_VAL, - self._FAKE_JOB_PATH) - self.vhdutils._get_resize_method.assert_called_once() - self.mock_img_svc.ExpandVirtualHardDisk.assert_called_once_with( - Path=self._FAKE_VHD_PATH, MaxInternalSize=self._FAKE_VHD_SIZE) + self._setup_mocks() + self._vhdutils = vhdutils.VHDUtils() + self._vhdutils._msft_vendor_id = 'fake_vendor_id' + + self.addCleanup(mock.patch.stopall) + + def _setup_mocks(self): + fake_ctypes = mock.Mock() + # Use this in order to make assertions on the variables parsed by + # references. + fake_ctypes.byref = lambda x: x + fake_ctypes.c_wchar_p = lambda x: x + + mock.patch.multiple( + 'cinder.volume.drivers.windows.vhdutils', ctypes=fake_ctypes, + windll=mock.DEFAULT, wintypes=mock.DEFAULT, kernel32=mock.DEFAULT, + virtdisk=mock.DEFAULT, Win32_GUID=mock.DEFAULT, + Win32_RESIZE_VIRTUAL_DISK_PARAMETERS=mock.DEFAULT, + Win32_CREATE_VIRTUAL_DISK_PARAMETERS=mock.DEFAULT, + Win32_VIRTUAL_STORAGE_TYPE=mock.DEFAULT, + create=True).start() + + def _test_convert_vhd(self, convertion_failed=False): + self._vhdutils._get_device_id_by_path = mock.Mock( + side_effect=(vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHD, + vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHDX)) + self._vhdutils._close = mock.Mock() + + fake_params = ( + vhdutils.Win32_CREATE_VIRTUAL_DISK_PARAMETERS.return_value) + fake_vst = mock.Mock() + fake_source_vst = mock.Mock() + + vhdutils.Win32_VIRTUAL_STORAGE_TYPE = mock.Mock( + side_effect=[fake_vst, None, fake_source_vst]) + vhdutils.virtdisk.CreateVirtualDisk.return_value = int( + convertion_failed) + + if convertion_failed: + self.assertRaises(exception.VolumeBackendAPIException, + self._vhdutils.convert_vhd, + self._FAKE_VHD_PATH, self._FAKE_DEST_PATH, + self._FAKE_TYPE) + else: + self._vhdutils.convert_vhd(self._FAKE_VHD_PATH, + self._FAKE_DEST_PATH, + self._FAKE_TYPE) + + self.assertEqual(vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHDX, + fake_vst.DeviceId) + self.assertEqual(vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHD, + fake_source_vst.DeviceId) + + vhdutils.virtdisk.CreateVirtualDisk.assert_called_with( + vhdutils.ctypes.byref(fake_vst), + vhdutils.ctypes.c_wchar_p(self._FAKE_DEST_PATH), + vhdutils.VIRTUAL_DISK_ACCESS_NONE, None, + vhdutils.CREATE_VIRTUAL_DISK_FLAG_NONE, 0, + vhdutils.ctypes.byref(fake_params), None, + vhdutils.ctypes.byref(vhdutils.wintypes.HANDLE())) + self.assertTrue(self._vhdutils._close.called) + + def test_convert_vhd_successfully(self): + self._test_convert_vhd() + + def test_convert_vhd_exception(self): + self._test_convert_vhd(True) + + def _test_open(self, open_failed=False): + vhdutils.virtdisk.OpenVirtualDisk.return_value = int(open_failed) + + fake_vst = vhdutils.Win32_VIRTUAL_STORAGE_TYPE.return_value + + if open_failed: + self.assertRaises(exception.VolumeBackendAPIException, + self._vhdutils._open, + self._FAKE_DEVICE_ID, self._FAKE_VHD_PATH) + else: + self._vhdutils._open(self._FAKE_DEVICE_ID, + self._FAKE_VHD_PATH) + + vhdutils.virtdisk.OpenVirtualDisk.assert_called_with( + vhdutils.ctypes.byref(fake_vst), + vhdutils.ctypes.c_wchar_p(self._FAKE_VHD_PATH), + vhdutils.VIRTUAL_DISK_ACCESS_ALL, + vhdutils.CREATE_VIRTUAL_DISK_FLAG_NONE, 0, + vhdutils.ctypes.byref(vhdutils.wintypes.HANDLE())) + self.assertEqual(self._FAKE_DEVICE_ID, fake_vst.DeviceId) + + def test_open_success(self): + self._test_open() + + def test_open_failed(self): + self._test_open(open_failed=True) + + def _test_get_device_id_by_path(self, + get_device_failed=False): + if get_device_failed: + self.assertRaises(exception.VolumeBackendAPIException, + self._vhdutils._get_device_id_by_path, + self._FAKE_VHD_PATH[:-4]) + else: + ret_val = self._vhdutils._get_device_id_by_path( + self._FAKE_VHD_PATH) + + self.assertEqual( + ret_val, + vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHD) + + def test_get_device_id_by_path_success(self): + self._test_get_device_id_by_path() + + def test_get_device_id_by_path_failed(self): + self._test_get_device_id_by_path(get_device_failed=True) + + def _test_resize_vhd(self, resize_failed=False): + fake_params = ( + vhdutils.Win32_RESIZE_VIRTUAL_DISK_PARAMETERS.return_value) + + self._vhdutils._open = mock.Mock( + return_value=vhdutils.ctypes.byref( + vhdutils.wintypes.HANDLE())) + self._vhdutils._close = mock.Mock() + self._vhdutils._get_device_id_by_path = mock.Mock(return_value=2) + + vhdutils.virtdisk.ResizeVirtualDisk.return_value = int( + resize_failed) + + if resize_failed: + self.assertRaises(exception.VolumeBackendAPIException, + self._vhdutils.resize_vhd, + self._FAKE_VHD_PATH, + self._FAKE_VHD_SIZE) + else: + self._vhdutils.resize_vhd(self._FAKE_VHD_PATH, + self._FAKE_VHD_SIZE) + + vhdutils.virtdisk.ResizeVirtualDisk.assert_called_with( + vhdutils.ctypes.byref(vhdutils.wintypes.HANDLE()), + vhdutils.RESIZE_VIRTUAL_DISK_FLAG_NONE, + vhdutils.ctypes.byref(fake_params), + None) + self.assertTrue(self._vhdutils._close.called) + + def test_resize_vhd_success(self): + self._test_resize_vhd() + + def test_resize_vhd_failed(self): + self._test_resize_vhd(resize_failed=True) diff --git a/cinder/tests/windows/test_vhdutilsv2.py b/cinder/tests/windows/test_vhdutilsv2.py deleted file mode 100644 index 667cfb118..000000000 --- a/cinder/tests/windows/test_vhdutilsv2.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2014 Cloudbase Solutions Srl -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock - -from cinder import test -from cinder.volume.drivers.windows import vhdutilsv2 -from cinder.volume.drivers.windows import windows_utils - - -class VHDUtilsV2TestCase(test.TestCase): - - _FAKE_FORMAT = 2 - _FAKE_TYPE = 3 - _FAKE_JOB_PATH = 'fake_job_path' - _FAKE_VHD_PATH = r'C:\fake\vhd.vhd' - _FAKE_DESTINATION_PATH = r'C:\fake\destination.vhd' - _FAKE_RET_VAL = 0 - _FAKE_VHD_SIZE = 1024 - - def setUp(self): - super(VHDUtilsV2TestCase, self).setUp() - windows_utils.WindowsUtils.__init__ = lambda x: None - vhdutilsv2.VHDUtilsV2.__init__ = lambda x: None - self.wutils = windows_utils.WindowsUtils() - self.wutils.check_ret_val = mock.MagicMock() - self.vhdutilsv2 = vhdutilsv2.VHDUtilsV2() - self.vhdutilsv2._conn = mock.MagicMock() - self.vhdutilsv2.utils = self.wutils - self.mock_img_svc = ( - self.vhdutilsv2._conn.Msvm_ImageManagementService()[0]) - self.vhdutilsv2._get_resize_method = mock.Mock( - return_value=self.mock_img_svc.ResizeVirtualHardDisk) - - def test_convert_vhd(self): - self.mock_img_svc.ConvertVirtualHardDisk.return_value = ( - self._FAKE_JOB_PATH, self._FAKE_RET_VAL) - - self.vhdutilsv2.convert_vhd(self._FAKE_VHD_PATH, - self._FAKE_DESTINATION_PATH, - self._FAKE_TYPE) - - self.mock_img_svc.ConvertVirtualHardDisk.assert_called_once() - self.wutils.check_ret_val.assert_called_once_with( - self._FAKE_RET_VAL, self._FAKE_JOB_PATH) - - def test_resize_vhd(self): - self.mock_img_svc.ResizeVirtualHardDisk.return_value = ( - self._FAKE_JOB_PATH, self._FAKE_RET_VAL) - - self.vhdutilsv2.resize_vhd(self._FAKE_VHD_PATH, - self._FAKE_VHD_SIZE) - - self.mock_img_svc.ResizeVirtualHardDisk.assert_called_once() - self.wutils.check_ret_val.assert_called_once_with(self._FAKE_RET_VAL, - self._FAKE_JOB_PATH) - self.vhdutilsv2._get_resize_method.assert_called_once() - self.mock_img_svc.ResizeVirtualHardDisk.assert_called_once_with( - Path=self._FAKE_VHD_PATH, MaxInternalSize=self._FAKE_VHD_SIZE) diff --git a/cinder/tests/windows/test_windows.py b/cinder/tests/windows/test_windows.py index dfae30c34..c92e77cbb 100644 --- a/cinder/tests/windows/test_windows.py +++ b/cinder/tests/windows/test_windows.py @@ -34,7 +34,6 @@ from cinder.openstack.common import fileutils from cinder.tests.windows import db_fakes from cinder.volume import configuration as conf from cinder.volume.drivers.windows import constants -from cinder.volume.drivers.windows import utilsfactory from cinder.volume.drivers.windows import vhdutils from cinder.volume.drivers.windows import windows from cinder.volume.drivers.windows import windows_utils @@ -67,12 +66,7 @@ class TestWindowsDriver(test.TestCase): def fake_wutils__init__(self): pass - def fake_get_vhdutils(): - return vhdutils.VHDUtils() - windows_utils.WindowsUtils.__init__ = fake_wutils__init__ - vhdutils.VHDUtils.__init__ = lambda x: None - utilsfactory.get_vhdutils = fake_get_vhdutils def fake_local_path(self, volume): return os.path.join(CONF.windows_iscsi_lun_path, @@ -333,7 +327,8 @@ class TestWindowsDriver(test.TestCase): temp_vhd_path) if supported_format == 'vhdx': upload_image = upload_image[:-1] - vhdutils.VHDUtils.convert_vhd(temp_vhd_path, upload_image) + vhdutils.VHDUtils.convert_vhd(temp_vhd_path, upload_image, + constants.VHD_TYPE_DYNAMIC) image_utils.upload_volume(None, None, image_meta, upload_image, 'vpc') diff --git a/cinder/tests/windows/test_windows_utils.py b/cinder/tests/windows/test_windows_utils.py deleted file mode 100644 index e237b664c..000000000 --- a/cinder/tests/windows/test_windows_utils.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright 2014 Cloudbase Solutions Srl -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock - -from cinder import exception -from cinder import test -from cinder.volume.drivers.windows import constants -from cinder.volume.drivers.windows import windows_utils - - -class WindowsUtilsTestCase(test.TestCase): - - _FAKE_JOB_PATH = 'fake_job_path' - _FAKE_RET_VAL = 0 - _FAKE_RET_VAL_ERROR = 10 - _FAKE_VHD_SIZE = 1024 - _FAKE_JOB = 'fake_job' - - def setUp(self): - super(WindowsUtilsTestCase, self).setUp() - windows_utils.WindowsUtils.__init__ = lambda x: None - self.wutils = windows_utils.WindowsUtils() - self.wutils.time = mock.MagicMock() - - def _test_check_ret_val(self, job_started, job_failed): - self.wutils._wait_for_job = mock.Mock(return_value=self._FAKE_JOB) - if job_started: - ret_val = self.wutils.check_ret_val( - constants.WMI_JOB_STATUS_STARTED, self._FAKE_JOB_PATH) - self.assertEqual(ret_val, self._FAKE_JOB) - self.wutils._wait_for_job.assert_called_once_with( - self._FAKE_JOB_PATH) - - elif job_failed: - self.assertRaises(exception.VolumeBackendAPIException, - self.wutils.check_ret_val, - 10, self._FAKE_JOB_PATH) - - def test_check_ret_val_failed_job(self): - self._test_check_ret_val(False, True) - - def test_check_ret_val_job_started(self): - self._test_check_ret_val(True, False) - - def _test_wait_for_job(self, job_running, job_failed): - fake_job = mock.MagicMock() - fake_job2 = mock.MagicMock() - fake_job2.JobState = constants.WMI_JOB_STATE_COMPLETED - - if job_running: - fake_job.JobState = constants.WMI_JOB_STATE_RUNNING - elif job_failed: - fake_job.JobState = self._FAKE_RET_VAL_ERROR - fake_job.GetError = mock.Mock(return_value=( - 1, self._FAKE_RET_VAL_ERROR)) - else: - fake_job.JobState = constants.WMI_JOB_STATE_COMPLETED - - self.wutils._get_wmi_obj = mock.Mock(side_effect=[fake_job, fake_job2]) - - if job_failed: - self.assertRaises(exception.VolumeBackendAPIException, - self.wutils._wait_for_job, - self._FAKE_JOB_PATH) - else: - self.wutils._wait_for_job(self._FAKE_JOB_PATH) - if job_running: - call_count = 2 - else: - call_count = 1 - self.assertEqual(call_count, self.wutils._get_wmi_obj.call_count) - - def test_wait_for_running_job(self): - self._test_wait_for_job(True, False) - - def test_wait_for_failed_job(self): - self._test_wait_for_job(False, True) diff --git a/cinder/volume/drivers/windows/constants.py b/cinder/volume/drivers/windows/constants.py index 4c964d93d..6616d3fab 100644 --- a/cinder/volume/drivers/windows/constants.py +++ b/cinder/volume/drivers/windows/constants.py @@ -12,9 +12,5 @@ # License for the specific language governing permissions and limitations # under the License. -WMI_JOB_STATUS_STARTED = 4096 -WMI_JOB_STATE_RUNNING = 4 -WMI_JOB_STATE_COMPLETED = 7 - VHD_TYPE_FIXED = 2 VHD_TYPE_DYNAMIC = 3 diff --git a/cinder/volume/drivers/windows/utilsfactory.py b/cinder/volume/drivers/windows/utilsfactory.py deleted file mode 100644 index 57a36927f..000000000 --- a/cinder/volume/drivers/windows/utilsfactory.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2014 Cloudbase Solutions Srl -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from cinder.openstack.common import log as logging -from cinder.volume.drivers.windows import vhdutils -from cinder.volume.drivers.windows import vhdutilsv2 -from cinder.volume.drivers.windows import windows_utils - -LOG = logging.getLogger(__name__) - - -def _get_class(v1_class, v2_class): - # V2 classes are supported starting from Hyper-V Server 2012 and - # Windows Server 2012 (kernel version 6.2) - if not windows_utils.WindowsUtils().check_min_windows_version(6, 2): - cls = v2_class - else: - cls = v1_class - LOG.debug("Loading class: %(module_name)s.%(class_name)s", - {'module_name': cls.__module__, 'class_name': cls.__name__}) - return cls - - -def get_vhdutils(): - return _get_class(vhdutils.VHDUtils, vhdutilsv2.VHDUtilsV2)() diff --git a/cinder/volume/drivers/windows/vhdutils.py b/cinder/volume/drivers/windows/vhdutils.py index a6b3d80c1..385bb4f53 100644 --- a/cinder/volume/drivers/windows/vhdutils.py +++ b/cinder/volume/drivers/windows/vhdutils.py @@ -23,35 +23,184 @@ See "Download the Specifications Without Registering" Official VHDX format specs can be retrieved at: http://www.microsoft.com/en-us/download/details.aspx?id=34750 """ +import ctypes import os if os.name == 'nt': - import wmi + from ctypes import windll + from ctypes import wintypes + kernel32 = windll.kernel32 + virtdisk = windll.virtdisk + +from cinder import exception +from cinder.openstack.common.gettextutils import _ from cinder.openstack.common import log as logging -from cinder.volume.drivers.windows import windows_utils +from cinder.volume.drivers.windows import constants LOG = logging.getLogger(__name__) +if os.name == 'nt': + class Win32_GUID(ctypes.Structure): + _fields_ = [("Data1", wintypes.DWORD), + ("Data2", wintypes.WORD), + ("Data3", wintypes.WORD), + ("Data4", wintypes.BYTE * 8)] + + class Win32_VIRTUAL_STORAGE_TYPE(ctypes.Structure): + _fields_ = [ + ('DeviceId', wintypes.DWORD), + ('VendorId', Win32_GUID) + ] + + class Win32_RESIZE_VIRTUAL_DISK_PARAMETERS(ctypes.Structure): + _fields_ = [ + ('Version', wintypes.DWORD), + ('NewSize', ctypes.c_ulonglong) + ] + + class Win32_CREATE_VIRTUAL_DISK_PARAMETERS(ctypes.Structure): + _fields_ = [ + ('Version', wintypes.DWORD), + ('UniqueId', Win32_GUID), + ('MaximumSize', ctypes.c_ulonglong), + ('BlockSizeInBytes', wintypes.ULONG), + ('SectorSizeInBytes', wintypes.ULONG), + ('PhysicalSectorSizeInBytes', wintypes.ULONG), + ('ParentPath', wintypes.LPCWSTR), + ('SourcePath', wintypes.LPCWSTR), + ('OpenFlags', wintypes.DWORD), + ('ParentVirtualStorageType', Win32_VIRTUAL_STORAGE_TYPE), + ('SourceVirtualStorageType', Win32_VIRTUAL_STORAGE_TYPE), + ('ResiliencyGuid', Win32_GUID) + ] + +VIRTUAL_STORAGE_TYPE_DEVICE_ISO = 1 +VIRTUAL_STORAGE_TYPE_DEVICE_VHD = 2 +VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 3 +VIRTUAL_DISK_ACCESS_NONE = 0 +VIRTUAL_DISK_ACCESS_ALL = 0x003f0000 +VIRTUAL_DISK_ACCESS_CREATE = 0x00100000 +OPEN_VIRTUAL_DISK_FLAG_NONE = 0 +RESIZE_VIRTUAL_DISK_FLAG_NONE = 0 +RESIZE_VIRTUAL_DISK_VERSION_1 = 1 +CREATE_VIRTUAL_DISK_VERSION_2 = 2 +CREATE_VHD_PARAMS_DEFAULT_BLOCK_SIZE = 0 +CREATE_VIRTUAL_DISK_FLAG_NONE = 0 +CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION = 1 + class VHDUtils(object): def __init__(self): - self.utils = windows_utils.WindowsUtils() - self._conn = wmi.WMI(moniker='//./root/virtualization') + self._ext_device_id_map = { + 'vhd': VIRTUAL_STORAGE_TYPE_DEVICE_VHD, + 'vhdx': VIRTUAL_STORAGE_TYPE_DEVICE_VHDX} + self.create_virtual_disk_flags = { + constants.VHD_TYPE_FIXED: ( + CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION), + constants.VHD_TYPE_DYNAMIC: CREATE_VIRTUAL_DISK_FLAG_NONE + } + + if os.name == 'nt': + self._msft_vendor_id = ( + self.get_WIN32_VIRTUAL_STORAGE_TYPE_VENDOR_MSFT()) + + @staticmethod + def get_WIN32_VIRTUAL_STORAGE_TYPE_VENDOR_MSFT(): + guid = Win32_GUID() + guid.Data1 = 0xec984aec + guid.Data2 = 0xa0f9 + guid.Data3 = 0x47e9 + ByteArray8 = wintypes.BYTE * 8 + guid.Data4 = ByteArray8(0x90, 0x1f, 0x71, 0x41, 0x5a, 0x66, 0x34, 0x5b) + return guid + + def _open(self, device_id, vhd_path): + vst = Win32_VIRTUAL_STORAGE_TYPE() + vst.DeviceId = device_id + vst.VendorId = self._msft_vendor_id - def convert_vhd(self, src, dest, vhd_type=None): - image_man_svc = self._conn.Msvm_ImageManagementService()[0] - (job_path, ret_val) = image_man_svc.ConvertVirtualHardDisk( - SourcePath=src, DestinationPath=dest, Type=vhd_type) - self.utils.check_ret_val(ret_val, job_path) + handle = wintypes.HANDLE() + ret_val = virtdisk.OpenVirtualDisk(ctypes.byref(vst), + ctypes.c_wchar_p(vhd_path), + VIRTUAL_DISK_ACCESS_ALL, + OPEN_VIRTUAL_DISK_FLAG_NONE, + 0, ctypes.byref(handle)) + if ret_val: + raise exception.VolumeBackendAPIException( + _("Opening virtual disk failed with error: %s") % ret_val) + return handle - def _get_resize_method(self): - image_man_svc = self._conn.Msvm_ImageManagementService()[0] - return image_man_svc.ExpandVirtualHardDisk + def _close(self, handle): + kernel32.CloseHandle(handle) + + def _get_device_id_by_path(self, vhd_path): + ext = os.path.splitext(vhd_path)[1][1:].lower() + device_id = self._ext_device_id_map.get(ext) + if not device_id: + raise exception.VolumeBackendAPIException( + _("Unsupported virtual disk extension: %s") % ext) + return device_id def resize_vhd(self, vhd_path, new_max_size): - resize = self._get_resize_method() - (job_path, ret_val) = resize(Path=vhd_path, - MaxInternalSize=new_max_size) - self.utils.check_ret_val(ret_val, job_path) + device_id = self._get_device_id_by_path(vhd_path) + handle = self._open(device_id, vhd_path) + + params = Win32_RESIZE_VIRTUAL_DISK_PARAMETERS() + params.Version = RESIZE_VIRTUAL_DISK_VERSION_1 + params.NewSize = new_max_size + + ret_val = virtdisk.ResizeVirtualDisk( + handle, + RESIZE_VIRTUAL_DISK_FLAG_NONE, + ctypes.byref(params), + None) + self._close(handle) + + if ret_val: + raise exception.VolumeBackendAPIException( + _("Virtual disk resize failed with error: %s") % ret_val) + + def convert_vhd(self, src, dest, vhd_type): + src_device_id = self._get_device_id_by_path(src) + dest_device_id = self._get_device_id_by_path(dest) + + vst = Win32_VIRTUAL_STORAGE_TYPE() + vst.DeviceId = dest_device_id + vst.VendorId = self._msft_vendor_id + + params = Win32_CREATE_VIRTUAL_DISK_PARAMETERS() + params.Version = CREATE_VIRTUAL_DISK_VERSION_2 + params.UniqueId = Win32_GUID() + params.MaximumSize = 0 + params.BlockSizeInBytes = CREATE_VHD_PARAMS_DEFAULT_BLOCK_SIZE + params.SectorSizeInBytes = 0x200 + params.PhysicalSectorSizeInBytes = 0x200 + params.ParentPath = None + params.SourcePath = src + params.OpenFlags = OPEN_VIRTUAL_DISK_FLAG_NONE + params.ParentVirtualStorageType = Win32_VIRTUAL_STORAGE_TYPE() + params.SourceVirtualStorageType = Win32_VIRTUAL_STORAGE_TYPE() + params.SourceVirtualStorageType.DeviceId = src_device_id + params.SourceVirtualStorageType.VendorId = self._msft_vendor_id + params.ResiliencyGuid = Win32_GUID() + + handle = wintypes.HANDLE() + create_virtual_disk_flag = self.create_virtual_disk_flags.get(vhd_type) + + ret_val = virtdisk.CreateVirtualDisk( + ctypes.byref(vst), + ctypes.c_wchar_p(dest), + VIRTUAL_DISK_ACCESS_NONE, + None, + create_virtual_disk_flag, + 0, + ctypes.byref(params), + None, + ctypes.byref(handle)) + self._close(handle) + + if ret_val: + raise exception.VolumeBackendAPIException( + _("Virtual disk conversion failed with error: %s") % ret_val) diff --git a/cinder/volume/drivers/windows/vhdutilsv2.py b/cinder/volume/drivers/windows/vhdutilsv2.py deleted file mode 100644 index e50cca678..000000000 --- a/cinder/volume/drivers/windows/vhdutilsv2.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2014 Cloudbase Solutions Srl -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Utility class for VHD related operations. -Based on the "root/virtualization/v2" namespace available starting with -Hyper-V Server / Windows Server 2012. -""" -import os - -if os.name == 'nt': - import wmi - -from cinder.openstack.common import log as logging -from cinder.volume.drivers.windows import constants -from cinder.volume.drivers.windows import vhdutils -from cinder.volume.drivers.windows import windows_utils - -LOG = logging.getLogger(__name__) - - -class VHDUtilsV2(vhdutils.VHDUtils): - - _vhd_format_map = { - 'vhd': 2, - 'vhdx': 3, - } - - def __init__(self): - self.utils = windows_utils.WindowsUtils() - self._conn = wmi.WMI(moniker='//./root/virtualization/v2') - - def _get_resize_method(self): - image_man_svc = self._conn.Msvm_ImageManagementService()[0] - return image_man_svc.ResizeVirtualHardDisk - - def convert_vhd(self, src, dest, vhd_type=constants.VHD_TYPE_DYNAMIC): - vhd_info = self._conn.Msvm_VirtualHardDiskSettingData.new() - ext = os.path.splitext(dest)[1][1:] - format = self._vhd_format_map.get(ext) - - vhd_info.Type = vhd_type - vhd_info.Path = dest - vhd_info.Format = format - vhd_info.BlockSize = 0 - vhd_info.LogicalSectorSize = 0 - vhd_info.ParentPath = None - - image_man_svc = self._conn.Msvm_ImageManagementService()[0] - (job_path, ret_val) = image_man_svc.ConvertVirtualHardDisk( - SourcePath=src, VirtualDiskSettingData=vhd_info.GetText_(1)) - self.utils.check_ret_val(ret_val, job_path) diff --git a/cinder/volume/drivers/windows/windows.py b/cinder/volume/drivers/windows/windows.py index e4d8e2048..72044c062 100644 --- a/cinder/volume/drivers/windows/windows.py +++ b/cinder/volume/drivers/windows/windows.py @@ -27,7 +27,8 @@ from cinder.image import image_utils from cinder.openstack.common import fileutils from cinder.openstack.common import log as logging from cinder.volume import driver -from cinder.volume.drivers.windows import utilsfactory +from cinder.volume.drivers.windows import constants +from cinder.volume.drivers.windows import vhdutils from cinder.volume.drivers.windows import windows_utils LOG = logging.getLogger(__name__) @@ -60,7 +61,7 @@ class WindowsDriver(driver.ISCSIDriver): Validate the flags we care about """ self.utils = windows_utils.WindowsUtils() - self.vhdutils = utilsfactory.get_vhdutils() + self.vhdutils = vhdutils.VHDUtils() def check_for_setup_error(self): """Check that the driver is working and can communicate.""" @@ -196,7 +197,8 @@ class WindowsDriver(driver.ISCSIDriver): # convert the image to vhd before attempting upload if disk_format == 'vhdx': upload_image = upload_image[:-1] - self.vhdutils.convert_vhd(temp_vhd_path, upload_image) + self.vhdutils.convert_vhd(temp_vhd_path, upload_image, + constants.VHD_TYPE_DYNAMIC) image_utils.upload_volume(context, image_service, image_meta, upload_image, 'vpc') diff --git a/cinder/volume/drivers/windows/windows_utils.py b/cinder/volume/drivers/windows/windows_utils.py index 63dd94187..437b0d903 100644 --- a/cinder/volume/drivers/windows/windows_utils.py +++ b/cinder/volume/drivers/windows/windows_utils.py @@ -18,7 +18,6 @@ Utility class for Windows Storage Server 2012 volume related operations. import ctypes import os -import time from oslo.config import cfg @@ -372,13 +371,6 @@ class WindowsUtils(object): else: return constants.VHD_TYPE_FIXED - def check_ret_val(self, ret_val, job_path, success_values=[0]): - if ret_val == constants.WMI_JOB_STATUS_STARTED: - return self._wait_for_job(job_path) - elif ret_val not in success_values: - 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. @@ -391,47 +383,3 @@ class WindowsUtils(object): 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) - - while job.JobState == constants.WMI_JOB_STATE_RUNNING: - time.sleep(0.1) - job = self._get_wmi_obj(job_path) - if job.JobState != constants.WMI_JOB_STATE_COMPLETED: - job_state = job.JobState - if job.path().Class == "Msvm_ConcreteJob": - err_sum_desc = job.ErrorSummaryDescription - err_desc = job.ErrorDescription - err_code = job.ErrorCode - raise exception.VolumeBackendAPIException( - _("WMI job failed with status " - "%(job_state)d. Error details: " - "%(err_sum_desc)s - %(err_desc)s - " - "Error code: %(err_code)d") % - {'job_state': job_state, - 'err_sum_desc': err_sum_desc, - 'err_desc': err_desc, - 'err_code': err_code}) - else: - (error, ret_val) = job.GetError() - if not ret_val and error: - raise exception.VolumeBackendAPIException( - _("WMI job failed with status %(job_state)d. " - "Job path: %(job_path)s Error details: " - "%(error)s") % {'job_state': job_state, - 'error': error, - 'job_path': job_path}) - else: - raise exception.VolumeBackendAPIException( - _("WMI job failed with status %d. No error " - "description available") % job_state) - desc = job.Description - elap = job.ElapsedTime - LOG.debug("WMI job succeeded: %(desc)s, Elapsed=%(elap)s" % - {'desc': desc, 'elap': elap}) - return job - - def _get_wmi_obj(self, path): - return wmi.WMI(moniker=path.replace('\\', '/')) -- 2.45.2