Skip to main content

kvmfleet-bmc-adapters

Async Python library for out-of-band server management — Redfish across Dell / HPE / Supermicro / Lenovo / OpenBMC, IPMI for pre-Redfish hardware, smart PDUs (APC / Eaton / Raritan), and Wake-on-LAN. Vendor quirks absorbed so they don't bleed into your code.

Used in production by the hosted access-governance platform at kvmfleet.io. Apache 2.0.

Quick install

pip install kvmfleet-bmc-adapters           # Redfish + WoL only
pip install 'kvmfleet-bmc-adapters[ipmi]'   # + IPMI (pyghmi)
pip install 'kvmfleet-bmc-adapters[pdu]'    # + PDU SNMP (pysnmp)
pip install 'kvmfleet-bmc-adapters[schema]' # + JSON-Schema validation (jsonschema)
pip install 'kvmfleet-bmc-adapters[all]'    # everything

Command-line interface (v0.7.0+)

Installing the package puts a kvmfleet-bmc command on your PATH — an operator CLI over the Redfish client for quick one-off checks and scripts:

# Inventory: make / model / serial / firmware
kvmfleet-bmc --host https://idrac.example.com -u root info

# Power state, then a graceful reboot (password from env, not the cmdline)
export BMC_PASSWORD=…
kvmfleet-bmc --host https://idrac.example.com -u root power status
kvmfleet-bmc --host https://idrac.example.com -u root power reboot

# Thermals as JSON, or a scriptable health check (exit 3 if not OK)
kvmfleet-bmc --host https://ilo.example.com -u admin --json thermal
kvmfleet-bmc --host https://ilo.example.com -u admin health || page-oncall

Power verbs: status, on, off, off-hard, cycle, reboot. TLS verification is off by default (BMCs ship self-signed certs); pass --verify-tls to enforce it. The CLI is a thin wrapper over RedfishClient — anything it does, the library does too.

What this is

An async Python library that covers the operator-facing surface of out-of-band server management across four protocols:

Protocol Vendors / scope Module
Redfish Dell iDRAC, HPE iLO, Supermicro, Lenovo XCC, OpenBMC bmc_adapters.RedfishClient
IPMI pre-Redfish hardware (iDRAC6/7/8, iLO3/4, SMC X9/X10/X11, OEM Aspeed BMCs) bmc_adapters.ipmi.IPMIClient
PDU APC AP86xx/88xx/89xx, Eaton ePDU G4, Raritan PX2/3/4 (Legrand) bmc_adapters.pdu.*
Wake-on-LAN any NIC with magic-packet support enabled in BIOS bmc_adapters.wake_on_lan

Vendor quirks (cipher 17 vs cipher 3 negotiation, SDR cache bugs on SMC X9/X10, RAKP timing on iLO3, outlet 0-vs-1-indexing on Raritan, ATX power-LED state polling on PiKVM, ...) live inside the clients, not in your code.

Redfish — the original surface (v0.1.0+)

Covered operations across Dell iDRAC, HPE iLO, Supermicro, Lenovo XCC, and OpenBMC via the DMTF Redfish standard:

Heartbeat + identity

  • heartbeat() — power state + first temp + health rollup
  • detect_vendor() — vendor from Oem keys / Manager @odata.id
  • system_info() — manufacturer, model, serial, asset tag, host name, UUID, BIOS + BMC firmware versions

Sensors

  • temperatures() — full thermal-sensor list with thresholds
  • fans() — RPM / PWM% per fan
  • power_supplies() — PSU model, capacity, input/output, status
  • power_metrics() — chassis-aggregated consumed / avg / min / max

Hardware inventory

  • processor_inventory() — CPU sockets
  • memory_inventory() — DIMMs
  • drive_inventory(max_drives=None) — physical drives across all storage controllers
  • volume_inventory() — RAID / logical volumes (read-only)
  • network_adapter_inventory() — host NICs
  • firmware_inventory() — firmware versions per component

Boot management

  • boot_config() — current one-time + persistent boot setup
  • set_next_boot(target, mode="UEFI") — one-time override
  • set_boot_order(devices) — persistent boot order

