diff --git a/sysinv/sysinv/sysinv/scripts/manage-partitions b/sysinv/sysinv/sysinv/scripts/manage-partitions index b37dc98484..e999bc5a09 100755 --- a/sysinv/sysinv/sysinv/scripts/manage-partitions +++ b/sysinv/sysinv/sysinv/scripts/manage-partitions @@ -169,8 +169,17 @@ def _get_disk_device_path(part_device_path): :param part_device_path: the partition's device path :returns the device path of the disk on which the partition resides """ - return re.match('(/dev/disk/by-path/(.+))-part([0-9]+)', - part_device_path).group(1) + disk_device_path = "" + if 'by-path' in part_device_path: + disk_device_path = re.match('(/dev/disk/by-path/(.+))-part([0-9]+)', + part_device_path).group(1) + if constants.DEVICE_NAME_MPATH in part_device_path: + match_path = re.match('(/dev/disk/by-id/.+)-part([0-9]+)(-mpath.*)', + part_device_path) + if match_path: + disk_device_path = match_path.group(1) + match_path.group(3) + + return disk_device_path def _get_partition_number(part_device_path): @@ -178,7 +187,7 @@ def _get_partition_number(part_device_path): :param part_device_path: the partition's device path :returns the partition's number """ - return re.match('.*?([0-9]+)$', part_device_path).group(1) + return utils.get_part_number(part_device_path) def _partition_exists(part_device_path): @@ -335,7 +344,8 @@ def _create_partition(disk_device_path, part_number, start_mib, size_mib, # After a partition is created we have to wait for udev to create the # corresponding device node. Otherwise if we try to open it will fail. - part_device_path = '{}-part{}'.format(disk_device_path, part_number) + part_device_path = utils.get_part_device_path(disk_device_path, + part_number) _wait_for_partition(part_device_path, loop_wait_time=PARTITION_LOOP_WAIT_TIME) @@ -524,8 +534,8 @@ def create_partitions(data, mode, pfile): new_partition = _create_partition( disk_device_path, partition_number, start_mib, size_mib, type_code) - part_device_path = '{}-part{}'.format(disk_device_path, - partition_number) + part_device_path = utils.get_part_device_path(disk_device_path, + partition_number) output, _, _ = _command(["udevadm", "settle", "-E", disk_device_path]) diff --git a/sysinv/sysinv/sysinv/sysinv/agent/disk.py b/sysinv/sysinv/sysinv/sysinv/agent/disk.py index a51f7e0ace..5cdcfa1e1a 100644 --- a/sysinv/sysinv/sysinv/sysinv/agent/disk.py +++ b/sysinv/sysinv/sysinv/sysinv/agent/disk.py @@ -89,6 +89,8 @@ class DiskOperator(object): if device is not None: if constants.DEVICE_NAME_NVME in device: re_line = re.compile(r'^(nvme[0-9]*n[0-9]*)') + elif constants.DEVICE_NAME_DM in device: + return utils.get_mpath_from_dm(os.path.join("/dev", device)) else: re_line = re.compile(r'^(\D*)') match = re_line.search(device) @@ -251,9 +253,16 @@ class DiskOperator(object): continue if device['MAJOR'] in constants.VALID_MAJOR_LIST: + device_node = device.device_node if 'ID_PATH' in device: device_path = "/dev/disk/by-path/" + device['ID_PATH'] LOG.debug("[DiskEnum] device_path: %s ", device_path) + elif (constants.DEVICE_NAME_MPATH in device.get("DM_NAME", "") + and 'DM_UUID' in device): + device_path = "/dev/disk/by-id/dm-uuid-" + device['DM_UUID'] + LOG.debug("[DiskEnum] device_path: %s ", device_path) + device_node = utils.get_mpath_from_dm(device.device_node) + LOG.debug("[DiskEnum] device_node: %s ", device_node) else: # We should always have a udev supplied /dev/disk/by-path # value as a matter of normal operation. We do not expect @@ -266,7 +275,7 @@ class DiskOperator(object): # system. device_path = None LOG.error("Device %s does not have an ID_PATH value provided " - "by udev" % device.device_node) + "by udev" % device_node) size_mib = 0 available_mib = 0 @@ -276,14 +285,14 @@ class DiskOperator(object): # Can merge all try/except in one block but this allows at # least attributes with no exception to be filled try: - size_mib = utils.get_disk_capacity_mib(device.device_node) + size_mib = utils.get_disk_capacity_mib(device_node) except Exception as e: self.handle_exception("Could not retrieve disk size - %s " % e) try: available_mib = self.get_disk_available_mib( - device_node=device.device_node) + device_node=device_node) except Exception as e: self.handle_exception("Could not retrieve disk %s free space" % e) @@ -331,6 +340,8 @@ class DiskOperator(object): try: if 'ID_SCSI_SERIAL' in device: serial_id = device['ID_SCSI_SERIAL'] + elif constants.DEVICE_NAME_MPATH in device.get('DM_UUID', ''): + serial_id = device.get('DM_UUID').split('-')[1] else: serial_id = device['ID_SERIAL_SHORT'] except Exception as e: @@ -341,7 +352,7 @@ class DiskOperator(object): if model_num: capabilities.update({'model_num': model_num}) - if self.get_rootfs_node() == device.device_node: + if self.get_rootfs_node() == device_node: capabilities.update({'stor_function': 'rootfs'}) rotational = self.is_rotational(device) @@ -365,7 +376,7 @@ class DiskOperator(object): device_id, device_wwn = self.get_device_id_wwn(device) attr = { - 'device_node': device.device_node, + 'device_node': device_node, 'device_num': device.device_number, 'device_type': device_type, 'device_path': device_path, diff --git a/sysinv/sysinv/sysinv/sysinv/agent/partition.py b/sysinv/sysinv/sysinv/sysinv/agent/partition.py index fc390a95ae..2882c1d6a6 100644 --- a/sysinv/sysinv/sysinv/sysinv/agent/partition.py +++ b/sysinv/sysinv/sysinv/sysinv/agent/partition.py @@ -93,8 +93,9 @@ class PartitionOperator(object): partition_number) else: part_device_node = '{}{}'.format(device_node, partition_number) - part_device_path = '{}-part{}'.format(device_path, - partition_number) + + part_device_path = utils.get_part_device_path(device_path, + partition_number) start_mib = partition.get('start_mib') end_mib = partition.get('end_mib') @@ -132,8 +133,13 @@ class PartitionOperator(object): continue if device['MAJOR'] in constants.VALID_MAJOR_LIST: - device_path = "/dev/disk/by-path/" + device['ID_PATH'] - device_node = device.device_node + if 'ID_PATH' in device: + device_path = "/dev/disk/by-path/" + device['ID_PATH'] + device_node = device.device_node + elif (constants.DEVICE_NAME_MPATH in device.get("DM_NAME", "") + and 'DM_UUID' in device): + device_path = "/dev/disk/by-id/dm-uuid-" + device['DM_UUID'] + device_node = utils.get_mpath_from_dm(device.device_node) try: new_partitions = self.get_partition_info(device_path=device_path, diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/partition.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/partition.py index b1f46203cf..76cf12cbdb 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/partition.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/partition.py @@ -411,13 +411,14 @@ def _build_device_node_path(partition): (idisk.device_node, len(partitions) + 1) else: device_node = "%s%s" % (idisk.device_node, len(partitions) + 1) - device_path = "%s-part%s" % (idisk.device_path, len(partitions) + 1) + device_path = cutils.get_part_device_path(idisk.device_path, + str(len(partitions) + 1)) else: if constants.DEVICE_NAME_NVME in idisk.device_node: device_node = idisk.device_node + "p1" else: device_node = idisk.device_node + '1' - device_path = idisk.device_path + '-part1' + device_path = cutils.get_part_device_path(idisk.device_path, "1") return device_node, device_path diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/storage.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/storage.py index 6de2575070..0037cc6387 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/storage.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/storage.py @@ -23,7 +23,6 @@ from eventlet.green import subprocess import jsonpatch import pecan from pecan import rest -import re import six import wsme from wsme import types as wtypes @@ -163,16 +162,16 @@ class Storage(base.APIBase): for d in disks: if (stor.journal_path is not None and d.device_path is not None and - d.device_path in stor.journal_path): - partition_number = (re.match('.*?([0-9]+)$', - stor.journal_path).group(1)) + cutils.is_part_of_disk(stor.journal_path, + d.device_path)): + part_number = cutils.get_part_number(stor.journal_path) if (d.device_node is not None and constants.DEVICE_NAME_NVME in d.device_node): stor.journal_node = "{}p{}".format(d.device_node, - partition_number) + part_number) else: stor.journal_node = "{}{}".format(d.device_node, - partition_number) + part_number) break # never expose the ihost_id attribute, allow exposure for now @@ -1024,12 +1023,14 @@ def _create_journal(journal_location, journal_size_mib, stor): # Determine if the journal partition is collocated or not. if stor.uuid == journal_location: # The collocated journal is always on /dev/sdX2. - journal_device_path = journal_onistor_idisk.device_path + "-part" + "2" + journal_device_path = cutils.get_part_device_path( + journal_onistor_idisk.device_path, "2") else: # Obtain the last partition index on which the journal will reside. last_index = len(pecan.request.dbapi.journal_get_all(journal_location)) - journal_device_path = (journal_onistor_idisk.device_path + "-part" + - str(last_index + 1)) + journal_device_path = cutils.get_part_device_path( + journal_onistor_idisk.device_path, + str(last_index + 1)) journal_values = {'device_path': journal_device_path, 'size_mib': journal_size_mib, diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index cc847a53b9..f7934b1310 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -808,6 +808,9 @@ DEVICE_TYPE_NVME = 'NVME' DEVICE_TYPE_UNDETERMINED = 'Undetermined' DEVICE_TYPE_NA = 'N/A' DEVICE_NAME_NVME = 'nvme' +DEVICE_NAME_DM = 'dm-' +DEVICE_NAME_MPATH = 'mpath' +DEVICE_FS_TYPE_MPATH = 'mpath_member' # Disk model types. DEVICE_MODEL_UNKNOWN = 'Unknown' diff --git a/sysinv/sysinv/sysinv/sysinv/common/utils.py b/sysinv/sysinv/sysinv/sysinv/common/utils.py index f53ff33b3a..dc31167b36 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/utils.py +++ b/sysinv/sysinv/sysinv/sysinv/common/utils.py @@ -48,6 +48,7 @@ import math import os import pathlib import psutil +import pyudev import pwd import random import re @@ -555,6 +556,7 @@ def is_system_usable_block_device(pydev_device): Example devices: o local block devices: local HDDs, SSDs, RAID arrays o remote devices: iscsi mounted, LIO, EMC + o mpath partition and member devices o non permanent devices: USB stick :return bool: True if device can be used else False """ @@ -564,6 +566,13 @@ def is_system_usable_block_device(pydev_device): if pydev_device.get("DM_VG_NAME") or pydev_device.get("DM_LV_NAME"): # Skip LVM devices return False + if (constants.DEVICE_NAME_MPATH in pydev_device.get("DM_NAME", "") + and pydev_device.get("ID_PART_ENTRY_NAME")): + # Skip mpath partition devices + return False + if pydev_device.get("ID_FS_TYPE") == constants.DEVICE_FS_TYPE_MPATH: + # Skip mpath member devices + return False id_path = pydev_device.get("ID_PATH", "") if "iqn." in id_path or "eui." in id_path: # Skip all iSCSI devices, they are links for volume storage. @@ -1633,7 +1642,7 @@ def is_partition_the_last(dbapi, partition): """ idisk_uuid = partition.get('idisk_uuid') onidisk_parts = dbapi.partition_get_by_idisk(idisk_uuid) - part_number = re.match('.*?([0-9]+)$', + part_number = re.match('.*?-part([0-9]+)', partition.get('device_path')).group(1) if int(part_number) != len(onidisk_parts): @@ -3517,3 +3526,76 @@ def get_module_name_from_entry_point(entry_point): raise exception.SysinvException(_( "Module name for entry point {} " "could not be determined.".format(entry_point))) + + +def get_mpath_from_dm(dm_device): + """Get mpath node from /dev/dm-N""" + mpath_device = None + + context = pyudev.Context() + + pydev_device = pyudev.Device.from_device_file(context, dm_device) + + if constants.DEVICE_NAME_MPATH in pydev_device.get("DM_NAME", ""): + re_line = re.compile(r'^(\D*)') + match = re_line.search(pydev_device.get("DM_NAME")) + if match: + mpath_device = os.path.join("/dev/mapper", match.group(1)) + + return mpath_device + + +def get_part_device_path(disk_device_path, part_number): + """Get the partition device path. + :param disk_device_path: the device path of the disk on which the + partition resides + :param part_number: the partition number + :returns the partition device path + """ + if constants.DEVICE_NAME_MPATH in disk_device_path: + path_split = disk_device_path.split(constants.DEVICE_NAME_MPATH) + part_device_path = '{}part{}-{}{}'.format(path_split[0], + part_number, + constants.DEVICE_NAME_MPATH, + path_split[1]) + else: + part_device_path = '{}-part{}'.format(disk_device_path, part_number) + + return part_device_path + + +def get_part_number(part_device_path): + """Obtain the number of a partition. + :param part_device_path: the partition's device path + :returns the partition's number + """ + part_num = "" + if 'by-path' in part_device_path: + part_num = re.match('.*?([0-9]+)$', part_device_path).group(1) + + if constants.DEVICE_NAME_MPATH in part_device_path: + match_path = re.match('(/dev/disk/by-id/.+)-part([0-9]+)(-mpath.*)', + part_device_path) + if match_path: + part_num = match_path.group(2) + + return part_num + + +def is_part_of_disk(part_device_path, disk_device_path): + """Check if a partition is part of a disk + :param part_device_path: the partition's device path + :param disk_device_path: the disk's device path + :returns the partition's number + """ + is_part_of_disk = False + + if disk_device_path in part_device_path: + is_part_of_disk = True + elif constants.DEVICE_NAME_MPATH in disk_device_path: + path_split = disk_device_path.split(constants.DEVICE_NAME_MPATH) + if (path_split[0] in part_device_path and + path_split[1] in part_device_path): + is_part_of_disk = True + + return is_part_of_disk diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index 87cd7749af..46a5e60c65 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -4228,10 +4228,11 @@ class ConductorManager(service.PeriodicService): idisk_uuid, sort_key='device_path') if partitions: device_node = "%s%s" % (idisk.device_node, len(partitions) + 1) - device_path = "%s-part%s" % (idisk.device_path, len(partitions) + 1) + device_path = cutils.get_part_device_path(idisk.device_path, + str(len(partitions) + 1)) else: device_node = idisk.device_node + '1' - device_path = idisk.device_path + '-part1' + device_path = cutils.get_part_device_path(idisk.device_path, "1") return device_node, device_path @@ -4507,8 +4508,18 @@ class ConductorManager(service.PeriodicService): continue # Obtain the disk the partition is on. - part_disk = next((d for d in db_disks - if d.device_path in db_part.device_path), None) + part_disk = None + for d in db_disks: + if d.device_path in db_part.device_path: + part_disk = d + break + elif constants.DEVICE_NAME_MPATH in d.device_node: + path_split = d.device_path.split(constants.DEVICE_NAME_MPATH) + if (path_split[0] in db_part.device_path and + path_split[1] in db_part.device_path): + is_part_of_disk = True + part_disk = d + break if not part_disk: # Should not happen as we only store partitions associated @@ -4581,7 +4592,14 @@ class ConductorManager(service.PeriodicService): LOG.debug("PART conductor - partition not found, adding...") # Complete disk info. for db_disk in db_disks: + is_part_of_disk = False if db_disk.device_path in ipart['device_path']: + is_part_of_disk = True + elif constants.DEVICE_NAME_MPATH in db_disk.device_node: + path_split = db_disk.device_path.split(constants.DEVICE_NAME_MPATH) + if path_split[0] in ipart['device_path'] and path_split[1] in ipart['device_path']: + is_part_of_disk = True + if is_part_of_disk: part_dict.update({'idisk_id': db_disk.id, 'idisk_uuid': db_disk.uuid}) LOG.debug("PART conductor - disk - part_dict: %s " % @@ -12338,7 +12356,7 @@ class ConductorManager(service.PeriodicService): "host %s " % active_controller.hostname)) # The partition for cinder volumes is always the first. - cinder_device_partition = '{}{}'.format(cinder_device, '-part1') + cinder_device_partition = cutils.get_part_device_path(cinder_device, '1') cinder_size = self.get_partition_size(context, cinder_device_partition) return cinder_size diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py index 1a13097560..9cb92bff72 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py @@ -21,7 +21,6 @@ import eventlet -import re from oslo_config import cfg from oslo_db import exception as db_exc @@ -2987,11 +2986,9 @@ class Connection(api.Connection): # Update the journal device path. journals = self.journal_get_all(stor.uuid) for journal in journals: - partition_number = re.match('.*?([0-9]+)$', - journal.device_path).group(1) - device_path = "{}{}{}".format(disk['device_path'], - "-part", - partition_number) + partition_number = utils.get_part_number(journal.device_path) + device_path = utils.get_part_device_path(disk['device_path'], + partition_number) updates = {'device_path': device_path} self.journal_update(journal['uuid'], updates) @@ -3015,8 +3012,9 @@ class Connection(api.Connection): for journal in journals: # Update DB journal_path = journal_disk.device_path - updates = {'device_path': journal_path + "-part" + - str(partition_index)} + updates = {'device_path': + utils.get_part_device_path(journal_path, + str(partition_index))} self.journal_update(journal.id, updates) partition_index += 1 # Update output @@ -3062,8 +3060,9 @@ class Connection(api.Connection): for journal in journals: stor = self.istor_get(journal.foristorid) disk = self.idisk_get_by_istor(stor.uuid)[0] + device_path = utils.get_part_device_path(disk.device_path, "2") journal_vals = {'onistor_uuid': stor.uuid, - 'device_path': disk.device_path + "-part" + "2", + 'device_path': device_path, 'size_mib': CONF.journal.journal_default_size} self.journal_update(journal.id, journal_vals) @@ -3153,8 +3152,8 @@ class Connection(api.Connection): if value == istor_obj['uuid']: # If the journal becomes collocated, assign second # partition. - journal_vals['device_path'] = new_onidisk.device_path + \ - "-part" + "2" + journal_vals['device_path'] = utils.get_part_device_path( + new_onidisk.device_path, "2") del values[key] diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/ceph.py b/sysinv/sysinv/sysinv/sysinv/puppet/ceph.py index 93a20ec821..4f0e697be4 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/ceph.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/ceph.py @@ -338,11 +338,12 @@ class CephPuppet(openstack.OpenstackBasePuppet): if stor.function == constants.STOR_FUNCTION_OSD: # platform_ceph_osd puppet resource parameters + data_path = utils.get_part_device_path(disk.device_path, '1') osd = { 'osd_id': stor.osdid, 'osd_uuid': stor.uuid, 'disk_path': disk.device_path, - 'data_path': disk.device_path + '-part1', + 'data_path': data_path, 'journal_path': stor.journal_path, 'tier_name': stor.tier_name, } diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/storage.py b/sysinv/sysinv/sysinv/sysinv/puppet/storage.py index c42a6f4c42..90ea4c67b1 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/storage.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/storage.py @@ -8,6 +8,7 @@ import json import re from sysinv.common import constants +from sysinv.common import utils from sysinv.puppet import base @@ -299,7 +300,7 @@ class StoragePuppet(base.BasePuppet): # add the disk partition to the disk path partition_number = re.match('.*?([0-9]+)$', pv.lvm_pv_name).group(1) - pv_path += "-part%s" % partition_number + pv_path = utils.get_part_device_path(pv_path, partition_number) if (pv.pv_state == constants.PV_ADD): adding_pvs.append(pv_path) diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_partition.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_partition.py index 058fda4252..2b22700bbf 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/test_partition.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_partition.py @@ -50,7 +50,7 @@ class TestPartition(base.FunctionalTest): API_HEADERS = {'User-Agent': 'sysinv-test'} disk_device_path = '/dev/disk/by-path/pci-0000:00:0d.0-ata-1.0' - partition_device_path = '/dev/disk/by-path/pci-0000:00:0d.0-ata-1.1' + partition_device_path = '/dev/disk/by-path/pci-0000:00:0d.0-ata-1.0-part1' def setUp(self): super(TestPartition, self).setUp()