From b05506c3943d49abbe6e35b33d26a0e76478759b Mon Sep 17 00:00:00 2001 From: Paul Cuzner Date: Fri, 2 Dec 2022 16:27:14 +1300 Subject: [PATCH] orchestrator: improve host ls output This patch provides a --detail 'switch' to orch host ls to show more host related data for cephadm based clusters. Signed-off-by: Paul Cuzner (cherry picked from commit b0aaf02446ac8e0e3fef0f24684e6d8c67707454) --- src/pybind/mgr/orchestrator/module.py | 130 ++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 7 deletions(-) diff --git a/src/pybind/mgr/orchestrator/module.py b/src/pybind/mgr/orchestrator/module.py index 73bd137484db6..94b4d439573a3 100644 --- a/src/pybind/mgr/orchestrator/module.py +++ b/src/pybind/mgr/orchestrator/module.py @@ -1,9 +1,10 @@ import enum import errno import json -from typing import List, Set, Optional, Iterator, cast, Dict, Any, Union, Sequence +from typing import List, Set, Optional, Iterator, cast, Dict, Any, Union, Sequence, Mapping import re import datetime +import math import yaml from prettytable import PrettyTable @@ -45,6 +46,89 @@ def nice_bytes(v: Optional[int]) -> str: return format_bytes(v, 5) +class HostDetails: + def __init__(self, + host: Optional[HostSpec] = None, + facts: Optional[Dict[str, Any]] = None, + object_dump: Optional[Dict[str, Any]] = None): + self._hostspec = host + self._facts = facts + self.hdd_summary = 'N/A' + self.ram = 'N/A' + self.cpu_summary = 'N/A' + self.server = 'N/A' + self.os = 'N/A' + self.ssd_summary = 'N/A' + self.nic_count = 'N/A' + + assert host or object_dump + if object_dump: + self._load(object_dump) + else: + self._build() + + def _load(self, object_dump: Dict[str, Any]) -> None: + """Build the object from predefined dictionary""" + self.addr = object_dump.get('addr') + self.hostname = object_dump.get('hostname') + self.labels = object_dump.get('labels') + self.status = object_dump.get('status') + self.location = object_dump.get('location') + self.server = object_dump.get('server', 'N/A') + self.hdd_summary = object_dump.get('hdd_summary', 'N/A') + self.ssd_summary = object_dump.get('ssd_summary', 'N/A') + self.os = object_dump.get('os', 'N/A') + self.cpu_summary = object_dump.get('cpu_summary', 'N/A') + self.ram = object_dump.get('ram', 'N/A') + self.nic_count = object_dump.get('nic_count', 'N/A') + + def _build(self) -> None: + """build host details from the HostSpec and facts""" + for a in self._hostspec.__dict__: + setattr(self, a, getattr(self._hostspec, a)) + + if self._facts: + self.server = f"{self._facts.get('vendor', '').strip()} {self._facts.get('model', '').strip()}" + _cores = self._facts.get('cpu_cores', 0) * self._facts.get('cpu_count', 0) + _threads = self._facts.get('cpu_threads', 0) * _cores + self.os = self._facts.get('operating_system', 'N/A') + self.cpu_summary = f"{_cores}C/{_threads}T" if _cores > 0 else 'N/A' + + _total_bytes = self._facts.get('memory_total_kb', 0) * 1024 + divisor, suffix = (1073741824, 'GiB') if _total_bytes > 1073741824 else (1048576, 'MiB') + self.ram = f'{math.ceil(_total_bytes / divisor)} {suffix}' + _hdd_capacity = self._facts.get('hdd_capacity', '') + _ssd_capacity = self._facts.get('flash_capacity', '') + if _hdd_capacity: + if self._facts.get('hdd_count', 0) == 0: + self.hdd_summary = '-' + else: + self.hdd_summary = f"{self._facts.get('hdd_count', 0)}/{self._facts.get('hdd_capacity', 0)}" + + if _ssd_capacity: + if self._facts.get('flash_count', 0) == 0: + self.ssd_summary = '-' + else: + self.ssd_summary = f"{self._facts.get('flash_count', 0)}/{self._facts.get('flash_capacity', 0)}" + + self.nic_count = self._facts.get('nic_count', '') + + def to_json(self) -> Dict[str, Any]: + return {k: v for k, v in self.__dict__.items() if not k.startswith('_')} + + @classmethod + def from_json(cls, host_details: dict) -> 'HostDetails': + _cls = cls(object_dump=host_details) + return _cls + + @staticmethod + def yaml_representer(dumper: 'yaml.SafeDumper', data: 'HostDetails') -> Any: + return dumper.represent_dict(cast(Mapping, data.to_json().items())) + + +yaml.add_representer(HostDetails, HostDetails.yaml_representer) + + class ServiceType(enum.Enum): mon = 'mon' mgr = 'mgr' @@ -377,11 +461,19 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, return HandleCommandResult(stdout=completion.result_str()) @_cli_read_command('orch host ls') - def _get_hosts(self, format: Format = Format.plain, host_pattern: str = '', label: str = '', host_status: str = '') -> HandleCommandResult: - """List hosts""" + def _get_hosts(self, + format: Format = Format.plain, + host_pattern: str = '', + label: str = '', + host_status: str = '', + detail: bool = False) -> HandleCommandResult: + """List high level host information""" completion = self.get_hosts() hosts = raise_if_exception(completion) + cephadm_active = True if self._select_orchestrator() == "cephadm" else False + show_detail = cephadm_active and detail + filter_spec = PlacementSpec( host_pattern=host_pattern, label=label @@ -392,18 +484,42 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule, if host_status: hosts = [h for h in hosts if h.status.lower() == host_status] + if show_detail: + # switch to a HostDetails based representation + _hosts = [] + for h in hosts: + facts_completion = self.get_facts(h.hostname) + host_facts = raise_if_exception(facts_completion) + _hosts.append(HostDetails(host=h, facts=host_facts[0])) + hosts: List[HostDetails] = _hosts # type: ignore [no-redef] + if format != Format.plain: - output = to_format(hosts, format, many=True, cls=HostSpec) + if show_detail: + output = to_format(hosts, format, many=True, cls=HostDetails) + else: + output = to_format(hosts, format, many=True, cls=HostSpec) else: + if show_detail: + table_headings = ['HOST', 'ADDR', 'LABELS', 'STATUS', + 'VENDOR/MODEL', 'CPU', 'RAM', 'HDD', 'SSD', 'NIC'] + else: + table_headings = ['HOST', 'ADDR', 'LABELS', 'STATUS'] + table = PrettyTable( - ['HOST', 'ADDR', 'LABELS', 'STATUS'], + table_headings, border=False) table.align = 'l' table.left_padding_width = 0 table.right_padding_width = 2 for host in natsorted(hosts, key=lambda h: h.hostname): - table.add_row((host.hostname, host.addr, ' '.join( - host.labels), host.status.capitalize())) + row = (host.hostname, host.addr, ','.join( + host.labels), host.status.capitalize()) + + if show_detail and isinstance(host, HostDetails): + row += (host.server, host.cpu_summary, host.ram, + host.hdd_summary, host.ssd_summary, host.nic_count) + + table.add_row(row) output = table.get_string() if format == Format.plain: output += f'\n{len(hosts)} hosts in cluster' -- 2.39.5