From: Guillaume Abrioux Date: Fri, 13 Oct 2023 12:09:56 +0000 (+0000) Subject: node-proxy: collect firmwares details X-Git-Tag: v19.3.0~102^2~44 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=800bd02dd698486c6f13ad9533bfd0dc7d636b7c;p=ceph.git 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 --- diff --git a/src/cephadm/cephadmlib/node_proxy/baseredfishsystem.py b/src/cephadm/cephadmlib/node_proxy/baseredfishsystem.py index b41983000cf1..76edee0b2514 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 d853e967cd34..6c5804063346 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 a906a3098c16..394c111bc7fb 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 f6a01664629c..000000000000 --- 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 3238f9e8a373..000000000000 --- 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 7d4d6f1a4a01..b1edba425e49 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 fab4f2ca8a0c..28e1516f84f9 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 d112af710e31..625753a8825f 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 {}