From 440754fd0b7cab0317a32b73bea32bdcb9aeb90e Mon Sep 17 00:00:00 2001 From: Guillaume Abrioux Date: Fri, 13 Oct 2023 12:09:56 +0000 Subject: [PATCH] node-proxy: collect firmwares details This makes all the required changes in order to support collecting, pushing and exposing data regarding firmwares status and versions for all the underlying hardware. This also refactors the redfish dell corresponding logic: Having so many nested/inheritance classes seems unnecessary. Signed-off-by: Guillaume Abrioux (cherry picked from commit a9afa2f6adad2cff04b54bfd69e8883b4b9fb1cb) --- .../node_proxy/baseredfishsystem.py | 41 ++++--- .../cephadmlib/node_proxy/basesystem.py | 3 + src/cephadm/cephadmlib/node_proxy/main.py | 13 +- .../cephadmlib/node_proxy/redfish_dell.py | 14 --- .../node_proxy/redfishdellchassis.py | 67 ---------- .../node_proxy/redfishdellsystem.py | 115 ++++++++++++++---- src/pybind/mgr/cephadm/agent.py | 6 + src/pybind/mgr/cephadm/inventory.py | 19 +++ 8 files changed, 145 insertions(+), 133 deletions(-) delete mode 100644 src/cephadm/cephadmlib/node_proxy/redfish_dell.py delete mode 100644 src/cephadm/cephadmlib/node_proxy/redfishdellchassis.py diff --git a/src/cephadm/cephadmlib/node_proxy/baseredfishsystem.py b/src/cephadm/cephadmlib/node_proxy/baseredfishsystem.py index b41983000cf1d..76edee0b2514c 100644 --- a/src/cephadm/cephadmlib/node_proxy/baseredfishsystem.py +++ b/src/cephadm/cephadmlib/node_proxy/baseredfishsystem.py @@ -10,6 +10,9 @@ from typing import Dict, Any, List class BaseRedfishSystem(BaseSystem): def __init__(self, **kw: Any) -> None: super().__init__(**kw) + self.common_endpoints: List[str] = kw.get('common_endpoints', ['/Systems/System.Embedded.1', + '/UpdateService']) + self.chassis_endpoint: str = kw.get('chassis_endpoint', '/Chassis/System.Embedded.1') self.log = Logger(__name__) self.host: str = kw['host'] self.username: str = kw['username'] @@ -25,6 +28,7 @@ class BaseRedfishSystem(BaseSystem): self.lock: Lock = Lock() self.data: Dict[str, Dict[str, Any]] = {} self._system: Dict[str, Dict[str, Any]] = {} + self._sys: Dict[str, Any] = {} self.start_client() def start_client(self) -> None: @@ -49,13 +53,14 @@ class BaseRedfishSystem(BaseSystem): self.log.logger.debug("lock acquired.") try: self._update_system() - update_funcs = [self._update_metadata, - self._update_memory, + self._update_sn() + update_funcs = [self._update_memory, self._update_power, self._update_fans, self._update_network, self._update_processors, - self._update_storage] + self._update_storage, + self._update_firmwares] with concurrent.futures.ThreadPoolExecutor() as executor: executor.map(lambda f: f(), update_funcs) @@ -93,18 +98,10 @@ class BaseRedfishSystem(BaseSystem): raise RuntimeError(f"Could not get path: {path}") return result - def get_members(self, path: str) -> List: - _path = self._system[path]['@odata.id'] - data = self._get_path(_path) - return [self._get_path(member['@odata.id']) for member in data['Members']] - - def build_data(self, - fields: List, - path: str) -> Dict[str, Dict[str, Dict]]: - raise NotImplementedError() - - # def _update_system(self) -> None: - # raise NotImplementedError() + def get_members(self, data: Dict[str, Any], path: str) -> List: + _path = data[path]['@odata.id'] + _data = self._get_path(_path) + return [self._get_path(member['@odata.id']) for member in _data['Members']] def get_system(self) -> Dict[str, Dict[str, Dict]]: result = { @@ -117,15 +114,18 @@ class BaseRedfishSystem(BaseSystem): 'memory': self.get_memory(), 'power': self.get_power(), 'fans': self.get_fans() - } + }, + 'firmwares': self.get_firmwares() } return result def _update_system(self) -> None: - redfish_system = self.client.get_path(self.system_endpoint) - self._system = {**redfish_system, **self._system} + for endpoint in self.common_endpoints: + result = self.client.get_path(endpoint) + _endpoint = endpoint.strip('/').split('/')[0] + self._system[_endpoint] = result - def _update_metadata(self) -> None: + def _update_sn(self) -> None: raise NotImplementedError() def _update_memory(self) -> None: @@ -145,3 +145,6 @@ class BaseRedfishSystem(BaseSystem): def _update_storage(self) -> None: raise NotImplementedError() + + def _update_firmwares(self) -> None: + raise NotImplementedError() diff --git a/src/cephadm/cephadmlib/node_proxy/basesystem.py b/src/cephadm/cephadmlib/node_proxy/basesystem.py index d853e967cd34a..6c58040633468 100644 --- a/src/cephadm/cephadmlib/node_proxy/basesystem.py +++ b/src/cephadm/cephadmlib/node_proxy/basesystem.py @@ -34,6 +34,9 @@ class BaseSystem: def get_storage(self) -> Dict[str, Dict[str, Dict]]: raise NotImplementedError() + def get_firmwares(self) -> Dict[str, Dict[str, Dict]]: + raise NotImplementedError() + def get_sn(self) -> str: raise NotImplementedError() diff --git a/src/cephadm/cephadmlib/node_proxy/main.py b/src/cephadm/cephadmlib/node_proxy/main.py index a906a3098c16f..394c111bc7fbb 100644 --- a/src/cephadm/cephadmlib/node_proxy/main.py +++ b/src/cephadm/cephadmlib/node_proxy/main.py @@ -1,6 +1,6 @@ import cherrypy from threading import Thread -from .redfish_dell import RedfishDell +from .redfishdellsystem import RedfishDellSystem from .reporter import Reporter from .util import Config, Logger from typing import Dict, Any, Optional @@ -211,19 +211,16 @@ class NodeProxy(Thread): def main(self) -> None: # TODO: add a check and fail if host/username/password/data aren't passed - config = Config('/etc/ceph/node-proxy.yml', default_config=DEFAULT_CONFIG) - log = Logger(__name__, level=config.__dict__['logging']['level']) # create the redfish system and the obsever log.logger.info(f"Server initialization...") try: - self.system = RedfishDell(host=self.__dict__['host'], - username=self.__dict__['username'], - password=self.__dict__['password'], - system_endpoint='/Systems/System.Embedded.1', - config=config) + self.system = RedfishDellSystem(host=self.__dict__['host'], + username=self.__dict__['username'], + password=self.__dict__['password'], + config=config) except RuntimeError: log.logger.error("Can't initialize the redfish system.") raise diff --git a/src/cephadm/cephadmlib/node_proxy/redfish_dell.py b/src/cephadm/cephadmlib/node_proxy/redfish_dell.py deleted file mode 100644 index f6a01664629c9..0000000000000 --- a/src/cephadm/cephadmlib/node_proxy/redfish_dell.py +++ /dev/null @@ -1,14 +0,0 @@ -from .redfishdellchassis import RedfishDellChassis -from .redfishdellsystem import RedfishDellSystem -from .util import Logger -from typing import Any - - -class RedfishDell(RedfishDellSystem, RedfishDellChassis): - def __init__(self, **kw: Any) -> None: - if kw.get('system_endpoint') is None: - kw['system_endpoint'] = '/Systems/System.Embedded.1' - if kw.get('chassis_endpoint') is None: - kw['chassis_endpoint'] = '/Chassis/System.Embedded.1' - super().__init__(**kw) - self.log = Logger(__name__) diff --git a/src/cephadm/cephadmlib/node_proxy/redfishdellchassis.py b/src/cephadm/cephadmlib/node_proxy/redfishdellchassis.py deleted file mode 100644 index 3238f9e8a373f..0000000000000 --- a/src/cephadm/cephadmlib/node_proxy/redfishdellchassis.py +++ /dev/null @@ -1,67 +0,0 @@ -from .baseredfishsystem import BaseRedfishSystem -from .redfish_client import RedFishClient -from threading import Thread, Lock -from time import sleep -from .util import Logger, retry, normalize_dict, to_snake_case -from typing import Dict, Any, List, Union - - -class RedfishDellChassis(BaseRedfishSystem): - def __init__(self, **kw: Any) -> None: - self.chassis_endpoint = kw.get('chassis_endpoint', '/Chassis/System.Embedded.1') - super().__init__(**kw) - self.log = Logger(__name__) - self.log.logger.info(f"{__name__} initialization.") - - def get_power(self) -> Dict[str, Dict[str, Dict]]: - return self._system['power'] - - def get_fans(self) -> Dict[str, Dict[str, Dict]]: - return self._system['fans'] - - def get_chassis(self) -> Dict[str, Dict[str, Dict]]: - result = { - 'power': self.get_power(), - 'fans': self.get_fans() - } - return result - - def _update_power(self) -> None: - fields = { - "PowerSupplies": [ - "Name", - "Model", - "Manufacturer", - "Status" - ] - } - self.log.logger.debug("Updating powersupplies") - self._system['power'] = self.build_chassis_data(fields, 'Power') - - def _update_fans(self) -> None: - fields = { - "Fans": [ - "Name", - "PhysicalContext", - "Status" - ], - } - self.log.logger.debug("Updating fans") - self._system['fans'] = self.build_chassis_data(fields, 'Thermal') - - def build_chassis_data(self, - fields: Dict[str, List[str]], - path: str) -> Dict[str, Dict[str, Dict]]: - result: Dict[str, Dict[str, Dict]] = dict() - data = self._get_path(f"{self.chassis_endpoint}/{path}") - - for elt, _fields in fields.items(): - for member_elt in data[elt]: - _id = member_elt['MemberId'] - result[_id] = dict() - for field in _fields: - try: - result[_id][to_snake_case(field)] = member_elt[field] - except KeyError: - self.log.logger.warning(f"Could not find field: {field} in data: {data[elt]}") - return normalize_dict(result) diff --git a/src/cephadm/cephadmlib/node_proxy/redfishdellsystem.py b/src/cephadm/cephadmlib/node_proxy/redfishdellsystem.py index 7d4d6f1a4a010..b1edba425e499 100644 --- a/src/cephadm/cephadmlib/node_proxy/redfishdellsystem.py +++ b/src/cephadm/cephadmlib/node_proxy/redfishdellsystem.py @@ -5,15 +5,15 @@ from typing import Dict, Any, List class RedfishDellSystem(BaseRedfishSystem): def __init__(self, **kw: Any) -> None: - self.system_endpoint = kw.get('systemd_endpoint', '/Systems/System.Embedded.1') super().__init__(**kw) self.log = Logger(__name__) - def build_system_data(self, - fields: List, - path: str) -> Dict[str, Dict[str, Dict]]: + def build_common_data(self, + data: Dict[str, Any], + fields: List, + path: str) -> Dict[str, Dict[str, Dict]]: result: Dict[str, Dict[str, Dict]] = dict() - for member_info in self.get_members(path): + for member_info in self.get_members(data, path): member_id = member_info['Id'] result[member_id] = dict() for field in fields: @@ -24,35 +24,57 @@ class RedfishDellSystem(BaseRedfishSystem): return normalize_dict(result) + def build_chassis_data(self, + fields: Dict[str, List[str]], + path: str) -> Dict[str, Dict[str, Dict]]: + result: Dict[str, Dict[str, Dict]] = dict() + data = self._get_path(f"{self.chassis_endpoint}/{path}") + + for elt, _fields in fields.items(): + for member_elt in data[elt]: + _id = member_elt['MemberId'] + result[_id] = dict() + for field in _fields: + try: + result[_id][to_snake_case(field)] = member_elt[field] + except KeyError: + self.log.logger.warning(f"Could not find field: {field} in data: {data[elt]}") + return normalize_dict(result) + + def get_sn(self) -> str: - return self._system['SKU'] + return self._sys['SKU'] def get_status(self) -> Dict[str, Dict[str, Dict]]: - return self._system['status'] - - def get_metadata(self) -> Dict[str, Dict[str, Dict]]: - return self._system['metadata'] + return self._sys['status'] def get_memory(self) -> Dict[str, Dict[str, Dict]]: - return self._system['memory'] + return self._sys['memory'] def get_processors(self) -> Dict[str, Dict[str, Dict]]: - return self._system['processors'] + return self._sys['processors'] def get_network(self) -> Dict[str, Dict[str, Dict]]: - return self._system['network'] + return self._sys['network'] def get_storage(self) -> Dict[str, Dict[str, Dict]]: - return self._system['storage'] + return self._sys['storage'] + + def get_firmwares(self) -> Dict[str, Dict[str, Dict]]: + return self._sys['firmwares'] + + def get_power(self) -> Dict[str, Dict[str, Dict]]: + return self._sys['power'] - # def _update_system(self) -> None: - # redfish_system = self.client.get_path(self.system_endpoint) - # self._system = {**redfish_system, **self._system} + def get_fans(self) -> Dict[str, Dict[str, Dict]]: + return self._sys['fans'] def _update_network(self) -> None: fields = ['Description', 'Name', 'SpeedMbps', 'Status'] self.log.logger.debug('Updating network') - self._system['network'] = self.build_system_data(fields, 'EthernetInterfaces') + self._sys['network'] = self.build_common_data(data=self._system['Systems'], + fields=fields, + path='EthernetInterfaces') def _update_processors(self) -> None: fields = ['Description', @@ -63,7 +85,9 @@ class RedfishDellSystem(BaseRedfishSystem): 'Status', 'Manufacturer'] self.log.logger.debug('Updating processors') - self._system['processors'] = self.build_system_data(fields, 'Processors') + self._sys['processors'] = self.build_common_data(data=self._system['Systems'], + fields=fields, + path='Processors') def _update_storage(self) -> None: fields = ['Description', @@ -71,7 +95,8 @@ class RedfishDellSystem(BaseRedfishSystem): 'Model', 'Protocol', 'SerialNumber', 'Status', 'PhysicalLocation'] - entities = self.get_members('Storage') + entities = self.get_members(data=self._system['Systems'], + path='Storage') self.log.logger.debug('Updating storage') result: Dict[str, Dict[str, Dict]] = dict() for entity in entities: @@ -83,11 +108,11 @@ class RedfishDellSystem(BaseRedfishSystem): for field in fields: result[drive_id][to_snake_case(field)] = drive_info[field] result[drive_id]['entity'] = entity['Id'] - self._system['storage'] = normalize_dict(result) + self._sys['storage'] = normalize_dict(result) - def _update_metadata(self) -> None: - self.log.logger.debug('Updating metadata') - pass + def _update_sn(self) -> None: + self.log.logger.debug('Updating serial number') + self._sys['SKU'] = self._system['Systems']['SKU'] def _update_memory(self) -> None: fields = ['Description', @@ -95,4 +120,44 @@ class RedfishDellSystem(BaseRedfishSystem): 'CapacityMiB', 'Status'] self.log.logger.debug('Updating memory') - self._system['memory'] = self.build_system_data(fields, 'Memory') + self._sys['memory'] = self.build_common_data(data=self._system['Systems'], + fields=fields, + path='Memory') + + def _update_power(self) -> None: + fields = { + 'PowerSupplies': [ + 'Name', + 'Model', + 'Manufacturer', + 'Status' + ] + } + self.log.logger.debug('Updating powersupplies') + self._sys['power'] = self.build_chassis_data(fields, 'Power') + + def _update_fans(self) -> None: + fields = { + 'Fans': [ + 'Name', + 'PhysicalContext', + 'Status' + ], + } + self.log.logger.debug('Updating fans') + self._sys['fans'] = self.build_chassis_data(fields, 'Thermal') + + def _update_firmwares(self) -> None: + fields = [ + 'Name', + 'Description', + 'ReleaseDate', + 'Version', + 'Updateable', + 'Status', + ] + self.log.logger.debug('Updating firmwares') + self._sys['firmwares'] = self.build_common_data(data=self._system['UpdateService'], + fields=fields, + path='FirmwareInventory') + self.log.logger.warning(f"guits-debug1:{self._sys['firmwares']}") diff --git a/src/pybind/mgr/cephadm/agent.py b/src/pybind/mgr/cephadm/agent.py index fab4f2ca8a0c2..28e1516f84f9c 100644 --- a/src/pybind/mgr/cephadm/agent.py +++ b/src/pybind/mgr/cephadm/agent.py @@ -229,6 +229,12 @@ class NodeProxy: def common(self, **kw) -> Dict[str, Any]: return self.mgr.node_proxy.common(**kw) + @cherrypy.expose + @cherrypy.tools.allow(methods=['GET']) + @cherrypy.tools.json_out() + def firmwares(self, **kw) -> Dict[str, Any]: + return self.mgr.node_proxy.firmwares(**kw) + def dispatch(self, hostname='', cmd=''): kw = dict(hostname=hostname, cmd=cmd) try: diff --git a/src/pybind/mgr/cephadm/inventory.py b/src/pybind/mgr/cephadm/inventory.py index 1be1ef6eeb553..b434c68183e3b 100644 --- a/src/pybind/mgr/cephadm/inventory.py +++ b/src/pybind/mgr/cephadm/inventory.py @@ -1481,6 +1481,25 @@ class NodeProxyCache: else: return _result + def firmwares(self, **kw: Any) -> Dict[str, Any]: + """ + Retrieves firmware information for a specific hostname or all hosts. + + If a 'hostname' is provided in the keyword arguments, retrieves firmware + information for that specific host. Otherwise, retrieves firmware + information for all available hosts. + + :param kw: Keyword arguments, including 'hostname' if specified. + :type kw: dict + + :return: A dictionary containing firmware information for each host. + :rtype: Dict[str, Any] + """ + hostname = kw.get('hostname') + hosts = [hostname] if hostname else self.data.keys() + + return {host: self.data[host]['firmwares'] for host in hosts} + def criticals(self, **kw): return {} -- 2.39.5