Power + virtual media + serial

  • power_action("on" / "off" / "off_hard" / "cycle" / "reboot")
  • insert_virtual_media(url) / eject_virtual_media()
  • nmi_trigger() — kernel-panic dump trigger

Logs

  • sel_entries(limit=100) — System Event Log / Lifecycle Log / IML — tries the standard SEL path first, falls back per vendor
  • clear_sel() — clear the log

Network

  • network_info() — BMC NIC config + NTP + DNS

Chassis control + recovery

  • chassis_health() — per-subsystem health rollup
  • indicator_led(state) — chassis locator LED
  • reset_bmc() — soft-reset the management controller

Read-only inventory

  • bmc_users() — list BMC accounts
  • license_info() — best-effort vendor license detection (iDRAC Express/Enterprise/Datacenter, iLO Standard/Advanced)

Plus session lifecycle handling — SessionService + Basic-auth fallback, token refresh, retry-on-401 — so the auth quirks don't bleed into your code.

Event streaming over Server-Sent Events (v0.7.0+)

When the BMC's Redfish EventService advertises an SSE endpoint, you can stream alerts (power, thermal, lifecycle) as they happen instead of polling the System Event Log:

async with RedfishClient(base_url="https://idrac.example", username="root", password="…") as bmc:
    if await bmc.event_service_sse_uri():            # None when SSE is unsupported
        async for ev in bmc.stream_events():         # runs until the BMC closes the stream
            print(ev.severity, ev.message_id, ev.message)
    else:
        sel = await bmc.sel_entries(limit=50)        # fall back to SEL polling

stream_events() yields RedfishEventRecords (message_id, message, severity, event_type, event_timestamp, origin_of_condition, raw). SSE support is vendor- and firmware-dependent — common on recent iDRAC9 / iLO5 / OpenBMC, absent on older boxes — so check event_service_sse_uri() first and keep sel_entries() polling as the fallback.

Fleet-scale collection reads (v0.7.0+)

Every collection the client walks (processors, memory, drives, volumes, NICs, firmware, SEL, BMC users) follows Members@odata.nextLink, so a dense chassis or a long event log doesn't silently lose members past the first page. To read a whole collection in one round-trip instead of N+1, use $expand:

# All firmware components in a single $expand'd, paginated sweep
fw = await bmc.expand_collection("/redfish/v1/UpdateService/FirmwareInventory")

expand_collection() returns inlined member bodies when the service supports $expand, and transparently falls back to plain paginated reads when it doesn't (older firmware).

Validating responses (v0.7.0+)

Opt-in validation flags non-compliant BMC firmware. The structural-contract check is dependency-free and always available:

issues = await bmc.validate_resource("/redfish/v1/Systems/1")
for i in issues:
    print(i.severity, i.field, i.message)   # e.g. error @odata.id missing required @odata.id

