From: Guillaume Abrioux Date: Wed, 6 Dec 2023 14:25:28 +0000 (+0000) Subject: node-proxy: move the output formatting logic to orchestrator X-Git-Tag: v19.3.0~102^2~18 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=f262579ef0cdebb4bed1b149f11a2db163488707;p=ceph.git node-proxy: move the output formatting logic to orchestrator Implementing this in the cephadm module doesn't follow the general idea of the orchestrator interface. This is where the output formatting should be done so let's move the logic to the orchestrator module. Signed-off-by: Guillaume Abrioux --- diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py index 85d5e5b0fbfc..3029b7b6b5b4 100644 --- a/src/pybind/mgr/cephadm/module.py +++ b/src/pybind/mgr/cephadm/module.py @@ -1653,105 +1653,20 @@ Then run the following: return self._add_host(spec) @handle_orch_error - def hardware_status(self, - hostname: Optional[str] = None, - category: str = 'summary', - format: Format = Format.plain) -> str: - """ - Display hardware status summary - - :param hostname: hostname - """ - - table_heading_mapping = { - 'summary': ['HOST', 'STORAGE', 'CPU', 'NET', 'MEMORY', 'POWER', 'FANS'], - 'firmwares': ['HOST', 'COMPONENT', 'NAME', 'DATE', 'VERSION', 'STATUS'], - 'criticals': ['HOST', 'COMPONENT', 'NAME', 'STATUS', 'STATE'], - 'memory': ['HOST', 'NAME', 'STATUS', 'STATE'], - 'storage': ['HOST', 'NAME', 'MODEL', 'SIZE', 'PROTOCOL', 'SN', 'STATUS', 'STATE'], - 'processors': ['HOST', 'NAME', 'MODEL', 'CORES', 'THREADS', 'STATUS', 'STATE'], - 'network': ['HOST', 'NAME', 'SPEED', 'STATUS', 'STATE'], - 'power': ['HOST', 'ID', 'NAME', 'MODEL', 'MANUFACTURER', 'STATUS', 'STATE'], - 'fans': ['HOST', 'ID', 'NAME', 'STATUS', 'STATE'] - } - - if category not in table_heading_mapping.keys(): - return f"'{category}' is not a valid category." + def node_proxy_summary(self, hostname: Optional[str] = None) -> Dict[str, Any]: + return self.node_proxy.summary(hostname=hostname) - table_headings = table_heading_mapping.get(category, []) - table = PrettyTable(table_headings,border=True) - output = None - - if category == 'summary': - data = self.node_proxy.summary(hostname=hostname) - if format == Format.json: - output = json.dumps(data) - else: - for k, v in data.items(): - row = [k] - row.extend([v['status'][key] for key in ['storage', 'processors', 'network', 'memory', 'power', 'fans']]) - table.add_row(row) - output = table.get_string() - elif category == 'firmwares': - output = "Missing host name" if hostname is None else self._firmwares_table(hostname, table, format) - elif category == 'criticals': - output = self._criticals_table(hostname, table, format) - else: - output = self._common_table(category, hostname, table, format) - - return output if output else table.get_string() - - def _firmwares_table(self, hostname, table, format): - data = self.node_proxy.firmwares(hostname=hostname) - if format == Format.json: - return json.dumps(data) - for host, details in data.items(): - for k, v in details.items(): - table.add_row((host, k, v['name'], v['release_date'], v['version'], v['status']['health'])) - return table.get_string() - - def _criticals_table(self, hostname, table, format): - data = self.node_proxy.criticals(hostname=hostname) - if format == Format.json: - return json.dumps(data) - for host, host_details in data.items(): - for component, component_details in host_details.items(): - for member, member_details in component_details.items(): - description = member_details.get('description') or member_details.get('name') - table.add_row((host, component, description, member_details['status']['health'], member_details['status']['state'])) - return table.get_string() - - def _common_table(self, category, hostname, table, format): - data = self.node_proxy.common(endpoint=category, hostname=hostname) - if format == Format.json: - return json.dumps(data) - mapping = { - 'memory': ('description', 'health', 'state'), - 'storage': ('description', 'model', 'capacity_bytes', 'protocol', 'serial_number', 'health', 'state'), - 'processors': ('model', 'total_cores', 'total_threads', 'health', 'state'), - 'network': ('name', 'speed_mbps', 'health', 'state'), - 'power': ('name', 'model', 'manufacturer', 'health', 'state'), - 'fans': ('name', 'health', 'state') - } - - fields = mapping.get(category, ()) - for host, details in data.items(): - for k, v in details.items(): - row = [] - for field in fields: - if field in v: - row.append(v[field]) - elif field in v.get('status', {}): - row.append(v['status'][field]) - else: - row.append('') - if category in ('power', 'fans', 'processors'): - table.add_row((host,) + (k,) + tuple(row)) - else: - table.add_row((host,) + tuple(row)) + @handle_orch_error + def node_proxy_firmwares(self, hostname: Optional[str] = None) -> Dict[str, Any]: + return self.node_proxy.firmwares(hostname=hostname) + @handle_orch_error + def node_proxy_criticals(self, hostname: Optional[str] = None) -> Dict[str, Any]: + return self.node_proxy.criticals(hostname=hostname) - return table.get_string() + @handle_orch_error + def node_proxy_common(self, category: str, hostname: Optional[str] = None) -> Dict[str, Any]: + return self.node_proxy.common(category, hostname=hostname) @handle_orch_error def remove_host(self, host: str, force: bool = False, offline: bool = False, rm_crush_entry: bool = False) -> str: diff --git a/src/pybind/mgr/orchestrator/_interface.py b/src/pybind/mgr/orchestrator/_interface.py index b36ffd4f89a3..f97b61e8f9b5 100644 --- a/src/pybind/mgr/orchestrator/_interface.py +++ b/src/pybind/mgr/orchestrator/_interface.py @@ -368,6 +368,38 @@ class Orchestrator(object): """ raise NotImplementedError() + def node_proxy_summary(self, hostname: Optional[str] = None) -> OrchResult[Dict[str, Any]]: + """ + Return node-proxy summary + + :param hostname: hostname + """ + raise NotImplementedError() + + def node_proxy_firmwares(self, hostname: Optional[str] = None) -> OrchResult[Dict[str, Any]]: + """ + Return node-proxy firmwares report + + :param hostname: hostname + """ + raise NotImplementedError() + + def node_proxy_criticals(self, hostname: Optional[str] = None) -> OrchResult[Dict[str, Any]]: + """ + Return node-proxy criticals report + + :param hostname: hostname + """ + raise NotImplementedError() + + def node_proxy_common(self, category: str, hostname: Optional[str] = None) -> OrchResult[Dict[str, Any]]: + """ + Return node-proxy generic report + + :param hostname: hostname + """ + raise NotImplementedError() + def remove_host(self, host: str, force: bool, offline: bool, rm_crush_entry: bool) -> OrchResult[str]: """ Remove a host from the orchestrator inventory. diff --git a/src/pybind/mgr/orchestrator/module.py b/src/pybind/mgr/orchestrator/module.py index 31a219f06b06..27d91d1a4d35 100644 --- a/src/pybind/mgr/orchestrator/module.py +++ b/src/pybind/mgr/orchestrator/module.py @@ -490,10 +490,106 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, @_cli_write_command('orch hardware status') def _hardware_status(self, hostname: Optional[str] = None, _end_positional_: int = 0, category: str = 'summary', format: Format = Format.plain) -> HandleCommandResult: - """Display hardware status""" - completion = self.hardware_status(hostname, category, format) - raise_if_exception(completion) - return HandleCommandResult(stdout=completion.result_str()) + """ + Display hardware status summary + + :param hostname: hostname + """ + table_heading_mapping = { + 'summary': ['HOST', 'STORAGE', 'CPU', 'NET', 'MEMORY', 'POWER', 'FANS'], + 'firmwares': ['HOST', 'COMPONENT', 'NAME', 'DATE', 'VERSION', 'STATUS'], + 'criticals': ['HOST', 'COMPONENT', 'NAME', 'STATUS', 'STATE'], + 'memory': ['HOST', 'NAME', 'STATUS', 'STATE'], + 'storage': ['HOST', 'NAME', 'MODEL', 'SIZE', 'PROTOCOL', 'SN', 'STATUS', 'STATE'], + 'processors': ['HOST', 'NAME', 'MODEL', 'CORES', 'THREADS', 'STATUS', 'STATE'], + 'network': ['HOST', 'NAME', 'SPEED', 'STATUS', 'STATE'], + 'power': ['HOST', 'ID', 'NAME', 'MODEL', 'MANUFACTURER', 'STATUS', 'STATE'], + 'fans': ['HOST', 'ID', 'NAME', 'STATUS', 'STATE'] + } + + if category not in table_heading_mapping.keys(): + return HandleCommandResult(stdout=f"'{category}' is not a valid category.") + + table_headings = table_heading_mapping.get(category, []) + table = PrettyTable(table_headings, border=True) + output = '' + + if category == 'summary': + completion = self.node_proxy_summary(hostname=hostname) + summary: Dict[str, Any] = raise_if_exception(completion) + if format == Format.json: + output = json.dumps(summary) + else: + for k, v in summary.items(): + row = [k] + row.extend([v['status'][key] for key in ['storage', 'processors', 'network', 'memory', 'power', 'fans']]) + table.add_row(row) + output = table.get_string() + elif category == 'firmwares': + output = "Missing host name" if hostname is None else self._firmwares_table(hostname, table, format) + elif category == 'criticals': + output = self._criticals_table(hostname, table, format) + else: + output = self._common_table(category, hostname, table, format) + + return HandleCommandResult(stdout=output) + + def _firmwares_table(self, hostname: Optional[str], table: PrettyTable, format: Format) -> str: + completion = self.node_proxy_firmwares(hostname=hostname) + data = raise_if_exception(completion) + # data = self.node_proxy_firmware(hostname=hostname) + if format == Format.json: + return json.dumps(data) + for host, details in data.items(): + for k, v in details.items(): + table.add_row((host, k, v['name'], v['release_date'], v['version'], v['status']['health'])) + return table.get_string() + + def _criticals_table(self, hostname: Optional[str], table: PrettyTable, format: Format) -> str: + completion = self.node_proxy_criticals(hostname=hostname) + data = raise_if_exception(completion) + # data = self.node_proxy_criticals(hostname=hostname) + if format == Format.json: + return json.dumps(data) + for host, host_details in data.items(): + for component, component_details in host_details.items(): + for member, member_details in component_details.items(): + description = member_details.get('description') or member_details.get('name') + table.add_row((host, component, description, member_details['status']['health'], member_details['status']['state'])) + return table.get_string() + + def _common_table(self, category: str, hostname: Optional[str], table: PrettyTable, format: Format) -> str: + completion = self.node_proxy_common(category=category, hostname=hostname) + data = raise_if_exception(completion) + # data = self.node_proxy_common(category=category, hostname=hostname) + if format == Format.json: + return json.dumps(data) + mapping = { + 'memory': ('description', 'health', 'state'), + 'storage': ('description', 'model', 'capacity_bytes', 'protocol', 'serial_number', 'health', 'state'), + 'processors': ('model', 'total_cores', 'total_threads', 'health', 'state'), + 'network': ('name', 'speed_mbps', 'health', 'state'), + 'power': ('name', 'model', 'manufacturer', 'health', 'state'), + 'fans': ('name', 'health', 'state') + } + + fields = mapping.get(category, ()) + for host, details in data.items(): + for k, v in details.items(): + row = [] + for field in fields: + if field in v: + row.append(v[field]) + elif field in v.get('status', {}): + row.append(v['status'][field]) + else: + row.append('') + if category in ('power', 'fans', 'processors'): + table.add_row((host,) + (k,) + tuple(row)) + else: + table.add_row((host,) + tuple(row)) + + return table.get_string() @_cli_write_command('orch host rm') def _remove_host(self, hostname: str, force: bool = False, offline: bool = False, rm_crush_entry: bool = False) -> HandleCommandResult: