]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
node-proxy: collect firmwares details
authorGuillaume Abrioux <gabrioux@ibm.com>
Fri, 13 Oct 2023 12:09:56 +0000 (12:09 +0000)
committerGuillaume Abrioux <gabrioux@ibm.com>
Thu, 25 Jan 2024 15:07:20 +0000 (15:07 +0000)
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 <gabrioux@ibm.com>
src/cephadm/cephadmlib/node_proxy/baseredfishsystem.py
src/cephadm/cephadmlib/node_proxy/basesystem.py
src/cephadm/cephadmlib/node_proxy/main.py
src/cephadm/cephadmlib/node_proxy/redfish_dell.py [deleted file]
src/cephadm/cephadmlib/node_proxy/redfishdellchassis.py [deleted file]
src/cephadm/cephadmlib/node_proxy/redfishdellsystem.py
src/pybind/mgr/cephadm/agent.py
src/pybind/mgr/cephadm/inventory.py

index b41983000cf1dd84c0e433c508ee066707e882c6..76edee0b2514c3a5b6e5d246c5a81350382b65db 100644 (file)
@@ -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()
index d853e967cd34a29f28769fc73074bf79f0a9f09d..6c58040633468b1991d774284f7682db2e6c5209 100644 (file)
@@ -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()
 
index a906a3098c16f1669d11c36af3d94eb6342916b3..394c111bc7fbb05b4c11b3872b3a1dd6b90fc511 100644 (file)
@@ -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 (file)
index f6a0166..0000000
+++ /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 (file)
index 3238f9e..0000000
+++ /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)
index 7d4d6f1a4a0101fb74d174bad591bde82c0f9fcb..b1edba425e499bfde81168a77f174be697b87d00 100644 (file)
@@ -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']}")
index fab4f2ca8a0c22fb07e2c6de6520f33d967b6d5c..28e1516f84f9cf5989e244126eaf4942866881e3 100644 (file)
@@ -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:
index d112af710e31832e5f34305d387450ff18c38f34..625753a8825fafd771c8216021e193d8fae3c009 100644 (file)
@@ -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 {}