188 lines
7.2 KiB
Python
188 lines
7.2 KiB
Python
# 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 __future__ import annotations
|
|
|
|
|
|
import functools
|
|
import glob
|
|
import os
|
|
import typing
|
|
from typing import Optional
|
|
|
|
from oslo_concurrency import lockutils
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import reflection
|
|
from oslo_utils import timeutils
|
|
|
|
from os_brick import exception
|
|
from os_brick import initiator
|
|
from os_brick.initiator import host_driver
|
|
from os_brick.initiator import initiator_connector
|
|
from os_brick.initiator import linuxscsi
|
|
from os_brick import utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
|
|
|
|
def synchronized(name, lock_file_prefix='os-brick-', external=False,
|
|
lock_path=None, semaphores=None, delay=0.01, fair=False,
|
|
blocking=True):
|
|
"""os-brick synchronization decorator
|
|
|
|
Like the one in lock_utils but defaulting the prefix to os-brick- and using
|
|
our own lock_path.
|
|
|
|
Cannot use lock_utils one because when using the default we don't know the
|
|
value until setup has been called, which can be after the code using the
|
|
decorator has been loaded.
|
|
"""
|
|
def wrap(f):
|
|
@functools.wraps(f)
|
|
def inner(*args, **kwargs):
|
|
t1 = timeutils.now()
|
|
t2 = None
|
|
gotten = True
|
|
lpath = lock_path or CONF.os_brick.lock_path
|
|
# TODO: (AA Release) Remove this failsafe
|
|
if not lpath and CONF.oslo_concurrency.lock_path:
|
|
LOG.warning("Service needs to call os_brick.setup() before "
|
|
"connecting volumes, if it doesn't it will break "
|
|
"on the next release")
|
|
lpath = CONF.oslo_concurrency.lock_path
|
|
f_name = reflection.get_callable_name(f)
|
|
try:
|
|
LOG.debug('Acquiring lock "%s" by "%s"', name, f_name)
|
|
with lockutils.lock(name, lock_file_prefix, external, lpath,
|
|
do_log=False, semaphores=semaphores,
|
|
delay=delay, fair=fair, blocking=blocking):
|
|
t2 = timeutils.now()
|
|
LOG.debug('Lock "%(name)s" acquired by "%(function)s" :: '
|
|
'waited %(wait_secs)0.3fs',
|
|
{'name': name,
|
|
'function': f_name,
|
|
'wait_secs': (t2 - t1)})
|
|
return f(*args, **kwargs)
|
|
except lockutils.AcquireLockFailedException:
|
|
gotten = False
|
|
finally:
|
|
t3 = timeutils.now()
|
|
if t2 is None:
|
|
held_secs = "N/A"
|
|
else:
|
|
held_secs = "%0.3fs" % (t3 - t2)
|
|
LOG.debug('Lock "%(name)s" "%(gotten)s" by "%(function)s" ::'
|
|
' held %(held_secs)s',
|
|
{'name': name,
|
|
'gotten': 'released' if gotten else 'unacquired',
|
|
'function': f_name,
|
|
'held_secs': held_secs})
|
|
return inner
|
|
|
|
return wrap
|
|
|
|
|
|
class BaseLinuxConnector(initiator_connector.InitiatorConnector):
|
|
os_type = initiator.OS_TYPE_LINUX
|
|
|
|
def __init__(self, root_helper: str, driver=None, execute=None,
|
|
*args, **kwargs):
|
|
self._linuxscsi = linuxscsi.LinuxSCSI(root_helper, execute=execute)
|
|
|
|
if not driver:
|
|
driver = host_driver.HostDriver()
|
|
self.set_driver(driver)
|
|
|
|
super(BaseLinuxConnector, self).__init__(root_helper, execute=execute,
|
|
*args, **kwargs)
|
|
|
|
@staticmethod
|
|
def get_connector_properties(root_helper: str, *args, **kwargs) -> dict:
|
|
"""The generic connector properties."""
|
|
multipath = kwargs['multipath']
|
|
enforce_multipath = kwargs['enforce_multipath']
|
|
props = {}
|
|
|
|
props['multipath'] = (multipath and
|
|
linuxscsi.LinuxSCSI.is_multipath_running(
|
|
enforce_multipath, root_helper,
|
|
execute=kwargs.get('execute')))
|
|
|
|
return props
|
|
|
|
def check_valid_device(self, path: str, run_as_root: bool = True) -> bool:
|
|
return utils.check_valid_device(self, path)
|
|
|
|
def get_all_available_volumes(
|
|
self,
|
|
connection_properties: Optional[dict] = None) -> list:
|
|
volumes = []
|
|
path = self.get_search_path()
|
|
if path:
|
|
# now find all entries in the search path
|
|
if os.path.isdir(path):
|
|
path_items = [path, '/*']
|
|
file_filter = ''.join(path_items)
|
|
volumes = glob.glob(file_filter)
|
|
|
|
return volumes
|
|
|
|
def _discover_mpath_device(self,
|
|
device_wwn: str,
|
|
connection_properties: dict,
|
|
device_name: str) -> tuple[str, str]:
|
|
"""This method discovers a multipath device.
|
|
|
|
Discover a multipath device based on a defined connection_property
|
|
and a device_wwn and return the multipath_id and path of the multipath
|
|
enabled device if there is one.
|
|
"""
|
|
|
|
path = self._linuxscsi.find_multipath_device_path(device_wwn)
|
|
device_path = None
|
|
multipath_id = None
|
|
|
|
if path is None:
|
|
# find_multipath_device only accept realpath not symbolic path
|
|
device_realpath = os.path.realpath(device_name)
|
|
mpath_info = self._linuxscsi.find_multipath_device(
|
|
device_realpath)
|
|
if mpath_info:
|
|
device_path = mpath_info['device']
|
|
multipath_id = device_wwn
|
|
else:
|
|
# we didn't find a multipath device.
|
|
# so we assume the kernel only sees 1 device
|
|
device_path = device_name
|
|
LOG.debug("Unable to find multipath device name for "
|
|
"volume. Using path %(device)s for volume.",
|
|
{'device': device_path})
|
|
else:
|
|
device_path = path
|
|
multipath_id = device_wwn
|
|
if connection_properties.get('access_mode', '') != 'ro':
|
|
try:
|
|
# Sometimes the multipath devices will show up as read only
|
|
# initially and need additional time/rescans to get to RW.
|
|
self._linuxscsi.wait_for_rw(device_wwn, device_path)
|
|
except exception.BlockDeviceReadOnly:
|
|
LOG.warning('Block device %s is still read-only. '
|
|
'Continuing anyway.', device_path)
|
|
|
|
device_path = typing.cast(str, device_path)
|
|
multipath_id = typing.cast(str, multipath_id)
|
|
return device_path, multipath_id
|