From dea7ac0fbfc0330964868c4d5c838b9fd34f5ddd Mon Sep 17 00:00:00 2001 From: Paul Cuzner Date: Mon, 31 Aug 2020 14:20:15 +1200 Subject: [PATCH] orchestrator: intergrate LSM information with device ls libstoragemgmt metatdata is now available with ceph-volume, so this patch reformats the plain text output mode of the orch device ls command to include device health and LED states. In addition ; the headings have been camel cased for readability, the device size is given in dec (since you buy drives in GB not GiB), a new wide mode has been added and the default output focuses on the most frequently referred to fields to keep it brief. Signed-off-by: Paul Cuzner --- src/pybind/mgr/orchestrator/module.py | 82 ++++++++++++++----- .../ceph/deployment/inventory.py | 5 +- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/src/pybind/mgr/orchestrator/module.py b/src/pybind/mgr/orchestrator/module.py index f46ab29f634..4650d38ca89 100644 --- a/src/pybind/mgr/orchestrator/module.py +++ b/src/pybind/mgr/orchestrator/module.py @@ -12,7 +12,7 @@ from ceph.deployment.inventory import Device from ceph.deployment.drive_group import DriveGroupSpec, DeviceSelection from ceph.deployment.service_spec import PlacementSpec, ServiceSpec -from mgr_util import format_bytes, to_pretty_timedelta +from mgr_util import format_bytes, to_pretty_timedelta, format_dimless from mgr_module import MgrModule, HandleCommandResult from ._interface import OrchestratorClientMixin, DeviceLightLoc, _cli_read_command, \ @@ -364,9 +364,10 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, 'orch device ls', "name=hostname,type=CephString,n=N,req=false " "name=format,type=CephChoices,strings=plain|json|json-pretty|yaml,req=false " - "name=refresh,type=CephBool,req=false", + "name=refresh,type=CephBool,req=false " + "name=wide,type=CephBool,req=false", 'List devices on a host') - def _list_devices(self, hostname=None, format='plain', refresh=False): + def _list_devices(self, hostname=None, format='plain', refresh=False, wide=False): # type: (Optional[List[str]], str, bool) -> HandleCommandResult """ Provide information about storage devices present in cluster hosts @@ -385,32 +386,71 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, if format != 'plain': return HandleCommandResult(stdout=to_format(completion.result, format, many=True, cls=InventoryHost)) else: - out = [] + display_map = { + "Unsupported": "N/A", + "N/A": "N/A", + "On": "On", + "Off": "Off", + True: "Yes", + False: "No", + } - table = PrettyTable( - ['HOST', 'PATH', 'TYPE', 'SIZE', 'DEVICE_ID', 'MODEL', 'VENDOR', 'ROTATIONAL', 'AVAIL', - 'REJECT REASONS'], - border=False) + out = [] + if wide: + table = PrettyTable( + ['Hostname', 'Path', 'Type', 'Transport', 'RPM', 'Vendor', 'Model', 'Serial', 'Size', 'Health', + 'Ident', 'Fault', 'Available', 'Reject Reasons'], + border=False) + else: + table = PrettyTable( + ['Hostname', 'Path', 'Type', 'Serial', 'Size', 'Health', 'Ident', 'Fault', 'Available'], + border=False) table.align = 'l' table._align['SIZE'] = 'r' table.left_padding_width = 0 table.right_padding_width = 2 for host_ in completion.result: # type: InventoryHost for d in host_.devices.devices: # type: Device - table.add_row( - ( - host_.name, - d.path, - d.human_readable_type, - format_bytes(d.sys_api.get('size', 0), 5), - d.device_id, - d.sys_api.get('model') or 'n/a', - d.sys_api.get('vendor') or 'n/a', - d.sys_api.get('rotational') or 'n/a', - d.available, - ', '.join(d.rejected_reasons) + + led_ident = 'N/A' + led_fail = 'N/A' + if d.lsm_data.get('ledSupport', None): + led_ident = d.lsm_data['ledSupport']['IDENTstatus'] + led_fail = d.lsm_data['ledSupport']['FAILstatus'] + + if wide: + table.add_row( + ( + host_.name, + d.path, + d.human_readable_type, + d.lsm_data.get('transport', 'Unknown'), + d.lsm_data.get('rpm', 'Unknown'), + d.sys_api.get('vendor') or 'N/A', + d.sys_api.get('model') or 'N/A', + d.lsm_data.get('serialNum', d.device_id.split('_')[-1]), + format_dimless(d.sys_api.get('size', 0), 5), + d.lsm_data.get('health', 'Unknown'), + display_map[led_ident], + display_map[led_fail], + display_map[d.available], + ', '.join(d.rejected_reasons) + ) + ) + else: + table.add_row( + ( + host_.name, + d.path, + d.human_readable_type, + d.lsm_data.get('serialNum', d.device_id.split('_')[-1]), + format_dimless(d.sys_api.get('size', 0), 5), + d.lsm_data.get('health', 'Unknown'), + display_map[led_ident], + display_map[led_fail], + display_map[d.available] + ) ) - ) out.append(table.get_string()) return HandleCommandResult(stdout='\n'.join(out)) diff --git a/src/python-common/ceph/deployment/inventory.py b/src/python-common/ceph/deployment/inventory.py index 94dcb5024b5..9bea5c8cdf0 100644 --- a/src/python-common/ceph/deployment/inventory.py +++ b/src/python-common/ceph/deployment/inventory.py @@ -40,7 +40,8 @@ class Device(object): 'sys_api', 'lvs', 'human_readable_type', - 'device_id' + 'device_id', + 'lsm_data', ] def __init__(self, @@ -50,6 +51,7 @@ class Device(object): rejected_reasons=None, # type: Optional[List[str]] lvs=None, # type: Optional[List[str]] device_id=None, # type: Optional[str] + lsm_data=None, # type: Optional[str] ): self.path = path self.sys_api = sys_api if sys_api is not None else {} # type: Dict[str, Any] @@ -57,6 +59,7 @@ class Device(object): self.rejected_reasons = rejected_reasons if rejected_reasons is not None else [] self.lvs = lvs self.device_id = device_id + self.lsm_data = lsm_data def to_json(self): # type: () -> dict -- 2.39.5