--- /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)
--- /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)
--- /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 oslo_log import log as logging
+import six
+
+from cinder.brick.remotefs import remotefs
+from cinder import exception
+from cinder.i18n import _, _LE, _LI
+
+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(
+ six.text_type(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(_LI('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(_LE("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 oslo_log import log as logging
+from oslo_utils import units
+
+from cinder import exception
+from cinder.i18n import _
+from cinder.image import image_utils
+from cinder.openstack.common import fileutils
+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, volume_name=None):
+ # 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)