fix(xfusion): emit OCP NIC ports with no serial so reanimator keys them by MAC
Verified against reanimator/core: it keys a NIC component on serial → MAC → synthetic(slot). A BMC dump carrying the card serial collapses both ports into one serial-keyed component, while a live-CD bundle (no serial) keeps two MAC-keyed components, so alternating the two sources produced a spurious COMPONENT_REMOVED + 2x COMPONENT_INSTALLED every time. Emitting the per-port NetworkAdapter entries with an empty serial makes both sources key on the port MAC and yield the same two components. The card serial still reaches the export via the FRU record and the legacy NIC entry. Also records ADL-062 findings from reading the ingesting audit tool: it persists only vendor/model/serial per component (no size/speed/link/clock/wattage), so the remaining one-sided field diffs between the two bundles cannot generate change events. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VFy4m7cVv4cqp25jJh2gSB
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
9b2c654182
commit
8e7f22077d
@@ -1726,12 +1726,44 @@ the work between the two vendor parsers and the shared exporter:
|
||||
taken from the bundle's `ipmitool-fru.txt` chassis "Product Part Number" to match
|
||||
the BMC value.
|
||||
**Consequences:** For the reference server every physical component now appears in
|
||||
both exports keyed identically (serial, or BDF for PCIe). Residual diffs are
|
||||
one-sided *enrichment* only — PCIe link speed/width, NVMe block sizes, drive
|
||||
`size_gb`, CPU clocks, PSU firmware/wattage, `board.uuid`, and the extra firmware
|
||||
components (CPLD/U-Boot/SDK/…) — where one collector's source simply has data the
|
||||
other's does not; none is a value that *conflicts*. Whether the audit treats a
|
||||
null→value transition on an unchanged component as a change is a config question
|
||||
on the audit side. Tests: `TestCanonicalMemorySlot`, `TestCanonicalGPUModel`,
|
||||
both exports keyed identically (serial, or BDF for PCIe). Tests:
|
||||
`TestCanonicalMemorySlot`, `TestCanonicalGPUModel`,
|
||||
`TestCanonicalStorageMediaAndInterface`, `TestIsOnboardControllerPCIeDevice`,
|
||||
`TestNormalizePSUSlots`, updated `TestParse_ServerFileExport_NetworkAdaptersAndFirmware`.
|
||||
|
||||
**Verified against `reanimator/core` (the ingesting audit tool), 2026-08-31.**
|
||||
Reanimator persists only a narrow slice of what the export carries, so most
|
||||
residual field diffs cannot generate a change event:
|
||||
- Component identity = `vendor_serial` only (`parts` table). Fallback chain per
|
||||
`flattenPCIe`: real serial → MAC → `{board}-PCIE-{slot}`. Memory/storage/PSU:
|
||||
serial → synthetic-by-slot. `parts` stores only vendor, model, serial — **no
|
||||
size, speed, link width, block size, clocks, wattage, location, numa/iommu**, so
|
||||
a null→value transition on any of those is a no-op.
|
||||
- `vendor`/`model` are re-canonicalized on ingest via the alias resolver **using
|
||||
`vendor_id`/`device_id`** ("prefer the authoritative pci.ids name"). Both bundles
|
||||
carry the same PCI IDs, so GPU model and NIC vendor already converge server-side
|
||||
regardless of the raw strings — the logpile-side `canonicalGPUModel` / OEM-vendor
|
||||
blanking are belt-and-suspenders for non-reanimator consumers.
|
||||
- `slot_name` is written once at install and only re-patched when the install
|
||||
itself changes (`res.Changed`); a component that stays continuously present
|
||||
never gets a slot-change event, so differing slot labels are harmless once
|
||||
presence is stable.
|
||||
- Asset firmware = `machine_firmware_states` keyed by `(machine_id, device_name)`;
|
||||
an event fires only on a version change for the *same* name. A name absent from
|
||||
an import is left untouched (no downgrade), so the 6 BMC-only firmware entries
|
||||
are inert. `iBMC` (BMC dump) vs `BMC` (live CD) stay two independent, non-churning
|
||||
rows — deliberately **not** normalized, because unifying the name would then
|
||||
churn on the version (`3.08.05.85` vs `3.08`).
|
||||
- Reanimator has its own `IsDeviceIgnored(vendor, model)` alias-driven ignore list
|
||||
for "onboard controllers, service USB flash drives" — the logpile-side
|
||||
`isRemovableUSBStorageDevice`/`isOnboardControllerPCIeDevice` filters overlap it
|
||||
but remove the dependency on the alias dict being configured.
|
||||
|
||||
The one real cross-collector reinstall risk was the **OCP NIC**: with the card
|
||||
serial on it, reanimator collapses both ports to one serial-keyed component, while
|
||||
a live-CD bundle (no serial) stays two MAC-keyed components → remove + 2×install
|
||||
on every source switch. Fixed by emitting the xFusion NIC as per-port entries
|
||||
**with no serial**, so both sources key on the port MAC and produce the same two
|
||||
components. Net: after this work the only fields that still differ between the two
|
||||
bundles are ones reanimator does not track, so alternating collection methods for
|
||||
one server produces no spurious install/remove/firmware events.
|
||||
|
||||
+21
-4
@@ -710,9 +710,17 @@ func mergeNetworkAdapters(cards []xfusionNICCard, snapshots []xfusionNetcardSnap
|
||||
}
|
||||
|
||||
// Emit one adapter per PCI function (port), keyed by that port's own BDF,
|
||||
// so the card matches an OS-level lspci view of the same NIC. Ports share
|
||||
// the physical card's serial/part number. Fall back to a single
|
||||
// card-level entry only when no per-port BDF is available.
|
||||
// so the card matches an OS-level lspci view of the same NIC. Fall back to
|
||||
// a single card-level entry only when no per-port BDF is available.
|
||||
//
|
||||
// The per-port entries deliberately carry NO serial number. The physical
|
||||
// card has one serial across both ports; an in-band collector (lspci) sees
|
||||
// none and identifies each port by its MAC. Reanimator keys a NIC on
|
||||
// serial → MAC → synthetic(slot): if we emitted the card serial here, its
|
||||
// two ports would collapse to one component keyed by that serial, while
|
||||
// the same NIC from a live-CD bundle stays two components keyed by MAC —
|
||||
// producing a spurious remove+reinstall every time the two sources
|
||||
// alternate. Leaving serial empty makes both sources key on MAC and agree.
|
||||
type portEntry struct {
|
||||
bdf string
|
||||
mac string
|
||||
@@ -734,6 +742,15 @@ func mergeNetworkAdapters(cards []xfusionNICCard, snapshots []xfusionNetcardSnap
|
||||
} else if len(ports) == 1 {
|
||||
portMACs = macs
|
||||
}
|
||||
// No serial: an lspci-based collector has none, and reanimator keys a
|
||||
// serial-bearing NIC on the serial (collapsing ports) but a
|
||||
// serial-less one on the MAC. Keeping this empty makes a BMC dump and
|
||||
// a live-CD run agree. The card serial still reaches the export via
|
||||
// this card's FRU record.
|
||||
serialPerPort := ""
|
||||
if len(portMACs) == 0 {
|
||||
serialPerPort = card.SerialNumber
|
||||
}
|
||||
adapters = append(adapters, models.NetworkAdapter{
|
||||
Slot: firstNonEmpty(p.bdf, slot),
|
||||
Location: "OCP",
|
||||
@@ -744,7 +761,7 @@ func mergeNetworkAdapters(cards []xfusionNICCard, snapshots []xfusionNetcardSnap
|
||||
Vendor: manufacturer,
|
||||
VendorID: card.VendorID,
|
||||
DeviceID: card.DeviceID,
|
||||
SerialNumber: card.SerialNumber,
|
||||
SerialNumber: serialPerPort,
|
||||
PartNumber: card.PartNumber,
|
||||
Firmware: firmware,
|
||||
PortCount: 1,
|
||||
|
||||
+7
-2
@@ -218,8 +218,13 @@ Product Name: G5500 V7
|
||||
if a.Firmware != "26.39.2048" {
|
||||
t.Fatalf("adapter %s firmware = %q, want 26.39.2048", a.BDF, a.Firmware)
|
||||
}
|
||||
if a.SerialNumber != "02Y238X6RC000058" {
|
||||
t.Fatalf("adapter %s serial = %q, want 02Y238X6RC000058", a.BDF, a.SerialNumber)
|
||||
// Ports keyed by MAC, not by the shared card serial, so a BMC dump and an
|
||||
// lspci-based bundle produce the same components.
|
||||
if a.SerialNumber != "" {
|
||||
t.Fatalf("adapter %s serial = %q, want empty (MAC-keyed)", a.BDF, a.SerialNumber)
|
||||
}
|
||||
if a.PartNumber != "0302Y238" {
|
||||
t.Fatalf("adapter %s part number = %q, want 0302Y238", a.BDF, a.PartNumber)
|
||||
}
|
||||
}
|
||||
if got := byBDF["0000:27:00.0"].MACAddresses; len(got) != 1 || got[0] != "44:1A:4C:16:E8:03" {
|
||||
|
||||
Reference in New Issue
Block a user