fix(parser,exporter): reconcile BMC-dump and live-CD exports of the same server

The audit tool that ingests Reanimator exports treats any per-component field
change between imports as a component replacement. Importing an xFusion BMC dump
and an easy_bee BEE-SP bundle for one server produced large spurious diffs.

xfusion:
- parseMemInfo tolerates a stray 0x0A inside the binary SPD "bom number" column
  (it was splitting a DIMM record in two and emitting a phantom "slot s" module)
- DIMM slot from the "dimm name" column ("DIMM071"), mainboard "location" dropped
- GPU slot = BDF (Reanimator contract)
- NIC emitted per PCI function from netcard_info.txt (BDF + per-port MAC, shared
  card serial) instead of one card-level adapter with the wrong BDF, which had
  been colliding with a GPU and vanishing in dedup
- NIC manufacturer left blank when it is the system OEM so the exporter resolves
  the silicon vendor from pci.ids
- "(U6216)" chip designator stripped from firmware versions

easy_bee:
- PSU bay numbers rebased 0-indexed -> 1-indexed; bare single-letter PSU
  "firmware" (a leaked FRU version) cleared
- board.part_number taken from the bundle's ipmitool-fru.txt chassis
  "Product Part Number" to match the BMC value

exporter (cross-vendor):
- canonicalMemorySlot: drop dmidecode "(J)" channel tag, Memory111 -> DIMM111
- canonicalGPUModel: NVIDIA DC GPUs reduce to the bare chip token
- canonicalStorageMediaAndInterface: NVMe is a bus not a medium
- manufacturerFromStorageModel: fill blank drive vendor from the model prefix
- isRemovableUSBStorageDevice: drop live-CD boot sticks
- isOnboardControllerPCIeDevice: drop SATA/NVMe/MegaRAID/PCIe-switch controller
  functions that only an lspci scan reports (keep add-in cards with an identity)

For the reference server every physical component now appears in both exports
keyed identically; residual diffs are one-sided enrichment only.

Refs ADL-061, ADL-062.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFy4m7cVv4cqp25jJh2gSB
This commit is contained in:
Mikhail Chusavitin
2026-08-31 22:50:07 +03:00
co-authored by Claude Sonnet 5
parent 5b65c99b0e
commit 9b2c654182
7 changed files with 595 additions and 61 deletions
@@ -2270,3 +2270,75 @@ func TestConvertToReanimator_ExportsLicenses(t *testing.T) {
t.Errorf("Status = %q, want %q", scoped.Status, "Warning")
}
}
func TestCanonicalMemorySlot(t *testing.T) {
cases := map[string]string{
"DIMM111(J)": "DIMM111",
"DIMM111 (J)": "DIMM111",
"Memory111": "DIMM111",
"DIMM111": "DIMM111",
"P1-DIMMA1": "P1-DIMMA1",
"CPU0_C0D0": "CPU0_C0D0",
}
for in, want := range cases {
if got := canonicalMemorySlot(in); got != want {
t.Errorf("canonicalMemorySlot(%q) = %q, want %q", in, got, want)
}
}
}
func TestCanonicalGPUModel(t *testing.T) {
cases := []struct{ model, mfr, want string }{
{"NVIDIA H200 NVL", "NVIDIA Corporation", "H200"},
{"H200", "NVIDIA Corporation", "H200"},
{"NVIDIA H200 141G", "", "H200"},
{"GH100 [H200 NVL]", "NVIDIA Corporation", "H200"},
{"Radeon Instinct MI300X", "AMD", "Radeon Instinct MI300X"},
}
for _, c := range cases {
if got := canonicalGPUModel(c.model, c.mfr); got != c.want {
t.Errorf("canonicalGPUModel(%q,%q) = %q, want %q", c.model, c.mfr, got, c.want)
}
}
}
func TestCanonicalStorageMediaAndInterface(t *testing.T) {
cases := []struct{ inT, inI, wantT, wantI string }{
{"NVMe", "PCIe", "SSD", "NVMe"},
{"NVMe", "", "SSD", "NVMe"},
{"SSD", "PCIe", "SSD", "NVMe"},
{"SSD", "SATA", "SSD", "SATA"},
{"HDD", "SAS", "HDD", "SAS"},
}
for _, c := range cases {
gotT, gotI := canonicalStorageMediaAndInterface(c.inT, c.inI)
if gotT != c.wantT || gotI != c.wantI {
t.Errorf("canonicalStorageMediaAndInterface(%q,%q) = %q,%q want %q,%q", c.inT, c.inI, gotT, gotI, c.wantT, c.wantI)
}
}
}
func TestIsOnboardControllerPCIeDevice(t *testing.T) {
drop := []models.HardwareDevice{
{Kind: models.DeviceKindPCIe, DeviceClass: "SATA controller", Model: "Sapphire Rapids SATA AHCI Controller"},
{Kind: models.DeviceKindPCIe, DeviceClass: "Non-Volatile memory controller", Model: "NVMe SSD Controller CD8P"},
{Kind: models.DeviceKindPCIe, DeviceClass: "MassStorageController", Model: "MegaRAID 12GSAS/PCIe Secure SAS38xx"},
{Kind: models.DeviceKindPCIe, DeviceClass: "StorageController", Model: "PCIe Switch management endpoint"},
}
for _, d := range drop {
if !isOnboardControllerPCIeDevice(d) {
t.Errorf("expected %q/%q to be dropped", d.DeviceClass, d.Model)
}
}
keep := []models.HardwareDevice{
{Kind: models.DeviceKindGPU, DeviceClass: "VideoController", Model: "H200"},
{Kind: models.DeviceKindNetwork, DeviceClass: "EthernetController", Model: "ConnectX-6 Lx"},
{Kind: models.DeviceKindPCIe, DeviceClass: "raid_controller", Model: "RAID Controller", PartNumber: "XC170-M-8i"},
{Kind: models.DeviceKindPCIe, DeviceClass: "MassStorageController", Model: "HBA", SerialNumber: "ABC123"},
}
for _, d := range keep {
if isOnboardControllerPCIeDevice(d) {
t.Errorf("expected %q/%q to be kept", d.DeviceClass, d.Model)
}
}
}