Add Windows SMBFS connector
This patch adds a Windows SMBFS connector. Also, a Windows RemoteFS class is added, being used by this connector, having a similar interface with the Linux RemoteFS client class. The patch using Windows os-brick connectors in the Hyper-V Nova driver: https://review.openstack.org/#/c/273504/ The Windows connector factory function has been removed as it's not needed anymore. Change-Id: I0c753a667d58391da7a903d11ab1f4729e68461a Implements: blueprint os-brick-windows-support
This commit is contained in:
parent
c5e3d8affb
commit
4045300fa9
@ -44,6 +44,7 @@ FIBRE_CHANNEL = "FIBRE_CHANNEL"
|
||||
AOE = "AOE"
|
||||
DRBD = "DRBD"
|
||||
NFS = "NFS"
|
||||
SMBFS = 'SMBFS'
|
||||
GLUSTERFS = "GLUSTERFS"
|
||||
LOCAL = "LOCAL"
|
||||
HUAWEISDSHYPERVISOR = "HUAWEISDSHYPERVISOR"
|
||||
|
@ -92,6 +92,7 @@ connector_list = [
|
||||
'os_brick.initiator.connectors.disco.DISCOConnector',
|
||||
'os_brick.initiator.windows.base.BaseWindowsConnector',
|
||||
'os_brick.initiator.windows.iscsi.WindowsISCSIConnector',
|
||||
'os_brick.initiator.windows.smbfs.WindowsSMBFSConnector',
|
||||
]
|
||||
|
||||
# Mappings used to determine who to contruct in the factory
|
||||
@ -146,6 +147,8 @@ _connector_mapping_linux_s390x = {
|
||||
_connector_mapping_windows = {
|
||||
initiator.ISCSI:
|
||||
'os_brick.initiator.windows.iscsi.WindowsISCSIConnector',
|
||||
initiator.SMBFS:
|
||||
'os_brick.initiator.windows.smbfs.WindowsSMBFSConnector',
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,43 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
|
||||
from os_brick.i18n import _
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# TODO(lpetrut): once we move the protocol name constants to a
|
||||
# separate module, use that instead.
|
||||
_connector_dict = {
|
||||
'ISCSI':
|
||||
'os_brick.initiator.windows.iscsi.WindowsISCSIConnector',
|
||||
}
|
||||
|
||||
|
||||
def factory(protocol, *args, **kwargs):
|
||||
LOG.debug("Retrieving connector for protocol: %s.", protocol)
|
||||
|
||||
connector = _connector_dict.get(protocol.upper())
|
||||
if not connector:
|
||||
msg = (_("Invalid InitiatorConnector protocol "
|
||||
"specified %(protocol)s") %
|
||||
dict(protocol=protocol))
|
||||
raise ValueError(msg)
|
||||
|
||||
conn_cls = importutils.import_class(connector)
|
||||
return conn_cls(*args, **kwargs)
|
94
os_brick/initiator/windows/smbfs.py
Normal file
94
os_brick/initiator/windows/smbfs.py
Normal file
@ -0,0 +1,94 @@
|
||||
# Copyright 2016 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
|
||||
|
||||
from os_win import utilsfactory
|
||||
|
||||
from os_brick.initiator.windows import base as win_conn_base
|
||||
from os_brick.remotefs import windows_remotefs as remotefs
|
||||
from os_brick import utils
|
||||
|
||||
|
||||
class WindowsSMBFSConnector(win_conn_base.BaseWindowsConnector):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WindowsSMBFSConnector, self).__init__(*args, **kwargs)
|
||||
# If this flag is set, we use the local paths in case of local
|
||||
# shares. This is in fact mandatory in some cases, for example
|
||||
# for the Hyper-C scenario.
|
||||
self._local_path_for_loopback = kwargs.get('local_path_for_loopback',
|
||||
False)
|
||||
self._remotefsclient = remotefs.WindowsRemoteFsClient(
|
||||
mount_type='smbfs',
|
||||
*args, **kwargs)
|
||||
self._smbutils = utilsfactory.get_smbutils()
|
||||
|
||||
@staticmethod
|
||||
def get_connector_properties(*args, **kwargs):
|
||||
# No connector properties updates in this case.
|
||||
return {}
|
||||
|
||||
@utils.trace
|
||||
def connect_volume(self, connection_properties):
|
||||
self.ensure_share_mounted(connection_properties)
|
||||
disk_path = self._get_disk_path(connection_properties)
|
||||
device_info = {'type': 'file',
|
||||
'path': disk_path}
|
||||
return device_info
|
||||
|
||||
@utils.trace
|
||||
def disconnect_volume(self, connection_properties):
|
||||
export_path = self._get_export_path(connection_properties)
|
||||
self._remotefsclient.unmount(export_path)
|
||||
|
||||
def _get_export_path(self, connection_properties):
|
||||
return connection_properties['export'].replace('/', '\\')
|
||||
|
||||
def _get_disk_path(self, connection_properties):
|
||||
# This is expected to be the share address, as an UNC path.
|
||||
export_path = self._get_export_path(connection_properties)
|
||||
mount_base = self._remotefsclient.get_mount_base()
|
||||
use_local_path = (self._local_path_for_loopback and
|
||||
self._smbutils.is_local_share(export_path))
|
||||
|
||||
disk_dir = export_path
|
||||
if mount_base:
|
||||
# This will be a symlink pointing to either the share
|
||||
# path directly or to the local share path, if requested
|
||||
# and available.
|
||||
disk_dir = self._remotefsclient.get_mount_point(
|
||||
export_path)
|
||||
elif use_local_path:
|
||||
share_name = self._remotefsclient.get_share_name(export_path)
|
||||
disk_dir = self._remotefsclient.get_local_share_path(share_name)
|
||||
|
||||
disk_name = connection_properties['name']
|
||||
disk_path = os.path.join(disk_dir, disk_name)
|
||||
return disk_path
|
||||
|
||||
def get_search_path(self):
|
||||
return self._remotefsclient.get_mount_base()
|
||||
|
||||
@utils.trace
|
||||
def get_volume_paths(self, connection_properties):
|
||||
return [self._get_disk_path(connection_properties)]
|
||||
|
||||
def ensure_share_mounted(self, connection_properties):
|
||||
export_path = self._get_export_path(connection_properties)
|
||||
mount_options = connection_properties.get('options')
|
||||
self._remotefsclient.mount(export_path, mount_options)
|
||||
|
||||
def extend_volume(self, connection_properties):
|
||||
raise NotImplementedError
|
122
os_brick/remotefs/windows_remotefs.py
Normal file
122
os_brick/remotefs/windows_remotefs.py
Normal file
@ -0,0 +1,122 @@
|
||||
# Copyright 2016 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.
|
||||
|
||||
"""Windows remote filesystem client utilities."""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from os_win import utilsfactory
|
||||
|
||||
from os_brick import exception
|
||||
from os_brick.i18n import _, _LI
|
||||
from os_brick.remotefs import remotefs
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WindowsRemoteFsClient(remotefs.RemoteFsClient):
|
||||
_username_regex = re.compile(r'user(?:name)?=([^, ]+)')
|
||||
_password_regex = re.compile(r'pass(?:word)?=([^, ]+)')
|
||||
_loopback_share_map = {}
|
||||
|
||||
def __init__(self, mount_type, root_helper=None,
|
||||
execute=None, *args, **kwargs):
|
||||
mount_type_to_option_prefix = {
|
||||
'cifs': 'smbfs',
|
||||
'smbfs': 'smbfs',
|
||||
}
|
||||
|
||||
self._local_path_for_loopback = kwargs.get('local_path_for_loopback',
|
||||
False)
|
||||
|
||||
if mount_type not in mount_type_to_option_prefix:
|
||||
raise exception.ProtocolNotSupported(protocol=mount_type)
|
||||
|
||||
self._mount_type = mount_type
|
||||
option_prefix = mount_type_to_option_prefix[mount_type]
|
||||
|
||||
self._mount_base = kwargs.get(option_prefix + '_mount_point_base')
|
||||
self._mount_options = kwargs.get(option_prefix + '_mount_options')
|
||||
|
||||
self._smbutils = utilsfactory.get_smbutils()
|
||||
self._pathutils = utilsfactory.get_pathutils()
|
||||
|
||||
def get_local_share_path(self, share, expect_existing=True):
|
||||
local_share_path = self._smbutils.get_smb_share_path(share)
|
||||
if not local_share_path and expect_existing:
|
||||
err_msg = _("Could not find the local "
|
||||
"share path for %(share)s.")
|
||||
raise exception.VolumePathsNotFound(err_msg % dict(share=share))
|
||||
|
||||
return local_share_path
|
||||
|
||||
def get_share_name(self, share):
|
||||
return share.replace('/', '\\').lstrip('\\').split('\\', 1)[1]
|
||||
|
||||
def mount(self, share, flags=None):
|
||||
share = share.replace('/', '\\')
|
||||
use_local_path = (self._local_path_for_loopback and
|
||||
self._smbutils.is_local_share(share))
|
||||
|
||||
if use_local_path:
|
||||
LOG.info(_LI("Skipping mounting local share %(share_path)s."),
|
||||
dict(share_path=share))
|
||||
else:
|
||||
mount_options = " ".join(
|
||||
[self._mount_options or '', flags or ''])
|
||||
username, password = self._parse_credentials(mount_options)
|
||||
|
||||
if not self._smbutils.check_smb_mapping(
|
||||
share):
|
||||
self._smbutils.mount_smb_share(share,
|
||||
username=username,
|
||||
password=password)
|
||||
|
||||
if self._mount_base:
|
||||
share_name = self.get_share_name(share)
|
||||
symlink_dest = (share if not use_local_path
|
||||
else self.get_local_share_path(share_name))
|
||||
self._create_mount_point(symlink_dest)
|
||||
|
||||
def unmount(self, share):
|
||||
self._smbutils.unmount_smb_share(share.replace('/', '\\'))
|
||||
|
||||
def _create_mount_point(self, share):
|
||||
mnt_point = self.get_mount_point(share)
|
||||
|
||||
if not os.path.isdir(self._mount_base):
|
||||
os.makedirs(self._mount_base)
|
||||
|
||||
if os.path.exists(mnt_point):
|
||||
if not self._pathutils.is_symlink(mnt_point):
|
||||
raise exception.BrickException(_("Link path already exists "
|
||||
"and it's not a symlink"))
|
||||
else:
|
||||
self._pathutils.create_sym_link(mnt_point, share)
|
||||
|
||||
def _parse_credentials(self, opts_str):
|
||||
if not opts_str:
|
||||
return None, None
|
||||
|
||||
match = self._username_regex.findall(opts_str)
|
||||
username = match[0] if match and match[0] != 'guest' else None
|
||||
|
||||
match = self._password_regex.findall(opts_str)
|
||||
password = match[0] if match else None
|
||||
|
||||
return username, password
|
136
os_brick/tests/remotefs/test_windows_remotefs.py
Normal file
136
os_brick/tests/remotefs/test_windows_remotefs.py
Normal file
@ -0,0 +1,136 @@
|
||||
# Copyright 2016 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 ddt
|
||||
import mock
|
||||
|
||||
from os_brick import exception
|
||||
from os_brick.remotefs import windows_remotefs
|
||||
from os_brick.tests import base
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class WindowsRemotefsClientTestCase(base.TestCase):
|
||||
_FAKE_SHARE_NAME = 'fake_share'
|
||||
_FAKE_SHARE_SERVER = 'fake_share_server'
|
||||
_FAKE_SHARE = '\\\\%s\\%s' % (_FAKE_SHARE_SERVER,
|
||||
_FAKE_SHARE_NAME)
|
||||
|
||||
@mock.patch.object(windows_remotefs, 'utilsfactory')
|
||||
def setUp(self, mock_utilsfactory):
|
||||
super(WindowsRemotefsClientTestCase, self).setUp()
|
||||
|
||||
self._remotefs = windows_remotefs.WindowsRemoteFsClient(
|
||||
mount_type='smbfs')
|
||||
self._remotefs._mount_base = mock.sentinel.mount_base
|
||||
|
||||
self._smbutils = self._remotefs._smbutils
|
||||
self._pathutils = self._remotefs._pathutils
|
||||
|
||||
@ddt.data({},
|
||||
{'expect_existing': False},
|
||||
{'local_path': mock.sentinel.local_path})
|
||||
@ddt.unpack
|
||||
def test_get_local_share_path(self, expect_existing=True,
|
||||
local_path=None):
|
||||
self._smbutils.get_smb_share_path.return_value = local_path
|
||||
if not local_path and expect_existing:
|
||||
self.assertRaises(
|
||||
exception.VolumePathsNotFound,
|
||||
self._remotefs.get_local_share_path,
|
||||
mock.sentinel.share_name,
|
||||
expect_existing=expect_existing)
|
||||
else:
|
||||
share_path = self._remotefs.get_local_share_path(
|
||||
mock.sentinel.share_name,
|
||||
expect_existing=expect_existing)
|
||||
self.assertEqual(local_path, share_path)
|
||||
|
||||
def test_get_share_name(self):
|
||||
resulted_name = self._remotefs.get_share_name(self._FAKE_SHARE)
|
||||
self.assertEqual(self._FAKE_SHARE_NAME, resulted_name)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch.object(windows_remotefs.WindowsRemoteFsClient,
|
||||
'_create_mount_point')
|
||||
@mock.patch.object(windows_remotefs.WindowsRemoteFsClient,
|
||||
'get_local_share_path')
|
||||
def test_mount(self, is_local_share,
|
||||
mock_get_local_share_path,
|
||||
mock_create_mount_point):
|
||||
flags = '-o pass=password'
|
||||
self._remotefs._mount_options = '-o user=username,randomopt'
|
||||
self._remotefs._local_path_for_loopback = True
|
||||
|
||||
self._smbutils.check_smb_mapping.return_value = False
|
||||
self._smbutils.is_local_share.return_value = is_local_share
|
||||
|
||||
self._remotefs.mount(self._FAKE_SHARE, flags)
|
||||
|
||||
if is_local_share:
|
||||
expected_link_dest = mock_get_local_share_path.return_value
|
||||
|
||||
self.assertFalse(self._smbutils.check_smb_mapping.called)
|
||||
self.assertFalse(self._smbutils.mount_smb_share.called)
|
||||
mock_get_local_share_path.assert_called_once_with(
|
||||
self._FAKE_SHARE_NAME)
|
||||
else:
|
||||
expected_link_dest = self._FAKE_SHARE
|
||||
|
||||
self._smbutils.check_smb_mapping.assert_called_once_with(
|
||||
self._FAKE_SHARE)
|
||||
self._smbutils.mount_smb_share.assert_called_once_with(
|
||||
self._FAKE_SHARE,
|
||||
username='username',
|
||||
password='password')
|
||||
self.assertFalse(mock_get_local_share_path.called)
|
||||
|
||||
mock_create_mount_point.assert_called_once_with(expected_link_dest)
|
||||
|
||||
def test_unmount(self):
|
||||
self._remotefs.unmount(self._FAKE_SHARE)
|
||||
self._smbutils.unmount_smb_share.assert_called_once_with(
|
||||
self._FAKE_SHARE)
|
||||
|
||||
@ddt.data({},
|
||||
{'path_exists': True, 'is_symlink': True},
|
||||
{'path_exists': True})
|
||||
@mock.patch.object(windows_remotefs.WindowsRemoteFsClient,
|
||||
'get_mount_point')
|
||||
@mock.patch.object(windows_remotefs, 'os')
|
||||
@ddt.unpack
|
||||
def test_create_mount_point(self, mock_os, mock_get_mount_point,
|
||||
path_exists=False, is_symlink=False):
|
||||
mock_os.path.exists.return_value = path_exists
|
||||
mock_os.isdir.return_value = False
|
||||
self._pathutils.is_symlink.return_value = is_symlink
|
||||
|
||||
if path_exists and not is_symlink:
|
||||
self.assertRaises(exception.BrickException,
|
||||
self._remotefs._create_mount_point,
|
||||
mock.sentinel.share)
|
||||
else:
|
||||
self._remotefs._create_mount_point(mock.sentinel.share)
|
||||
|
||||
mock_get_mount_point.assert_called_once_with(mock.sentinel.share)
|
||||
mock_os.path.isdir.assert_called_once_with(mock.sentinel.mount_base)
|
||||
|
||||
if path_exists:
|
||||
self._pathutils.is_symlink.assert_called_once_with(
|
||||
mock_get_mount_point.return_value)
|
||||
else:
|
||||
self._pathutils.create_sym_link.assert_called_once_with(
|
||||
mock_get_mount_point.return_value,
|
||||
mock.sentinel.share)
|
@ -19,13 +19,16 @@ import mock
|
||||
from os_brick import initiator
|
||||
from os_brick.initiator import connector
|
||||
from os_brick.initiator.windows import iscsi
|
||||
from os_brick.initiator.windows import smbfs
|
||||
from os_brick.tests.windows import test_base
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class WindowsConnectorFactoryTestCase(test_base.WindowsConnectorTestBase):
|
||||
@ddt.data({'proto': initiator.ISCSI,
|
||||
'expected_cls': iscsi.WindowsISCSIConnector})
|
||||
'expected_cls': iscsi.WindowsISCSIConnector},
|
||||
{'proto': initiator.SMBFS,
|
||||
'expected_cls': smbfs.WindowsSMBFSConnector})
|
||||
@ddt.unpack
|
||||
@mock.patch('sys.platform', 'win32')
|
||||
def test_factory(self, proto, expected_cls):
|
||||
|
137
os_brick/tests/windows/test_smbfs.py
Normal file
137
os_brick/tests/windows/test_smbfs.py
Normal file
@ -0,0 +1,137 @@
|
||||
# Copyright 2016 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 ddt
|
||||
import mock
|
||||
|
||||
from os_brick.initiator.windows import smbfs
|
||||
from os_brick.remotefs import windows_remotefs
|
||||
from os_brick.tests.windows import test_base
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class WindowsSMBFSConnectorTestCase(test_base.WindowsConnectorTestBase):
|
||||
@mock.patch.object(windows_remotefs, 'WindowsRemoteFsClient')
|
||||
def setUp(self, mock_remotefs_cls):
|
||||
super(WindowsSMBFSConnectorTestCase, self).setUp()
|
||||
|
||||
self._connector = smbfs.WindowsSMBFSConnector()
|
||||
self._remotefs = mock_remotefs_cls.return_value
|
||||
|
||||
@mock.patch.object(smbfs.WindowsSMBFSConnector, '_get_disk_path')
|
||||
@mock.patch.object(smbfs.WindowsSMBFSConnector, 'ensure_share_mounted')
|
||||
def test_connect_volume(self, mock_ensure_mounted,
|
||||
mock_get_disk_path):
|
||||
device_info = self._connector.connect_volume(mock.sentinel.conn_props)
|
||||
expected_info = dict(type='file',
|
||||
path=mock_get_disk_path.return_value)
|
||||
|
||||
self.assertEqual(expected_info, device_info)
|
||||
mock_ensure_mounted.assert_called_once_with(mock.sentinel.conn_props)
|
||||
mock_get_disk_path.assert_called_once_with(mock.sentinel.conn_props)
|
||||
|
||||
@mock.patch.object(smbfs.WindowsSMBFSConnector, '_get_export_path')
|
||||
def test_disconnect_volume(self, mock_get_export_path):
|
||||
self._connector.disconnect_volume(mock.sentinel.conn_props)
|
||||
|
||||
self._remotefs.unmount.assert_called_once_with(
|
||||
mock_get_export_path.return_value)
|
||||
mock_get_export_path.assert_called_once_with(mock.sentinel.conn_props)
|
||||
|
||||
def test_get_export_path(self):
|
||||
fake_export = '//ip/share'
|
||||
fake_conn_props = dict(export=fake_export)
|
||||
|
||||
expected_export = fake_export.replace('/', '\\')
|
||||
export_path = self._connector._get_export_path(fake_conn_props)
|
||||
self.assertEqual(expected_export, export_path)
|
||||
|
||||
@ddt.data({},
|
||||
{'mount_base': mock.sentinel.mount_base},
|
||||
{'is_local_share': True},
|
||||
{'is_local_share': True,
|
||||
'local_path_for_loopbk': True})
|
||||
@ddt.unpack
|
||||
def test_get_disk_path(self, mount_base=None,
|
||||
local_path_for_loopbk=False,
|
||||
is_local_share=False):
|
||||
fake_mount_point = r'C:\\fake_mount_point'
|
||||
fake_share_name = 'fake_share'
|
||||
fake_local_share_path = 'C:\\%s' % fake_share_name
|
||||
fake_export_path = '\\\\host\\%s' % fake_share_name
|
||||
fake_disk_name = 'fake_disk.vhdx'
|
||||
fake_conn_props = dict(name=fake_disk_name,
|
||||
export=fake_export_path)
|
||||
|
||||
self._remotefs.get_mount_base.return_value = mount_base
|
||||
self._remotefs.get_mount_point.return_value = fake_mount_point
|
||||
self._remotefs.get_local_share_path.return_value = (
|
||||
fake_local_share_path)
|
||||
self._remotefs.get_share_name.return_value = fake_share_name
|
||||
self._connector._local_path_for_loopback = local_path_for_loopbk
|
||||
self._connector._smbutils.is_local_share.return_value = is_local_share
|
||||
|
||||
expecting_local = local_path_for_loopbk and is_local_share
|
||||
|
||||
if mount_base:
|
||||
expected_export_path = fake_mount_point
|
||||
elif expecting_local:
|
||||
# In this case, we expect the local share export path to be
|
||||
# used directly.
|
||||
expected_export_path = fake_local_share_path
|
||||
else:
|
||||
expected_export_path = fake_export_path
|
||||
expected_disk_path = os.path.join(expected_export_path,
|
||||
fake_disk_name)
|
||||
|
||||
disk_path = self._connector._get_disk_path(fake_conn_props)
|
||||
self.assertEqual(expected_disk_path, disk_path)
|
||||
|
||||
if mount_base:
|
||||
self._remotefs.get_mount_point.assert_called_once_with(
|
||||
fake_export_path)
|
||||
elif expecting_local:
|
||||
self._connector._smbutils.is_local_share.assert_called_once_with(
|
||||
fake_export_path)
|
||||
self._remotefs.get_share_name.assert_called_once_with(
|
||||
fake_export_path)
|
||||
self._remotefs.get_local_share_path.assert_called_once_with(
|
||||
fake_share_name)
|
||||
|
||||
def test_get_search_path(self):
|
||||
search_path = self._connector.get_search_path()
|
||||
self.assertEqual(search_path,
|
||||
self._remotefs.get_mount_base.return_value)
|
||||
|
||||
@mock.patch.object(smbfs.WindowsSMBFSConnector, '_get_disk_path')
|
||||
def test_volume_paths(self, mock_get_disk_path):
|
||||
expected_paths = [mock_get_disk_path.return_value]
|
||||
volume_paths = self._connector.get_volume_paths(
|
||||
mock.sentinel.conn_props)
|
||||
|
||||
self.assertEqual(expected_paths, volume_paths)
|
||||
mock_get_disk_path.assert_called_once_with(
|
||||
mock.sentinel.conn_props)
|
||||
|
||||
@mock.patch.object(smbfs.WindowsSMBFSConnector, '_get_export_path')
|
||||
def test_ensure_share_mounted(self, mock_get_export_path):
|
||||
fake_conn_props = dict(options=mock.sentinel.mount_opts)
|
||||
|
||||
self._connector.ensure_share_mounted(fake_conn_props)
|
||||
self._remotefs.mount.assert_called_once_with(
|
||||
mock_get_export_path.return_value,
|
||||
mock.sentinel.mount_opts)
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Add Windows SMBFS connector support.
|
Loading…
Reference in New Issue
Block a user