COMPONENT_SPECS can now be a single spec or a list of specs per component.
Data from all sources is merged. Unavailable paths are skipped.
Extract get_component_data() from update_component() to support the merge logic.
Fixes: https://tracker.ceph.com/issues/74749
Signed-off-by: Guillaume Abrioux <gabrioux@ibm.com>
ComponentUpdateSpec,
Endpoint,
EndpointMgr,
+ get_component_data,
update_component,
)
from ceph_node_proxy.redfish_client import RedFishClient
"Status",
]
- COMPONENT_SPECS: Dict[str, ComponentUpdateSpec] = {
- "network": ComponentUpdateSpec(
- "systems", "EthernetInterfaces", NETWORK_FIELDS, None
- ),
- "processors": ComponentUpdateSpec(
- "systems", "Processors", PROCESSORS_FIELDS, None
- ),
- "memory": ComponentUpdateSpec("systems", "Memory", MEMORY_FIELDS, None),
+ COMPONENT_SPECS: Dict[str, List[ComponentUpdateSpec]] = {
+ "network": [
+ ComponentUpdateSpec("systems", "EthernetInterfaces", NETWORK_FIELDS, None),
+ ComponentUpdateSpec("systems", "NetworkInterfaces", NETWORK_FIELDS, None),
+ ],
+ "processors": [
+ ComponentUpdateSpec("systems", "Processors", PROCESSORS_FIELDS, None),
+ ],
+ "memory": [
+ ComponentUpdateSpec("systems", "Memory", MEMORY_FIELDS, None),
+ ],
# Power supplies: Chassis/.../PowerSubsystem/PowerSupplies (not like other components: like Systems/.../Memory)
- "power": ComponentUpdateSpec(
- "chassis", "PowerSubsystem/PowerSupplies", POWER_FIELDS, None
- ),
- "fans": ComponentUpdateSpec("chassis", "Thermal", FANS_FIELDS, "Fans"),
- "firmwares": ComponentUpdateSpec(
- "update_service", "FirmwareInventory", FIRMWARES_FIELDS, None
- ),
+ "power": [
+ ComponentUpdateSpec(
+ "chassis", "PowerSubsystem/PowerSupplies", POWER_FIELDS, None
+ ),
+ ],
+ "fans": [
+ ComponentUpdateSpec("chassis", "Thermal", FANS_FIELDS, "Fans"),
+ ],
+ "firmwares": [
+ ComponentUpdateSpec(
+ "update_service", "FirmwareInventory", FIRMWARES_FIELDS, None
+ ),
+ ],
}
def __init__(self, **kw: Any) -> None:
def get_component_spec_overrides(self) -> Dict[str, Dict[str, Any]]:
return {}
- def get_update_spec(self, component: str) -> ComponentUpdateSpec:
- spec = self.COMPONENT_SPECS[component]
+ def get_specs(self, component: str) -> List[ComponentUpdateSpec]:
+ return [
+ self._apply_spec_overrides(component, spec)
+ for spec in self.COMPONENT_SPECS[component]
+ ]
+
+ def _apply_spec_overrides(
+ self, component: str, spec: ComponentUpdateSpec
+ ) -> ComponentUpdateSpec:
overrides = self.get_component_spec_overrides().get(component)
if not overrides:
return spec
def _run_update(self, component: str) -> None:
self.log.debug(f"Updating {component}")
- spec = self.get_update_spec(component)
- self.update(
- spec.collection, component, spec.path, spec.fields, attribute=spec.attribute
- )
+ specs = self.get_specs(component)
+ self._sys[component] = {}
+ use_single_key = len(specs) == 1
+ for spec in specs:
+ try:
+ result = get_component_data(
+ self.endpoints,
+ spec.collection,
+ spec.path,
+ spec.fields,
+ self.log,
+ attribute=spec.attribute,
+ )
+ path_prefix = spec.path.split("/")[-1].lower() or component
+ for sys_id, members in result.items():
+ if sys_id not in self._sys[component]:
+ self._sys[component][sys_id] = {}
+ for member_id, data in members.items():
+ key = (
+ member_id
+ if use_single_key
+ else f"{path_prefix}_{member_id}"
+ )
+ self._sys[component][sys_id][key] = data
+ except Exception as e:
+ self.log.debug(
+ "Skipping %s path %s (not available on this hardware): %s",
+ component,
+ spec.path,
+ e,
+ )
def _update_network(self) -> None:
self._run_update("network")
except KeyError as e:
self.log.error(f"KeyError while querying {url}: {e}")
except HTTPError as e:
- self.log.error(
- f"HTTP error while querying {url} - {e.code} - {e.reason}"
- )
+ self.log.error(f"HTTP error while querying {url} - {e.code} - {e.reason}")
except json.JSONDecodeError as e:
self.log.error(f"JSON decode error while querying {url}: {e}")
except Exception as e:
return current
-def update_component(
+def get_component_data(
endpoints: EndpointMgr,
collection: str,
- component: str,
path: str,
fields: List[str],
- _sys: Dict[str, Any],
log: Any,
attribute: Optional[str] = None,
-) -> None:
- """Update _sys[component] from Redfish endpoints using the given spec.
- path can be a single segment ('Memory') or multiple ('PowerSubsystem/PowerSupplies').
- """
+) -> Dict[str, Any]:
+ """Build component data from Redfish endpoints. Returns dict sys_id -> member_id -> data."""
members: List[str] = endpoints[collection].get_members_names()
result: Dict[str, Any] = {}
if not members:
data=data, fields=fields, log=log, attribute=attribute
)
except HTTPError as e:
- log.error(f"Error while updating {component}: {e}")
+ log.error(f"Error while updating {path}: {e}")
continue
- _sys[component] = result
+ return result
+
+
+def update_component(
+ endpoints: EndpointMgr,
+ collection: str,
+ component: str,
+ path: str,
+ fields: List[str],
+ _sys: Dict[str, Any],
+ log: Any,
+ attribute: Optional[str] = None,
+) -> None:
+ """Update _sys[component] from Redfish endpoints using the given spec.
+ path can be a single segment ('Memory') or multiple ('PowerSubsystem/PowerSupplies').
+ """
+ _sys[component] = get_component_data(
+ endpoints, collection, path, fields, log, attribute=attribute
+ )
assert "firmwares" in result
-class TestBaseRedfishSystemGetUpdateSpec:
-
- def test_get_update_spec_network(self, system):
- spec = system.get_update_spec("network")
- assert isinstance(spec, ComponentUpdateSpec)
- assert spec.collection == "systems"
- assert spec.path == "EthernetInterfaces"
- assert "Name" in spec.fields
- assert spec.attribute is None
-
- def test_get_update_spec_memory(self, system):
- spec = system.get_update_spec("memory")
- assert spec.collection == "systems"
- assert spec.path == "Memory"
- assert "CapacityMiB" in spec.fields
-
- def test_get_update_spec_power(self, system):
- spec = system.get_update_spec("power")
- assert spec.collection == "chassis"
- assert "PowerSubsystem" in spec.path
- assert spec.attribute is None
-
- def test_get_update_spec_fans(self, system):
- spec = system.get_update_spec("fans")
- assert spec.collection == "chassis"
- assert spec.path == "Thermal"
- assert spec.attribute == "Fans"
+class TestBaseRedfishSystemGetSpecs:
+
+ def test_get_specs_network(self, system):
+ specs = system.get_specs("network")
+ assert len(specs) == 2
+ assert all(isinstance(s, ComponentUpdateSpec) for s in specs)
+ paths = [s.path for s in specs]
+ assert "EthernetInterfaces" in paths
+ assert "NetworkInterfaces" in paths
+ assert specs[0].collection == "systems"
+ assert "Name" in specs[0].fields
+ assert specs[0].attribute is None
+
+ def test_get_specs_memory(self, system):
+ specs = system.get_specs("memory")
+ assert len(specs) == 1
+ assert specs[0].collection == "systems"
+ assert specs[0].path == "Memory"
+ assert "CapacityMiB" in specs[0].fields
+
+ def test_get_specs_power(self, system):
+ specs = system.get_specs("power")
+ assert len(specs) == 1
+ assert specs[0].collection == "chassis"
+ assert "PowerSubsystem" in specs[0].path
+ assert specs[0].attribute is None
+
+ def test_get_specs_fans(self, system):
+ specs = system.get_specs("fans")
+ assert len(specs) == 1
+ assert specs[0].collection == "chassis"
+ assert specs[0].path == "Thermal"
+ assert specs[0].attribute == "Fans"
def test_get_component_spec_overrides_empty(self, system):
assert system.get_component_spec_overrides() == {}