From 1f6972f3fdfb87b09c03f3c2c7ab1870d90e0dc2 Mon Sep 17 00:00:00 2001 From: Lucian Petrut Date: Tue, 8 Apr 2014 18:57:41 +0300 Subject: [PATCH] Fixes cinder volume create on Windows Server 2012 R2 Windows Server 2012 R2 does not support vhd images as iSCSI disks, requiring VHDX images. For this reason, the cinder driver fails to create volumes. For the moment, the default format is vhd. On WSS 2012 R2, we should use vhdx images as default. This patch introduces vhd/vhdx related methods for both v1 and v2 wmi virtualization namespaces. Closes-Bug: #1299150 Change-Id: I272988dc5b0e3b8129c5e4d3c79bea1c292701c8 --- cinder/tests/windows/test_vhdutils.py | 70 +++++++++++++++++++ cinder/tests/windows/test_vhdutilsv2.py | 70 +++++++++++++++++++ cinder/tests/{ => windows}/test_windows.py | 60 ++++++++++++---- .../tests/{ => windows}/test_windows_utils.py | 32 --------- cinder/volume/drivers/windows/utilsfactory.py | 37 ++++++++++ cinder/volume/drivers/windows/vhdutils.py | 57 +++++++++++++++ cinder/volume/drivers/windows/vhdutilsv2.py | 64 +++++++++++++++++ cinder/volume/drivers/windows/windows.py | 43 ++++++++---- .../volume/drivers/windows/windows_utils.py | 32 +++++---- 9 files changed, 391 insertions(+), 74 deletions(-) create mode 100644 cinder/tests/windows/test_vhdutils.py create mode 100644 cinder/tests/windows/test_vhdutilsv2.py rename cinder/tests/{ => windows}/test_windows.py (84%) rename cinder/tests/{ => windows}/test_windows_utils.py (69%) create mode 100644 cinder/volume/drivers/windows/utilsfactory.py create mode 100644 cinder/volume/drivers/windows/vhdutils.py create mode 100644 cinder/volume/drivers/windows/vhdutilsv2.py diff --git a/cinder/tests/windows/test_vhdutils.py b/cinder/tests/windows/test_vhdutils.py new file mode 100644 index 000000000..21af78165 --- /dev/null +++ b/cinder/tests/windows/test_vhdutils.py @@ -0,0 +1,70 @@ +# 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 vhdutils +from cinder.volume.drivers.windows import windows_utils + + +class VHDUtilsTestCase(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(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) diff --git a/cinder/tests/windows/test_vhdutilsv2.py b/cinder/tests/windows/test_vhdutilsv2.py new file mode 100644 index 000000000..667cfb118 --- /dev/null +++ b/cinder/tests/windows/test_vhdutilsv2.py @@ -0,0 +1,70 @@ +# 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/test_windows.py b/cinder/tests/windows/test_windows.py similarity index 84% rename from cinder/tests/test_windows.py rename to cinder/tests/windows/test_windows.py index 99cd3cbe9..bd73e3ab1 100644 --- a/cinder/tests/test_windows.py +++ b/cinder/tests/windows/test_windows.py @@ -30,9 +30,12 @@ from cinder import test from cinder.image import image_utils +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 @@ -56,7 +59,6 @@ class TestWindowsDriver(test.TestCase): self._setup_stubs() configuration = conf.Configuration(None) configuration.append_config_values(windows.windows_opts) - self._driver = windows.WindowsDriver(configuration=configuration) self._driver.do_setup({}) @@ -64,7 +66,13 @@ 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, @@ -257,14 +265,17 @@ class TestWindowsDriver(test.TestCase): volume = db_fakes.get_fake_volume_info() + fake_get_supported_type = lambda x: constants.VHD_TYPE_FIXED self.stubs.Set(drv, 'local_path', self.fake_local_path) + self.stubs.Set(windows_utils.WindowsUtils, 'get_supported_vhd_type', + fake_get_supported_type) self.mox.StubOutWithMock(os, 'makedirs') self.mox.StubOutWithMock(os, 'unlink') self.mox.StubOutWithMock(image_utils, 'create_temporary_file') self.mox.StubOutWithMock(image_utils, 'fetch_to_vhd') - self.mox.StubOutWithMock(windows_utils.WindowsUtils, 'convert_vhd') - self.mox.StubOutWithMock(windows_utils.WindowsUtils, 'resize_vhd') + self.mox.StubOutWithMock(vhdutils.VHDUtils, 'convert_vhd') + self.mox.StubOutWithMock(vhdutils.VHDUtils, 'resize_vhd') self.mox.StubOutWithMock(windows_utils.WindowsUtils, 'change_disk_status') @@ -283,11 +294,11 @@ class TestWindowsDriver(test.TestCase): windows_utils.WindowsUtils.change_disk_status(volume['name'], mox.IsA(bool)) os.unlink(mox.IsA(str)) - windows_utils.WindowsUtils.convert_vhd(fake_temp_path, - fake_volume_path, - constants.VHD_TYPE_FIXED) - windows_utils.WindowsUtils.resize_vhd(fake_volume_path, - volume['size'] << 30) + vhdutils.VHDUtils.convert_vhd(fake_temp_path, + fake_volume_path, + constants.VHD_TYPE_FIXED) + vhdutils.VHDUtils.resize_vhd(fake_volume_path, + volume['size'] << 30) windows_utils.WindowsUtils.change_disk_status(volume['name'], mox.IsA(bool)) os.unlink(mox.IsA(str)) @@ -296,32 +307,49 @@ class TestWindowsDriver(test.TestCase): drv.copy_image_to_volume(None, volume, None, None) - def test_copy_volume_to_image(self): + def _test_copy_volume_to_image(self, supported_format): drv = self._driver vol = db_fakes.get_fake_volume_info() image_meta = db_fakes.get_fake_image_meta() + fake_get_supported_format = lambda x: supported_format self.stubs.Set(drv, 'local_path', self.fake_local_path) + self.stubs.Set(windows_utils.WindowsUtils, 'get_supported_format', + fake_get_supported_format) + self.mox.StubOutWithMock(fileutils, 'delete_if_exists') self.mox.StubOutWithMock(image_utils, 'upload_volume') + self.mox.StubOutWithMock(windows_utils.WindowsUtils, 'copy_vhd_disk') + self.mox.StubOutWithMock(vhdutils.VHDUtils, 'convert_vhd') temp_vhd_path = os.path.join(CONF.image_conversion_dir, - str(image_meta['id']) + ".vhd") - - image_utils.upload_volume(None, None, image_meta, temp_vhd_path, 'vpc') - - self.mox.StubOutWithMock(windows_utils.WindowsUtils, - 'copy_vhd_disk') + str(image_meta['id']) + "." + + supported_format) + upload_image = temp_vhd_path windows_utils.WindowsUtils.copy_vhd_disk(self.fake_local_path(vol), temp_vhd_path) + if supported_format == 'vhdx': + upload_image = upload_image[:-1] + vhdutils.VHDUtils.convert_vhd(temp_vhd_path, upload_image) + + image_utils.upload_volume(None, None, image_meta, upload_image, 'vpc') + + fileutils.delete_if_exists(temp_vhd_path) + fileutils.delete_if_exists(upload_image) self.mox.ReplayAll() drv.copy_volume_to_image(None, vol, None, image_meta) + def test_copy_volume_to_image_using_vhd(self): + self._test_copy_volume_to_image('vhd') + + def test_copy_volume_to_image_using_vhdx(self): + self._test_copy_volume_to_image('vhdx') + def test_create_cloned_volume(self): drv = self._driver @@ -331,6 +359,8 @@ class TestWindowsDriver(test.TestCase): self.mox.StubOutWithMock(windows_utils.WindowsUtils, 'create_volume') + self.stubs.Set(drv, 'local_path', self.fake_local_path) + windows_utils.WindowsUtils.create_volume(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) diff --git a/cinder/tests/test_windows_utils.py b/cinder/tests/windows/test_windows_utils.py similarity index 69% rename from cinder/tests/test_windows_utils.py rename to cinder/tests/windows/test_windows_utils.py index 7cee500af..e237b664c 100644 --- a/cinder/tests/test_windows_utils.py +++ b/cinder/tests/windows/test_windows_utils.py @@ -22,11 +22,7 @@ from cinder.volume.drivers.windows import windows_utils class WindowsUtilsTestCase(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_RET_VAL_ERROR = 10 _FAKE_VHD_SIZE = 1024 @@ -36,36 +32,8 @@ class WindowsUtilsTestCase(test.TestCase): super(WindowsUtilsTestCase, self).setUp() windows_utils.WindowsUtils.__init__ = lambda x: None self.wutils = windows_utils.WindowsUtils() - self.wutils._conn_virt = mock.MagicMock() self.wutils.time = mock.MagicMock() - def test_convert_vhd(self): - self.wutils.check_ret_val = mock.MagicMock() - mock_img_svc = self.wutils._conn_virt.Msvm_ImageManagementService()[0] - mock_img_svc.ConvertVirtualHardDisk.return_value = ( - self._FAKE_JOB_PATH, self._FAKE_RET_VAL) - - self.wutils.convert_vhd(self._FAKE_VHD_PATH, - self._FAKE_DESTINATION_PATH, - self._FAKE_TYPE) - - 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.wutils.check_ret_val = mock.MagicMock() - mock_img_svc = self.wutils._conn_virt.Msvm_ImageManagementService()[0] - mock_img_svc.ExpandVirtualHardDisk.return_value = (self._FAKE_JOB_PATH, - self._FAKE_RET_VAL) - - self.wutils.resize_vhd(self._FAKE_VHD_PATH, - self._FAKE_VHD_SIZE) - - mock_img_svc.ExpandVirtualHardDisk.assert_called_once() - self.wutils.check_ret_val.assert_called_once_with(self._FAKE_RET_VAL, - self._FAKE_JOB_PATH) - 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: diff --git a/cinder/volume/drivers/windows/utilsfactory.py b/cinder/volume/drivers/windows/utilsfactory.py new file mode 100644 index 000000000..57a36927f --- /dev/null +++ b/cinder/volume/drivers/windows/utilsfactory.py @@ -0,0 +1,37 @@ +# 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 new file mode 100644 index 000000000..a6b3d80c1 --- /dev/null +++ b/cinder/volume/drivers/windows/vhdutils.py @@ -0,0 +1,57 @@ +# 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. + +Official VHD format specs can be retrieved at: +http://technet.microsoft.com/en-us/library/bb676673.aspx +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 os + +if os.name == 'nt': + import wmi + +from cinder.openstack.common import log as logging +from cinder.volume.drivers.windows import windows_utils + +LOG = logging.getLogger(__name__) + + +class VHDUtils(object): + + def __init__(self): + self.utils = windows_utils.WindowsUtils() + self._conn = wmi.WMI(moniker='//./root/virtualization') + + 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) + + def _get_resize_method(self): + image_man_svc = self._conn.Msvm_ImageManagementService()[0] + return image_man_svc.ExpandVirtualHardDisk + + 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) diff --git a/cinder/volume/drivers/windows/vhdutilsv2.py b/cinder/volume/drivers/windows/vhdutilsv2.py new file mode 100644 index 000000000..e50cca678 --- /dev/null +++ b/cinder/volume/drivers/windows/vhdutilsv2.py @@ -0,0 +1,64 @@ +# 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 fbafb85fb..f88a9bc78 100644 --- a/cinder/volume/drivers/windows/windows.py +++ b/cinder/volume/drivers/windows/windows.py @@ -24,9 +24,10 @@ import os from oslo.config import cfg 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 constants +from cinder.volume.drivers.windows import utilsfactory from cinder.volume.drivers.windows import windows_utils LOG = logging.getLogger(__name__) @@ -59,6 +60,7 @@ class WindowsDriver(driver.ISCSIDriver): Validate the flags we care about """ self.utils = windows_utils.WindowsUtils() + self.vhdutils = utilsfactory.get_vhdutils() def check_for_setup_error(self): """Check that the driver is working and can communicate.""" @@ -97,12 +99,15 @@ class WindowsDriver(driver.ISCSIDriver): self.utils.create_volume(vhd_path, vol_name, vol_size) - def local_path(self, volume): + 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) - return os.path.join(base_vhd_folder, str(volume['name']) + ".vhd") + if not format: + format = self.utils.get_supported_format() + return os.path.join(base_vhd_folder, str(volume['name']) + "." + + format) def delete_volume(self, volume): """Driver entry point for destroying existing volumes.""" @@ -168,6 +173,7 @@ class WindowsDriver(driver.ISCSIDriver): def copy_image_to_volume(self, context, volume, image_service, image_id): """Fetch the image from image_service and create a volume using it.""" # Convert to VHD and file back to VHD + vhd_type = self.utils.get_supported_vhd_type() if (CONF.image_conversion_dir and not os.path.exists(CONF.image_conversion_dir)): os.makedirs(CONF.image_conversion_dir) @@ -179,21 +185,32 @@ class WindowsDriver(driver.ISCSIDriver): # the desired image. self.utils.change_disk_status(volume['name'], False) os.unlink(volume_path) - self.utils.convert_vhd(tmp, volume_path, - constants.VHD_TYPE_FIXED) - self.utils.resize_vhd(volume_path, - volume['size'] << 30) + self.vhdutils.convert_vhd(tmp, volume_path, + vhd_type) + self.vhdutils.resize_vhd(volume_path, + volume['size'] << 30) self.utils.change_disk_status(volume['name'], True) def copy_volume_to_image(self, context, volume, image_service, image_meta): """Copy the volume to the specified image.""" - - # Copy the volume to the image conversion dir + disk_format = self.utils.get_supported_format() temp_vhd_path = os.path.join(self.configuration.image_conversion_dir, - str(image_meta['id']) + ".vhd") - self.utils.copy_vhd_disk(self.local_path(volume), temp_vhd_path) - image_utils.upload_volume(context, image_service, image_meta, - temp_vhd_path, 'vpc') + str(image_meta['id']) + '.' + disk_format) + upload_image = temp_vhd_path + + try: + self.utils.copy_vhd_disk(self.local_path(volume), temp_vhd_path) + # qemu-img does not yet fully support vhdx format, so we'll first + # 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) + + image_utils.upload_volume(context, image_service, image_meta, + upload_image, 'vpc') + finally: + fileutils.delete_if_exists(temp_vhd_path) + fileutils.delete_if_exists(upload_image) def create_cloned_volume(self, volume, src_vref): """Creates a clone of the specified volume.""" diff --git a/cinder/volume/drivers/windows/windows_utils.py b/cinder/volume/drivers/windows/windows_utils.py index d99958384..6b30561ee 100644 --- a/cinder/volume/drivers/windows/windows_utils.py +++ b/cinder/volume/drivers/windows/windows_utils.py @@ -37,7 +37,6 @@ class WindowsUtils(object): # Set the flags self._conn_wmi = wmi.WMI(moniker='//./root/wmi') self._conn_cimv2 = wmi.WMI(moniker='//./root/cimv2') - self._conn_virt = wmi.WMI(moniker='//./root/virtualization') def check_for_setup_error(self): """Check that the driver is working and can communicate. @@ -322,19 +321,24 @@ class WindowsUtils(object): LOG.error(err_msg) raise exception.VolumeBackendAPIException(data=err_msg) - def convert_vhd(self, src, dest, vhd_type): - # Due to the fact that qemu does not fully support vhdx format yet, - # we must use WMI make conversions between vhd and vhdx formats - image_man_svc = self._conn_virt.Msvm_ImageManagementService()[0] - (job_path, ret_val) = image_man_svc.ConvertVirtualHardDisk( - SourcePath=src, DestinationPath=dest, Type=vhd_type) - self.check_ret_val(ret_val, job_path) - - def resize_vhd(self, vhd_path, new_max_size): - image_man_svc = self._conn_virt.Msvm_ImageManagementService()[0] - (job_path, ret_val) = image_man_svc.ExpandVirtualHardDisk( - Path=vhd_path, MaxInternalSize=new_max_size) - self.check_ret_val(ret_val, job_path) + 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] + + def get_windows_version(self): + return self._conn_cimv2.Win32_OperatingSystem()[0].Version + + def get_supported_format(self): + if self.check_min_windows_version(6, 3): + return 'vhdx' + else: + return 'vhd' + + def get_supported_vhd_type(self): + if self.check_min_windows_version(6, 3): + return constants.VHD_TYPE_DYNAMIC + 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: -- 2.45.2