From: Guillaume Abrioux Date: Mon, 15 Jun 2026 13:18:48 +0000 (+0200) Subject: node-proxy: rename firmwares to firmware with legacy aliases X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=1cd4b32d72a4a14cbd797187e791e038422f4fc3;p=ceph.git node-proxy: rename firmwares to firmware with legacy aliases Let's use 'firmware' as the standard name in node-proxy, cephadm, and orch hardware status. We still accept 'firmwares' as a deprecated alias and read legacy cache payloads transparently for backward compatibility. Fixes: https://tracker.ceph.com/issues/77410 Signed-off-by: Guillaume Abrioux --- diff --git a/doc/hardware-monitoring/index.rst b/doc/hardware-monitoring/index.rst index 033b89aa610..900a9129456 100644 --- a/doc/hardware-monitoring/index.rst +++ b/doc/hardware-monitoring/index.rst @@ -89,7 +89,7 @@ Supported categories are: * power * fans * temperatures -* firmwares +* firmware * criticals @@ -144,7 +144,7 @@ ________________ .. prompt:: bash # auto - # ceph orch hardware status node-10 --category firmwares + # ceph orch hardware status node-10 --category firmware +------------+----------------------------------------------------------------------------+--------------------------------------------------------------+----------------------+-------------+--------+ | HOST | COMPONENT | NAME | DATE | VERSION | STATUS | +------------+----------------------------------------------------------------------------+--------------------------------------------------------------+----------------------+-------------+--------+ @@ -189,6 +189,6 @@ For Developers .. automethod:: NodeProxyEndpoint.processors .. automethod:: NodeProxyEndpoint.fans .. automethod:: NodeProxyEndpoint.temperatures -.. automethod:: NodeProxyEndpoint.firmwares +.. automethod:: NodeProxyEndpoint.firmware .. 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 e51a1a33977..563ac88c8f7 100644 --- a/src/ceph-node-proxy/ceph_node_proxy/api.py +++ b/src/ceph-node-proxy/ceph_node_proxy/api.py @@ -116,11 +116,17 @@ class API(Server): def temperatures(self) -> Dict[str, Any]: return {"temperatures": self.backend.get_temperatures()} + @cherrypy.expose + @cherrypy.tools.allow(methods=["GET"]) + @cherrypy.tools.json_out() + def firmware(self) -> Dict[str, Any]: + return {"firmware": self.backend.get_firmware()} + @cherrypy.expose @cherrypy.tools.allow(methods=["GET"]) @cherrypy.tools.json_out() def firmwares(self) -> Dict[str, Any]: - return {"firmwares": self.backend.get_firmwares()} + return {"firmwares": self.backend.get_firmware()} def _cp_dispatch(self, vpath: List[str]) -> "API": if vpath[0] == "led" and len(vpath) > 1: # /led/{type}/{id} diff --git a/src/ceph-node-proxy/ceph_node_proxy/baseredfishsystem.py b/src/ceph-node-proxy/ceph_node_proxy/baseredfishsystem.py index 15b513da6f0..3da5a0f9398 100644 --- a/src/ceph-node-proxy/ceph_node_proxy/baseredfishsystem.py +++ b/src/ceph-node-proxy/ceph_node_proxy/baseredfishsystem.py @@ -47,7 +47,7 @@ class BaseRedfishSystem(BaseSystem): "ReadingUnits", "Status", ] - FIRMWARES_FIELDS: List[str] = [ + FIRMWARE_FIELDS: List[str] = [ "Name", "Description", "ReleaseDate", @@ -81,9 +81,9 @@ class BaseRedfishSystem(BaseSystem): "chassis", "Thermal", TEMPERATURES_FIELDS, "Temperatures" ), ], - "firmwares": [ + "firmware": [ ComponentUpdateSpec( - "update_service", "FirmwareInventory", FIRMWARES_FIELDS, None + "update_service", "FirmwareInventory", FIRMWARE_FIELDS, None ), ], } @@ -124,7 +124,7 @@ class BaseRedfishSystem(BaseSystem): "network", "processors", "storage", - "firmwares", + "firmware", ], ) self.update_funcs: List[Callable] = [] @@ -230,7 +230,7 @@ class BaseRedfishSystem(BaseSystem): "fans": self.get_fans(), "temperatures": self.get_temperatures(), }, - "firmwares": self.get_firmwares(), + "firmware": self.get_firmware(), } return result @@ -262,8 +262,8 @@ class BaseRedfishSystem(BaseSystem): def get_storage(self) -> Dict[str, Dict[str, Dict]]: return dict(self._sys.get("storage", {})) - def get_firmwares(self) -> Dict[str, Dict[str, Dict]]: - return dict(self._sys.get("firmwares", {})) + def get_firmware(self) -> Dict[str, Dict[str, Dict]]: + return dict(self._sys.get("firmware", {})) def get_power(self) -> Dict[str, Dict[str, Dict]]: return dict(self._sys.get("power", {})) @@ -387,8 +387,8 @@ class BaseRedfishSystem(BaseSystem): def _update_temperatures(self) -> None: self._run_update("temperatures") - def _update_firmwares(self) -> None: - self._run_update("firmwares") + def _update_firmware(self) -> None: + self._run_update("firmware") def device_led_on(self, device: str) -> int: raise NotImplementedError() diff --git a/src/ceph-node-proxy/ceph_node_proxy/basesystem.py b/src/ceph-node-proxy/ceph_node_proxy/basesystem.py index 025638b5cde..aecc0e137c0 100644 --- a/src/ceph-node-proxy/ceph_node_proxy/basesystem.py +++ b/src/ceph-node-proxy/ceph_node_proxy/basesystem.py @@ -48,7 +48,7 @@ class BaseSystem(BaseThread): def get_storage(self) -> Dict[str, Dict[str, Dict]]: raise NotImplementedError() - def get_firmwares(self) -> Dict[str, Dict[str, Dict]]: + def get_firmware(self) -> Dict[str, Dict[str, Dict]]: raise NotImplementedError() def get_sn(self) -> str: diff --git a/src/ceph-node-proxy/ceph_node_proxy/protocols.py b/src/ceph-node-proxy/ceph_node_proxy/protocols.py index 2b322e8ed7e..e82d425bea5 100644 --- a/src/ceph-node-proxy/ceph_node_proxy/protocols.py +++ b/src/ceph-node-proxy/ceph_node_proxy/protocols.py @@ -18,7 +18,7 @@ class SystemBackend(Protocol): def get_temperatures(self) -> Dict[str, Any]: ... - def get_firmwares(self) -> Dict[str, Any]: ... + def get_firmware(self) -> Dict[str, Any]: ... def get_led(self) -> Dict[str, Any]: ... diff --git a/src/ceph-node-proxy/tests/test_baseredfishsystem.py b/src/ceph-node-proxy/tests/test_baseredfishsystem.py index 2939081935c..28a90521406 100644 --- a/src/ceph-node-proxy/tests/test_baseredfishsystem.py +++ b/src/ceph-node-proxy/tests/test_baseredfishsystem.py @@ -46,7 +46,7 @@ class TestBaseRedfishSystemInit: assert "network" in system.component_list assert "processors" in system.component_list assert "storage" in system.component_list - assert "firmwares" in system.component_list + assert "firmware" in system.component_list assert "fans" in system.component_list assert "temperatures" in system.component_list @@ -110,8 +110,8 @@ class TestBaseRedfishSystemGetters: def test_get_temperatures_empty(self, system): assert system.get_temperatures() == {} - def test_get_firmwares_empty(self, system): - assert system.get_firmwares() == {} + def test_get_firmware_empty(self, system): + assert system.get_firmware() == {} def test_get_status_empty(self, system): assert system.get_status() == {} @@ -127,7 +127,7 @@ class TestBaseRedfishSystemGetSystem: system._sys["power"] = {} system._sys["fans"] = {} system._sys["temperatures"] = {} - system._sys["firmwares"] = {} + system._sys["firmware"] = {} result = system.get_system() assert "host" in result assert "sn" in result @@ -140,7 +140,7 @@ class TestBaseRedfishSystemGetSystem: assert result["status"]["power"] == {} assert result["status"]["fans"] == {} assert result["status"]["temperatures"] == {} - assert "firmwares" in result + assert "firmware" in result class TestBaseRedfishSystemGetSpecs: @@ -248,7 +248,7 @@ class TestBaseRedfishSystemComponentSpecs: assert "power" in BaseRedfishSystem.COMPONENT_SPECS assert "fans" in BaseRedfishSystem.COMPONENT_SPECS assert "temperatures" in BaseRedfishSystem.COMPONENT_SPECS - assert "firmwares" in BaseRedfishSystem.COMPONENT_SPECS + assert "firmware" in BaseRedfishSystem.COMPONENT_SPECS def test_field_lists_non_empty(self): assert len(BaseRedfishSystem.NETWORK_FIELDS) > 0 diff --git a/src/pybind/mgr/cephadm/agent.py b/src/pybind/mgr/cephadm/agent.py index 166d9ba48c0..a7405c969a2 100644 --- a/src/pybind/mgr/cephadm/agent.py +++ b/src/pybind/mgr/cephadm/agent.py @@ -206,7 +206,7 @@ class NodeProxyEndpoint: for sys_id in data.keys(): for member in data[sys_id].keys(): member_data = data[sys_id][member] - if member == 'firmwares': + if member in ('firmware', 'firmwares'): continue _status = self._get_health_value(member_data) if _status and _status != 'ok': @@ -626,11 +626,17 @@ class NodeProxyEndpoint: @cherrypy.tools.allow(methods=['GET']) @cherrypy.tools.json_out() def firmwares(self, **kw: Any) -> Dict[str, Any]: + return self.firmware(**kw) + + @cherrypy.expose + @cherrypy.tools.allow(methods=['GET']) + @cherrypy.tools.json_out() + def firmware(self, **kw: Any) -> Dict[str, Any]: """ Handles GET request to retrieve firmware information. This function is exposed to handle GET requests and fetches firmware data using - the 'firmwares' method from the NodeProxyCache class. + the 'firmware' method from the NodeProxyCache class. :param kw: Keyword arguments for the request. :type kw: dict @@ -641,7 +647,7 @@ class NodeProxyEndpoint: :raises cherrypy.HTTPError 404: If the passed hostname is not found. """ try: - results = self.mgr.node_proxy_cache.firmwares(**kw) + results = self.mgr.node_proxy_cache.firmware(**kw) except (KeyError, OrchestratorError): raise cherrypy.HTTPError(404, f"{kw.get('hostname')} not found.") return results diff --git a/src/pybind/mgr/cephadm/inventory.py b/src/pybind/mgr/cephadm/inventory.py index 3f6f75df5ac..7e396401e38 100644 --- a/src/pybind/mgr/cephadm/inventory.py +++ b/src/pybind/mgr/cephadm/inventory.py @@ -1636,6 +1636,12 @@ class NodeProxyCache: self.oob: Dict[str, Any] = {} self.keyrings: Dict[str, str] = {} + @staticmethod + def _host_firmware(host_data: Dict[str, Any]) -> Any: + if 'firmware' in host_data: + return host_data['firmware'] + return host_data.get('firmwares', {}) + def load(self) -> None: _oob = self.mgr.get_store(f'{NODE_PROXY_CACHE_PREFIX}/oob', '{}') self.oob = json.loads(_oob) @@ -1755,7 +1761,7 @@ class NodeProxyCache: _result[host]['status'][component] = state _result[host]['sn'] = data['sn'] _result[host]['host'] = data['host'] - _result[host]['status']['firmwares'] = data['firmwares'] + _result[host]['status']['firmware'] = self._host_firmware(data) return _result def common(self, endpoint: str, **kw: Any) -> Dict[str, Any]: @@ -1783,7 +1789,7 @@ class NodeProxyCache: raise KeyError(f'Invalid host {host} or component {endpoint}.') return _result - def firmwares(self, **kw: Any) -> Dict[str, Any]: + def firmware(self, **kw: Any) -> Dict[str, Any]: """ Retrieves firmware information for a specific hostname or all hosts. @@ -1798,7 +1804,7 @@ class NodeProxyCache: :rtype: Dict[str, Any] """ hosts = self._resolve_hosts(**kw) - return {host: self.data[host]['firmwares'] for host in hosts} + return {host: self._host_firmware(self.data[host]) for host in hosts} def get_critical_from_host(self, hostname: str) -> Dict[str, Any]: if hostname not in self.data: diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py index 3bd1bbfb73d..7edfe244d70 100644 --- a/src/pybind/mgr/cephadm/module.py +++ b/src/pybind/mgr/cephadm/module.py @@ -2300,9 +2300,13 @@ Then run the following: def node_proxy_summary(self, hostname: Optional[str] = None) -> Dict[str, Any]: return self.node_proxy_cache.summary(hostname=hostname) + @handle_orch_error + def node_proxy_firmware(self, hostname: Optional[str] = None) -> Dict[str, Any]: + return self.node_proxy_cache.firmware(hostname=hostname) + @handle_orch_error def node_proxy_firmwares(self, hostname: Optional[str] = None) -> Dict[str, Any]: - return self.node_proxy_cache.firmwares(hostname=hostname) + return self.node_proxy_cache.firmware(hostname=hostname) @handle_orch_error def node_proxy_criticals(self, hostname: Optional[str] = None) -> Dict[str, Any]: diff --git a/src/pybind/mgr/cephadm/tests/node_proxy_data.py b/src/pybind/mgr/cephadm/tests/node_proxy_data.py index 6f103dbeba1..3c01582d756 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': {}} +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'}}}}}, 'firmware': {}} 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', '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'}}}}} +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'}}}}}, 'firmware': {'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'}}}}}, 'firmware': {'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 dd6593fd273..ba311b19421 100644 --- a/src/pybind/mgr/cephadm/tests/test_node_proxy.py +++ b/src/pybind/mgr/cephadm/tests/test_node_proxy.py @@ -331,14 +331,18 @@ class TestNodeProxyEndpoint(helper.CPWebCase): self.getPage("/host03/temperatures", method="GET") self.assertStatus('404 Not Found') - def test_firmwares_with_valid_hostname(self): - self.getPage("/host02/firmwares", method="GET") + def test_firmware_with_valid_hostname(self): + self.getPage("/host02/firmware", method="GET") self.assertStatus('200 OK') - def test_firmwares_no_hostname(self): - self.getPage("/firmwares", method="GET") + def test_firmware_no_hostname(self): + self.getPage("/firmware", method="GET") self.assertStatus('200 OK') - def test_firmwares_with_invalid_hostname(self): - self.getPage("/host03/firmwares", method="GET") + def test_firmware_with_invalid_hostname(self): + self.getPage("/host03/firmware", method="GET") self.assertStatus('404 Not Found') + + def test_firmwares_legacy_endpoint(self): + self.getPage("/host02/firmwares", method="GET") + self.assertStatus('200 OK') diff --git a/src/pybind/mgr/orchestrator/_interface.py b/src/pybind/mgr/orchestrator/_interface.py index a17d20f0961..1266b84df55 100644 --- a/src/pybind/mgr/orchestrator/_interface.py +++ b/src/pybind/mgr/orchestrator/_interface.py @@ -367,9 +367,17 @@ class Orchestrator(object): """ raise NotImplementedError() + def node_proxy_firmware(self, hostname: Optional[str] = None) -> OrchResult[Dict[str, Any]]: + """ + Return node-proxy firmware report + + :param hostname: hostname + """ + raise NotImplementedError() + def node_proxy_firmwares(self, hostname: Optional[str] = None) -> OrchResult[Dict[str, Any]]: """ - Return node-proxy firmwares report + Return node-proxy firmware report (deprecated alias) :param hostname: hostname """ diff --git a/src/pybind/mgr/orchestrator/module.py b/src/pybind/mgr/orchestrator/module.py index 0930d3f267b..c6024549618 100644 --- a/src/pybind/mgr/orchestrator/module.py +++ b/src/pybind/mgr/orchestrator/module.py @@ -527,7 +527,7 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule): table_heading_mapping = { 'summary': ['HOST', 'SN', 'STORAGE', 'CPU', 'NET', 'MEMORY', 'POWER', 'FANS'], 'fullreport': [], - 'firmwares': ['HOST', 'COMPONENT', 'NAME', 'DATE', 'VERSION', 'STATUS'], + 'firmware': ['HOST', 'COMPONENT', 'NAME', 'DATE', 'VERSION', 'STATUS'], 'criticals': ['HOST', 'SYS_ID', 'COMPONENT', 'NAME', 'STATUS', 'STATE'], 'memory': ['HOST', 'SYS_ID', 'NAME', 'STATUS', 'STATE'], 'storage': ['HOST', 'SYS_ID', 'NAME', 'MODEL', 'SIZE', 'PROTOCOL', 'SN', 'SLOT', 'FW', 'STATUS', 'STATE'], @@ -538,6 +538,9 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule): 'temperatures': ['HOST', 'CHASSIS_ID', 'ID', 'NAME', 'READING', 'UNITS', 'STATUS', 'STATE'], } + if category == 'firmwares': + category = 'firmware' + if category not in table_heading_mapping.keys(): return HandleCommandResult(stdout=f"'{category}' is not a valid category.") @@ -567,8 +570,8 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule): completion = self.node_proxy_fullreport(hostname=hostname) fullreport: Dict[str, Any] = raise_if_exception(completion) output = json.dumps(fullreport) - elif category == 'firmwares': - output = 'Missing host name' if hostname is None else self._firmwares_table(hostname, table, format) + elif category == 'firmware': + output = 'Missing host name' if hostname is None else self._firmware_table(hostname, table, format) elif category == 'criticals': output = self._criticals_table(hostname, table, format) else: @@ -576,8 +579,8 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule): return HandleCommandResult(stdout=output) - def _firmwares_table(self, hostname: Optional[str], table: PrettyTable, format: Format) -> str: - completion = self.node_proxy_firmwares(hostname=hostname) + def _firmware_table(self, hostname: Optional[str], table: PrettyTable, format: Format) -> str: + completion = self.node_proxy_firmware(hostname=hostname) data = raise_if_exception(completion) # data = self.node_proxy_firmware(hostname=hostname) if format == Format.json: