os-brick refactor get_connector_properties
This patch changes how the get_connector_properties works. It migrates the code for each specific connector type's connector_properties (initiator) into the connector itself. It also only calls each connector that matches the platform and the os type. This will allow us to add Windows based connectors in the future. So, when os-brick is running on windows, it won't call any of the linux based connectors to fetch the initiator information, as they won't work. I'm not sure it's necessary to filter by platform as well, but it seemed easy to do at the time. Change-Id: I32290beea81d2a39b828fd8bf3ef805358c7f971
This commit is contained in:
parent
9640b73cc4
commit
80703d3e20
@ -38,13 +38,11 @@ from oslo_concurrency import lockutils
|
|||||||
from oslo_concurrency import processutils as putils
|
from oslo_concurrency import processutils as putils
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_service import loopingcall
|
from oslo_service import loopingcall
|
||||||
|
from oslo_utils import importutils
|
||||||
from oslo_utils import strutils
|
from oslo_utils import strutils
|
||||||
import six
|
import six
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
|
|
||||||
S390X = "s390x"
|
|
||||||
S390 = "s390"
|
|
||||||
|
|
||||||
from os_brick import exception
|
from os_brick import exception
|
||||||
from os_brick import executor
|
from os_brick import executor
|
||||||
from os_brick import utils
|
from os_brick import utils
|
||||||
@ -54,7 +52,6 @@ from os_brick.initiator import linuxfc
|
|||||||
from os_brick.initiator import linuxrbd
|
from os_brick.initiator import linuxrbd
|
||||||
from os_brick.initiator import linuxscsi
|
from os_brick.initiator import linuxscsi
|
||||||
from os_brick.initiator import linuxsheepdog
|
from os_brick.initiator import linuxsheepdog
|
||||||
from os_brick.privileged import rootwrap as priv_rootwrap
|
|
||||||
from os_brick.remotefs import remotefs
|
from os_brick.remotefs import remotefs
|
||||||
from os_brick.i18n import _, _LE, _LI, _LW
|
from os_brick.i18n import _, _LE, _LI, _LW
|
||||||
|
|
||||||
@ -66,6 +63,15 @@ MULTIPATH_ERROR_REGEX = re.compile("\w{3} \d+ \d\d:\d\d:\d\d \|.*$")
|
|||||||
MULTIPATH_DEV_CHECK_REGEX = re.compile("\s+dm-\d+\s+")
|
MULTIPATH_DEV_CHECK_REGEX = re.compile("\s+dm-\d+\s+")
|
||||||
MULTIPATH_PATH_CHECK_REGEX = re.compile("\s+\d+:\d+:\d+:\d+\s+")
|
MULTIPATH_PATH_CHECK_REGEX = re.compile("\s+\d+:\d+:\d+:\d+\s+")
|
||||||
|
|
||||||
|
PLATFORM_ALL = 'ALL'
|
||||||
|
PLATFORM_x86 = 'X86'
|
||||||
|
PLATFORM_S390 = 'S390'
|
||||||
|
OS_TYPE_ALL = 'ALL'
|
||||||
|
OS_TYPE_LINUX = 'LINUX'
|
||||||
|
|
||||||
|
S390X = "s390x"
|
||||||
|
S390 = "s390"
|
||||||
|
|
||||||
ISCSI = "ISCSI"
|
ISCSI = "ISCSI"
|
||||||
ISER = "ISER"
|
ISER = "ISER"
|
||||||
FIBRE_CHANNEL = "FIBRE_CHANNEL"
|
FIBRE_CHANNEL = "FIBRE_CHANNEL"
|
||||||
@ -84,19 +90,21 @@ DISCO = "DISCO"
|
|||||||
VZSTORAGE = "VZSTORAGE"
|
VZSTORAGE = "VZSTORAGE"
|
||||||
SHEEPDOG = "SHEEPDOG"
|
SHEEPDOG = "SHEEPDOG"
|
||||||
|
|
||||||
|
connector_list = [
|
||||||
def _check_multipathd_running(root_helper, enforce_multipath):
|
'os_brick.initiator.connector.InitiatorConnector',
|
||||||
try:
|
'os_brick.initiator.connector.ISCSIConnector',
|
||||||
priv_rootwrap.execute('multipathd', 'show', 'status',
|
'os_brick.initiator.connector.FibreChannelConnector',
|
||||||
run_as_root=True, root_helper=root_helper)
|
'os_brick.initiator.connector.FibreChannelConnectorS390X',
|
||||||
except putils.ProcessExecutionError as err:
|
'os_brick.initiator.connector.AoEConnector',
|
||||||
LOG.error(_LE('multipathd is not running: exit code %(err)s'),
|
'os_brick.initiator.connector.RemoteFsConnector',
|
||||||
{'err': err.exit_code})
|
'os_brick.initiator.connector.RBDConnector',
|
||||||
if enforce_multipath:
|
'os_brick.initiator.connector.LocalConnector',
|
||||||
raise
|
'os_brick.initiator.connector.DRBDConnector',
|
||||||
return False
|
'os_brick.initiator.connector.HuaweiStorHyperConnector',
|
||||||
|
'os_brick.initiator.connector.HGSTConnector',
|
||||||
return True
|
'os_brick.initiator.connector.ScaleIOConnector',
|
||||||
|
'os_brick.initiator.connector.DISCOConnector',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_connector_properties(root_helper, my_ip, multipath, enforce_multipath,
|
def get_connector_properties(root_helper, my_ip, multipath, enforce_multipath,
|
||||||
@ -123,32 +131,39 @@ def get_connector_properties(root_helper, my_ip, multipath, enforce_multipath,
|
|||||||
:type enforce_multipath: bool
|
:type enforce_multipath: bool
|
||||||
:returns: dict containing all of the collected initiator values.
|
:returns: dict containing all of the collected initiator values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
iscsi = ISCSIConnector(root_helper=root_helper)
|
|
||||||
fc = linuxfc.LinuxFibreChannel(root_helper=root_helper)
|
|
||||||
|
|
||||||
props = {}
|
props = {}
|
||||||
props['ip'] = my_ip
|
|
||||||
props['host'] = host if host else socket.gethostname()
|
|
||||||
initiator = iscsi.get_initiator()
|
|
||||||
if initiator:
|
|
||||||
props['initiator'] = initiator
|
|
||||||
wwpns = fc.get_fc_wwpns()
|
|
||||||
if wwpns:
|
|
||||||
props['wwpns'] = wwpns
|
|
||||||
wwnns = fc.get_fc_wwnns()
|
|
||||||
if wwnns:
|
|
||||||
props['wwnns'] = wwnns
|
|
||||||
props['multipath'] = (multipath and
|
|
||||||
_check_multipathd_running(root_helper,
|
|
||||||
enforce_multipath))
|
|
||||||
props['platform'] = platform.machine()
|
props['platform'] = platform.machine()
|
||||||
props['os_type'] = sys.platform
|
props['os_type'] = sys.platform
|
||||||
|
props['ip'] = my_ip
|
||||||
|
props['host'] = host if host else socket.gethostname()
|
||||||
|
|
||||||
|
for item in connector_list:
|
||||||
|
connector = importutils.import_class(item)
|
||||||
|
|
||||||
|
if (utils.platform_matches(props['platform'], connector.platform) and
|
||||||
|
utils.os_matches(props['os_type'], connector.os_type)):
|
||||||
|
LOG.debug("Fetching connector for %s" % connector.__name__)
|
||||||
|
props = utils.merge_dict(props,
|
||||||
|
connector.get_connector_properties(
|
||||||
|
root_helper,
|
||||||
|
host=host,
|
||||||
|
multipath=multipath,
|
||||||
|
enforce_multipath=enforce_multipath))
|
||||||
|
|
||||||
return props
|
return props
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class InitiatorConnector(executor.Executor):
|
class InitiatorConnector(executor.Executor):
|
||||||
|
|
||||||
|
# This object can be used on any platform (x86, S390)
|
||||||
|
platform = PLATFORM_ALL
|
||||||
|
|
||||||
|
# This object can be used on any os type (linux, windows)
|
||||||
|
# TODO(walter-boring) This class stil has a reliance on
|
||||||
|
# linuxscsi object, making it specific to linux. Need to fix that.
|
||||||
|
os_type = OS_TYPE_LINUX
|
||||||
|
|
||||||
def __init__(self, root_helper, driver=None, execute=None,
|
def __init__(self, root_helper, driver=None, execute=None,
|
||||||
device_scan_attempts=DEVICE_SCAN_ATTEMPTS_DEFAULT,
|
device_scan_attempts=DEVICE_SCAN_ATTEMPTS_DEFAULT,
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
@ -162,9 +177,21 @@ class InitiatorConnector(executor.Executor):
|
|||||||
|
|
||||||
def set_driver(self, driver):
|
def set_driver(self, driver):
|
||||||
"""The driver is used to find used LUNs."""
|
"""The driver is used to find used LUNs."""
|
||||||
|
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The generic connector properties."""
|
||||||
|
multipath = kwargs['multipath']
|
||||||
|
enforce_multipath = kwargs['enforce_multipath']
|
||||||
|
props = {}
|
||||||
|
# TODO(walter-boring) move this into platform specific lib
|
||||||
|
props['multipath'] = (multipath and
|
||||||
|
linuxscsi.LinuxSCSI.is_multipath_running(
|
||||||
|
enforce_multipath, root_helper))
|
||||||
|
|
||||||
|
return props
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def factory(protocol, root_helper, driver=None,
|
def factory(protocol, root_helper, driver=None,
|
||||||
use_multipath=False,
|
use_multipath=False,
|
||||||
@ -514,6 +541,17 @@ class ISCSIConnector(InitiatorConnector):
|
|||||||
self.use_multipath = use_multipath
|
self.use_multipath = use_multipath
|
||||||
self.transport = self._validate_iface_transport(transport)
|
self.transport = self._validate_iface_transport(transport)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The iSCSI connector properties."""
|
||||||
|
props = {}
|
||||||
|
iscsi = ISCSIConnector(root_helper=root_helper)
|
||||||
|
initiator = iscsi.get_initiator()
|
||||||
|
if initiator:
|
||||||
|
props['initiator'] = initiator
|
||||||
|
|
||||||
|
return props
|
||||||
|
|
||||||
def get_search_path(self):
|
def get_search_path(self):
|
||||||
"""Where do we look for iSCSI based volumes."""
|
"""Where do we look for iSCSI based volumes."""
|
||||||
return '/dev/disk/by-path'
|
return '/dev/disk/by-path'
|
||||||
@ -1312,6 +1350,21 @@ class FibreChannelConnector(InitiatorConnector):
|
|||||||
self._linuxscsi.set_execute(execute)
|
self._linuxscsi.set_execute(execute)
|
||||||
self._linuxfc.set_execute(execute)
|
self._linuxfc.set_execute(execute)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The Fibre Channel connector properties."""
|
||||||
|
props = {}
|
||||||
|
fc = linuxfc.LinuxFibreChannel(root_helper)
|
||||||
|
|
||||||
|
wwpns = fc.get_fc_wwpns()
|
||||||
|
if wwpns:
|
||||||
|
props['wwpns'] = wwpns
|
||||||
|
wwnns = fc.get_fc_wwnns()
|
||||||
|
if wwnns:
|
||||||
|
props['wwnns'] = wwnns
|
||||||
|
|
||||||
|
return props
|
||||||
|
|
||||||
def get_search_path(self):
|
def get_search_path(self):
|
||||||
"""Where do we look for FC based volumes."""
|
"""Where do we look for FC based volumes."""
|
||||||
return '/dev/disk/by-path'
|
return '/dev/disk/by-path'
|
||||||
@ -1544,6 +1597,7 @@ class FibreChannelConnector(InitiatorConnector):
|
|||||||
|
|
||||||
class FibreChannelConnectorS390X(FibreChannelConnector):
|
class FibreChannelConnectorS390X(FibreChannelConnector):
|
||||||
"""Connector class to attach/detach Fibre Channel volumes on S390X arch."""
|
"""Connector class to attach/detach Fibre Channel volumes on S390X arch."""
|
||||||
|
platform = PLATFORM_S390
|
||||||
|
|
||||||
def __init__(self, root_helper, driver=None,
|
def __init__(self, root_helper, driver=None,
|
||||||
execute=None, use_multipath=False,
|
execute=None, use_multipath=False,
|
||||||
@ -1607,6 +1661,7 @@ class FibreChannelConnectorS390X(FibreChannelConnector):
|
|||||||
|
|
||||||
class AoEConnector(InitiatorConnector):
|
class AoEConnector(InitiatorConnector):
|
||||||
"""Connector class to attach/detach AoE volumes."""
|
"""Connector class to attach/detach AoE volumes."""
|
||||||
|
|
||||||
def __init__(self, root_helper, driver=None,
|
def __init__(self, root_helper, driver=None,
|
||||||
device_scan_attempts=DEVICE_SCAN_ATTEMPTS_DEFAULT,
|
device_scan_attempts=DEVICE_SCAN_ATTEMPTS_DEFAULT,
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
@ -1616,6 +1671,11 @@ class AoEConnector(InitiatorConnector):
|
|||||||
device_scan_attempts=device_scan_attempts,
|
device_scan_attempts=device_scan_attempts,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The AoE connector properties."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_search_path(self):
|
def get_search_path(self):
|
||||||
return '/dev/etherd'
|
return '/dev/etherd'
|
||||||
|
|
||||||
@ -1779,6 +1839,11 @@ class RemoteFsConnector(InitiatorConnector):
|
|||||||
device_scan_attempts=device_scan_attempts,
|
device_scan_attempts=device_scan_attempts,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The RemoteFS connector properties."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def set_execute(self, execute):
|
def set_execute(self, execute):
|
||||||
super(RemoteFsConnector, self).set_execute(execute)
|
super(RemoteFsConnector, self).set_execute(execute)
|
||||||
self._remotefsclient.set_execute(execute)
|
self._remotefsclient.set_execute(execute)
|
||||||
@ -1846,6 +1911,11 @@ class RBDConnector(InitiatorConnector):
|
|||||||
device_scan_attempts,
|
device_scan_attempts,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The RBD connector properties."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_volume_paths(self, connection_properties):
|
def get_volume_paths(self, connection_properties):
|
||||||
# TODO(walter-boring): don't know where the connector
|
# TODO(walter-boring): don't know where the connector
|
||||||
# looks for RBD volumes.
|
# looks for RBD volumes.
|
||||||
@ -1932,6 +2002,11 @@ class LocalConnector(InitiatorConnector):
|
|||||||
super(LocalConnector, self).__init__(root_helper, driver=driver,
|
super(LocalConnector, self).__init__(root_helper, driver=driver,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The Local connector properties."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_volume_paths(self, connection_properties):
|
def get_volume_paths(self, connection_properties):
|
||||||
path = connection_properties['device_path']
|
path = connection_properties['device_path']
|
||||||
return [path]
|
return [path]
|
||||||
@ -1981,6 +2056,20 @@ class LocalConnector(InitiatorConnector):
|
|||||||
class DRBDConnector(InitiatorConnector):
|
class DRBDConnector(InitiatorConnector):
|
||||||
""""Connector class to attach/detach DRBD resources."""
|
""""Connector class to attach/detach DRBD resources."""
|
||||||
|
|
||||||
|
def __init__(self, root_helper, driver=None,
|
||||||
|
execute=putils.execute, *args, **kwargs):
|
||||||
|
|
||||||
|
super(DRBDConnector, self).__init__(root_helper, driver=driver,
|
||||||
|
execute=execute, *args, **kwargs)
|
||||||
|
|
||||||
|
self._execute = execute
|
||||||
|
self._root_helper = root_helper
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The DRBD connector properties."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def check_valid_device(self, path, run_as_root=True):
|
def check_valid_device(self, path, run_as_root=True):
|
||||||
"""Verify an existing volume."""
|
"""Verify an existing volume."""
|
||||||
# TODO(linbit): check via drbdsetup first, to avoid blocking/hanging
|
# TODO(linbit): check via drbdsetup first, to avoid blocking/hanging
|
||||||
@ -2052,6 +2141,7 @@ class DRBDConnector(InitiatorConnector):
|
|||||||
|
|
||||||
class HuaweiStorHyperConnector(InitiatorConnector):
|
class HuaweiStorHyperConnector(InitiatorConnector):
|
||||||
""""Connector class to attach/detach SDSHypervisor volumes."""
|
""""Connector class to attach/detach SDSHypervisor volumes."""
|
||||||
|
|
||||||
attached_success_code = 0
|
attached_success_code = 0
|
||||||
has_been_attached_code = 50151401
|
has_been_attached_code = 50151401
|
||||||
attach_mnid_done_code = 50151405
|
attach_mnid_done_code = 50151405
|
||||||
@ -2074,6 +2164,11 @@ class HuaweiStorHyperConnector(InitiatorConnector):
|
|||||||
driver=driver,
|
driver=driver,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The HuaweiStor connector properties."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_search_path(self):
|
def get_search_path(self):
|
||||||
# TODO(walter-boring): Where is the location on the filesystem to
|
# TODO(walter-boring): Where is the location on the filesystem to
|
||||||
# look for Huawei volumes to show up?
|
# look for Huawei volumes to show up?
|
||||||
@ -2210,6 +2305,7 @@ class HuaweiStorHyperConnector(InitiatorConnector):
|
|||||||
|
|
||||||
class HGSTConnector(InitiatorConnector):
|
class HGSTConnector(InitiatorConnector):
|
||||||
"""Connector class to attach/detach HGST volumes."""
|
"""Connector class to attach/detach HGST volumes."""
|
||||||
|
|
||||||
VGCCLUSTER = 'vgc-cluster'
|
VGCCLUSTER = 'vgc-cluster'
|
||||||
|
|
||||||
def __init__(self, root_helper, driver=None,
|
def __init__(self, root_helper, driver=None,
|
||||||
@ -2221,6 +2317,11 @@ class HGSTConnector(InitiatorConnector):
|
|||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
self._vgc_host = None
|
self._vgc_host = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The HGST connector properties."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def _log_cli_err(self, err):
|
def _log_cli_err(self, err):
|
||||||
"""Dumps the full command output to a logfile in error cases."""
|
"""Dumps the full command output to a logfile in error cases."""
|
||||||
LOG.error(_LE("CLI fail: '%(cmd)s' = %(code)s\nout: %(stdout)s\n"
|
LOG.error(_LE("CLI fail: '%(cmd)s' = %(code)s\nout: %(stdout)s\n"
|
||||||
@ -2357,6 +2458,7 @@ class HGSTConnector(InitiatorConnector):
|
|||||||
|
|
||||||
class ScaleIOConnector(InitiatorConnector):
|
class ScaleIOConnector(InitiatorConnector):
|
||||||
"""Class implements the connector driver for ScaleIO."""
|
"""Class implements the connector driver for ScaleIO."""
|
||||||
|
|
||||||
OK_STATUS_CODE = 200
|
OK_STATUS_CODE = 200
|
||||||
VOLUME_NOT_MAPPED_ERROR = 84
|
VOLUME_NOT_MAPPED_ERROR = 84
|
||||||
VOLUME_ALREADY_MAPPED_ERROR = 81
|
VOLUME_ALREADY_MAPPED_ERROR = 81
|
||||||
@ -2384,6 +2486,11 @@ class ScaleIOConnector(InitiatorConnector):
|
|||||||
self.iops_limit = None
|
self.iops_limit = None
|
||||||
self.bandwidth_limit = None
|
self.bandwidth_limit = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The ScaleIO connector properties."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_search_path(self):
|
def get_search_path(self):
|
||||||
return "/dev/disk/by-id"
|
return "/dev/disk/by-id"
|
||||||
|
|
||||||
@ -2824,6 +2931,11 @@ class DISCOConnector(InitiatorConnector):
|
|||||||
self.server_port = None
|
self.server_port = None
|
||||||
self.server_ip = None
|
self.server_ip = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The DISCO connector properties."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_search_path(self):
|
def get_search_path(self):
|
||||||
"""Get directory path where to get DISCO volumes."""
|
"""Get directory path where to get DISCO volumes."""
|
||||||
return "/dev"
|
return "/dev"
|
||||||
@ -2984,6 +3096,11 @@ class SheepdogConnector(InitiatorConnector):
|
|||||||
device_scan_attempts,
|
device_scan_attempts,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connector_properties(root_helper, *args, **kwargs):
|
||||||
|
"""The Sheepdog connector properties."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_volume_paths(self, connection_properties):
|
def get_volume_paths(self, connection_properties):
|
||||||
# TODO(lixiaoy1): don't know where the connector
|
# TODO(lixiaoy1): don't know where the connector
|
||||||
# looks for sheepdog volumes.
|
# looks for sheepdog volumes.
|
||||||
|
@ -25,8 +25,10 @@ from oslo_log import log as logging
|
|||||||
|
|
||||||
from os_brick import exception
|
from os_brick import exception
|
||||||
from os_brick import executor
|
from os_brick import executor
|
||||||
|
from os_brick.i18n import _LE
|
||||||
from os_brick.i18n import _LI
|
from os_brick.i18n import _LI
|
||||||
from os_brick.i18n import _LW
|
from os_brick.i18n import _LW
|
||||||
|
from os_brick.privileged import rootwrap as priv_rootwrap
|
||||||
from os_brick import utils
|
from os_brick import utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -110,6 +112,21 @@ class LinuxSCSI(executor.Executor):
|
|||||||
root_helper=self._root_helper)
|
root_helper=self._root_helper)
|
||||||
return out.strip()
|
return out.strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_multipath_running(enforce_multipath, root_helper):
|
||||||
|
try:
|
||||||
|
priv_rootwrap.execute('multipathd', 'show', 'status',
|
||||||
|
run_as_root=True,
|
||||||
|
root_helper=root_helper)
|
||||||
|
except putils.ProcessExecutionError as err:
|
||||||
|
LOG.error(_LE('multipathd is not running: exit code %(err)s'),
|
||||||
|
{'err': err.exit_code})
|
||||||
|
if enforce_multipath:
|
||||||
|
raise
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def remove_multipath_device(self, device):
|
def remove_multipath_device(self, device):
|
||||||
"""This removes LUNs associated with a multipath device
|
"""This removes LUNs associated with a multipath device
|
||||||
and the multipath device itself.
|
and the multipath device itself.
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import platform
|
import platform
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -77,6 +78,29 @@ class ConnectorUtilsTestCase(base.TestCase):
|
|||||||
'platform': platform}
|
'platform': platform}
|
||||||
self.assertEqual(props, props_actual)
|
self.assertEqual(props, props_actual)
|
||||||
|
|
||||||
|
def test_brick_get_connector_properties_connectors_called(self):
|
||||||
|
"""Make sure every connector is called."""
|
||||||
|
|
||||||
|
mock_list = []
|
||||||
|
# Make sure every connector is called
|
||||||
|
for item in connector.connector_list:
|
||||||
|
patched = mock.MagicMock()
|
||||||
|
patched.platform = platform.machine()
|
||||||
|
patched.os_type = sys.platform
|
||||||
|
patched.__name__ = item
|
||||||
|
patched.get_connector_properties.return_value = {}
|
||||||
|
patcher = mock.patch(item, new=patched)
|
||||||
|
patcher.start()
|
||||||
|
self.addCleanup(patcher.stop)
|
||||||
|
mock_list.append(patched)
|
||||||
|
|
||||||
|
connector.get_connector_properties('sudo',
|
||||||
|
MY_IP,
|
||||||
|
True, True)
|
||||||
|
|
||||||
|
for item in mock_list:
|
||||||
|
assert item.get_connector_properties.called
|
||||||
|
|
||||||
def test_brick_get_connector_properties(self):
|
def test_brick_get_connector_properties(self):
|
||||||
self._test_brick_get_connector_properties(False, False, False)
|
self._test_brick_get_connector_properties(False, False, False)
|
||||||
|
|
||||||
@ -138,6 +162,37 @@ class ConnectorTestCase(base.TestCase):
|
|||||||
def test_disconnect_volume(self):
|
def test_disconnect_volume(self):
|
||||||
self.connector = connector.FakeConnector(None)
|
self.connector = connector.FakeConnector(None)
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
with mock.patch.object(priv_rootwrap, 'execute') as mock_exec:
|
||||||
|
mock_exec.return_value = True
|
||||||
|
multipath = True
|
||||||
|
enforce_multipath = True
|
||||||
|
props = connector.InitiatorConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=multipath,
|
||||||
|
enforce_multipath=enforce_multipath)
|
||||||
|
|
||||||
|
expected_props = {'multipath': True}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
|
multipath = False
|
||||||
|
enforce_multipath = True
|
||||||
|
props = connector.InitiatorConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=multipath,
|
||||||
|
enforce_multipath=enforce_multipath)
|
||||||
|
|
||||||
|
expected_props = {'multipath': False}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
|
with mock.patch.object(priv_rootwrap, 'execute',
|
||||||
|
side_effect=putils.ProcessExecutionError):
|
||||||
|
multipath = True
|
||||||
|
enforce_multipath = True
|
||||||
|
self.assertRaises(
|
||||||
|
putils.ProcessExecutionError,
|
||||||
|
connector.InitiatorConnector.get_connector_properties,
|
||||||
|
'sudo', multipath=multipath,
|
||||||
|
enforce_multipath=enforce_multipath)
|
||||||
|
|
||||||
def test_factory(self):
|
def test_factory(self):
|
||||||
obj = connector.InitiatorConnector.factory('iscsi', None)
|
obj = connector.InitiatorConnector.factory('iscsi', None)
|
||||||
self.assertEqual(obj.__class__.__name__, "ISCSIConnector")
|
self.assertEqual(obj.__class__.__name__, "ISCSIConnector")
|
||||||
@ -217,6 +272,7 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
|
|||||||
mock.patch.object(self.connector._linuxscsi, 'get_name_from_path',
|
mock.patch.object(self.connector._linuxscsi, 'get_name_from_path',
|
||||||
return_value="/dev/sdb").start()
|
return_value="/dev/sdb").start()
|
||||||
self.addCleanup(mock.patch.stopall)
|
self.addCleanup(mock.patch.stopall)
|
||||||
|
self._fake_iqn = 'iqn.1234-56.foo.bar:01:23456789abc'
|
||||||
|
|
||||||
def generate_device(self, location, iqn, transport=None, lun=1):
|
def generate_device(self, location, iqn, transport=None, lun=1):
|
||||||
dev_format = "ip-%s-iscsi-%s-lun-%s" % (location, iqn, lun)
|
dev_format = "ip-%s-iscsi-%s-lun-%s" % (location, iqn, lun)
|
||||||
@ -267,29 +323,41 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _initiator_get_text(self, *arg, **kwargs):
|
||||||
|
text = ('## DO NOT EDIT OR REMOVE THIS FILE!\n'
|
||||||
|
'## If you remove this file, the iSCSI daemon '
|
||||||
|
'will not start.\n'
|
||||||
|
'## If you change the InitiatorName, existing '
|
||||||
|
'access control lists\n'
|
||||||
|
'## may reject this initiator. The InitiatorName must '
|
||||||
|
'be unique\n'
|
||||||
|
'## for each iSCSI initiator. Do NOT duplicate iSCSI '
|
||||||
|
'InitiatorNames.\n'
|
||||||
|
'InitiatorName=%s' % self._fake_iqn)
|
||||||
|
return text, None
|
||||||
|
|
||||||
def test_get_initiator(self):
|
def test_get_initiator(self):
|
||||||
def initiator_no_file(*args, **kwargs):
|
def initiator_no_file(*args, **kwargs):
|
||||||
raise putils.ProcessExecutionError('No file')
|
raise putils.ProcessExecutionError('No file')
|
||||||
|
|
||||||
def initiator_get_text(*arg, **kwargs):
|
|
||||||
text = ('## DO NOT EDIT OR REMOVE THIS FILE!\n'
|
|
||||||
'## If you remove this file, the iSCSI daemon '
|
|
||||||
'will not start.\n'
|
|
||||||
'## If you change the InitiatorName, existing '
|
|
||||||
'access control lists\n'
|
|
||||||
'## may reject this initiator. The InitiatorName must '
|
|
||||||
'be unique\n'
|
|
||||||
'## for each iSCSI initiator. Do NOT duplicate iSCSI '
|
|
||||||
'InitiatorNames.\n'
|
|
||||||
'InitiatorName=iqn.1234-56.foo.bar:01:23456789abc')
|
|
||||||
return text, None
|
|
||||||
|
|
||||||
self.connector._execute = initiator_no_file
|
self.connector._execute = initiator_no_file
|
||||||
initiator = self.connector.get_initiator()
|
initiator = self.connector.get_initiator()
|
||||||
self.assertIsNone(initiator)
|
self.assertIsNone(initiator)
|
||||||
self.connector._execute = initiator_get_text
|
self.connector._execute = self._initiator_get_text
|
||||||
initiator = self.connector.get_initiator()
|
initiator = self.connector.get_initiator()
|
||||||
self.assertEqual(initiator, 'iqn.1234-56.foo.bar:01:23456789abc')
|
self.assertEqual(initiator, self._fake_iqn)
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
with mock.patch.object(priv_rootwrap, 'execute') as mock_exec:
|
||||||
|
mock_exec.return_value = self._initiator_get_text()
|
||||||
|
multipath = True
|
||||||
|
enforce_multipath = True
|
||||||
|
props = connector.ISCSIConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=multipath,
|
||||||
|
enforce_multipath=enforce_multipath)
|
||||||
|
|
||||||
|
expected_props = {'initiator': self._fake_iqn}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
@mock.patch.object(connector.ISCSIConnector, '_run_iscsiadm_bare')
|
@mock.patch.object(connector.ISCSIConnector, '_run_iscsiadm_bare')
|
||||||
def test_brick_iscsi_validate_transport(self, mock_iscsiadm):
|
def test_brick_iscsi_validate_transport(self, mock_iscsiadm):
|
||||||
@ -1161,6 +1229,20 @@ class FibreChannelConnectorTestCase(ConnectorTestCase):
|
|||||||
'target_lun': 1,
|
'target_lun': 1,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
|
||||||
|
def test_get_connector_properties(self, mock_hbas):
|
||||||
|
mock_hbas.return_value = self.fake_get_fc_hbas()
|
||||||
|
multipath = True
|
||||||
|
enforce_multipath = True
|
||||||
|
props = connector.FibreChannelConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=multipath,
|
||||||
|
enforce_multipath=enforce_multipath)
|
||||||
|
|
||||||
|
hbas = self.fake_get_fc_hbas()
|
||||||
|
expected_props = {'wwpns': [hbas[0]['port_name'].replace('0x', '')],
|
||||||
|
'wwnns': [hbas[0]['node_name'].replace('0x', '')]}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
def test_get_search_path(self):
|
def test_get_search_path(self):
|
||||||
search_path = self.connector.get_search_path()
|
search_path = self.connector.get_search_path()
|
||||||
expected = "/dev/disk/by-path"
|
expected = "/dev/disk/by-path"
|
||||||
@ -1568,6 +1650,13 @@ class AoEConnectorTestCase(ConnectorTestCase):
|
|||||||
paths = self.connector.get_volume_paths(self.connection_properties)
|
paths = self.connector.get_volume_paths(self.connection_properties)
|
||||||
self.assertEqual(expected, paths)
|
self.assertEqual(expected, paths)
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
props = connector.AoEConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=True, enforce_multipath=True)
|
||||||
|
|
||||||
|
expected_props = {}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
@mock.patch.object(os.path, 'exists', side_effect=[True, True])
|
@mock.patch.object(os.path, 'exists', side_effect=[True, True])
|
||||||
def test_connect_volume(self, exists_mock):
|
def test_connect_volume(self, exists_mock):
|
||||||
"""Ensure that if path exist aoe-revalidate was called."""
|
"""Ensure that if path exist aoe-revalidate was called."""
|
||||||
@ -1645,6 +1734,13 @@ class RemoteFsConnectorTestCase(ConnectorTestCase):
|
|||||||
connector.RemoteFsConnector('scality', root_helper='sudo')
|
connector.RemoteFsConnector('scality', root_helper='sudo')
|
||||||
self.assertEqual(1, mock_scality_remotefs_client.call_count)
|
self.assertEqual(1, mock_scality_remotefs_client.call_count)
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
props = connector.RemoteFsConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=True, enforce_multipath=True)
|
||||||
|
|
||||||
|
expected_props = {}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
def test_get_search_path(self):
|
def test_get_search_path(self):
|
||||||
expected = self.TEST_BASE
|
expected = self.TEST_BASE
|
||||||
actual = self.connector.get_search_path()
|
actual = self.connector.get_search_path()
|
||||||
@ -1683,6 +1779,13 @@ class LocalConnectorTestCase(ConnectorTestCase):
|
|||||||
'device_path': '/tmp/bar'}
|
'device_path': '/tmp/bar'}
|
||||||
self.connector = connector.LocalConnector(None)
|
self.connector = connector.LocalConnector(None)
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
props = connector.LocalConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=True, enforce_multipath=True)
|
||||||
|
|
||||||
|
expected_props = {}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
def test_get_search_path(self):
|
def test_get_search_path(self):
|
||||||
actual = self.connector.get_search_path()
|
actual = self.connector.get_search_path()
|
||||||
self.assertIsNone(actual)
|
self.assertIsNone(actual)
|
||||||
@ -1778,6 +1881,13 @@ class HuaweiStorHyperConnectorTestCase(ConnectorTestCase):
|
|||||||
HuaweiStorHyperConnectorTestCase.attached = True
|
HuaweiStorHyperConnectorTestCase.attached = True
|
||||||
return 'ret_code=330155007', None
|
return 'ret_code=330155007', None
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
props = connector.HuaweiStorHyperConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=True, enforce_multipath=True)
|
||||||
|
|
||||||
|
expected_props = {}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
def test_get_search_path(self):
|
def test_get_search_path(self):
|
||||||
actual = self.connector.get_search_path()
|
actual = self.connector.get_search_path()
|
||||||
self.assertIsNone(actual)
|
self.assertIsNone(actual)
|
||||||
@ -2030,6 +2140,13 @@ Request Succeeded
|
|||||||
self.assertEqual('space', dev_info['device'])
|
self.assertEqual('space', dev_info['device'])
|
||||||
self.assertEqual('/dev/space', dev_info['path'])
|
self.assertEqual('/dev/space', dev_info['path'])
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
props = connector.HGSTConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=True, enforce_multipath=True)
|
||||||
|
|
||||||
|
expected_props = {}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
def test_connect_volume_nohost_fail(self):
|
def test_connect_volume_nohost_fail(self):
|
||||||
"""This host should not be found, connect should fail."""
|
"""This host should not be found, connect should fail."""
|
||||||
self._fail_set_apphosts = False
|
self._fail_set_apphosts = False
|
||||||
@ -2145,6 +2262,13 @@ class RBDConnectorTestCase(ConnectorTestCase):
|
|||||||
actual = rbd.get_volume_paths(self.connection_properties)
|
actual = rbd.get_volume_paths(self.connection_properties)
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
props = connector.RBDConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=True, enforce_multipath=True)
|
||||||
|
|
||||||
|
expected_props = {}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
@mock.patch('os_brick.initiator.linuxrbd.rbd')
|
@mock.patch('os_brick.initiator.linuxrbd.rbd')
|
||||||
@mock.patch('os_brick.initiator.linuxrbd.rados')
|
@mock.patch('os_brick.initiator.linuxrbd.rados')
|
||||||
def test_connect_volume(self, mock_rados, mock_rbd):
|
def test_connect_volume(self, mock_rados, mock_rbd):
|
||||||
@ -2216,6 +2340,13 @@ class DRBDConnectorTestCase(ConnectorTestCase):
|
|||||||
# out, err
|
# out, err
|
||||||
return ('', '')
|
return ('', '')
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
props = connector.DRBDConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=True, enforce_multipath=True)
|
||||||
|
|
||||||
|
expected_props = {}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
def test_connect_volume(self):
|
def test_connect_volume(self):
|
||||||
"""Test connect_volume."""
|
"""Test connect_volume."""
|
||||||
|
|
||||||
@ -2393,6 +2524,13 @@ class ScaleIOConnectorTestCase(ConnectorTestCase):
|
|||||||
self.fake_connection_properties)
|
self.fake_connection_properties)
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
props = connector.ScaleIOConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=True, enforce_multipath=True)
|
||||||
|
|
||||||
|
expected_props = {}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
def test_connect_volume(self):
|
def test_connect_volume(self):
|
||||||
"""Successful connect to volume"""
|
"""Successful connect to volume"""
|
||||||
self.connector.connect_volume(self.fake_connection_properties)
|
self.connector.connect_volume(self.fake_connection_properties)
|
||||||
@ -2560,6 +2698,13 @@ class DISCOConnectorTestCase(ConnectorTestCase):
|
|||||||
volume_path = ''.join(volume_items)
|
volume_path = ''.join(volume_items)
|
||||||
return [volume_path]
|
return [volume_path]
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
props = connector.DISCOConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=True, enforce_multipath=True)
|
||||||
|
|
||||||
|
expected_props = {}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
def test_get_search_path(self):
|
def test_get_search_path(self):
|
||||||
"""DISCO volumes should be under /dev."""
|
"""DISCO volumes should be under /dev."""
|
||||||
expected = "/dev"
|
expected = "/dev"
|
||||||
@ -2641,6 +2786,13 @@ class SheepdogConnectorTestCase(ConnectorTestCase):
|
|||||||
'ports': self.ports,
|
'ports': self.ports,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def test_get_connector_properties(self):
|
||||||
|
props = connector.SheepdogConnector.get_connector_properties(
|
||||||
|
'sudo', multipath=True, enforce_multipath=True)
|
||||||
|
|
||||||
|
expected_props = {}
|
||||||
|
self.assertEqual(expected_props, props)
|
||||||
|
|
||||||
def test_get_search_path(self):
|
def test_get_search_path(self):
|
||||||
sheepdog = connector.SheepdogConnector(None)
|
sheepdog = connector.SheepdogConnector(None)
|
||||||
path = sheepdog.get_search_path()
|
path = sheepdog.get_search_path()
|
||||||
|
@ -56,3 +56,42 @@ def retry(exceptions, interval=1, retries=3, backoff_rate=2):
|
|||||||
return _wrapper
|
return _wrapper
|
||||||
|
|
||||||
return _decorator
|
return _decorator
|
||||||
|
|
||||||
|
|
||||||
|
def platform_matches(current_platform, connector_platform):
|
||||||
|
curr_p = current_platform.upper()
|
||||||
|
conn_p = connector_platform.upper()
|
||||||
|
if conn_p == 'ALL':
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Add tests against families of platforms
|
||||||
|
if curr_p == conn_p:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def os_matches(current_os, connector_os):
|
||||||
|
curr_os = current_os.upper()
|
||||||
|
conn_os = connector_os.upper()
|
||||||
|
if conn_os == 'ALL':
|
||||||
|
return True
|
||||||
|
|
||||||
|
# add tests against OSs
|
||||||
|
if (conn_os == curr_os or
|
||||||
|
conn_os in curr_os):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def merge_dict(dict1, dict2):
|
||||||
|
"""Try to safely merge 2 dictionaries."""
|
||||||
|
if type(dict1) is not dict:
|
||||||
|
raise Exception("dict1 is not a dictionary")
|
||||||
|
if type(dict2) is not dict:
|
||||||
|
raise Exception("dict2 is not a dictionary")
|
||||||
|
|
||||||
|
dict3 = dict1.copy()
|
||||||
|
dict3.update(dict2)
|
||||||
|
return dict3
|
||||||
|
Loading…
Reference in New Issue
Block a user