add dataclass RedfishResource, update discover_redfish_resources, process_power_supply

This commit is contained in:
2026-01-30 15:02:33 +01:00
parent 381d06ae58
commit f3c4bc1953

View File

@@ -19,6 +19,22 @@ from prometheus_client import (
)
@dataclass
class RedfishResource:
"""Container for Redfish resource URLs."""
chassis: str | None = None
systems: str | None = None
power: str | None = None
session_service: str | None = None
@dataclass
class PowerMetrics:
"""Container for power metrics."""
voltage: float | None = None
watts: float | None = None
amps: float | None = None
serial: str | None = None
@dataclass
class RedfishSession:
"""Container for Redfish session data."""
@@ -132,8 +148,8 @@ async def login_hpe(session, host: HostConfig) -> bool:
try:
async with session.post(login_url, json=payload, ssl=False, timeout=10) as login_resp:
if login_resp.status == 201:
host.session_token = login_resp.headers.get("X-Auth-Token")
host.session_logout = login_resp.headers.get("Location")
host.session.token = login_resp.headers.get("X-Auth-Token")
host.session.logout_url = login_resp.headers.get("Location")
if not host.session.token or not host.session.logout_url:
raise RuntimeError("Invalid login response")
@@ -228,23 +244,25 @@ async def fetch_with_retry(session, host: HostConfig, url: str) -> dict | None:
return None
async def discover_redfish_resources(session, host: HostConfig) -> dict:
async def discover_redfish_resources(session, host: HostConfig) -> RedfishResource | None:
"""Discover available Redfish resources and return relevant URLs"""
root_url = f"https://{host.fqdn}/redfish/v1/"
data = await fetch_with_retry(session, host, root_url)
if not data:
return {}
# Extrahiere Links aus der Root-Antwort
links = {
"Chassis": data.get("Chassis", {}).get("@odata.id"),
"Systems": data.get("Systems", {}).get("@odata.id"),
"SessionService": data.get("SessionService", {}).get("@odata.id"),
}
if not links["Chassis"]:
# Create RedfishRessource object
resources = RedfishResource(
chassis=data.get("Chassis", {}).get("@odata.id"),
systems=data.get("Systems", {}).get("@odata.id"),
session_service=data.get("SessionService", {}).get("@odata.id"),
)
if not resources.chassis:
logging.error("No valid Chassis URL found for host %s", host.fqdn)
return {}
return links
return None
return resources
def get_power_resource_info(
@@ -313,54 +331,46 @@ def process_power_supplies(
async def process_power_supply(
session, host: HostConfig, psu_data: dict, power_resource_type: str
):
) -> PowerMetrics | None:
"""Extract metrics from PowerSupply"""
serial = psu_data.get("SerialNumber")
metrics = PowerMetrics(serial=serial)
if power_resource_type == "PowerSubsystem":
# Newer Redfish API: Metrics are an own "Metrics" ressource
# New Redfish API: Metrics are an own "Metrics" ressource
metrics_url = psu_data.get("Metrics", {}).get("@odata.id")
if not metrics_url:
logging.warning("No Metrics found for PowerSupply %s", psu_data.get("Id"))
return
return None
metrics_url = f"https://{host.fqdn}{metrics_url}"
metrics_data = await fetch_with_retry(session, host, metrics_url)
if not metrics_data:
return
return None
# Get metrics from Metrics ressource
line_input_v = metrics_data.get("InputVoltage", {}).get("Reading")
watts_input = metrics_data.get("InputPowerWatts", {}).get("Reading")
amps_input = metrics_data.get("InputCurrentAmps", {}).get("Reading")
metrics.voltage = metrics_data.get("InputVoltage", {}).get("Reading")
metrics.watts = metrics_data.get("InputPowerWatts", {}).get("Reading")
metrics.amps = metrics_data.get("InputCurrentAmps", {}).get("Reading")
elif power_resource_type == "Power":
# Older Redfish API: Metrics are direct in PowerSupply as an array
line_input_v = psu_data.get("LineInputVoltage")
watts_input = psu_data.get("PowerInputWatts")
if watts_input is None:
watts_input = psu_data.get("LastPowerOutputWatts")
amps_input = psu_data.get("InputCurrentAmps")
if amps_input is None:
if line_input_v and watts_input:
amps_input = round(watts_input / line_input_v, 2)
metrics.voltage = psu_data.get("LineInputVoltage")
metrics.watts = psu_data.get("PowerInputWatts")
if metrics.watts is None:
metrics.watts = psu_data.get("LastPowerOutputWatts")
metrics.amps = psu_data.get("InputCurrentAmps")
if metrics.amps is None and metrics.voltage and metrics.watts:
metrics.amps = round(metrics.watts / metrics.voltage, 2)
else:
logging.error(
"Unknown power resource type for PowerSupply %s", psu_data.get("Id")
)
return
if amps_input is None and line_input_v and watts_input:
amps_input = round(watts_input / line_input_v, 2)
return None
# Update Prometheus metrics
if line_input_v is not None:
VOLTAGE_GAUGE.labels(host=host.fqdn, psu_serial=serial).set(line_input_v)
if watts_input is not None:
WATTS_GAUGE.labels(host=host.fqdn, psu_serial=serial).set(watts_input)
if amps_input is not None:
AMPS_GAUGE.labels(host=host.fqdn, psu_serial=serial).set(amps_input)
return metrics
def normalize_url(url: str) -> str: