b72c034885
OS-Brick uses file locks to ensure single access to critital sections, and uses the oslo concurrency "lock_path" configuration option to determine where to create these locks. This is fine when each host has a single service using os-brick, which is the case of Compute nodes and Controller nodes where Glance is not using Cinder as its backend. The problem happens when we have an HCI deployment where Cinder and Nova are collocated on the same host or when Glance uses Cinder as its backend and is running on the same host. In those scenarios os-brick will create file locks in different paths for each service, which is not the intended behavior. A possible solutions is to set the "lock_path" of all the services to the same location, which is not great, not only because we'll have all nova, cinder, glance, and os-brick locks mixed in a single location (service prefixes help a bit here), but also because then Cinder will have permissions on the Nova and Glance locks, which doesn't seem right. This patch introduces a new mechanism in os-brick to have its own "lock_path" configuration option in the "os_brick" group. It defaults to the current behavior if not explicitly defined, so it will use olso concurrency's "lock_path". To do this the patch introduces the oslo.config dependency and adds a new "setup" method that sets the default of the os_brick "lock_path". This new "setup" method is required because the order in which configuration options for the different namespaces are imported cannot be guaranteed, so the setup must be called *after* the service has already imported all (or at least the ones os-brick cares about) configuration option namespaces. In other words, when os-brick files are loaded the value for oslo concurrency's "lock_path" is not yet known, so it cannot be used as a default, and the oslo_config substitution feature does not support values from other namespaces, so we cannot default the "lock_path" from the os_brick namespace to the value in "oslo_concurrency". Since the value that is going to be used as the "lock_path" is not known until the "setup" method is called, we cannot use the standard "synchronized" method from "oslo_concurrency.lock_utils" and we need to use our own. In the current os-brick code, each connector that requires a lock is importing and creating its own "synchronized" instance, but this patch changes this behavior and creates a single instance, just like Cinder does. This feature requires changes in multiple projects -os-brick, cinder, nova, glance, glance_store, devstack- to be fully supported, but this is the base of all this effort. Change-Id: Ic52338278eb5bb3d90ce582fe6b23f37eb5568c4
310 lines
11 KiB
Python
310 lines
11 KiB
Python
# Copyright 2013 OpenStack Foundation.
|
|
# 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.
|
|
"""Brick Connector objects for each supported transport protocol.
|
|
|
|
.. module: connector
|
|
|
|
The connectors here are responsible for discovering and removing volumes for
|
|
each of the supported transport protocols.
|
|
"""
|
|
|
|
import platform
|
|
import socket
|
|
import sys
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_utils import importutils
|
|
|
|
from os_brick import exception
|
|
from os_brick.i18n import _
|
|
from os_brick import initiator
|
|
from os_brick import utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
# List of connectors to call when getting
|
|
# the connector properties for a host
|
|
windows_connector_list = [
|
|
'os_brick.initiator.windows.base.BaseWindowsConnector',
|
|
'os_brick.initiator.windows.iscsi.WindowsISCSIConnector',
|
|
'os_brick.initiator.windows.fibre_channel.WindowsFCConnector',
|
|
'os_brick.initiator.windows.rbd.WindowsRBDConnector',
|
|
'os_brick.initiator.windows.smbfs.WindowsSMBFSConnector'
|
|
]
|
|
|
|
unix_connector_list = [
|
|
'os_brick.initiator.connectors.base.BaseLinuxConnector',
|
|
'os_brick.initiator.connectors.iscsi.ISCSIConnector',
|
|
'os_brick.initiator.connectors.fibre_channel.FibreChannelConnector',
|
|
('os_brick.initiator.connectors.fibre_channel_s390x.'
|
|
'FibreChannelConnectorS390X'),
|
|
('os_brick.initiator.connectors.fibre_channel_ppc64.'
|
|
'FibreChannelConnectorPPC64'),
|
|
'os_brick.initiator.connectors.remotefs.RemoteFsConnector',
|
|
'os_brick.initiator.connectors.rbd.RBDConnector',
|
|
'os_brick.initiator.connectors.local.LocalConnector',
|
|
'os_brick.initiator.connectors.gpfs.GPFSConnector',
|
|
'os_brick.initiator.connectors.huawei.HuaweiStorHyperConnector',
|
|
'os_brick.initiator.connectors.scaleio.ScaleIOConnector',
|
|
'os_brick.initiator.connectors.vmware.VmdkConnector',
|
|
'os_brick.initiator.connectors.storpool.StorPoolConnector',
|
|
'os_brick.initiator.connectors.nvmeof.NVMeOFConnector',
|
|
'os_brick.initiator.connectors.lightos.LightOSConnector',
|
|
]
|
|
|
|
|
|
def _get_connector_list():
|
|
if sys.platform != 'win32':
|
|
return unix_connector_list
|
|
else:
|
|
return windows_connector_list
|
|
|
|
|
|
# Mappings used to determine who to construct in the factory
|
|
_connector_mapping_linux = {
|
|
initiator.GLUSTERFS:
|
|
'os_brick.initiator.connectors.remotefs.RemoteFsConnector',
|
|
initiator.NFS:
|
|
'os_brick.initiator.connectors.remotefs.RemoteFsConnector',
|
|
initiator.SCALITY:
|
|
'os_brick.initiator.connectors.remotefs.RemoteFsConnector',
|
|
initiator.QUOBYTE:
|
|
'os_brick.initiator.connectors.remotefs.RemoteFsConnector',
|
|
initiator.VZSTORAGE:
|
|
'os_brick.initiator.connectors.remotefs.RemoteFsConnector',
|
|
|
|
initiator.ISCSI:
|
|
'os_brick.initiator.connectors.iscsi.ISCSIConnector',
|
|
initiator.ISER:
|
|
'os_brick.initiator.connectors.iscsi.ISCSIConnector',
|
|
initiator.FIBRE_CHANNEL:
|
|
'os_brick.initiator.connectors.fibre_channel.FibreChannelConnector',
|
|
|
|
initiator.LOCAL:
|
|
'os_brick.initiator.connectors.local.LocalConnector',
|
|
initiator.HUAWEISDSHYPERVISOR:
|
|
'os_brick.initiator.connectors.huawei.HuaweiStorHyperConnector',
|
|
initiator.RBD:
|
|
'os_brick.initiator.connectors.rbd.RBDConnector',
|
|
initiator.SCALEIO:
|
|
'os_brick.initiator.connectors.scaleio.ScaleIOConnector',
|
|
initiator.VMDK:
|
|
'os_brick.initiator.connectors.vmware.VmdkConnector',
|
|
initiator.GPFS:
|
|
'os_brick.initiator.connectors.gpfs.GPFSConnector',
|
|
initiator.STORPOOL:
|
|
'os_brick.initiator.connectors.storpool.StorPoolConnector',
|
|
# Leave this in for backwards compatibility
|
|
# This isn't an NVME connector, but NVME Over Fabrics
|
|
initiator.NVME:
|
|
'os_brick.initiator.connectors.nvmeof.NVMeOFConnector',
|
|
initiator.NVMEOF:
|
|
'os_brick.initiator.connectors.nvmeof.NVMeOFConnector',
|
|
initiator.LIGHTOS:
|
|
'os_brick.initiator.connectors.lightos.LightOSConnector',
|
|
}
|
|
|
|
# Mapping for the S390X platform
|
|
_connector_mapping_linux_s390x = {
|
|
initiator.FIBRE_CHANNEL:
|
|
'os_brick.initiator.connectors.fibre_channel_s390x.'
|
|
'FibreChannelConnectorS390X',
|
|
initiator.NFS:
|
|
'os_brick.initiator.connectors.remotefs.RemoteFsConnector',
|
|
initiator.ISCSI:
|
|
'os_brick.initiator.connectors.iscsi.ISCSIConnector',
|
|
initiator.LOCAL:
|
|
'os_brick.initiator.connectors.local.LocalConnector',
|
|
initiator.RBD:
|
|
'os_brick.initiator.connectors.rbd.RBDConnector',
|
|
initiator.GPFS:
|
|
'os_brick.initiator.connectors.gpfs.GPFSConnector',
|
|
}
|
|
|
|
# Mapping for the PPC64 platform
|
|
_connector_mapping_linux_ppc64 = {
|
|
initiator.FIBRE_CHANNEL:
|
|
('os_brick.initiator.connectors.fibre_channel_ppc64.'
|
|
'FibreChannelConnectorPPC64'),
|
|
initiator.NFS:
|
|
'os_brick.initiator.connectors.remotefs.RemoteFsConnector',
|
|
initiator.ISCSI:
|
|
'os_brick.initiator.connectors.iscsi.ISCSIConnector',
|
|
initiator.LOCAL:
|
|
'os_brick.initiator.connectors.local.LocalConnector',
|
|
initiator.RBD:
|
|
'os_brick.initiator.connectors.rbd.RBDConnector',
|
|
initiator.GPFS:
|
|
'os_brick.initiator.connectors.gpfs.GPFSConnector',
|
|
initiator.VZSTORAGE:
|
|
'os_brick.initiator.connectors.remotefs.RemoteFsConnector',
|
|
initiator.ISER:
|
|
'os_brick.initiator.connectors.iscsi.ISCSIConnector',
|
|
}
|
|
|
|
# Mapping for the windows connectors
|
|
_connector_mapping_windows = {
|
|
initiator.ISCSI:
|
|
'os_brick.initiator.windows.iscsi.WindowsISCSIConnector',
|
|
initiator.FIBRE_CHANNEL:
|
|
'os_brick.initiator.windows.fibre_channel.WindowsFCConnector',
|
|
initiator.RBD:
|
|
'os_brick.initiator.windows.rbd.WindowsRBDConnector',
|
|
initiator.SMBFS:
|
|
'os_brick.initiator.windows.smbfs.WindowsSMBFSConnector',
|
|
}
|
|
|
|
|
|
# Create aliases to the old names until 2.0.0
|
|
# TODO(smcginnis) Remove this lookup once unit test code is updated to
|
|
# point to the correct location
|
|
def _set_aliases():
|
|
conn_list = _get_connector_list()
|
|
# TODO(lpetrut): Cinder is explicitly trying to use those two
|
|
# connectors. We should drop this once we fix Cinder and
|
|
# get passed the backwards compatibility period.
|
|
if sys.platform == 'win32':
|
|
conn_list += [
|
|
'os_brick.initiator.connectors.iscsi.ISCSIConnector',
|
|
('os_brick.initiator.connectors.fibre_channel.'
|
|
'FibreChannelConnector'),
|
|
]
|
|
|
|
for item in conn_list:
|
|
_name = item.split('.')[-1]
|
|
globals()[_name] = importutils.import_class(item)
|
|
|
|
|
|
_set_aliases()
|
|
|
|
|
|
@utils.trace
|
|
def get_connector_properties(root_helper, my_ip, multipath, enforce_multipath,
|
|
host=None, execute=None):
|
|
"""Get the connection properties for all protocols.
|
|
|
|
When the connector wants to use multipath, multipath=True should be
|
|
specified. If enforce_multipath=True is specified too, an exception is
|
|
thrown when multipathd is not running. Otherwise, it falls back to
|
|
multipath=False and only the first path shown up is used.
|
|
For the compatibility reason, even if multipath=False is specified,
|
|
some cinder storage drivers may export the target for multipath, which
|
|
can be found via sendtargets discovery.
|
|
|
|
:param root_helper: The command prefix for executing as root.
|
|
:type root_helper: str
|
|
:param my_ip: The IP address of the local host.
|
|
:type my_ip: str
|
|
:param multipath: Enable multipath?
|
|
:type multipath: bool
|
|
:param enforce_multipath: Should we enforce that the multipath daemon is
|
|
running? If the daemon isn't running then raise
|
|
ProcessExecutionError to the caller.
|
|
:type enforce_multipath: bool
|
|
:param host: hostname.
|
|
:param execute: execute helper.
|
|
:returns: dict containing all of the collected initiator values.
|
|
"""
|
|
props = {}
|
|
props['platform'] = platform.machine()
|
|
props['os_type'] = sys.platform
|
|
props['ip'] = my_ip
|
|
props['host'] = host if host else socket.gethostname()
|
|
|
|
for item in _get_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)):
|
|
props = utils.merge_dict(props,
|
|
connector.get_connector_properties(
|
|
root_helper,
|
|
host=host,
|
|
multipath=multipath,
|
|
enforce_multipath=enforce_multipath,
|
|
execute=execute))
|
|
|
|
return props
|
|
|
|
|
|
def get_connector_mapping(arch=None):
|
|
"""Get connector mapping based on platform.
|
|
|
|
This is used by Nova to get the right connector information.
|
|
|
|
:param arch: The architecture being requested.
|
|
"""
|
|
|
|
# We do this instead of assigning it in the definition
|
|
# to help mocking for unit tests
|
|
if arch is None:
|
|
arch = platform.machine()
|
|
|
|
# Set the correct mapping for imports
|
|
if sys.platform == 'win32':
|
|
return _connector_mapping_windows
|
|
elif arch in (initiator.S390, initiator.S390X):
|
|
return _connector_mapping_linux_s390x
|
|
elif arch in (initiator.PPC64, initiator.PPC64LE):
|
|
return _connector_mapping_linux_ppc64
|
|
|
|
else:
|
|
return _connector_mapping_linux
|
|
|
|
|
|
# TODO(walter-boring) We have to keep this class defined here
|
|
# so we don't break backwards compatibility
|
|
class InitiatorConnector(object):
|
|
|
|
@staticmethod
|
|
def factory(protocol, root_helper, driver=None,
|
|
use_multipath=False,
|
|
device_scan_attempts=initiator.DEVICE_SCAN_ATTEMPTS_DEFAULT,
|
|
arch=None,
|
|
*args, **kwargs):
|
|
"""Build a Connector object based upon protocol and architecture."""
|
|
|
|
_mapping = get_connector_mapping(arch)
|
|
|
|
LOG.debug("Factory for %(protocol)s on %(arch)s",
|
|
{'protocol': protocol, 'arch': arch})
|
|
protocol = protocol.upper()
|
|
|
|
# set any special kwargs needed by connectors
|
|
if protocol in (initiator.NFS, initiator.GLUSTERFS,
|
|
initiator.SCALITY, initiator.QUOBYTE,
|
|
initiator.VZSTORAGE):
|
|
kwargs.update({'mount_type': protocol.lower()})
|
|
elif protocol == initiator.ISER:
|
|
kwargs.update({'transport': 'iser'})
|
|
|
|
# now set all the default kwargs
|
|
kwargs.update(
|
|
{'root_helper': root_helper,
|
|
'driver': driver,
|
|
'use_multipath': use_multipath,
|
|
'device_scan_attempts': device_scan_attempts,
|
|
})
|
|
|
|
connector = _mapping.get(protocol)
|
|
if not connector:
|
|
msg = (_("Invalid InitiatorConnector protocol "
|
|
"specified %(protocol)s") %
|
|
dict(protocol=protocol))
|
|
raise exception.InvalidConnectorProtocol(msg)
|
|
|
|
conn_cls = importutils.import_class(connector)
|
|
return conn_cls(*args, **kwargs)
|