From 749ef3432fa86a8faa013973645933589ecc0a7b Mon Sep 17 00:00:00 2001 From: Paul Cuzner Date: Wed, 29 Jul 2020 10:16:41 +1200 Subject: [PATCH] ceph-volume: add libstoragemgmt support Initial support for libstoragemgmt integration to provide additional metadata to the device inventory. LSM provides health, rpm, linkspeed, linktype as well as visibility of the disks's LED status indicators (specifically the IDENT and FAULT lights) Signed-off-by: Paul Cuzner (cherry picked from commit 3d3bbc13ea74ce2f5b902785df588174dedb3c24) --- src/ceph-volume/ceph_volume/util/device.py | 23 +++ src/ceph-volume/ceph_volume/util/lsmdisk.py | 203 ++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 src/ceph-volume/ceph_volume/util/lsmdisk.py diff --git a/src/ceph-volume/ceph_volume/util/device.py b/src/ceph-volume/ceph_volume/util/device.py index 2d00f6da3e59..deb7aa8cb4fa 100644 --- a/src/ceph-volume/ceph_volume/util/device.py +++ b/src/ceph-volume/ceph_volume/util/device.py @@ -5,6 +5,7 @@ from functools import total_ordering from ceph_volume import sys_info, process from ceph_volume.api import lvm from ceph_volume.util import disk +from ceph_volume.util.lsmdisk import LSMDisk from ceph_volume.util.constants import ceph_disk_guids report_template = """ @@ -63,6 +64,7 @@ class Device(object): 'path', 'sys_api', 'device_id', + 'lsm_data', ] pretty_report_sys_fields = [ 'human_readable_size', @@ -90,6 +92,7 @@ class Device(object): self._exists = None self._is_lvm_member = None self._parse() + self.lsm_data = self.fetch_lsm() self.available_lvm, self.rejected_reasons_lvm = self._check_lvm_reject_reasons() self.available_raw, self.rejected_reasons_raw = self._check_raw_reject_reasons() @@ -99,6 +102,26 @@ class Device(object): self.device_id = self._get_device_id() + def fetch_lsm(self): + ''' + Attempt to fetch libstoragemgmt (LSM) metadata, and return to the caller + as a dict. An empty dict is passed back to the caller if the target path + is not a block device, or lsm is unavailable on the host. Otherwise the + json returned will provide LSM attributes, and any associated errors that + lsm encountered when probing the device. + ''' + devName = self.path.split('/')[-1] + if not os.path.exists('/sys/block/{}'.format(devName)): + return dict() + + lsm_disk = LSMDisk(self.path) + if not lsm_disk.lsm_available: + return dict() + + lsm_json = lsm_disk.json_report() + + return lsm_json + def __lt__(self, other): ''' Implementing this method and __eq__ allows the @total_ordering diff --git a/src/ceph-volume/ceph_volume/util/lsmdisk.py b/src/ceph-volume/ceph_volume/util/lsmdisk.py new file mode 100644 index 000000000000..5eaf64bc6921 --- /dev/null +++ b/src/ceph-volume/ceph_volume/util/lsmdisk.py @@ -0,0 +1,203 @@ +""" +This module handles the interaction with libstoragemgmt for local disk +devices. Interaction may fail with LSM for a number of issues, but the +intent here is to make this a soft fail, since LSM related data is not +a critical component of ceph-volume. +""" +import logging + +try: + import lsm + from lsm._local_disk import LocalDisk, LsmError +except ImportError: + lsm_available = False +else: + lsm_available = True + +logger = logging.getLogger(__name__) + + +class LSMDisk: + def __init__(self, dev_path): + self.dev_path = dev_path + self.error_list = set() + + if lsm_available: + self.lsm_available = True + self.disk = LocalDisk() + else: + self.lsm_available = False + self.error_list.add("libstoragemgmt (lsm module) is unavailable") + logger.info("LSM information is unavailable: libstoragemgmt is not installed") + self.disk = None + + self.led_bits = None + + @property + def errors(self): + """show any errors that the LSM interaction has encountered (str)""" + return ",".join(self.error_list) + + def _query_lsm(self, func, path): + """Common method used to call the LSM functions, returning the function's result or None""" + + # if disk is None, lsm is unavailable so all calls should return None + if self.disk is None: + return None + + method = getattr(self.disk, func) + try: + output = method(path) + except LsmError as err: + # logger.exception("LSM Error: {}".format(err._msg)) + self.error_list.add(err._msg) + return None + else: + return output + + @property + def led_status(self): + """Fetch LED status, store in the LSMDisk object and return current status (int)""" + if self.led_bits is None: + bitfield = self._query_lsm('led_status_get', self.dev_path) or 1 + self.led_bits = bitfield + return bitfield + else: + return self.led_bits + + @property + def led_ident_state(self): + """Query a disks IDENT LED state to discover when it is On, Off or Unknown (str)""" + led_state = self.led_status + if led_state == 1: + return "Unsupported" + if led_state & lsm.Disk.LED_STATUS_IDENT_ON == lsm.Disk.LED_STATUS_IDENT_ON: + return "On" + elif led_state & lsm.Disk.LED_STATUS_IDENT_OFF == lsm.Disk.LED_STATUS_IDENT_OFF: + return "Off" + elif led_state & lsm.Disk.LED_STATUS_IDENT_UNKNOWN == lsm.Disk.LED_STATUS_IDENT_UNKNOWN: + return "Unknown" + + return "Unsupported" + + @property + def led_fault_state(self): + """Query a disks FAULT LED state to discover when it is On, Off or Unknown (str)""" + led_state = self.led_status + if led_state == 1: + return "Unsupported" + if led_state & lsm.Disk.LED_STATUS_FAULT_ON == lsm.Disk.LED_STATUS_FAULT_ON: + return "On" + elif led_state & lsm.Disk.LED_STATUS_FAULT_OFF == lsm.Disk.LED_STATUS_FAULT_OFF: + return "Off" + elif led_state & lsm.Disk.LED_STATUS_FAULT_UNKNOWN == lsm.Disk.LED_STATUS_FAULT_UNKNOWN: + return "Unknown" + + return "Unsupported" + + @property + def led_ident_support(self): + """Query the LED state to determine IDENT support: Unknown, Supported, Unsupported (str)""" + led_state = self.led_status + if led_state == 1: + return "Unknown" + + ident_states = ( + lsm.Disk.LED_STATUS_IDENT_ON + + lsm.Disk.LED_STATUS_IDENT_OFF + + lsm.Disk.LED_STATUS_IDENT_UNKNOWN + ) + + if (led_state & ident_states) == 0: + return "Unsupported" + + return "Supported" + + @property + def led_fault_support(self): + """Query the LED state to determine FAULT support: Unknown, Supported, Unsupported (str)""" + led_state = self.led_status + if led_state == 1: + return "Unknown" + + fail_states = ( + lsm.Disk.LED_STATUS_FAULT_ON + + lsm.Disk.LED_STATUS_FAULT_OFF + + lsm.Disk.LED_STATUS_FAULT_UNKNOWN + ) + + if led_state & fail_states == 0: + return "Unsupported" + + return "Supported" + + @property + def health(self): + """Determine the health of the disk from LSM : Unknown, Fail, Warn or Good (str)""" + health_map = { + -1: "Unknown", + 0: "Fail", + 1: "Warn", + 2: "Good", + } + _health_int = self._query_lsm('health_status_get', self.dev_path) + if _health_int is None: + _health_int = -1 + return health_map.get(_health_int, "Unknown") + + @property + def transport(self): + """Translate a disks link type to a human readable format (str)""" + _link_type = self._query_lsm('link_type_get', self.dev_path) + + if _link_type is not None: + _transport_map = { + lsm.Disk.LINK_TYPE_UNKNOWN: "Unavailable", + lsm.Disk.LINK_TYPE_FC: "Fibre Channel", + lsm.Disk.LINK_TYPE_SSA: "IBM SSA", + lsm.Disk.LINK_TYPE_SBP: "Serial Bus", + lsm.Disk.LINK_TYPE_SRP: "SCSI RDMA", + lsm.Disk.LINK_TYPE_ISCSI: "iSCSI", + lsm.Disk.LINK_TYPE_SAS: "SAS", + lsm.Disk.LINK_TYPE_ADT: "ADT (Tape)", + lsm.Disk.LINK_TYPE_ATA: "ATA/SATA", + lsm.Disk.LINK_TYPE_USB: "USB", + lsm.Disk.LINK_TYPE_SOP: "SCSI over PCI-E", + lsm.Disk.LINK_TYPE_PCIE: "PCI-E", + } + return _transport_map.get(_link_type, "Unknown") + else: + return "Unknown" + + @property + def media_type(self): + """Use the rpm value to determine the type of disk media: Flash or HDD (str)""" + _rpm = self._query_lsm('rpm_get', self.dev_path) + if _rpm is not None: + if _rpm == 0: + return "Flash" + elif _rpm > 1: + return "HDD" + + return "Unknown" + + def json_report(self): + """Return the LSM related metadata for the current local disk (dict)""" + if self.lsm_available: + return { + "serialNum": self._query_lsm('serial_num_get', self.dev_path) or "Unknown", + "transport": self.transport, + "mediaType": self.media_type, + "rpm": self._query_lsm('rpm_get', self.dev_path) or "Unknown", + "linkSpeed": self._query_lsm('link_speed_get', self.dev_path) or "Unknown", + "health": self.health, + "ledSupport": { + "IDENTsupport": self.led_ident_support, + "IDENTstatus": self.led_ident_state, + "FAILsupport": self.led_fault_support, + "FAILstatus": self.led_fault_state, + }, + "errors": list(self.error_list) + } + else: + return dict() -- 2.47.3