From f262579ef0cdebb4bed1b149f11a2db163488707 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 --- 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 85d5e5b0fbf..3029b7b6b5b 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 b36ffd4f89a..f97b61e8f9b 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 31a219f06b0..27d91d1a4d3 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: -- 2.39.5