It checks the Redfish contract — required @odata.id, well-formed @odata.type, collection consistency, and legal Status / PowerState enum values — and never raises. For strict DSP2046 conformance, pass a JSON Schema you supply (we don't vendor the multi-MB DMTF bundle):

# pip install 'kvmfleet-bmc-adapters[schema]'
from bmc_adapters import validate_against_schema
issues = validate_against_schema(resource_dict, my_schema)   # uses jsonschema

IPMI — pre-Redfish hardware (v0.4.0+)

For the long tail of pre-2018 servers Redfish doesn't reach. Wraps pyghmi (Apache 2.0, used by OpenStack Ironic) behind the same async surface as RedfishClient.

from bmc_adapters.ipmi import IPMIClient, IPMIConfig

async with IPMIClient(IPMIConfig(
    host="bmc.example.com",
    username="ADMIN",
    password="...",
)) as bmc:
    await bmc.power_action("cycle")
    sensors = await bmc.sensors()
    sel = await bmc.sel_entries(limit=50)
    for finding in bmc.findings:
        audit.append(finding.to_dict())

Secure-leaning defaults:

  • Refuses IPMI 1.5 (allow_ipmi_1_5=False by default).
  • Rejects a config that asks for cipher suites 0, 1, 2, 6, 7, 8, 11, 12 (the ones pen-test reports flag as auth-bypass / no-encryption / weak-hash). Caveat, stated plainly: the actual RMCP+ session cipher is negotiated by the underlying pyghmi library against what the BMC offers — this adapter guards the config you pass, it does not override pyghmi's on-wire negotiation. If you need a hard cipher floor, enforce it on the BMC.
  • Default-credential detection without probing — constant-time compare against a documented vendor/user/password table; we never try the credential against the BMC, we just flag when the configured one matches a known default.
  • Emits structured BMCFinding records for default-credential matches and Pantsdown firmware windows (CVE-2019-6260) on AST2400 / AST2500 BMCs.

Smart PDU control (v0.4.0+)

When the BMC is dead or the NIC has hung, the operator reaches for the rack PDU. Async clients for the dominant vendors:

from bmc_adapters.pdu import APCPDUClient, RaritanPDUClient

# APC over SNMP (v2c or v3)
async with APCPDUClient("10.0.5.20", community="...") as pdu:
    outlets = await pdu.list_outlets()
    await pdu.outlet_cycle(3)

# Raritan via JSON-RPC over HTTPS
async with RaritanPDUClient(
    "https://pdu-2.example.com",
    username="admin",
    password="...",
) as pdu:
    await pdu.outlet_off("server-rack-7")  # by name

Vendor coverage in v0.4.0:

Vendor Models Protocol Module
APC (Schneider) AP86xx, AP88xx, AP89xx (NMC2 / NMC3) SNMPv2c, SNMPv3 (PowerNet-MIB) APCPDUClient
Eaton ePDU G4 (Network-M2 / M3) SNMPv2c, SNMPv3 (EATON-EPDU-MIB) EatonPDUClient
Raritan / Legrand PX2, PX3, PX4 JSON-RPC over HTTPS RaritanPDUClient

Refuses SNMPv2c by default unless allow_snmpv2c=True is passed (plaintext community strings are not okay on a shared management network). Vendor auto-detect via SNMP sysObjectID available through vendor_from_sysobjectid().

Wake-on-LAN (v0.4.0+)

For systems without a BMC at all (edge boxes, homelab, consumer boards). One function, no dependency:

from bmc_adapters import wake_on_lan

await wake_on_lan("aa:bb:cc:dd:ee:ff")
# or directed broadcast:
await wake_on_lan("aa:bb:cc:dd:ee:ff", broadcast="10.0.5.255")

Magic packets are L2-pattern-matched by the NIC firmware before the IP stack, so the destination port is cosmetic; we default to UDP/9.

Cross-protocol orchestration

The BMC orchestrator composes per-protocol adapters and dispatches power actions across them. Borrowed from bmclib's registry pattern.

from bmc_adapters import BMC, RedfishClient
from bmc_adapters.ipmi import IPMIClient, IPMIConfig
from bmc_adapters.pdu import APCPDUClient

async with (
    RedfishClient(...) as redfish,
    IPMIClient(IPMIConfig(...)) as ipmi,
    APCPDUClient("10.0.5.20", community="...") as pdu,
):
    bmc = BMC(redfish=redfish, ipmi=ipmi, pdu=pdu, pdu_outlet=3)
    transport = await bmc.power_action("cycle")
    # transport == "redfish" | "ipmi" | "pdu"

BMC.power_action walks Redfish → IPMI → PDU outlet-cycle in order and returns the name of the transport that handled the action.

Security findings

Adapters emit structured BMCFinding records (default credentials matched, Pantsdown firmware window on AST2400/2500, SNMPv2c plaintext on PDUs) that callers can route into audit chains, SIEM events, or dashboards.

for finding in client.findings:
    audit.append(finding.to_dict())
    # {
    #   "code": "BMC_DEFAULT_CREDENTIALS_LIKELY",
    #   "severity": "high",
    #   "detail": "...",
    #   "cve": [],
    #   "vendor": "supermicro"
    # }

What this is NOT

  • Not a full Redfish client. We map the operations BMC operators reach for. If you need something we don't expose (vendor-specific Oem actions on resources we don't enumerate), go write that PR — the library is small enough to extend.
  • Not RIBCL. RIBCL (older HPE iLO) is intentionally dropped — iLO 4 firmware ≥ 2.30 has working Redfish since 2016, and iLO 5/6 are Redfish-first. (RACADM, Dell's CLI, is covered as of v0.6.0 — see bmc_adapters.racadm.)
  • Not certified Redfish-conformant. Real BMC firmware ships bugs; the library absorbs them rather than pretending the spec is the world.
  • No firmware updates, no BIOS attribute CRUD, no RAID configuration, no BMC-user CRUD. Each is vendor-quirk-hell with a different per-vendor shape; lumping them in would change the library's shape. They are intentionally out of scope.

Install

pip install kvmfleet-bmc-adapters

Requires Python 3.11+.

Quick start

import asyncio
from bmc_adapters import RedfishClient

async def main():
    async with RedfishClient(
        base_url="https://idrac.example.com",
        username="root",
        password="calvin",
    ) as client:
        snap = await client.heartbeat()
        print(snap)
        # HeartbeatSnapshot(online=True, power_state='On', cpu_temp_c=51.5, health='OK')

        info = await client.system_info()
        print(info.manufacturer, info.model, info.bios_version)

        for t in await client.temperatures():
            if t.status not in ("OK", None):
                print(f"unhealthy sensor: {t.name} = {t.reading_c}C ({t.status})")

        for d in await client.drive_inventory():
            if d.failure_predicted:
                print(f"drive failure predicted: {d.name} ({d.model})")

        await client.power_action("cycle")
        await client.insert_virtual_media("https://my-iso-host/ubuntu.iso")

asyncio.run(main())

Keeping secrets encrypted at rest

The password argument accepts a string OR a zero-arg callable (sync or async) returning a string. If you keep BMC creds encrypted in your own secret store, hand the library a getter:

async def get_pw():
    return await my_vault.decrypt(blob)

async with RedfishClient(
    base_url=..., username=..., password=get_pw,
) as client:
    ...

The library calls the getter exactly once per login (not per request) and never stores or logs the plaintext.

TLS

verify_tls defaults to false because roughly 98% of factory-shipped BMCs serve self-signed certs. The connection is still TLS-encrypted; we just don't validate the leaf chain in that default. Flip to true once you've pinned a real cert on the BMC:

RedfishClient(..., verify_tls=True)

If you need certificate-pinning (SPKI-pin enforcement), the underlying httpx.AsyncClient exposes the hooks; we may add a first-class verify_pin argument if there's demand.

Supported vendors

See docs/supported-vendors.md for the honest list of vendor + firmware version pairs. Tested in production: iDRAC 9 (4.40+), iLO 5, iLO 4 (2.78), Supermicro X11, Lenovo XCC2, OpenBMC. iDRAC 8 partially works (a virtual-media slot quirk). Everything else — including the recognised-not-verified vendors below — is fixture/mock-tested only. PRs adding a new pair (with a representative response fixture) are welcome.

detect_vendor() additionally recognises Cisco (CIMC/UCS), Fujitsu (iRMC), Huawei (iBMC), and the AMI MegaRAC whitebox family (Gigabyte, ASRock Rack, Tyan, Quanta, Inspur, and the generic ami-megarac OEM stack) from their documented vendor signatures. These speak standard Redfish, so the client's operations should work — but they are not yet in the tested fleet, so treat them as recognised, not verified. A fixture PR (or a pilot against real hardware) promotes them to tested.

Honest caveats

  • None means "no signal", not "zero". Vendors partially implement the Redfish schema; the dataclass types return None for any field the firmware leaves blank. Don't compare against 0 for sensor readings.
  • predicted_life_left_percent is unreliable on consumer SSDs. Vendor support varies wildly; enterprise drives are honest, consumer drives often lie or omit the field.
  • firmware_inventory() shape differs by vendor. iDRAC enumerates 30+ components (BIOS, BMC, each NIC, each drive, PSU FW); iLO is similar; Supermicro tends to expose fewer. Don't assume a fixed component set.
  • license_info() is best-effort. iDRAC and iLO surface this through different Oem trees; other vendors usually return mostly-empty. The absence of license info doesn't mean the system is unlicensed.

Why does this exist

We needed multi-vendor BMC access for KVM Fleet's hosted access-governance platform. The DMTF Redfish standard is the right shape for the protocol layer, but real BMC firmware ships with vendor quirks (SessionService returning 204 No Content, MediaTypes missing on single-slot configurations, basic-auth-only when the spec says otherwise) that no library we found absorbed cleanly. So we wrote our own.

Open-sourcing it because:

  • The protocol layer isn't where our value lives. Our value is the hosted access governance, audit chain, EU-resident retention, and the operational SLA on top.
  • Anyone building tooling against multi-vendor BMCs hits the same vendor-quirks pit we did. No need for everyone to re-hit it.
  • Open code means hostile reviewers can verify the auth flow, the retry logic, the TLS defaults. That trust transfer matters more to us than gatekeeping.

This is one of a series of OSS extractions from the KVM Fleet platform. See BUSINESS.md §N for the doctrine.

Comparison

  • Sushy (OpenStack / openstack/sushy): the reference multi-vendor Redfish client in Python (~15k LoC). Shaped for OpenStack Ironic. If you're standing up Ironic, use Sushy.
  • HPE python-ilorest-library: vendor-specific (iLO). Use it if you only run iLO and you want the full iLO surface including features we don't cover (firmware updates, BIOS attributes).
  • DMTF Redfish-Tacklebox: reference scripts + toolkit from DMTF itself. A reference, not a library to vendor.
  • check_redfish (bb-Ricardo): monitoring plugin. Solves a different shape — output to a monitoring system, not a Python library you import.

bmc-adapters sits in the gap: smaller and operator-shaped, not OpenStack-shaped; multi-vendor, not iLO-only; library, not monitoring plugin or script collection.

Coming soon

Shipped since earlier drafts of this README: the CLI (kvmfleet-bmc), the IPMI module (bmc_adapters.ipmi), smart-PDU clients, RACADM, Wake-on-LAN, and Redfish EventService SSE streaming. Still on the list:

  • Vendor contribution template for adding new BMC firmware to the test matrix without a maintainer review-cycle bottleneck.
  • Certificate pinning (verify_pin) as a first-class Redfish argument.

If you'd find either useful right now, open an issue saying so — real demand is what we prioritise on.

Contributing

Bug reports and patches welcome. The library is small enough that a serious contribution can be reviewed in a day. See docs/contributing.md for the local dev setup.

License

Apache 2.0 — see LICENSE. Copyright 2026 KVM Fleet.

Links

  • KVM Fleet — the hosted platform this came from
  • audit-verify — the OSS audit-chain verifier (BSL 1.1)
  • agent — the OSS device agent (Apache 2.0)
  • mcp — the read-only MCP server for AI assistants (MIT)

Metadata

Release files for kvmfleet-bmc-adapters 0.7.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kvmfleet-bmc-adapters 0.7.1
File Size Uploaded
kvmfleet_bmc_adapters-0.7.1.tar.gz 95.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for kvmfleet-bmc-adapters 0.7.1
File Interpreter ABI Platform
kvmfleet_bmc_adapters-0.7.1-py3-none-any.whl Python 3 none any Details

Total release size: 181.1 kB

Release files / kvmfleet_bmc_adapters-0.7.1.tar.gz

Download URL kvmfleet_bmc_adapters-0.7.1.tar.gz
Size 95.0 kB
Tags Source
SHA-256 checksum
How to use checksums
8475f03b14988ef6eb52169ee4f6e79db5c0f9bead1bbebf08635bbefcc39470
BLAKE2b-256 checksum
How to use checksums
a0246958dfb2573df5b508a9b4c65cb81de814cbb2884afc8a01b8e6898f4059
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 24, 2026.

Transparency log

Release files / kvmfleet_bmc_adapters-0.7.1-py3-none-any.whl

Download URL kvmfleet_bmc_adapters-0.7.1-py3-none-any.whl
Size 86.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4fc7dbb796f7ce067896c46f8a668c8d68de6977095cf9e61ef337e1ef23a48e
BLAKE2b-256 checksum
How to use checksums
303d8f66a3261aa311915485fbb3feb52dc825ed49838ecc402e988263265ab7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.7.1 This release

2 release files

0.7.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page