From: Guillaume Abrioux Date: Wed, 10 Jun 2026 10:51:14 +0000 (+0200) Subject: node-proxy: add temperatures and fan speed X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=deabf21144054ada7e6eb64d35c38adc44db130a;p=ceph.git node-proxy: add temperatures and fan speed This adds the temperatures category and fan speed information. Fixes: https://tracker.ceph.com/issues/77408 Signed-off-by: Guillaume Abrioux --- diff --git a/doc/hardware-monitoring/index.rst b/doc/hardware-monitoring/index.rst index de94f6ee6aa..033b89aa610 100644 --- a/doc/hardware-monitoring/index.rst +++ b/doc/hardware-monitoring/index.rst @@ -88,6 +88,7 @@ Supported categories are: * network * power * fans +* temperatures * firmwares * criticals @@ -187,6 +188,7 @@ For Developers .. automethod:: NodeProxyEndpoint.power .. automethod:: NodeProxyEndpoint.processors .. automethod:: NodeProxyEndpoint.fans +.. automethod:: NodeProxyEndpoint.temperatures .. automethod:: NodeProxyEndpoint.firmwares .. automethod:: NodeProxyEndpoint.led diff --git a/src/ceph-node-proxy/ceph_node_proxy/api.py b/src/ceph-node-proxy/ceph_node_proxy/api.py index 0bb700820f0..e51a1a33977 100644 --- a/src/ceph-node-proxy/ceph_node_proxy/api.py +++ b/src/ceph-node-proxy/ceph_node_proxy/api.py @@ -110,6 +110,12 @@ class API(Server): def fans(self) -> Dict[str, Any]: return {"fans": self.backend.get_fans()} + @cherrypy.expose + @cherrypy.tools.allow(methods=["GET"]) + @cherrypy.tools.json_out() + def temperatures(self) -> Dict[str, Any]: + return {"temperatures": self.backend.get_temperatures()} + @cherrypy.expose @cherrypy.tools.allow(methods=["GET"]) @cherrypy.tools.json_out() diff --git a/src/ceph-node-proxy/ceph_node_proxy/baseredfishsystem.py b/src/ceph-node-proxy/ceph_node_proxy/baseredfishsystem.py index 4c4051951f1..15b513da6f0 100644 --- a/src/ceph-node-proxy/ceph_node_proxy/baseredfishsystem.py +++ b/src/ceph-node-proxy/ceph_node_proxy/baseredfishsystem.py @@ -33,7 +33,20 @@ class BaseRedfishSystem(BaseSystem): "Status", ] POWER_FIELDS: List[str] = ["Name", "Model", "Manufacturer", "Status"] - FANS_FIELDS: List[str] = ["Name", "PhysicalContext", "Status"] + FANS_FIELDS: List[str] = [ + "Name", + "PhysicalContext", + "Reading", + "ReadingUnits", + "Status", + ] + TEMPERATURES_FIELDS: List[str] = [ + "Name", + "PhysicalContext", + "Reading", + "ReadingUnits", + "Status", + ] FIRMWARES_FIELDS: List[str] = [ "Name", "Description", @@ -63,6 +76,11 @@ class BaseRedfishSystem(BaseSystem): "fans": [ ComponentUpdateSpec("chassis", "Thermal", FANS_FIELDS, "Fans"), ], + "temperatures": [ + ComponentUpdateSpec( + "chassis", "Thermal", TEMPERATURES_FIELDS, "Temperatures" + ), + ], "firmwares": [ ComponentUpdateSpec( "update_service", "FirmwareInventory", FIRMWARES_FIELDS, None @@ -102,6 +120,7 @@ class BaseRedfishSystem(BaseSystem): "memory", "power", "fans", + "temperatures", "network", "processors", "storage", @@ -209,6 +228,7 @@ class BaseRedfishSystem(BaseSystem): "memory": self.get_memory(), "power": self.get_power(), "fans": self.get_fans(), + "temperatures": self.get_temperatures(), }, "firmwares": self.get_firmwares(), } @@ -251,6 +271,9 @@ class BaseRedfishSystem(BaseSystem): def get_fans(self) -> Dict[str, Dict[str, Dict]]: return dict(self._sys.get("fans", {})) + def get_temperatures(self) -> Dict[str, Dict[str, Dict]]: + return dict(self._sys.get("temperatures", {})) + def get_component_spec_overrides(self) -> Dict[str, Dict[str, Any]]: return {} @@ -361,6 +384,9 @@ class BaseRedfishSystem(BaseSystem): def _update_fans(self) -> None: self._run_update("fans") + def _update_temperatures(self) -> None: + self._run_update("temperatures") + def _update_firmwares(self) -> None: self._run_update("firmwares") diff --git a/src/ceph-node-proxy/ceph_node_proxy/basesystem.py b/src/ceph-node-proxy/ceph_node_proxy/basesystem.py index 3d02a7d4fad..025638b5cde 100644 --- a/src/ceph-node-proxy/ceph_node_proxy/basesystem.py +++ b/src/ceph-node-proxy/ceph_node_proxy/basesystem.py @@ -36,6 +36,9 @@ class BaseSystem(BaseThread): def get_fans(self) -> Dict[str, Dict[str, Dict]]: raise NotImplementedError() + def get_temperatures(self) -> Dict[str, Dict[str, Dict]]: + raise NotImplementedError() + def get_power(self) -> Dict[str, Dict[str, Dict]]: raise NotImplementedError() diff --git a/src/ceph-node-proxy/ceph_node_proxy/protocols.py b/src/ceph-node-proxy/ceph_node_proxy/protocols.py index e8b91d76051..2b322e8ed7e 100644 --- a/src/ceph-node-proxy/ceph_node_proxy/protocols.py +++ b/src/ceph-node-proxy/ceph_node_proxy/protocols.py @@ -16,6 +16,8 @@ class SystemBackend(Protocol): def get_fans(self) -> Dict[str, Any]: ... + def get_temperatures(self) -> Dict[str, Any]: ... + def get_firmwares(self) -> Dict[str, Any]: ... def get_led(self) -> Dict[str, Any]: ... diff --git a/src/ceph-node-proxy/ceph_node_proxy/redfish.py b/src/ceph-node-proxy/ceph_node_proxy/redfish.py index d51bf48d104..bbf17dad71e 100644 --- a/src/ceph-node-proxy/ceph_node_proxy/redfish.py +++ b/src/ceph-node-proxy/ceph_node_proxy/redfish.py @@ -231,6 +231,14 @@ class Endpoint: ) +def is_absent_member(item: Dict[str, Any]) -> bool: + status = item.get("Status") + if not isinstance(status, dict): + return False + state = status.get("State") + return isinstance(state, str) and state.lower() == "absent" + + def build_data( data: Dict[str, Any], fields: List[str], @@ -247,6 +255,10 @@ def build_data( except KeyError: log.debug(f"Could not find field: {field} in data: {d}") out[to_snake_case(field)] = None + if out.get("reading") is None and "ReadingCelsius" in d: + out["reading"] = d["ReadingCelsius"] + if out.get("reading_units") is None: + out["reading_units"] = "Cel" return out try: @@ -256,8 +268,9 @@ def build_data( data_items = [{"MemberId": k, **v} for k, v in data.items()] log.debug(f"build_data: data_items count={len(data_items)}") for d in data_items: + if is_absent_member(d): + continue member_id = d.get("MemberId") - result[member_id] = {} result[member_id] = process_data(member_id, fields, d) except (KeyError, TypeError, AttributeError) as e: log.error(f"Can't build data: {e}") diff --git a/src/ceph-node-proxy/tests/test_baseredfishsystem.py b/src/ceph-node-proxy/tests/test_baseredfishsystem.py index adac813a54c..2939081935c 100644 --- a/src/ceph-node-proxy/tests/test_baseredfishsystem.py +++ b/src/ceph-node-proxy/tests/test_baseredfishsystem.py @@ -48,6 +48,7 @@ class TestBaseRedfishSystemInit: assert "storage" in system.component_list assert "firmwares" in system.component_list assert "fans" in system.component_list + assert "temperatures" in system.component_list def test_init_update_funcs_populated(self, system): # Should have one callable per component that has _update_ @@ -106,6 +107,9 @@ class TestBaseRedfishSystemGetters: def test_get_fans_empty(self, system): assert system.get_fans() == {} + def test_get_temperatures_empty(self, system): + assert system.get_temperatures() == {} + def test_get_firmwares_empty(self, system): assert system.get_firmwares() == {} @@ -122,6 +126,7 @@ class TestBaseRedfishSystemGetSystem: system._sys["storage"] = {} system._sys["power"] = {} system._sys["fans"] = {} + system._sys["temperatures"] = {} system._sys["firmwares"] = {} result = system.get_system() assert "host" in result @@ -134,6 +139,7 @@ class TestBaseRedfishSystemGetSystem: assert result["status"]["storage"] == {} assert result["status"]["power"] == {} assert result["status"]["fans"] == {} + assert result["status"]["temperatures"] == {} assert "firmwares" in result @@ -170,6 +176,15 @@ class TestBaseRedfishSystemGetSpecs: assert specs[0].collection == "chassis" assert specs[0].path == "Thermal" assert specs[0].attribute == "Fans" + assert "Reading" in specs[0].fields + + def test_get_specs_temperatures(self, system): + specs = system.get_specs("temperatures") + assert len(specs) == 1 + assert specs[0].collection == "chassis" + assert specs[0].path == "Thermal" + assert specs[0].attribute == "Temperatures" + assert "Reading" in specs[0].fields def test_get_component_spec_overrides_empty(self, system): assert system.get_component_spec_overrides() == {} @@ -232,6 +247,7 @@ class TestBaseRedfishSystemComponentSpecs: assert "memory" in BaseRedfishSystem.COMPONENT_SPECS assert "power" in BaseRedfishSystem.COMPONENT_SPECS assert "fans" in BaseRedfishSystem.COMPONENT_SPECS + assert "temperatures" in BaseRedfishSystem.COMPONENT_SPECS assert "firmwares" in BaseRedfishSystem.COMPONENT_SPECS def test_field_lists_non_empty(self): diff --git a/src/pybind/mgr/cephadm/agent.py b/src/pybind/mgr/cephadm/agent.py index d767f1b24b5..166d9ba48c0 100644 --- a/src/pybind/mgr/cephadm/agent.py +++ b/src/pybind/mgr/cephadm/agent.py @@ -612,6 +612,16 @@ class NodeProxyEndpoint: raise cherrypy.HTTPError(404, f"{kw.get('hostname')} not found.") return results + @cherrypy.expose + @cherrypy.tools.allow(methods=['GET']) + @cherrypy.tools.json_out() + def temperatures(self, **kw: Any) -> Dict[str, Any]: + try: + results = self.mgr.node_proxy_cache.common('temperatures', **kw) + except (KeyError, OrchestratorError): + raise cherrypy.HTTPError(404, f"{kw.get('hostname')} not found.") + return results + @cherrypy.expose @cherrypy.tools.allow(methods=['GET']) @cherrypy.tools.json_out() diff --git a/src/pybind/mgr/cephadm/tests/node_proxy_data.py b/src/pybind/mgr/cephadm/tests/node_proxy_data.py index fa768f1d4c6..6f103dbeba1 100644 --- a/src/pybind/mgr/cephadm/tests/node_proxy_data.py +++ b/src/pybind/mgr/cephadm/tests/node_proxy_data.py @@ -1,3 +1,3 @@ full_set_with_critical = {'host': 'host01', 'sn': '12345', 'status': {'storage': {'1': {'disk.bay.0:enclosure.internal.0-1:raid.integrated.1-1': {'description': 'Solid State Disk 0:1:0', 'entity': 'RAID.Integrated.1-1', 'capacity_bytes': 959656755200, 'model': 'KPM5XVUG960G', 'protocol': 'SAS', 'serial_number': '8080A1CRTP5F', 'status': {'health': 'Critical', 'healthrollup': 'OK', 'state': 'Enabled'}, 'physical_location': {'partlocation': {'locationordinalvalue': 0, 'locationtype': 'Slot'}}}, 'disk.bay.9:enclosure.internal.0-1': {'description': 'PCIe SSD in Slot 9 in Bay 1', 'entity': 'CPU.1', 'capacity_bytes': 1600321314816, 'model': 'Dell Express Flash NVMe P4610 1.6TB SFF', 'protocol': 'PCIe', 'serial_number': 'PHLN035305MN1P6AGN', 'status': {'health': 'Critical', 'healthrollup': 'OK', 'state': 'Enabled'}, 'physical_location': {'partlocation': {'locationordinalvalue': 9, 'locationtype': 'Slot'}}}}}, 'processors': {'1': {'cpu.socket.2': {'description': 'Represents the properties of a Processor attached to this System', 'total_cores': 20, 'total_threads': 40, 'processor_type': 'CPU', 'model': 'Intel(R) Xeon(R) Gold 6230 CPU @ 2.10GHz', 'status': {'health': 'OK', 'state': 'Enabled'}, 'manufacturer': 'Intel'}}}, 'network': {'1': {'nic.slot.1-1-1': {'description': 'NIC in Slot 1 Port 1 Partition 1', 'name': 'System Ethernet Interface', 'speed_mbps': 0, 'status': {'health': 'OK', 'state': 'StandbyOffline'}}}}, 'memory': {'1': {'dimm.socket.a1': {'description': 'DIMM A1', 'memory_device_type': 'DDR4', 'capacity_mi_b': 31237, 'status': {'health': 'Critical', 'state': 'Enabled'}}}}}, 'firmwares': {}} mgr_inventory_cache = {'host01': {'hostname': 'host01', 'addr': '10.10.10.11', 'labels': ['_admin'], 'status': '', 'oob': {'hostname': '10.10.10.11', 'username': 'root', 'password': 'ceph123'}}, 'host02': {'hostname': 'host02', 'addr': '10.10.10.12', 'labels': [], 'status': '', 'oob': {'hostname': '10.10.10.12', 'username': 'root', 'password': 'ceph123'}}} -full_set = {'host01': {'host': 'host01', 'sn': 'FR8Y5X3', 'status': {'storage': {'1': {'disk.bay.8:enclosure.internal.0-1:nonraid.slot.2-1': {'description': 'Disk 8 in Backplane 1 of Storage Controller in Slot 2', 'entity': 'NonRAID.Slot.2-1', 'capacity_bytes': 20000588955136, 'model': 'ST20000NM008D-3D', 'protocol': 'SATA', 'serial_number': 'ZVT99QLL', 'status': {'health': 'OK', 'healthrollup': 'OK', 'state': 'Enabled'}, 'physical_location': {'partlocation': {'locationordinalvalue': 8, 'locationtype': 'Slot'}}}}}, 'processors': {'1': {'cpu.socket.2': {'description': 'Represents the properties of a Processor attached to this System', 'total_cores': 16, 'total_threads': 32, 'processor_type': 'CPU', 'model': 'Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz', 'status': {'health': 'OK', 'state': 'Enabled'}, 'manufacturer': 'Intel'}, 'cpu.socket.1': {'description': 'Represents the properties of a Processor attached to this System', 'total_cores': 16, 'total_threads': 32, 'processor_type': 'CPU', 'model': 'Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz', 'status': {'health': 'OK', 'state': 'Enabled'}, 'manufacturer': 'Intel'}}}, 'network': {'1': {'oslogicalnetwork.2': {'description': 'eno8303', 'name': 'eno8303', 'speed_mbps': 0, 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'memory': {'1': {'dimm.socket.a1': {'description': 'DIMM A1', 'memory_device_type': 'DDR4', 'capacity_mi_b': 16384, 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'power': {'1': {'0': {'name': 'PS1 Status', 'model': 'PWR SPLY,800W,RDNT,LTON', 'manufacturer': 'DELL', 'status': {'health': 'OK', 'state': 'Enabled'}}, '1': {'name': 'PS2 Status', 'model': 'PWR SPLY,800W,RDNT,LTON', 'manufacturer': 'DELL', 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'fans': {'1': {'0': {'name': 'System Board Fan1A', 'physical_context': 'SystemBoard', 'status': {'health': 'OK', 'state': 'Enabled'}}}}}, 'firmwares': {'installed-28897-6.10.30.20__usc.embedded.1:lc.embedded.1': {'name': 'Lifecycle Controller', 'description': 'Represents Firmware Inventory', 'release_date': '00:00:00Z', 'version': '6.10.30.20', 'updateable': True, 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'host02': {'host': 'host02', 'sn': 'FR8Y5X4', 'status': {'storage': {'1': {'disk.bay.8:enclosure.internal.0-1:nonraid.slot.2-1': {'description': 'Disk 8 in Backplane 1 of Storage Controller in Slot 2', 'entity': 'NonRAID.Slot.2-1', 'capacity_bytes': 20000588955136, 'model': 'ST20000NM008D-3D', 'protocol': 'SATA', 'serial_number': 'ZVT99QLL', 'status': {'health': 'OK', 'healthrollup': 'OK', 'state': 'Enabled'}, 'physical_location': {'partlocation': {'locationordinalvalue': 8, 'locationtype': 'Slot'}}}}}, 'processors': {'1': {'cpu.socket.2': {'description': 'Represents the properties of a Processor attached to this System', 'total_cores': 16, 'total_threads': 32, 'processor_type': 'CPU', 'model': 'Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz', 'status': {'health': 'OK', 'state': 'Enabled'}, 'manufacturer': 'Intel'}, 'cpu.socket.1': {'description': 'Represents the properties of a Processor attached to this System', 'total_cores': 16, 'total_threads': 32, 'processor_type': 'CPU', 'model': 'Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz', 'status': {'health': 'OK', 'state': 'Enabled'}, 'manufacturer': 'Intel'}}}, 'network': {'1': {'oslogicalnetwork.2': {'description': 'eno8303', 'name': 'eno8303', 'speed_mbps': 0, 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'memory': {'1': {'dimm.socket.a1': {'description': 'DIMM A1', 'memory_device_type': 'DDR4', 'capacity_mi_b': 16384, 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'power': {'1': {'0': {'name': 'PS1 Status', 'model': 'PWR SPLY,800W,RDNT,LTON', 'manufacturer': 'DELL', 'status': {'health': 'OK', 'state': 'Enabled'}}, '1': {'name': 'PS2 Status', 'model': 'PWR SPLY,800W,RDNT,LTON', 'manufacturer': 'DELL', 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'fans': {'1': {'0': {'name': 'System Board Fan1A', 'physical_context': 'SystemBoard', 'status': {'health': 'OK', 'state': 'Enabled'}}}}}, 'firmwares': {'installed-28897-6.10.30.20__usc.embedded.1:lc.embedded.1': {'name': 'Lifecycle Controller', 'description': 'Represents Firmware Inventory', 'release_date': '00:00:00Z', 'version': '6.10.30.20', 'updateable': True, 'status': {'health': 'OK', 'state': 'Enabled'}}}}} +full_set = {'host01': {'host': 'host01', 'sn': 'FR8Y5X3', 'status': {'storage': {'1': {'disk.bay.8:enclosure.internal.0-1:nonraid.slot.2-1': {'description': 'Disk 8 in Backplane 1 of Storage Controller in Slot 2', 'entity': 'NonRAID.Slot.2-1', 'capacity_bytes': 20000588955136, 'model': 'ST20000NM008D-3D', 'protocol': 'SATA', 'serial_number': 'ZVT99QLL', 'status': {'health': 'OK', 'healthrollup': 'OK', 'state': 'Enabled'}, 'physical_location': {'partlocation': {'locationordinalvalue': 8, 'locationtype': 'Slot'}}}}}, 'processors': {'1': {'cpu.socket.2': {'description': 'Represents the properties of a Processor attached to this System', 'total_cores': 16, 'total_threads': 32, 'processor_type': 'CPU', 'model': 'Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz', 'status': {'health': 'OK', 'state': 'Enabled'}, 'manufacturer': 'Intel'}, 'cpu.socket.1': {'description': 'Represents the properties of a Processor attached to this System', 'total_cores': 16, 'total_threads': 32, 'processor_type': 'CPU', 'model': 'Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz', 'status': {'health': 'OK', 'state': 'Enabled'}, 'manufacturer': 'Intel'}}}, 'network': {'1': {'oslogicalnetwork.2': {'description': 'eno8303', 'name': 'eno8303', 'speed_mbps': 0, 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'memory': {'1': {'dimm.socket.a1': {'description': 'DIMM A1', 'memory_device_type': 'DDR4', 'capacity_mi_b': 16384, 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'power': {'1': {'0': {'name': 'PS1 Status', 'model': 'PWR SPLY,800W,RDNT,LTON', 'manufacturer': 'DELL', 'status': {'health': 'OK', 'state': 'Enabled'}}, '1': {'name': 'PS2 Status', 'model': 'PWR SPLY,800W,RDNT,LTON', 'manufacturer': 'DELL', 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'fans': {'1': {'0': {'name': 'System Board Fan1A', 'physical_context': 'SystemBoard', 'reading': 4800, 'reading_units': 'RPM', 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'temperatures': {'1': {'0': {'name': 'System Board Inlet Temp', 'physical_context': 'Intake', 'reading': 24, 'reading_units': 'Cel', 'status': {'health': 'OK', 'state': 'Enabled'}}}}}, 'firmwares': {'installed-28897-6.10.30.20__usc.embedded.1:lc.embedded.1': {'name': 'Lifecycle Controller', 'description': 'Represents Firmware Inventory', 'release_date': '00:00:00Z', 'version': '6.10.30.20', 'updateable': True, 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'host02': {'host': 'host02', 'sn': 'FR8Y5X4', 'status': {'storage': {'1': {'disk.bay.8:enclosure.internal.0-1:nonraid.slot.2-1': {'description': 'Disk 8 in Backplane 1 of Storage Controller in Slot 2', 'entity': 'NonRAID.Slot.2-1', 'capacity_bytes': 20000588955136, 'model': 'ST20000NM008D-3D', 'protocol': 'SATA', 'serial_number': 'ZVT99QLL', 'status': {'health': 'OK', 'healthrollup': 'OK', 'state': 'Enabled'}, 'physical_location': {'partlocation': {'locationordinalvalue': 8, 'locationtype': 'Slot'}}}}}, 'processors': {'1': {'cpu.socket.2': {'description': 'Represents the properties of a Processor attached to this System', 'total_cores': 16, 'total_threads': 32, 'processor_type': 'CPU', 'model': 'Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz', 'status': {'health': 'OK', 'state': 'Enabled'}, 'manufacturer': 'Intel'}, 'cpu.socket.1': {'description': 'Represents the properties of a Processor attached to this System', 'total_cores': 16, 'total_threads': 32, 'processor_type': 'CPU', 'model': 'Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz', 'status': {'health': 'OK', 'state': 'Enabled'}, 'manufacturer': 'Intel'}}}, 'network': {'1': {'oslogicalnetwork.2': {'description': 'eno8303', 'name': 'eno8303', 'speed_mbps': 0, 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'memory': {'1': {'dimm.socket.a1': {'description': 'DIMM A1', 'memory_device_type': 'DDR4', 'capacity_mi_b': 16384, 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'power': {'1': {'0': {'name': 'PS1 Status', 'model': 'PWR SPLY,800W,RDNT,LTON', 'manufacturer': 'DELL', 'status': {'health': 'OK', 'state': 'Enabled'}}, '1': {'name': 'PS2 Status', 'model': 'PWR SPLY,800W,RDNT,LTON', 'manufacturer': 'DELL', 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'fans': {'1': {'0': {'name': 'System Board Fan1A', 'physical_context': 'SystemBoard', 'reading': 4800, 'reading_units': 'RPM', 'status': {'health': 'OK', 'state': 'Enabled'}}}}, 'temperatures': {'1': {'0': {'name': 'System Board Inlet Temp', 'physical_context': 'Intake', 'reading': 24, 'reading_units': 'Cel', 'status': {'health': 'OK', 'state': 'Enabled'}}}}}, 'firmwares': {'installed-28897-6.10.30.20__usc.embedded.1:lc.embedded.1': {'name': 'Lifecycle Controller', 'description': 'Represents Firmware Inventory', 'release_date': '00:00:00Z', 'version': '6.10.30.20', 'updateable': True, 'status': {'health': 'OK', 'state': 'Enabled'}}}}} diff --git a/src/pybind/mgr/cephadm/tests/test_node_proxy.py b/src/pybind/mgr/cephadm/tests/test_node_proxy.py index 75fe7d6fab9..dd6593fd273 100644 --- a/src/pybind/mgr/cephadm/tests/test_node_proxy.py +++ b/src/pybind/mgr/cephadm/tests/test_node_proxy.py @@ -319,6 +319,18 @@ class TestNodeProxyEndpoint(helper.CPWebCase): self.getPage("/host03/fans", method="GET") self.assertStatus('404 Not Found') + def test_temperatures_with_valid_hostname(self): + self.getPage("/host02/temperatures", method="GET") + self.assertStatus('200 OK') + + def test_temperatures_no_hostname(self): + self.getPage("/temperatures", method="GET") + self.assertStatus('200 OK') + + def test_temperatures_with_invalid_hostname(self): + self.getPage("/host03/temperatures", method="GET") + self.assertStatus('404 Not Found') + def test_firmwares_with_valid_hostname(self): self.getPage("/host02/firmwares", method="GET") self.assertStatus('200 OK') diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/hardware.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/hardware.enum.ts index 7956dfa5d7c..ae115d05001 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/hardware.enum.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/hardware.enum.ts @@ -4,5 +4,6 @@ export enum HardwareNameMapping { processors = 'CPU', network = 'Network', power = 'Power supply', - fans = 'Fan module' + fans = 'Fan module', + temperatures = 'Temperature' } diff --git a/src/pybind/mgr/dashboard/services/hardware.py b/src/pybind/mgr/dashboard/services/hardware.py index 31054ab4cc7..2aa89f422a3 100644 --- a/src/pybind/mgr/dashboard/services/hardware.py +++ b/src/pybind/mgr/dashboard/services/hardware.py @@ -75,7 +75,7 @@ class HardwareService(object): @staticmethod def validate_categories(categories: Optional[List[str]]) -> List[str]: categories_list = ['memory', 'storage', 'processors', - 'network', 'power', 'fans'] + 'network', 'power', 'fans', 'temperatures'] if isinstance(categories, str): categories = [categories] diff --git a/src/pybind/mgr/orchestrator/module.py b/src/pybind/mgr/orchestrator/module.py index 8a1cbea42fc..abb5f205435 100644 --- a/src/pybind/mgr/orchestrator/module.py +++ b/src/pybind/mgr/orchestrator/module.py @@ -534,7 +534,8 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule): 'processors': ['HOST', 'SYS_ID', 'NAME', 'MODEL', 'CORES', 'THREADS', 'STATUS', 'STATE'], 'network': ['HOST', 'SYS_ID', 'NAME', 'SPEED', 'STATUS', 'STATE'], 'power': ['HOST', 'CHASSIS_ID', 'ID', 'NAME', 'MODEL', 'MANUFACTURER', 'STATUS', 'STATE'], - 'fans': ['HOST', 'CHASSIS_ID', 'ID', 'NAME', 'STATUS', 'STATE'] + 'fans': ['HOST', 'CHASSIS_ID', 'ID', 'NAME', 'READING', 'UNITS', 'STATUS', 'STATE'], + 'temperatures': ['HOST', 'CHASSIS_ID', 'ID', 'NAME', 'READING', 'UNITS', 'STATUS', 'STATE'], } if category not in table_heading_mapping.keys(): @@ -619,7 +620,8 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule): 'processors': ('model', 'total_cores', 'total_threads', 'health', 'state'), 'network': ('name', 'speed_mbps', 'health', 'state'), 'power': ('name', 'model', 'manufacturer', 'health', 'state'), - 'fans': ('name', 'health', 'state') + 'fans': ('name', 'reading', 'reading_units', 'health', 'state'), + 'temperatures': ('name', 'reading', 'reading_units', 'health', 'state'), } fields = mapping.get(category, ()) @@ -634,7 +636,7 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule): row.append(v['status'][field]) else: row.append('') - if category in ('power', 'fans', 'processors'): + if category in ('power', 'fans', 'temperatures', 'processors'): table.add_row((host, sys_id,) + (k,) + tuple(row)) else: table.add_row((host, sys_id,) + tuple(row))