summaryrefslogtreecommitdiff
path: root/ironic_python_agent/hardware.py
diff options
context:
space:
mode:
Diffstat (limited to 'ironic_python_agent/hardware.py')
-rw-r--r--ironic_python_agent/hardware.py153
1 files changed, 150 insertions, 3 deletions
diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py
index 54de8d72..a4cc60fd 100644
--- a/ironic_python_agent/hardware.py
+++ b/ironic_python_agent/hardware.py
@@ -81,6 +81,8 @@ RAID_APPLY_CONFIGURATION_ARGSINFO = {
}
}
+MULTIPATH_ENABLED = None
+
def _get_device_info(dev, devclass, field):
"""Get the device info according to device class and field."""
@@ -138,6 +140,36 @@ def _load_ipmi_modules():
il_utils.try_execute('modprobe', 'ipmi_si')
+def _load_multipath_modules():
+ """Load multipath modules
+
+ This is required to be able to collect multipath information.
+
+ Two separate paths exist, one with a helper utility for Centos/RHEL
+ and another which is just load the modules, and trust multipathd
+ will do the needful.
+ """
+ if (os.path.isfile('/usr/sbin/mpathconf')
+ and not os.path.isfile('/etc/multipath.conf')):
+ # For Centos/Rhel/Etc which uses mpathconf, this does
+ # a couple different things, including configuration generation...
+ # which is not *really* required.. at least *shouldn't* be.
+ # WARNING(TheJulia): This command explicitly replaces local
+ # configuration.
+ il_utils.try_execute('/usr/sbin/mpathconf', '--enable',
+ '--find_multipaths', 'yes',
+ '--with_module', 'y',
+ '--with_multipathd', 'y')
+ else:
+ # Ensure modules are loaded. Configuration is not required
+ # and implied based upon compiled in defaults.
+ # NOTE(TheJulia): Debian/Ubuntu specifically just document
+ # using `multipath -t` output to start a new configuration
+ # file, if needed.
+ il_utils.try_execute('modprobe', 'dm_multipath')
+ il_utils.try_execute('modprobe', 'multipath')
+
+
def _check_for_iscsi():
"""Connect iSCSI shared connected via iBFT or OF.
@@ -181,6 +213,84 @@ def _get_md_uuid(raid_device):
return match.group(1)
+def _enable_multipath():
+ """Initialize multipath IO if possible.
+
+ :returns: True if the multipathd daemon and multipath command to enumerate
+ devices was scucessfully able to be called.
+ """
+ try:
+ _load_multipath_modules()
+ # This might not work, ideally it *should* already be running...
+ # NOTE(TheJulia): Testing locally, a prior running multipathd, the
+ # explicit multipathd start just appears to silently exit with a
+ # result code of 0.
+ il_utils.execute('multipathd')
+ # This is mainly to get the system to actually do the needful and
+ # identify/enumerate paths by combining what it can detect and what
+ # it already knows. This may be useful, and in theory this should be
+ # logged in the IPA log should it be needed.
+ il_utils.execute('multipath', '-ll')
+ return True
+ except FileNotFoundError as e:
+ LOG.warning('Attempted to determine if multipath tools were present. '
+ 'Not detected. Error recorded: %s', e)
+ return False
+ except processutils.ProcessExecutionError as e:
+ LOG.warning('Attempted to invoke multipath utilities, but we '
+ 'encountered an error: %s', e)
+ return False
+
+
+def _get_multipath_parent_device(device):
+ """Check and return a multipath device."""
+ if not device:
+ # if lsblk provides invalid output, this can be None.
+ return
+ check_device = os.path.join('/dev', str(device))
+ try:
+ # Explicitly run the check as regardless of if the device is mpath or
+ # not, multipath tools when using list always exits with a return
+ # code of 0.
+ il_utils.execute('multipath', '-c', check_device)
+ # path check with return an exit code of 1 if you send it a multipath
+ # device mapper device, like dm-0.
+ # NOTE(TheJulia): -ll is supposed to load from all available
+ # information, but may not force a rescan. It may be -f if we need
+ # that. That being said, it has been about a decade since I was
+ # running multipath tools on SAN connected gear, so my memory is
+ # definitely fuzzy.
+ out, _ = il_utils.execute('multipath', '-ll', check_device)
+ except processutils.ProcessExecutionError as e:
+ # FileNotFoundError if the utility does not exist.
+ # -1 return code if the device is not valid.
+ LOG.debug('Checked device %(dev)s and determined it was '
+ 'not a multipath device. %(error)s',
+ {'dev': check_device,
+ 'error': e})
+ return
+ except FileNotFoundError:
+ # This should never happen, as MULTIPATH_ENABLED would be False
+ # before this occurs.
+ LOG.warning('Attempted to check multipathing status, however '
+ 'the \'multipath\' binary is missing or not in the '
+ 'execution PATH.')
+ return
+ # Data format:
+ # MPATHDEVICENAME dm-0 TYPE,HUMANNAME
+ # size=56G features='1 retain_attached_hw_handler' hwhandler='0' wp=rw
+ # `-+- policy='service-time 0' prio=1 status=active
+ # `- 0:0:0:0 sda 8:0 active ready running
+ try:
+ lines = out.splitlines()
+ mpath_device = lines[0].split(' ')[1]
+ # give back something like dm-0 so we can log it.
+ return mpath_device
+ except IndexError:
+ # We didn't get any command output, so Nope.
+ pass
+
+
def get_component_devices(raid_device):
"""Get the component devices of a Software RAID device.
@@ -371,7 +481,8 @@ def _md_scan_and_assemble():
def list_all_block_devices(block_type='disk',
ignore_raid=False,
ignore_floppy=True,
- ignore_empty=True):
+ ignore_empty=True,
+ ignore_multipath=False):
"""List all physical block devices
The switches we use for lsblk: P for KEY="value" output, b for size output
@@ -388,6 +499,9 @@ def list_all_block_devices(block_type='disk',
:param ignore_floppy: Ignore floppy disk devices in the block device
list. By default, these devices are filtered out.
:param ignore_empty: Whether to ignore disks with size equal 0.
+ :param ignore_multipath: Whether to ignore devices backing multipath
+ devices. Default is to consider multipath
+ devices, if possible.
:return: A list of BlockDevices
"""
@@ -398,6 +512,8 @@ def list_all_block_devices(block_type='disk',
return True
return False
+ check_multipath = not ignore_multipath and get_multipath_status()
+
_udev_settle()
# map device names to /dev/disk/by-path symbolic links that points to it
@@ -428,7 +544,6 @@ def list_all_block_devices(block_type='disk',
'-o{}'.format(','.join(columns)))[0]
lines = report.splitlines()
context = pyudev.Context()
-
devices = []
for line in lines:
device = {}
@@ -450,10 +565,25 @@ def list_all_block_devices(block_type='disk',
LOG.debug('Ignoring floppy disk device: %s', line)
continue
+ dev_kname = device.get('KNAME')
+ if check_multipath:
+ # Net effect is we ignore base devices, and their base devices
+ # to what would be the mapped device name which would not pass the
+ # validation, but would otherwise be match-able.
+ mpath_parent_dev = _get_multipath_parent_device(dev_kname)
+ if mpath_parent_dev:
+ LOG.warning(
+ "We have identified a multipath device %(device)s, this "
+ "is being ignored in favor of %(mpath_device)s and its "
+ "related child devices.",
+ {'device': dev_kname,
+ 'mpath_device': mpath_parent_dev})
+ continue
# Search for raid in the reply type, as RAID is a
# disk device, and we should honor it if is present.
# Other possible type values, which we skip recording:
# lvm, part, rom, loop
+
if devtype != block_type:
if devtype is None or ignore_raid:
LOG.debug(
@@ -462,7 +592,7 @@ def list_all_block_devices(block_type='disk',
{'block_type': block_type, 'line': line})
continue
elif ('raid' in devtype
- and block_type in ['raid', 'disk']):
+ and block_type in ['raid', 'disk', 'mpath']):
LOG.debug(
"TYPE detected to contain 'raid', signifying a "
"RAID volume. Found: %s", line)
@@ -476,6 +606,11 @@ def list_all_block_devices(block_type='disk',
LOG.debug(
"TYPE detected to contain 'md', signifying a "
"RAID partition. Found: %s", line)
+ elif devtype == 'mpath' and block_type == 'disk':
+ LOG.debug(
+ "TYPE detected to contain 'mpath', "
+ "signifing a device mapper multipath device. "
+ "Found: %s", line)
else:
LOG.debug(
"TYPE did not match. Wanted: %(block_type)s but found: "
@@ -1001,6 +1136,10 @@ class GenericHardwareManager(HardwareManager):
_check_for_iscsi()
_md_scan_and_assemble()
_load_ipmi_modules()
+ global MULTIPATH_ENABLED
+ if MULTIPATH_ENABLED is None:
+ MULTIPATH_ENABLED = _enable_multipath()
+
self.wait_for_disks()
return HardwareSupport.GENERIC
@@ -2787,3 +2926,11 @@ def deduplicate_steps(candidate_steps):
deduped_steps[manager].append(winning_step)
return deduped_steps
+
+
+def get_multipath_status():
+ """Return the status of multipath initialization."""
+ # NOTE(TheJulia): Provides a nice place to mock out and simplify testing
+ # as if we directly try and work with the global var, we will be racing
+ # tests endlessly.
+ return MULTIPATH_ENABLED