]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Revert "Removing Windows drivers"
authorMike Perez <thingee@gmail.com>
Wed, 8 Apr 2015 06:06:57 +0000 (23:06 -0700)
committerMike Perez <thingee@gmail.com>
Wed, 8 Apr 2015 06:44:00 +0000 (23:44 -0700)
This reverts commit 85f0814e1a96ea043e8048cf7081f70fa024f20b.

Change-Id: I0b078a60e8d9c95d12185d1ff3ffe74fb71f10ab

cinder/tests/windows/test_smbfs.py [new file with mode: 0644]
cinder/tests/windows/test_windows_remotefs.py [new file with mode: 0644]
cinder/volume/drivers/windows/remotefs.py [new file with mode: 0644]
cinder/volume/drivers/windows/smbfs.py [new file with mode: 0644]

diff --git a/cinder/tests/windows/test_smbfs.py b/cinder/tests/windows/test_smbfs.py
new file mode 100644 (file)
index 0000000..9a7cd16
--- /dev/null
@@ -0,0 +1,320 @@
+#  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)
diff --git a/cinder/tests/windows/test_windows_remotefs.py b/cinder/tests/windows/test_windows_remotefs.py
new file mode 100644 (file)
index 0000000..26bbcfe
--- /dev/null
@@ -0,0 +1,164 @@
+#  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)
diff --git a/cinder/volume/drivers/windows/remotefs.py b/cinder/volume/drivers/windows/remotefs.py
new file mode 100644 (file)
index 0000000..6f7f9ae
--- /dev/null
@@ -0,0 +1,143 @@
+#  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
diff --git a/cinder/volume/drivers/windows/smbfs.py b/cinder/volume/drivers/windows/smbfs.py
new file mode 100644 (file)
index 0000000..8ec9688
--- /dev/null
@@ -0,0 +1,268 @@
+# 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)