diff --git a/python/redfish-api/pyproject.toml b/python/redfish-api/pyproject.toml index 94a2065..53ec7f1 100644 --- a/python/redfish-api/pyproject.toml +++ b/python/redfish-api/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "redfish-api" -version = "0.1.0" +name = "redfish-exporter" +version = "1.0.0" description = "Read redfish api powerdata" readme = "README.md" requires-python = ">=3.13" diff --git a/python/redfish-api/redfish_exporter_v9000.py b/python/redfish-api/redfish_exporter_v9000.py index ff4c03d..7b5baf7 100644 --- a/python/redfish-api/redfish_exporter_v9000.py +++ b/python/redfish-api/redfish_exporter_v9000.py @@ -9,7 +9,6 @@ import asyncio import aiohttp import urllib3 import yaml -import json from prometheus_client import ( Gauge, start_http_server, @@ -70,23 +69,23 @@ REQUEST_TIME = Summary("request_processing_seconds", "Time spent processing requ REQUEST_LATENCY = Histogram( "redfish_request_latency_seconds", "Time for Redfish request", ["host"] ) -up_gauge = Gauge("redfish_up", "Host up/down", ["host"]) -error_counter = Counter( +UP_GAUGE = Gauge("redfish_up", "Host up/down", ["host"]) +ERROR_COUNTER = Counter( "redfish_errors_total", "Total Redfish errors", ["host", "error"] ) -voltage_gauge = Gauge( +VOLTAGE_GAUGE = Gauge( "redfish_psu_line_input_voltage_volts", "Line Input Voltage per PSU", ["host", "psu_serial"], ) -watts_gauge = Gauge( +WATTS_GAUGE = Gauge( "redfish_psu_power_input_watts", "Power Input Watts per PSU", ["host", "psu_serial"] ) -amps_gauge = Gauge( +AMPS_GAUGE = Gauge( "redfish_psu_input_amps", "Current draw in Amps per PSU", ["host", "psu_serial"] ) # set info metric -system_info = Info( +SYSTEM_INFO = Info( "redfish_system_info", "System information (model, serial, etc.)", ["host"] ) @@ -103,7 +102,7 @@ async def fetch_with_retry(session, host: HostConfig, url: str) -> dict | None: logging.warning( "Skipping %s (in cool-down until %.1f)", host.fqdn, host.next_retry_time ) - up_gauge.labels(host=host.fqdn).set(0) + UP_GAUGE.labels(host=host.fqdn).set(0) return None if not host.vendor: @@ -182,7 +181,7 @@ async def fetch_with_retry(session, host: HostConfig, url: str) -> dict | None: ) else: - # Default: BasicAuth, like Supermicro and so + # Default: BasicAuth async with session.get( url, auth=aiohttp.BasicAuth(host.username, host.password), @@ -259,7 +258,7 @@ def get_power_resource_info( return None, None -def get_power_supplies_url( +def process_power_supplies_url( power_data: dict, power_resource_type: str, host_fqdn: str ) -> str | None: """Get the URL for PowerSupplies based on the Power resource type.""" @@ -278,8 +277,9 @@ def get_power_supplies_url( return None -def get_power_supplies( - power_data: dict, power_resource_type: str, host_fqdn: str +def process_power_supplies( + power_data: dict, + power_resource_type: str, ) -> list[dict] | None: """Get PowerSupplies data based on the Power resource type.""" if power_resource_type == "PowerSubsystem": @@ -325,7 +325,12 @@ async def process_power_supply( # 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) else: logging.error( @@ -338,11 +343,19 @@ async def process_power_supply( # Update Prometheus metrics if line_input_v is not None: - voltage_gauge.labels(host=host.fqdn, psu_serial=serial).set(line_input_v) + 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) + 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) + AMPS_GAUGE.labels(host=host.fqdn, psu_serial=serial).set(amps_input) + + +def normalize_url(url: str) -> str: + """Ensure URL does not end with a trailing slash.""" + # I needed this for realy old Redfish versions :S (<1.6.0) + if url.endswith("/"): + return url[:-1] # Remove trailing slash + return url async def get_power_data(session, host: HostConfig): @@ -351,7 +364,7 @@ async def get_power_data(session, host: HostConfig): logging.warning( "Skipping %s (in cool-down until %.1f)", host.fqdn, host.next_retry_time ) - up_gauge.labels(host=host.fqdn).set(0) + UP_GAUGE.labels(host=host.fqdn).set(0) return # Start time measurement @@ -361,26 +374,26 @@ async def get_power_data(session, host: HostConfig): if not resources: logging.error("Could not discover any resources for %s", host.fqdn) host.mark_failure() - up_gauge.labels(host=host.fqdn).set(0) + UP_GAUGE.labels(host=host.fqdn).set(0) return - + chassis_url = resources.get("Chassis") if not chassis_url: logging.error("No valid Chassis URL found for %s", host.fqdn) host.mark_failure() - up_gauge.labels(host=host.fqdn).set(0) + UP_GAUGE.labels(host=host.fqdn).set(0) return # Mark host as up host.mark_success() - up_gauge.labels(host=host.fqdn).set(1) + UP_GAUGE.labels(host=host.fqdn).set(1) # Get chassis ressource chassis_url = f"https://{host.fqdn}{chassis_url}" chassis_data = await fetch_with_retry(session, host, chassis_url) if not chassis_data: host.mark_failure() - up_gauge.labels(host=host.fqdn).set(0) + UP_GAUGE.labels(host=host.fqdn).set(0) return # loop over each member in chassis ressource @@ -389,8 +402,8 @@ async def get_power_data(session, host: HostConfig): if not chassis_member_url: continue - # Debug-Ausgabe für Chassis-Member-URL - print(f"Chassis Member URL: {chassis_member_url}") + # Normalize URL... I needed this for realy old Redfish versions :S (<1.6.0) + chassis_member_url = normalize_url(chassis_member_url) # Get chassis id from url ("/redfish/v1/Chassis/1" -> 1) chassis_member_id = chassis_member_url.split("/")[-1] @@ -401,13 +414,9 @@ async def get_power_data(session, host: HostConfig): member_url = f"https://{host.fqdn}{chassis_member_url}" member_data = await fetch_with_retry(session, host, member_url) - # Debug-Ausgabe für Chassis-Member-Daten - print(f"Chassis Member Data: {json.dumps(member_data, indent=4)}") if not member_data: continue - - # Get Power ressource (fallback to "Power") power_resource_url, power_resource_type = get_power_resource_info( member_data, host.fqdn @@ -503,7 +512,7 @@ async def get_system_info(session, host: HostConfig): serial_number = system_data.get("SerialNumber") # Hier könnte ihre Werbung stehen - system_info.labels(host=host.fqdn).info( + SYSTEM_INFO.labels(host=host.fqdn).info( { "manufacturer": manufacturer, "model": model, diff --git a/python/redfish-api/requirements.txt b/python/redfish-api/requirements.txt deleted file mode 100644 index 6f5d0d5..0000000 --- a/python/redfish-api/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -prometheus-client==0.23.1 -requests==2.32.5 -urllib3==2.5.0 -aiohttp==3.12.15 -asyncio==4.0.0 -PyYAML==6.0.2