--- /dev/null
+# 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 importlib
+import os
+import sys
+
+import mock
+
+from cinder import exception
+from cinder.image import image_utils
+from cinder import test
+
+
+class WindowsSmbFsTestCase(test.TestCase):
+
+ _FAKE_SHARE = '//1.2.3.4/share1'
+ _FAKE_MNT_BASE = 'c:\openstack\mnt'
+ _FAKE_MNT_POINT = os.path.join(_FAKE_MNT_BASE, 'fake_hash')
+ _FAKE_VOLUME_NAME = 'volume-4f711859-4928-4cb7-801a-a50c37ceaccc'
+ _FAKE_SNAPSHOT_NAME = _FAKE_VOLUME_NAME + '-snapshot.vhdx'
+ _FAKE_VOLUME_PATH = os.path.join(_FAKE_MNT_POINT,
+ _FAKE_VOLUME_NAME)
+ _FAKE_SNAPSHOT_PATH = os.path.join(_FAKE_MNT_POINT,
+ _FAKE_SNAPSHOT_NAME)
+ _FAKE_TOTAL_SIZE = '2048'
+ _FAKE_TOTAL_AVAILABLE = '1024'
+ _FAKE_TOTAL_ALLOCATED = 1024
+ _FAKE_VOLUME = {'id': 'e8d76af4-cbb9-4b70-8e9e-5a133f1a1a66',
+ 'size': 1,
+ 'provider_location': _FAKE_SHARE}
+ _FAKE_SNAPSHOT = {'id': '35a23942-7625-4683-ad84-144b76e87a80',
+ 'volume': _FAKE_VOLUME,
+ 'volume_size': _FAKE_VOLUME['size']}
+ _FAKE_SHARE_OPTS = '-o username=Administrator,password=12345'
+ _FAKE_VOLUME_PATH = os.path.join(_FAKE_MNT_POINT,
+ _FAKE_VOLUME_NAME + '.vhdx')
+ _FAKE_LISTDIR = [_FAKE_VOLUME_NAME + '.vhd',
+ _FAKE_VOLUME_NAME + '.vhdx', 'fake_folder']
+
+ def setUp(self):
+ super(WindowsSmbFsTestCase, self).setUp()
+ self._mock_wmi = mock.MagicMock()
+
+ self._platform_patcher = mock.patch('sys.platform', 'win32')
+
+ mock.patch.dict(sys.modules, wmi=self._mock_wmi,
+ ctypes=self._mock_wmi).start()
+
+ self._platform_patcher.start()
+ # self._wmi_patcher.start()
+ self.addCleanup(mock.patch.stopall)
+
+ smbfs = importlib.import_module(
+ 'cinder.volume.drivers.windows.smbfs')
+ smbfs.WindowsSmbfsDriver.__init__ = lambda x: None
+ self._smbfs_driver = smbfs.WindowsSmbfsDriver()
+ self._smbfs_driver._remotefsclient = mock.Mock()
+ self._smbfs_driver._delete = mock.Mock()
+ self._smbfs_driver.local_path = mock.Mock(
+ return_value=self._FAKE_VOLUME_PATH)
+ self._smbfs_driver.vhdutils = mock.Mock()
+ self._smbfs_driver._check_os_platform = mock.Mock()
+
+ def _test_create_volume(self, volume_exists=False, volume_format='vhdx'):
+ self._smbfs_driver.create_dynamic_vhd = mock.MagicMock()
+ fake_create = self._smbfs_driver.vhdutils.create_dynamic_vhd
+ self._smbfs_driver.get_volume_format = mock.Mock(
+ return_value=volume_format)
+
+ with mock.patch('os.path.exists', new=lambda x: volume_exists):
+ if volume_exists or volume_format not in ('vhd', 'vhdx'):
+ self.assertRaises(exception.InvalidVolume,
+ self._smbfs_driver._do_create_volume,
+ self._FAKE_VOLUME)
+ else:
+ fake_vol_path = self._FAKE_VOLUME_PATH
+ self._smbfs_driver._do_create_volume(self._FAKE_VOLUME)
+ fake_create.assert_called_once_with(
+ fake_vol_path, self._FAKE_VOLUME['size'] << 30)
+
+ def test_create_volume(self):
+ self._test_create_volume()
+
+ def test_create_existing_volume(self):
+ self._test_create_volume(True)
+
+ def test_create_volume_invalid_volume(self):
+ self._test_create_volume(volume_format="qcow")
+
+ def test_get_capacity_info(self):
+ self._smbfs_driver._remotefsclient.get_capacity_info = mock.Mock(
+ return_value=(self._FAKE_TOTAL_SIZE, self._FAKE_TOTAL_AVAILABLE))
+ self._smbfs_driver._get_total_allocated = mock.Mock(
+ return_value=self._FAKE_TOTAL_ALLOCATED)
+
+ ret_val = self._smbfs_driver._get_capacity_info(self._FAKE_SHARE)
+ expected_ret_val = [int(x) for x in [self._FAKE_TOTAL_SIZE,
+ self._FAKE_TOTAL_AVAILABLE,
+ self._FAKE_TOTAL_ALLOCATED]]
+ self.assertEqual(ret_val, expected_ret_val)
+
+ def test_get_total_allocated(self):
+ fake_listdir = mock.Mock(side_effect=[self._FAKE_LISTDIR,
+ self._FAKE_LISTDIR[:-1]])
+ fake_folder_path = os.path.join(self._FAKE_SHARE, 'fake_folder')
+ fake_isdir = lambda x: x == fake_folder_path
+ self._smbfs_driver._remotefsclient.is_symlink = mock.Mock(
+ return_value=False)
+ fake_getsize = mock.Mock(return_value=self._FAKE_VOLUME['size'])
+ self._smbfs_driver.vhdutils.get_vhd_size = mock.Mock(
+ return_value={'VirtualSize': 1})
+
+ with mock.patch.multiple('os.path', isdir=fake_isdir,
+ getsize=fake_getsize):
+ with mock.patch('os.listdir', fake_listdir):
+ ret_val = self._smbfs_driver._get_total_allocated(
+ self._FAKE_SHARE)
+ self.assertEqual(ret_val, 4)
+
+ def _test_get_img_info(self, backing_file=None):
+ self._smbfs_driver.vhdutils.get_vhd_parent_path.return_value = (
+ backing_file)
+
+ image_info = self._smbfs_driver._qemu_img_info(self._FAKE_VOLUME_PATH)
+ self.assertEqual(self._FAKE_VOLUME_NAME + '.vhdx',
+ image_info.image)
+ backing_file_name = backing_file and os.path.basename(backing_file)
+ self.assertEqual(backing_file_name, image_info.backing_file)
+
+ def test_get_img_info_without_backing_file(self):
+ self._test_get_img_info()
+
+ def test_get_snapshot_info(self):
+ self._test_get_img_info(self._FAKE_VOLUME_PATH)
+
+ def test_create_snapshot(self):
+ self._smbfs_driver.vhdutils.create_differencing_vhd = (
+ mock.Mock())
+ self._smbfs_driver._local_volume_dir = mock.Mock(
+ return_value=self._FAKE_MNT_POINT)
+
+ fake_create_diff = (
+ self._smbfs_driver.vhdutils.create_differencing_vhd)
+
+ self._smbfs_driver._do_create_snapshot(
+ self._FAKE_SNAPSHOT,
+ os.path.basename(self._FAKE_VOLUME_PATH),
+ self._FAKE_SNAPSHOT_PATH)
+
+ fake_create_diff.assert_called_once_with(self._FAKE_SNAPSHOT_PATH,
+ self._FAKE_VOLUME_PATH)
+
+ def _test_copy_volume_to_image(self, has_parent=False,
+ volume_format='vhd'):
+ drv = self._smbfs_driver
+
+ fake_image_meta = {'id': 'fake-image-id'}
+
+ if has_parent:
+ fake_volume_path = self._FAKE_SNAPSHOT_PATH
+ fake_parent_path = self._FAKE_VOLUME_PATH
+ else:
+ fake_volume_path = self._FAKE_VOLUME_PATH
+ fake_parent_path = None
+
+ if volume_format == drv._DISK_FORMAT_VHD:
+ fake_volume_path = fake_volume_path[:-1]
+
+ fake_active_image = os.path.basename(fake_volume_path)
+
+ drv.get_active_image_from_info = mock.Mock(
+ return_value=fake_active_image)
+ drv._local_volume_dir = mock.Mock(
+ return_value=self._FAKE_MNT_POINT)
+ drv.get_volume_format = mock.Mock(
+ return_value=volume_format)
+ drv.vhdutils.get_vhd_parent_path.return_value = (
+ fake_parent_path)
+
+ with mock.patch.object(image_utils, 'upload_volume') as (
+ fake_upload_volume):
+ drv.copy_volume_to_image(
+ mock.sentinel.context, self._FAKE_VOLUME,
+ mock.sentinel.image_service, fake_image_meta)
+
+ expected_conversion = (
+ has_parent or volume_format == drv._DISK_FORMAT_VHDX)
+
+ if expected_conversion:
+ fake_temp_image_name = '%s.temp_image.%s.%s' % (
+ self._FAKE_VOLUME['id'],
+ fake_image_meta['id'],
+ drv._DISK_FORMAT_VHD)
+ fake_temp_image_path = os.path.join(
+ self._FAKE_MNT_POINT,
+ fake_temp_image_name)
+ fake_active_image_path = os.path.join(
+ self._FAKE_MNT_POINT,
+ fake_active_image)
+ upload_path = fake_temp_image_path
+
+ drv.vhdutils.convert_vhd.assert_called_once_with(
+ fake_active_image_path,
+ fake_temp_image_path)
+ drv._delete.assert_called_once_with(
+ fake_temp_image_path)
+ else:
+ upload_path = fake_volume_path
+
+ fake_upload_volume.assert_called_once_with(
+ mock.sentinel.context, mock.sentinel.image_service,
+ fake_image_meta, upload_path, drv._DISK_FORMAT_VHD)
+
+ def test_copy_volume_to_image_having_snapshot(self):
+ self._test_copy_volume_to_image(has_parent=True)
+
+ def test_copy_vhdx_volume_to_image(self):
+ self._test_copy_volume_to_image(volume_format='vhdx')
+
+ def test_copy_vhd_volume_to_image(self):
+ self._test_copy_volume_to_image(volume_format='vhd')
+
+ def _test_copy_image_to_volume(self, qemu_version=[1, 7]):
+ drv = self._smbfs_driver
+
+ fake_image_id = 'fake_image_id'
+ fake_image_service = mock.MagicMock()
+ fake_image_service.show.return_value = (
+ {'id': fake_image_id, 'disk_format': 'raw'})
+
+ drv.get_volume_format = mock.Mock(
+ return_value='vhdx')
+ drv.local_path = mock.Mock(
+ return_value=self._FAKE_VOLUME_PATH)
+ drv._local_volume_dir = mock.Mock(
+ return_value=self._FAKE_MNT_POINT)
+ drv.get_qemu_version = mock.Mock(
+ return_value=qemu_version)
+ drv.configuration = mock.MagicMock()
+ drv.configuration.volume_dd_blocksize = mock.sentinel.block_size
+
+ with mock.patch.object(image_utils,
+ 'fetch_to_volume_format') as fake_fetch:
+ drv.copy_image_to_volume(
+ mock.sentinel.context, self._FAKE_VOLUME,
+ fake_image_service,
+ fake_image_id)
+
+ if qemu_version < [1, 7]:
+ fake_temp_image_name = '%s.temp_image.%s.vhd' % (
+ self._FAKE_VOLUME['id'],
+ fake_image_id)
+ fetch_path = os.path.join(self._FAKE_MNT_POINT,
+ fake_temp_image_name)
+ fetch_format = 'vpc'
+ drv.vhdutils.convert_vhd.assert_called_once_with(
+ fetch_path, self._FAKE_VOLUME_PATH)
+ drv._delete.assert_called_with(fetch_path)
+
+ else:
+ fetch_path = self._FAKE_VOLUME_PATH
+ fetch_format = 'vhdx'
+
+ fake_fetch.assert_called_once_with(
+ mock.sentinel.context, fake_image_service, fake_image_id,
+ fetch_path, fetch_format, mock.sentinel.block_size)
+
+ def test_copy_image_to_volume(self):
+ self._test_copy_image_to_volume()
+
+ def test_copy_image_to_volume_with_conversion(self):
+ self._test_copy_image_to_volume([1, 5])
+
+ def test_copy_volume_from_snapshot(self):
+ drv = self._smbfs_driver
+ fake_volume_info = {
+ self._FAKE_SNAPSHOT['id']: 'fake_snapshot_file_name'}
+ fake_img_info = mock.MagicMock()
+ fake_img_info.backing_file = self._FAKE_VOLUME_NAME + '.vhdx'
+
+ drv._local_path_volume_info = mock.Mock(
+ return_value=self._FAKE_VOLUME_PATH + '.info')
+ drv._local_volume_dir = mock.Mock(
+ return_value=self._FAKE_MNT_POINT)
+ drv._read_info_file = mock.Mock(
+ return_value=fake_volume_info)
+ drv._qemu_img_info = mock.Mock(
+ return_value=fake_img_info)
+ drv.local_path = mock.Mock(
+ return_value=mock.sentinel.new_volume_path)
+
+ drv._copy_volume_from_snapshot(
+ self._FAKE_SNAPSHOT, self._FAKE_VOLUME,
+ self._FAKE_VOLUME['size'])
+
+ drv._delete.assert_called_once_with(mock.sentinel.new_volume_path)
+ drv.vhdutils.convert_vhd.assert_called_once_with(
+ self._FAKE_VOLUME_PATH,
+ mock.sentinel.new_volume_path)
+ drv.vhdutils.resize_vhd.assert_called_once_with(
+ mock.sentinel.new_volume_path, self._FAKE_VOLUME['size'] << 30)
+
+ def test_rebase_img(self):
+ self._smbfs_driver._rebase_img(
+ self._FAKE_SNAPSHOT_PATH,
+ self._FAKE_VOLUME_NAME + '.vhdx', 'vhdx')
+ self._smbfs_driver.vhdutils.reconnect_parent.assert_called_once_with(
+ self._FAKE_SNAPSHOT_PATH, self._FAKE_VOLUME_PATH)
_FAKE_JOB_PATH = 'fake_job_path'
_FAKE_VHD_PATH = r'C:\fake\vhd.vhd'
_FAKE_DEST_PATH = r'C:\fake\destination.vhdx'
+ _FAKE_FILE_HANDLE = 'fake_file_handle'
_FAKE_RET_VAL = 0
_FAKE_VHD_SIZE = 1024
- _FAKE_DEVICE_ID = 'fake_device_id'
def setUp(self):
super(VHDUtilsTestCase, self).setUp()
# references.
fake_ctypes.byref = lambda x: x
fake_ctypes.c_wchar_p = lambda x: x
+ fake_ctypes.c_ulong = lambda x: x
mock.patch.multiple(
'cinder.volume.drivers.windows.vhdutils', ctypes=fake_ctypes,
Win32_RESIZE_VIRTUAL_DISK_PARAMETERS=mock.DEFAULT,
Win32_CREATE_VIRTUAL_DISK_PARAMETERS=mock.DEFAULT,
Win32_VIRTUAL_STORAGE_TYPE=mock.DEFAULT,
+ Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V1=mock.DEFAULT,
+ Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V2=mock.DEFAULT,
+ Win32_MERGE_VIRTUAL_DISK_PARAMETERS=mock.DEFAULT,
+ Win32_GET_VIRTUAL_DISK_INFO_PARAMETERS=mock.DEFAULT,
+ Win32_SET_VIRTUAL_DISK_INFO_PARAMETERS=mock.DEFAULT,
create=True).start()
- def _test_convert_vhd(self, convertion_failed=False):
+ def _test_create_vhd(self, src_path=None, max_internal_size=0,
+ parent_path=None, create_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))
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.Win32_VIRTUAL_STORAGE_TYPE.side_effect = [
+ fake_vst, None, fake_source_vst]
vhdutils.virtdisk.CreateVirtualDisk.return_value = int(
- convertion_failed)
+ create_failed)
- if convertion_failed:
+ if create_failed:
self.assertRaises(exception.VolumeBackendAPIException,
- self._vhdutils.convert_vhd,
- self._FAKE_VHD_PATH, self._FAKE_DEST_PATH,
- self._FAKE_TYPE)
+ self._vhdutils._create_vhd,
+ self._FAKE_DEST_PATH,
+ constants.VHD_TYPE_DYNAMIC,
+ src_path=src_path,
+ max_internal_size=max_internal_size,
+ parent_path=parent_path)
else:
- self._vhdutils.convert_vhd(self._FAKE_VHD_PATH,
- self._FAKE_DEST_PATH,
- self._FAKE_TYPE)
+ self._vhdutils._create_vhd(self._FAKE_DEST_PATH,
+ constants.VHD_TYPE_DYNAMIC,
+ src_path=src_path,
+ max_internal_size=max_internal_size,
+ parent_path=parent_path)
- self.assertEqual(vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHDX,
- fake_vst.DeviceId)
self.assertEqual(vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHD,
- fake_source_vst.DeviceId)
+ fake_vst.DeviceId)
+ self.assertEqual(parent_path, fake_params.ParentPath)
+ self.assertEqual(max_internal_size, fake_params.MaximumSize)
+
+ if src_path:
+ self.assertEqual(vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHDX,
+ fake_source_vst.DeviceId)
+ self.assertEqual(src_path, fake_params.SourcePath)
vhdutils.virtdisk.CreateVirtualDisk.assert_called_with(
vhdutils.ctypes.byref(fake_vst),
vhdutils.ctypes.byref(vhdutils.wintypes.HANDLE()))
self.assertTrue(self._vhdutils._close.called)
- def test_convert_vhd_successfully(self):
- self._test_convert_vhd()
+ def test_create_vhd_exception(self):
+ self._test_create_vhd(create_failed=True)
- def test_convert_vhd_exception(self):
- self._test_convert_vhd(True)
+ def test_create_dynamic_vhd(self):
+ self._test_create_vhd(max_internal_size=1 << 30)
+
+ def test_create_differencing_vhd(self):
+ self._test_create_vhd(parent_path=self._FAKE_VHD_PATH)
+
+ def test_convert_vhd(self):
+ self._test_create_vhd(src_path=self._FAKE_VHD_PATH)
def _test_open(self, open_failed=False):
+ fake_device_id = vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHD
+
vhdutils.virtdisk.OpenVirtualDisk.return_value = int(open_failed)
+ self._vhdutils._get_device_id_by_path = mock.Mock(
+ return_value=fake_device_id)
fake_vst = vhdutils.Win32_VIRTUAL_STORAGE_TYPE.return_value
+ fake_params = 'fake_params'
+ fake_access_mask = vhdutils.VIRTUAL_DISK_ACCESS_NONE
+ fake_open_flag = vhdutils.OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS
if open_failed:
self.assertRaises(exception.VolumeBackendAPIException,
self._vhdutils._open,
- self._FAKE_DEVICE_ID, self._FAKE_VHD_PATH)
+ self._FAKE_VHD_PATH)
else:
- self._vhdutils._open(self._FAKE_DEVICE_ID,
- self._FAKE_VHD_PATH)
+ self._vhdutils._open(self._FAKE_VHD_PATH,
+ open_flag=fake_open_flag,
+ open_access_mask=fake_access_mask,
+ open_params=fake_params)
- 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)
+ vhdutils.virtdisk.OpenVirtualDisk.assert_called_with(
+ vhdutils.ctypes.byref(fake_vst),
+ vhdutils.ctypes.c_wchar_p(self._FAKE_VHD_PATH),
+ fake_access_mask, fake_open_flag, fake_params,
+ vhdutils.ctypes.byref(vhdutils.wintypes.HANDLE()))
+
+ self.assertEqual(fake_device_id, fake_vst.DeviceId)
def test_open_success(self):
self._test_open()
vhdutils.Win32_RESIZE_VIRTUAL_DISK_PARAMETERS.return_value)
self._vhdutils._open = mock.Mock(
- return_value=vhdutils.ctypes.byref(
- vhdutils.wintypes.HANDLE()))
+ return_value=self._FAKE_FILE_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)
self._FAKE_VHD_SIZE)
vhdutils.virtdisk.ResizeVirtualDisk.assert_called_with(
- vhdutils.ctypes.byref(vhdutils.wintypes.HANDLE()),
+ self._FAKE_FILE_HANDLE,
vhdutils.RESIZE_VIRTUAL_DISK_FLAG_NONE,
vhdutils.ctypes.byref(fake_params),
None)
def test_resize_vhd_failed(self):
self._test_resize_vhd(resize_failed=True)
+
+ def _test_merge_vhd(self, merge_failed=False):
+ self._vhdutils._open = mock.Mock(
+ return_value=self._FAKE_FILE_HANDLE)
+ self._vhdutils._close = mock.Mock()
+
+ fake_open_params = vhdutils.Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V1()
+ fake_params = vhdutils.Win32_MERGE_VIRTUAL_DISK_PARAMETERS()
+
+ vhdutils.virtdisk.MergeVirtualDisk.return_value = int(
+ merge_failed)
+ vhdutils.Win32_RESIZE_VIRTUAL_DISK_PARAMETERS.return_value = (
+ fake_params)
+
+ if merge_failed:
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self._vhdutils.merge_vhd,
+ self._FAKE_VHD_PATH)
+ else:
+ self._vhdutils.merge_vhd(self._FAKE_VHD_PATH)
+
+ self._vhdutils._open.assert_called_once_with(
+ self._FAKE_VHD_PATH,
+ open_params=vhdutils.ctypes.byref(fake_open_params))
+ self.assertEqual(vhdutils.OPEN_VIRTUAL_DISK_VERSION_1,
+ fake_open_params.Version)
+ self.assertEqual(2, fake_open_params.RWDepth)
+ vhdutils.virtdisk.MergeVirtualDisk.assert_called_with(
+ self._FAKE_FILE_HANDLE,
+ vhdutils.MERGE_VIRTUAL_DISK_FLAG_NONE,
+ vhdutils.ctypes.byref(fake_params),
+ None)
+
+ def test_merge_vhd_success(self):
+ self._test_merge_vhd()
+
+ def test_merge_vhd_failed(self):
+ self._test_merge_vhd(merge_failed=True)
+
+ def _test_get_vhd_info_member(self, get_vhd_info_failed=False):
+ fake_params = vhdutils.Win32_GET_VIRTUAL_DISK_INFO_PARAMETERS()
+ fake_info_size = vhdutils.ctypes.sizeof(fake_params)
+
+ vhdutils.virtdisk.GetVirtualDiskInformation.return_value = (
+ get_vhd_info_failed)
+ self._vhdutils._close = mock.Mock()
+
+ if get_vhd_info_failed:
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self._vhdutils._get_vhd_info_member,
+ self._FAKE_VHD_PATH,
+ vhdutils.GET_VIRTUAL_DISK_INFO_SIZE)
+ self._vhdutils._close.assert_called_with(
+ self._FAKE_VHD_PATH)
+ else:
+ self._vhdutils._get_vhd_info_member(
+ self._FAKE_VHD_PATH,
+ vhdutils.GET_VIRTUAL_DISK_INFO_SIZE)
+
+ vhdutils.virtdisk.GetVirtualDiskInformation.assert_called_with(
+ self._FAKE_VHD_PATH,
+ vhdutils.ctypes.byref(
+ vhdutils.ctypes.c_ulong(fake_info_size)),
+ vhdutils.ctypes.byref(fake_params), 0)
+
+ def test_get_vhd_info_member_success(self):
+ self._test_get_vhd_info_member()
+
+ def test_get_vhd_info_member_failed(self):
+ self._test_get_vhd_info_member(get_vhd_info_failed=True)
+
+ def test_get_vhd_info(self):
+ fake_vhd_info = {'VirtualSize': self._FAKE_VHD_SIZE}
+ fake_info_member = vhdutils.GET_VIRTUAL_DISK_INFO_SIZE
+
+ self._vhdutils._open = mock.Mock(
+ return_value=self._FAKE_FILE_HANDLE)
+ self._vhdutils._close = mock.Mock()
+ self._vhdutils._get_vhd_info_member = mock.Mock(
+ return_value=fake_vhd_info)
+
+ ret_val = self._vhdutils.get_vhd_info(self._FAKE_VHD_PATH,
+ [fake_info_member])
+
+ self.assertEqual(fake_vhd_info, ret_val)
+ self._vhdutils._open.assert_called_once_with(
+ self._FAKE_VHD_PATH,
+ open_access_mask=vhdutils.VIRTUAL_DISK_ACCESS_GET_INFO)
+ self._vhdutils._get_vhd_info_member.assert_called_with(
+ self._FAKE_FILE_HANDLE, fake_info_member)
+ self._vhdutils._close.assert_called_once()
+
+ def test_parse_vhd_info(self):
+ fake_physical_size = self._FAKE_VHD_SIZE + 1
+ fake_info_member = vhdutils.GET_VIRTUAL_DISK_INFO_SIZE
+ fake_info = mock.Mock()
+ fake_info.VhdInfo.Size._fields_ = [
+ ("VirtualSize", vhdutils.wintypes.ULARGE_INTEGER),
+ ("PhysicalSize", vhdutils.wintypes.ULARGE_INTEGER)]
+ fake_info.VhdInfo.Size.VirtualSize = self._FAKE_VHD_SIZE
+ fake_info.VhdInfo.Size.PhysicalSize = fake_physical_size
+
+ ret_val = self._vhdutils._parse_vhd_info(fake_info, fake_info_member)
+ expected = {'VirtualSize': self._FAKE_VHD_SIZE,
+ 'PhysicalSize': fake_physical_size}
+
+ self.assertEqual(expected, ret_val)
+
+ def _test_reconnect_parent(self, reconnect_failed=False):
+ fake_params = vhdutils.Win32_SET_VIRTUAL_DISK_INFO_PARAMETERS()
+ fake_open_params = vhdutils.Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V2()
+
+ self._vhdutils._open = mock.Mock(return_value=self._FAKE_FILE_HANDLE)
+ self._vhdutils._close = mock.Mock()
+ vhdutils.virtdisk.SetVirtualDiskInformation.return_value = int(
+ reconnect_failed)
+
+ if reconnect_failed:
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self._vhdutils.reconnect_parent,
+ self._FAKE_VHD_PATH, self._FAKE_DEST_PATH)
+
+ else:
+ self._vhdutils.reconnect_parent(self._FAKE_VHD_PATH,
+ self._FAKE_DEST_PATH)
+
+ self._vhdutils._open.assert_called_once_with(
+ self._FAKE_VHD_PATH,
+ open_flag=vhdutils.OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS,
+ open_access_mask=vhdutils.VIRTUAL_DISK_ACCESS_NONE,
+ open_params=vhdutils.ctypes.byref(fake_open_params))
+ self.assertEqual(vhdutils.OPEN_VIRTUAL_DISK_VERSION_2,
+ fake_open_params.Version)
+ self.assertFalse(fake_open_params.GetInfoOnly)
+ vhdutils.virtdisk.SetVirtualDiskInformation.assert_called_once_with(
+ self._FAKE_FILE_HANDLE, vhdutils.ctypes.byref(fake_params))
+ self.assertEqual(self._FAKE_DEST_PATH, fake_params.ParentFilePath)
+
+ def test_reconnect_parent_success(self):
+ self._test_reconnect_parent()
+
+ def test_reconnect_parent_failed(self):
+ self._test_reconnect_parent(reconnect_failed=True)
--- /dev/null
+# 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 ctypes
+import os
+
+import mock
+
+from cinder import exception
+from cinder import test
+from cinder.volume.drivers.windows import remotefs
+
+
+class WindowsRemoteFsTestCase(test.TestCase):
+ _FAKE_SHARE = '//1.2.3.4/share1'
+ _FAKE_MNT_BASE = 'C:\OpenStack\mnt'
+ _FAKE_HASH = 'db0bf952c1734092b83e8990bd321131'
+ _FAKE_MNT_POINT = os.path.join(_FAKE_MNT_BASE, _FAKE_HASH)
+ _FAKE_SHARE_OPTS = '-o username=Administrator,password=12345'
+ _FAKE_OPTIONS_DICT = {'username': 'Administrator',
+ 'password': '12345'}
+
+ def setUp(self):
+ super(WindowsRemoteFsTestCase, self).setUp()
+
+ remotefs.ctypes.windll = mock.MagicMock()
+ remotefs.WindowsRemoteFsClient.__init__ = mock.Mock(return_value=None)
+
+ self._remotefs = remotefs.WindowsRemoteFsClient(
+ 'cifs', root_helper=None,
+ smbfs_mount_point_base=self._FAKE_MNT_BASE)
+ self._remotefs._mount_base = self._FAKE_MNT_BASE
+ self._remotefs.smb_conn = mock.MagicMock()
+ self._remotefs.conn_cimv2 = mock.MagicMock()
+
+ def _test_mount_share(self, mount_point_exists=True, is_symlink=True,
+ mount_base_exists=True):
+ fake_exists = mock.Mock(return_value=mount_point_exists)
+ fake_isdir = mock.Mock(return_value=mount_base_exists)
+ fake_makedirs = mock.Mock()
+ with mock.patch.multiple('os.path', exists=fake_exists,
+ isdir=fake_isdir):
+ with mock.patch('os.makedirs', fake_makedirs):
+ self._remotefs.is_symlink = mock.Mock(
+ return_value=is_symlink)
+ self._remotefs.create_sym_link = mock.MagicMock()
+ self._remotefs._mount = mock.MagicMock()
+ fake_norm_path = os.path.abspath(self._FAKE_SHARE)
+
+ if mount_point_exists:
+ if not is_symlink:
+ self.assertRaises(exception.SmbfsException,
+ self._remotefs.mount,
+ self._FAKE_MNT_POINT,
+ self._FAKE_OPTIONS_DICT)
+ else:
+ self._remotefs.mount(self._FAKE_SHARE,
+ self._FAKE_OPTIONS_DICT)
+ if not mount_base_exists:
+ fake_makedirs.assert_called_once_with(
+ self._FAKE_MNT_BASE)
+ self._remotefs._mount.assert_called_once_with(
+ fake_norm_path, self._FAKE_OPTIONS_DICT)
+ self._remotefs.create_sym_link.assert_called_once_with(
+ self._FAKE_MNT_POINT, fake_norm_path)
+
+ def test_mount_linked_share(self):
+ # The mountpoint contains a symlink targeting the share path
+ self._test_mount_share(True)
+
+ def test_mount_unlinked_share(self):
+ self._test_mount_share(False)
+
+ def test_mount_point_exception(self):
+ # The mountpoint already exists but it is not a symlink
+ self._test_mount_share(True, False)
+
+ def test_mount_base_missing(self):
+ # The mount point base dir does not exist
+ self._test_mount_share(mount_base_exists=False)
+
+ def _test_check_symlink(self, is_symlink=True, python_version=(2, 7),
+ is_dir=True):
+ fake_isdir = mock.Mock(return_value=is_dir)
+ fake_islink = mock.Mock(return_value=is_symlink)
+ with mock.patch('sys.version_info', python_version):
+ with mock.patch.multiple('os.path', isdir=fake_isdir,
+ islink=fake_islink):
+ if is_symlink:
+ ret_value = 0x400
+ else:
+ ret_value = 0x80
+ fake_get_attributes = mock.Mock(return_value=ret_value)
+ ctypes.windll.kernel32.GetFileAttributesW = fake_get_attributes
+
+ ret_value = self._remotefs.is_symlink(self._FAKE_MNT_POINT)
+ if python_version >= (3, 2):
+ fake_islink.assert_called_once_with(self._FAKE_MNT_POINT)
+ else:
+ fake_get_attributes.assert_called_once_with(
+ self._FAKE_MNT_POINT)
+ self.assertEqual(ret_value, is_symlink)
+
+ def test_is_symlink(self):
+ self._test_check_symlink()
+
+ def test_is_not_symlink(self):
+ self._test_check_symlink(False)
+
+ def test_check_symlink_python_gt_3_2(self):
+ self._test_check_symlink(python_version=(3, 3))
+
+ def test_create_sym_link_exception(self):
+ ctypes.windll.kernel32.CreateSymbolicLinkW.return_value = 0
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self._remotefs.create_sym_link,
+ self._FAKE_MNT_POINT, self._FAKE_SHARE)
+
+ def _test_check_smb_mapping(self, existing_mappings=False,
+ share_available=False):
+ with mock.patch('os.path.exists', lambda x: share_available):
+ fake_mapping = mock.MagicMock()
+ if existing_mappings:
+ fake_mappings = [fake_mapping]
+ else:
+ fake_mappings = []
+
+ self._remotefs.smb_conn.query.return_value = fake_mappings
+ ret_val = self._remotefs.check_smb_mapping(self._FAKE_SHARE)
+
+ if existing_mappings:
+ if share_available:
+ self.assertTrue(ret_val)
+ else:
+ fake_mapping.Remove.assert_called_once_with(True, True)
+ else:
+ self.assertFalse(ret_val)
+
+ def test_check_mapping(self):
+ self._test_check_smb_mapping()
+
+ def test_remake_unavailable_mapping(self):
+ self._test_check_smb_mapping(True, False)
+
+ def test_available_mapping(self):
+ self._test_check_smb_mapping(True, True)
+
+ def test_mount_smb(self):
+ fake_create = self._remotefs.smb_conn.Msft_SmbMapping.Create
+ self._remotefs._mount(self._FAKE_SHARE, {})
+ fake_create.assert_called_once_with(UserName=None,
+ Password=None,
+ RemotePath=self._FAKE_SHARE)
VHD_TYPE_FIXED = 2
VHD_TYPE_DYNAMIC = 3
+VHD_TYPE_DIFFERENCING = 4
--- /dev/null
+# 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 ctypes
+import os
+import sys
+
+if sys.platform == 'win32':
+ import wmi
+
+from cinder.brick.remotefs import remotefs
+from cinder import exception
+from cinder.openstack.common.gettextutils import _
+from cinder.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+class WindowsRemoteFsClient(remotefs.RemoteFsClient):
+ _FILE_ATTRIBUTE_REPARSE_POINT = 0x0400
+
+ def __init__(self, *args, **kwargs):
+ super(WindowsRemoteFsClient, self).__init__(*args, **kwargs)
+ self.smb_conn = wmi.WMI(moniker='root/Microsoft/Windows/SMB')
+ self.conn_cimv2 = wmi.WMI(moniker='root/cimv2')
+
+ def mount(self, export_path, mnt_options=None):
+ if not os.path.isdir(self._mount_base):
+ os.makedirs(self._mount_base)
+ export_hash = self._get_hash_str(export_path)
+
+ norm_path = os.path.abspath(export_path)
+ mnt_options = mnt_options or {}
+
+ if not self.check_smb_mapping(norm_path):
+ self._mount(norm_path, mnt_options)
+
+ link_path = os.path.join(self._mount_base, export_hash)
+ if os.path.exists(link_path):
+ if not self.is_symlink(link_path):
+ raise exception.SmbfsException(_("Link path already exists "
+ "and its not a symlink"))
+ else:
+ self.create_sym_link(link_path, norm_path)
+
+ def is_symlink(self, path):
+ if sys.version_info >= (3, 2):
+ return os.path.islink(path)
+
+ file_attr = ctypes.windll.kernel32.GetFileAttributesW(unicode(path))
+
+ return bool(os.path.isdir(path) and (
+ file_attr & self._FILE_ATTRIBUTE_REPARSE_POINT))
+
+ def create_sym_link(self, link, target, target_is_dir=True):
+ """If target_is_dir is True, a junction will be created.
+
+ NOTE: Juctions only work on same filesystem.
+ """
+ symlink = ctypes.windll.kernel32.CreateSymbolicLinkW
+ symlink.argtypes = (
+ ctypes.c_wchar_p,
+ ctypes.c_wchar_p,
+ ctypes.c_ulong,
+ )
+ symlink.restype = ctypes.c_ubyte
+ retcode = symlink(link, target, target_is_dir)
+ if retcode == 0:
+ err_msg = (_("Could not create symbolic link. "
+ "Link: %(link)s Target %(target)s")
+ % {'link': link, 'target': target})
+ raise exception.VolumeBackendAPIException(err_msg)
+
+ def check_smb_mapping(self, smbfs_share):
+ mappings = self.smb_conn.query("SELECT * FROM "
+ "MSFT_SmbMapping "
+ "WHERE RemotePath='%s'" %
+ smbfs_share)
+
+ if len(mappings) > 0:
+ if os.path.exists(smbfs_share):
+ LOG.debug('Share already mounted: %s' % smbfs_share)
+ return True
+ else:
+ LOG.debug('Share exists but is unavailable: %s '
+ % smbfs_share)
+ for mapping in mappings:
+ # Due to a bug in the WMI module, getting the output of
+ # methods returning None will raise an AttributeError
+ try:
+ mapping.Remove(True, True)
+ except AttributeError:
+ pass
+ return False
+
+ def _mount(self, smbfs_share, options):
+ smb_opts = {'RemotePath': smbfs_share}
+ smb_opts['UserName'] = (options.get('username') or
+ options.get('user'))
+ smb_opts['Password'] = (options.get('password') or
+ options.get('pass'))
+
+ try:
+ LOG.info(_('Mounting share: %s') % smbfs_share)
+ self.smb_conn.Msft_SmbMapping.Create(**smb_opts)
+ except wmi.x_wmi as exc:
+ err_msg = (_(
+ 'Unable to mount SMBFS share: %(smbfs_share)s '
+ 'WMI exception: %(wmi_exc)s'
+ 'Options: %(options)s') % {'smbfs_share': smbfs_share,
+ 'options': smb_opts,
+ 'wmi_exc': exc})
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def get_capacity_info(self, smbfs_share):
+ norm_path = os.path.abspath(smbfs_share)
+ kernel32 = ctypes.windll.kernel32
+
+ free_bytes = ctypes.c_ulonglong(0)
+ total_bytes = ctypes.c_ulonglong(0)
+ retcode = kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(norm_path),
+ None,
+ ctypes.pointer(total_bytes),
+ ctypes.pointer(free_bytes))
+ if retcode == 0:
+ LOG.error(_("Could not get share %s capacity info.") %
+ smbfs_share)
+ return 0, 0
+ return total_bytes.value, free_bytes.value
--- /dev/null
+# Copyright (c) 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.
+
+
+import os
+import re
+import sys
+
+from oslo.config import cfg
+
+from cinder import exception
+from cinder.image import image_utils
+from cinder.openstack.common import fileutils
+from cinder.openstack.common.gettextutils import _
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import units
+from cinder import utils
+from cinder.volume.drivers import smbfs
+from cinder.volume.drivers.windows import remotefs
+from cinder.volume.drivers.windows import vhdutils
+
+VERSION = '1.0.0'
+
+LOG = logging.getLogger(__name__)
+
+CONF = cfg.CONF
+CONF.set_default('smbfs_shares_config', r'C:\OpenStack\smbfs_shares.txt')
+CONF.set_default('smbfs_mount_point_base', r'C:\OpenStack\_mnt')
+CONF.set_default('smbfs_default_volume_format', 'vhd')
+
+
+class WindowsSmbfsDriver(smbfs.SmbfsDriver):
+ VERSION = VERSION
+
+ def __init__(self, *args, **kwargs):
+ super(WindowsSmbfsDriver, self).__init__(*args, **kwargs)
+ self.base = getattr(self.configuration,
+ 'smbfs_mount_point_base',
+ CONF.smbfs_mount_point_base)
+ opts = getattr(self.configuration,
+ 'smbfs_mount_options',
+ CONF.smbfs_mount_options)
+ self._remotefsclient = remotefs.WindowsRemoteFsClient(
+ 'cifs', root_helper=None, smbfs_mount_point_base=self.base,
+ smbfs_mount_options=opts)
+ self.vhdutils = vhdutils.VHDUtils()
+
+ def do_setup(self, context):
+ self._check_os_platform()
+ super(WindowsSmbfsDriver, self).do_setup(context)
+
+ def _check_os_platform(self):
+ if sys.platform != 'win32':
+ _msg = _("This system platform (%s) is not supported. This "
+ "driver supports only Win32 platforms.") % sys.platform
+ raise exception.SmbfsException(_msg)
+
+ def _do_create_volume(self, volume):
+ volume_path = self.local_path(volume)
+ volume_format = self.get_volume_format(volume)
+ volume_size_bytes = volume['size'] * units.Gi
+
+ if os.path.exists(volume_path):
+ err_msg = _('File already exists at: %s') % volume_path
+ raise exception.InvalidVolume(err_msg)
+
+ if volume_format not in (self._DISK_FORMAT_VHD,
+ self._DISK_FORMAT_VHDX):
+ err_msg = _("Unsupported volume format: %s ") % volume_format
+ raise exception.InvalidVolume(err_msg)
+
+ self.vhdutils.create_dynamic_vhd(volume_path, volume_size_bytes)
+
+ def _ensure_share_mounted(self, smbfs_share):
+ mnt_options = {}
+ if self.shares.get(smbfs_share) is not None:
+ mnt_flags = self.shares[smbfs_share]
+ mnt_options = self.parse_options(mnt_flags)[1]
+ self._remotefsclient.mount(smbfs_share, mnt_options)
+
+ def _delete(self, path):
+ fileutils.delete_if_exists(path)
+
+ def _get_capacity_info(self, smbfs_share):
+ """Calculate available space on the SMBFS share.
+
+ :param smbfs_share: example //172.18.194.100/var/smbfs
+ """
+ total_size, total_available = self._remotefsclient.get_capacity_info(
+ smbfs_share)
+ total_allocated = self._get_total_allocated(smbfs_share)
+ return_value = [total_size, total_available, total_allocated]
+ LOG.info('Smb share %s Total size %s Total allocated %s'
+ % (smbfs_share, total_size, total_allocated))
+ return [float(x) for x in return_value]
+
+ def _get_total_allocated(self, smbfs_share):
+ elements = os.listdir(smbfs_share)
+ total_allocated = 0
+ for element in elements:
+ element_path = os.path.join(smbfs_share, element)
+ if not self._remotefsclient.is_symlink(element_path):
+ if "snapshot" in element:
+ continue
+ if re.search(r'\.vhdx?$', element):
+ total_allocated += self.vhdutils.get_vhd_size(
+ element_path)['VirtualSize']
+ continue
+ if os.path.isdir(element_path):
+ total_allocated += self._get_total_allocated(element_path)
+ continue
+ total_allocated += os.path.getsize(element_path)
+
+ return total_allocated
+
+ def _img_commit(self, snapshot_path):
+ self.vhdutils.merge_vhd(snapshot_path)
+ self._delete(snapshot_path)
+
+ def _rebase_img(self, image, backing_file, volume_format):
+ # Relative path names are not supported in this case.
+ image_dir = os.path.dirname(image)
+ backing_file_path = os.path.join(image_dir, backing_file)
+ self.vhdutils.reconnect_parent(image, backing_file_path)
+
+ def _qemu_img_info(self, path):
+ # This code expects to deal only with relative filenames.
+ # As this method is needed by the upper class and qemu-img does
+ # not fully support vhdx images, for the moment we'll use Win32 API
+ # for retrieving image information.
+ parent_path = self.vhdutils.get_vhd_parent_path(path)
+ file_format = os.path.splitext(path)[1][1:].lower()
+
+ if parent_path:
+ backing_file_name = os.path.split(parent_path)[1].lower()
+ else:
+ backing_file_name = None
+
+ class ImageInfo(object):
+ def __init__(self, image, backing_file):
+ self.image = image
+ self.backing_file = backing_file
+ self.file_format = file_format
+
+ return ImageInfo(os.path.basename(path),
+ backing_file_name)
+
+ def _do_create_snapshot(self, snapshot, backing_file, new_snap_path):
+ backing_file_full_path = os.path.join(
+ self._local_volume_dir(snapshot['volume']),
+ backing_file)
+ self.vhdutils.create_differencing_vhd(new_snap_path,
+ backing_file_full_path)
+
+ def _do_extend_volume(self, volume_path, size_gb):
+ self.vhdutils.resize_vhd(volume_path, size_gb * units.Gi)
+
+ @utils.synchronized('smbfs', external=False)
+ def copy_volume_to_image(self, context, volume, image_service, image_meta):
+ """Copy the volume to the specified image."""
+
+ # If snapshots exist, flatten to a temporary image, and upload it
+
+ active_file = self.get_active_image_from_info(volume)
+ active_file_path = os.path.join(self._local_volume_dir(volume),
+ active_file)
+ backing_file = self.vhdutils.get_vhd_parent_path(active_file_path)
+ root_file_fmt = self.get_volume_format(volume)
+
+ temp_path = None
+
+ try:
+ if backing_file or root_file_fmt == self._DISK_FORMAT_VHDX:
+ temp_file_name = '%s.temp_image.%s.%s' % (
+ volume['id'],
+ image_meta['id'],
+ self._DISK_FORMAT_VHD)
+ temp_path = os.path.join(self._local_volume_dir(volume),
+ temp_file_name)
+
+ self.vhdutils.convert_vhd(active_file_path, temp_path)
+ upload_path = temp_path
+ else:
+ upload_path = active_file_path
+
+ image_utils.upload_volume(context,
+ image_service,
+ image_meta,
+ upload_path,
+ self._DISK_FORMAT_VHD)
+ finally:
+ if temp_path:
+ self._delete(temp_path)
+
+ def copy_image_to_volume(self, context, volume, image_service, image_id):
+ """Fetch the image from image_service and write it to the volume."""
+ volume_format = self.get_volume_format(volume, qemu_format=True)
+ image_meta = image_service.show(context, image_id)
+
+ fetch_format = volume_format
+ fetch_path = self.local_path(volume)
+ self._delete(fetch_path)
+ qemu_version = self.get_qemu_version()
+
+ needs_conversion = False
+
+ if (qemu_version < [1, 7] and (
+ volume_format == self._DISK_FORMAT_VHDX and
+ image_meta['disk_format'] != self._DISK_FORMAT_VHDX)):
+ needs_conversion = True
+ fetch_format = 'vpc'
+ temp_file_name = '%s.temp_image.%s.%s' % (
+ volume['id'],
+ image_meta['id'],
+ self._DISK_FORMAT_VHD)
+ fetch_path = os.path.join(self._local_volume_dir(volume),
+ temp_file_name)
+
+ image_utils.fetch_to_volume_format(
+ context, image_service, image_id,
+ fetch_path, fetch_format,
+ self.configuration.volume_dd_blocksize)
+
+ if needs_conversion:
+ self.vhdutils.convert_vhd(fetch_path, self.local_path(volume))
+ self._delete(fetch_path)
+
+ self.vhdutils.resize_vhd(self.local_path(volume),
+ volume['size'] * units.Gi)
+
+ def _copy_volume_from_snapshot(self, snapshot, volume, volume_size):
+ """Copy data from snapshot to destination volume."""
+
+ LOG.debug("snapshot: %(snap)s, volume: %(vol)s, "
+ "volume_size: %(size)s" %
+ {'snap': snapshot['id'],
+ 'vol': volume['id'],
+ 'size': snapshot['volume_size']})
+
+ info_path = self._local_path_volume_info(snapshot['volume'])
+ snap_info = self._read_info_file(info_path)
+ vol_dir = self._local_volume_dir(snapshot['volume'])
+
+ forward_file = snap_info[snapshot['id']]
+ forward_path = os.path.join(vol_dir, forward_file)
+
+ # Find the file which backs this file, which represents the point
+ # when this snapshot was created.
+ img_info = self._qemu_img_info(forward_path)
+ snapshot_path = os.path.join(vol_dir, img_info.backing_file)
+
+ volume_path = self.local_path(volume)
+ self._delete(volume_path)
+ self.vhdutils.convert_vhd(snapshot_path,
+ volume_path)
+ self.vhdutils.resize_vhd(volume_path, volume_size * units.Gi)
Official VHDX format specs can be retrieved at:
http://www.microsoft.com/en-us/download/details.aspx?id=34750
+
+VHD related Win32 API reference:
+http://msdn.microsoft.com/en-us/library/windows/desktop/dd323700.aspx
"""
import ctypes
import os
class Win32_VIRTUAL_STORAGE_TYPE(ctypes.Structure):
_fields_ = [
- ('DeviceId', wintypes.DWORD),
+ ('DeviceId', wintypes.ULONG),
('VendorId', Win32_GUID)
]
('NewSize', ctypes.c_ulonglong)
]
+ class Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V1(ctypes.Structure):
+ _fields_ = [
+ ('Version', wintypes.DWORD),
+ ('RWDepth', ctypes.c_ulong),
+ ]
+
+ class Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V2(ctypes.Structure):
+ _fields_ = [
+ ('Version', wintypes.DWORD),
+ ('GetInfoOnly', wintypes.BOOL),
+ ('ReadOnly', wintypes.BOOL),
+ ('ResiliencyGuid', Win32_GUID)
+ ]
+
+ class Win32_MERGE_VIRTUAL_DISK_PARAMETERS(ctypes.Structure):
+ _fields_ = [
+ ('Version', wintypes.DWORD),
+ ('MergeDepth', ctypes.c_ulong)
+ ]
+
class Win32_CREATE_VIRTUAL_DISK_PARAMETERS(ctypes.Structure):
_fields_ = [
('Version', wintypes.DWORD),
('ResiliencyGuid', Win32_GUID)
]
+ class Win32_SIZE(ctypes.Structure):
+ _fields_ = [("VirtualSize", wintypes.ULARGE_INTEGER),
+ ("PhysicalSize", wintypes.ULARGE_INTEGER),
+ ("BlockSize", wintypes.ULONG),
+ ("SectorSize", wintypes.ULONG)]
+
+ class Win32_PARENT_LOCATION(ctypes.Structure):
+ _fields_ = [('ParentResolved', wintypes.BOOL),
+ ('ParentLocationBuffer', wintypes.WCHAR * 512)]
+
+ class Win32_PHYSICAL_DISK(ctypes.Structure):
+ _fields_ = [("LogicalSectorSize", wintypes.ULONG),
+ ("PhysicalSectorSize", wintypes.ULONG),
+ ("IsRemote", wintypes.BOOL)]
+
+ class Win32_VHD_INFO(ctypes.Union):
+ _fields_ = [("Size", Win32_SIZE),
+ ("Identifier", Win32_GUID),
+ ("ParentLocation", Win32_PARENT_LOCATION),
+ ("ParentIdentifier", Win32_GUID),
+ ("ParentTimestamp", wintypes.ULONG),
+ ("VirtualStorageType", Win32_VIRTUAL_STORAGE_TYPE),
+ ("ProviderSubtype", wintypes.ULONG),
+ ("Is4kAligned", wintypes.BOOL),
+ ("PhysicalDisk", Win32_PHYSICAL_DISK),
+ ("VhdPhysicalSectorSize", wintypes.ULONG),
+ ("SmallestSafeVirtualSize",
+ wintypes.ULARGE_INTEGER),
+ ("FragmentationPercentage", wintypes.ULONG)]
+
+ class Win32_GET_VIRTUAL_DISK_INFO_PARAMETERS(ctypes.Structure):
+ _fields_ = [("VERSION", ctypes.wintypes.UINT),
+ ("VhdInfo", Win32_VHD_INFO)]
+
+ class Win32_SET_VIRTUAL_DISK_INFO_PARAMETERS(ctypes.Structure):
+ _fields_ = [
+ ('Version', wintypes.DWORD),
+ ('ParentFilePath', wintypes.LPCWSTR)
+ ]
+
+
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
+VIRTUAL_DISK_ACCESS_GET_INFO = 0x80000
OPEN_VIRTUAL_DISK_FLAG_NONE = 0
+OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS = 1
+OPEN_VIRTUAL_DISK_VERSION_1 = 1
+OPEN_VIRTUAL_DISK_VERSION_2 = 2
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
+MERGE_VIRTUAL_DISK_VERSION_1 = 1
+MERGE_VIRTUAL_DISK_FLAG_NONE = 0x00000000
+GET_VIRTUAL_DISK_INFO_SIZE = 1
+GET_VIRTUAL_DISK_INFO_PARENT_LOCATION = 3
+GET_VIRTUAL_DISK_INFO_VIRTUAL_STORAGE_TYPE = 6
+GET_VIRTUAL_DISK_INFO_PROVIDER_SUBTYPE = 7
+SET_VIRTUAL_DISK_INFO_PARENT_PATH = 1
class VHDUtils(object):
CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION),
constants.VHD_TYPE_DYNAMIC: CREATE_VIRTUAL_DISK_FLAG_NONE
}
+ self._vhd_info_members = {
+ GET_VIRTUAL_DISK_INFO_SIZE: 'Size',
+ GET_VIRTUAL_DISK_INFO_PARENT_LOCATION: 'ParentLocation',
+ GET_VIRTUAL_DISK_INFO_VIRTUAL_STORAGE_TYPE:
+ 'VirtualStorageType',
+ GET_VIRTUAL_DISK_INFO_PROVIDER_SUBTYPE: 'ProviderSubtype',
+ }
if os.name == 'nt':
self._msft_vendor_id = (
guid.Data4 = ByteArray8(0x90, 0x1f, 0x71, 0x41, 0x5a, 0x66, 0x34, 0x5b)
return guid
- def _open(self, device_id, vhd_path):
+ def _open(self, vhd_path, open_flag=OPEN_VIRTUAL_DISK_FLAG_NONE,
+ open_access_mask=VIRTUAL_DISK_ACCESS_ALL,
+ open_params=0):
+ device_id = self._get_device_id_by_path(vhd_path)
+
vst = Win32_VIRTUAL_STORAGE_TYPE()
vst.DeviceId = device_id
vst.VendorId = self._msft_vendor_id
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))
+ open_access_mask,
+ open_flag,
+ open_params,
+ ctypes.byref(handle))
if ret_val:
raise exception.VolumeBackendAPIException(
_("Opening virtual disk failed with error: %s") % ret_val)
return device_id
def resize_vhd(self, vhd_path, new_max_size):
- device_id = self._get_device_id_by_path(vhd_path)
- handle = self._open(device_id, vhd_path)
+ handle = self._open(vhd_path)
params = Win32_RESIZE_VIRTUAL_DISK_PARAMETERS()
params.Version = RESIZE_VIRTUAL_DISK_VERSION_1
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)
+ def merge_vhd(self, vhd_path):
+ open_params = Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V1()
+ open_params.Version = OPEN_VIRTUAL_DISK_VERSION_1
+ open_params.RWDepth = 2
+
+ handle = self._open(vhd_path,
+ open_params=ctypes.byref(open_params))
+
+ params = Win32_MERGE_VIRTUAL_DISK_PARAMETERS()
+ params.Version = MERGE_VIRTUAL_DISK_VERSION_1
+ params.MergeDepth = 1
+
+ ret_val = virtdisk.MergeVirtualDisk(
+ handle,
+ MERGE_VIRTUAL_DISK_FLAG_NONE,
+ ctypes.byref(params),
+ None)
+ self._close(handle)
+ if ret_val:
+ raise exception.VolumeBackendAPIException(
+ _("Virtual disk merge failed with error: %s") % ret_val)
+
+ def _create_vhd(self, new_vhd_path, new_vhd_type, src_path=None,
+ max_internal_size=0, parent_path=None):
+ new_device_id = self._get_device_id_by_path(new_vhd_path)
vst = Win32_VIRTUAL_STORAGE_TYPE()
- vst.DeviceId = dest_device_id
+ vst.DeviceId = new_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()
+ params.MaximumSize = max_internal_size
+ params.ParentPath = parent_path
+ params.ParentVirtualStorageType = Win32_VIRTUAL_STORAGE_TYPE()
+
+ if src_path:
+ src_device_id = self._get_device_id_by_path(src_path)
+ params.SourcePath = src_path
+ params.SourceVirtualStorageType = Win32_VIRTUAL_STORAGE_TYPE()
+ params.SourceVirtualStorageType.DeviceId = src_device_id
+ params.SourceVirtualStorageType.VendorId = self._msft_vendor_id
handle = wintypes.HANDLE()
- create_virtual_disk_flag = self.create_virtual_disk_flags.get(vhd_type)
+ create_virtual_disk_flag = self.create_virtual_disk_flags.get(
+ new_vhd_type)
ret_val = virtdisk.CreateVirtualDisk(
ctypes.byref(vst),
- ctypes.c_wchar_p(dest),
+ ctypes.c_wchar_p(new_vhd_path),
VIRTUAL_DISK_ACCESS_NONE,
None,
create_virtual_disk_flag,
if ret_val:
raise exception.VolumeBackendAPIException(
- _("Virtual disk conversion failed with error: %s") % ret_val)
+ _("Virtual disk creation failed with error: %s") % ret_val)
+
+ def get_vhd_info(self, vhd_path, info_members=None):
+ vhd_info = {}
+ info_members = info_members or self._vhd_info_members
+
+ handle = self._open(vhd_path,
+ open_access_mask=VIRTUAL_DISK_ACCESS_GET_INFO)
+
+ for member in info_members:
+ info = self._get_vhd_info_member(handle, member)
+ vhd_info = dict(vhd_info.items() + info.items())
+
+ self._close(handle)
+ return vhd_info
+
+ def _get_vhd_info_member(self, vhd_file, info_member):
+ virt_disk_info = Win32_GET_VIRTUAL_DISK_INFO_PARAMETERS()
+ virt_disk_info.VERSION = ctypes.c_uint(info_member)
+
+ infoSize = ctypes.sizeof(virt_disk_info)
+
+ virtdisk.GetVirtualDiskInformation.restype = wintypes.DWORD
+
+ ret_val = virtdisk.GetVirtualDiskInformation(
+ vhd_file, ctypes.byref(ctypes.c_ulong(infoSize)),
+ ctypes.byref(virt_disk_info), 0)
+
+ if (ret_val and info_member !=
+ GET_VIRTUAL_DISK_INFO_PARENT_LOCATION):
+ # Note(lpetrut): If the vhd has no parent image, this will
+ # return an non-zero exit code. No need to raise an exception
+ # in this case.
+ self._close(vhd_file)
+ raise exception.VolumeBackendAPIException(
+ "Error getting vhd info. Error code: %s" % ret_val)
+
+ return self._parse_vhd_info(virt_disk_info, info_member)
+
+ def _parse_vhd_info(self, virt_disk_info, info_member):
+ vhd_info = {}
+ vhd_info_member = self._vhd_info_members[info_member]
+ info = getattr(virt_disk_info.VhdInfo, vhd_info_member)
+
+ if hasattr(info, '_fields_'):
+ for field in info._fields_:
+ vhd_info[field[0]] = getattr(info, field[0])
+ else:
+ vhd_info[vhd_info_member] = info
+
+ return vhd_info
+
+ def get_vhd_size(self, vhd_path):
+ """Returns a dict containing the virtual size, physical size,
+ block size and sector size of the vhd.
+ """
+ size = self.get_vhd_info(vhd_path,
+ [GET_VIRTUAL_DISK_INFO_SIZE])
+ return size
+
+ def get_vhd_parent_path(self, vhd_path):
+ vhd_info = self.get_vhd_info(vhd_path,
+ [GET_VIRTUAL_DISK_INFO_PARENT_LOCATION])
+ parent_path = vhd_info['ParentLocationBuffer']
+
+ if len(parent_path) > 0:
+ return parent_path
+ return None
+
+ def create_dynamic_vhd(self, path, max_internal_size):
+ self._create_vhd(path,
+ constants.VHD_TYPE_DYNAMIC,
+ max_internal_size=max_internal_size)
+
+ def convert_vhd(self, src, dest,
+ vhd_type=constants.VHD_TYPE_DYNAMIC):
+ self._create_vhd(dest, vhd_type, src_path=src)
+
+ def create_differencing_vhd(self, path, parent_path):
+ self._create_vhd(path,
+ constants.VHD_TYPE_DIFFERENCING,
+ parent_path=parent_path)
+
+ def reconnect_parent(self, child_path, parent_path):
+ open_params = Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V2()
+ open_params.Version = OPEN_VIRTUAL_DISK_VERSION_2
+ open_params.GetInfoOnly = False
+
+ handle = self._open(
+ child_path,
+ open_flag=OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS,
+ open_access_mask=VIRTUAL_DISK_ACCESS_NONE,
+ open_params=ctypes.byref(open_params))
+
+ params = Win32_SET_VIRTUAL_DISK_INFO_PARAMETERS()
+ params.Version = SET_VIRTUAL_DISK_INFO_PARENT_PATH
+ params.ParentFilePath = parent_path
+
+ ret_val = virtdisk.SetVirtualDiskInformation(
+ handle,
+ ctypes.byref(params))
+ self._close(handle)
+
+ if ret_val:
+ raise exception.VolumeBackendAPIException(
+ _("Virtual disk reconnect failed with error: %s") % ret_val)