]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
ceph-volume: add libstoragemgmt support
authorPaul Cuzner <pcuzner@redhat.com>
Tue, 28 Jul 2020 22:16:41 +0000 (10:16 +1200)
committerJan Fajerski <jfajerski@suse.com>
Thu, 27 Aug 2020 12:30:28 +0000 (14:30 +0200)
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 <pcuzner@redhat.com>
(cherry picked from commit 3d3bbc13ea74ce2f5b902785df588174dedb3c24)

src/ceph-volume/ceph_volume/util/device.py
src/ceph-volume/ceph_volume/util/lsmdisk.py [new file with mode: 0644]

index 2d00f6da3e59638deda4cf214da1a75151d97672..deb7aa8cb4fadb431acfa24042f3f7094b174113 100644 (file)
@@ -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 (file)
index 0000000..5eaf64b
--- /dev/null
@@ -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()