From 3b3d0e4340af05047bb04360b1eb0ee7974e8672 Mon Sep 17 00:00:00 2001 From: Guillaume Abrioux Date: Wed, 6 Dec 2023 14:25:28 +0000 Subject: [PATCH] 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 (cherry picked from commit aa170850b8e7f63120169b89e39b21bee2c5287e) --- src/pybind/mgr/cephadm/module.py | 107 +++------------------- src/pybind/mgr/orchestrator/_interface.py | 32 +++++++ src/pybind/mgr/orchestrator/module.py | 104 ++++++++++++++++++++- 3 files changed, 143 insertions(+), 100 deletions(-) diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py index 249bdc11bd4c5..df5a846c5a396 100644 --- a/src/pybind/mgr/cephadm/module.py +++ b/src/pybind/mgr/cephadm/module.py @@ -1637,105 +1637,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) -> str: diff --git a/src/pybind/mgr/orchestrator/_interface.py b/src/pybind/mgr/orchestrator/_interface.py index 6157e5eec3044..5878f3ed64885 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) -> 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 1c288a848f2f6..561367195b786 100644 --- a/src/pybind/mgr/orchestrator/module.py +++ b/src/pybind/mgr/orchestrator/module.py @@ -489,10 +489,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) -> HandleCommandResult: -- 2.39.5