improve redfish collection progress and robust hardware dedup/serial parsing

This commit is contained in:
2026-02-28 16:07:42 +03:00
parent 8dbbec3610
commit 9a30705c9a
9 changed files with 871 additions and 60 deletions
+224
View File
@@ -369,6 +369,139 @@ func TestParsePCIeDevice_PrefersFunctionClassOverDeviceType(t *testing.T) {
}
}
func TestParseComponents_UseNestedSerialNumberFallback(t *testing.T) {
doc := map[string]interface{}{
"Name": "dev0",
"Id": "dev0",
"Model": "model0",
"Manufacturer": "vendor0",
"SerialNumber": "N/A",
"Oem": map[string]interface{}{
"SerialNumber": "SN-OK-001",
},
}
cpus := parseCPUs([]map[string]interface{}{doc})
if len(cpus) != 1 || cpus[0].SerialNumber != "SN-OK-001" {
t.Fatalf("expected CPU serial fallback, got %+v", cpus)
}
dimms := parseMemory([]map[string]interface{}{doc})
if len(dimms) != 1 || dimms[0].SerialNumber != "SN-OK-001" {
t.Fatalf("expected DIMM serial fallback, got %+v", dimms)
}
drive := parseDrive(doc)
if drive.SerialNumber != "SN-OK-001" {
t.Fatalf("expected drive serial fallback, got %q", drive.SerialNumber)
}
nic := parseNIC(doc)
if nic.SerialNumber != "SN-OK-001" {
t.Fatalf("expected NIC serial fallback, got %q", nic.SerialNumber)
}
psu := parsePSU(doc, 1)
if psu.SerialNumber != "SN-OK-001" {
t.Fatalf("expected PSU serial fallback, got %q", psu.SerialNumber)
}
pcie := parsePCIeDevice(doc, nil)
if pcie.SerialNumber != "SN-OK-001" {
t.Fatalf("expected PCIe device serial fallback, got %q", pcie.SerialNumber)
}
pcieFn := parsePCIeFunction(doc, 1)
if pcieFn.SerialNumber != "SN-OK-001" {
t.Fatalf("expected PCIe function serial fallback, got %q", pcieFn.SerialNumber)
}
}
func TestRedfishCollectionMemberRefs_IncludesOemPublicMembers(t *testing.T) {
collection := map[string]interface{}{
"Members": []interface{}{
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/Drives/OB01"},
},
"Oem": map[string]interface{}{
"Public": map[string]interface{}{
"Members": []interface{}{
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/Drives/FP00HDD00"},
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/Drives/FP00HDD02"},
},
},
},
}
got := redfishCollectionMemberRefs(collection)
if len(got) != 3 {
t.Fatalf("expected 3 member refs, got %d: %v", len(got), got)
}
}
func TestRecoverCriticalRedfishDocsPlanB_RetriesMembersFromExistingCollection(t *testing.T) {
t.Setenv("LOGPILE_REDFISH_CRITICAL_COOLDOWN", "0s")
t.Setenv("LOGPILE_REDFISH_CRITICAL_SLOW_GAP", "0s")
t.Setenv("LOGPILE_REDFISH_CRITICAL_PLANB_RETRIES", "1")
t.Setenv("LOGPILE_REDFISH_CRITICAL_RETRIES", "1")
t.Setenv("LOGPILE_REDFISH_CRITICAL_BACKOFF", "0s")
const memberPath = "/redfish/v1/Chassis/1/Drives/FP00HDD00"
mux := http.NewServeMux()
mux.HandleFunc(memberPath, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"Id": "FP00HDD00",
"Name": "FP00HDD00",
"Model": "HDD-TEST",
"MediaType": "HDD",
"Protocol": "SAS",
"CapacityBytes": int64(2000398934016),
"SerialNumber": "HDD-SN-001",
})
})
ts := httptest.NewServer(mux)
defer ts.Close()
rawTree := map[string]interface{}{
"/redfish/v1/Chassis/1/Drives": map[string]interface{}{
"Members": []interface{}{
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/Drives/OB01"},
},
"Oem": map[string]interface{}{
"Public": map[string]interface{}{
"Members": []interface{}{
map[string]interface{}{"@odata.id": memberPath},
},
},
},
},
}
fetchErrs := map[string]string{
memberPath: "Get \"https://example/redfish/v1/Chassis/1/Drives/FP00HDD00\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)",
}
c := NewRedfishConnector()
recovered := c.recoverCriticalRedfishDocsPlanB(
context.Background(),
ts.Client(),
Request{},
ts.URL,
[]string{"/redfish/v1/Chassis/1/Drives"},
rawTree,
fetchErrs,
nil,
)
if recovered == 0 {
t.Fatalf("expected plan-B to recover at least one document")
}
if _, ok := rawTree[memberPath]; !ok {
t.Fatalf("expected recovered member doc for %s", memberPath)
}
if _, ok := fetchErrs[memberPath]; ok {
t.Fatalf("expected fetch error for %s to be cleared after recovery", memberPath)
}
}
func TestReplayCollectStorage_ProbesSupermicroNVMeDiskBayWhenCollectionEmpty(t *testing.T) {
r := redfishSnapshotReader{tree: map[string]interface{}{
"/redfish/v1/Systems": map[string]interface{}{
@@ -551,6 +684,54 @@ func TestReplayCollectGPUs_FromGraphicsControllers(t *testing.T) {
}
}
func TestReplayCollectGPUs_DedupUsesRedfishPathBeforeHeuristics(t *testing.T) {
r := redfishSnapshotReader{tree: map[string]interface{}{
"/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{
"Members": []interface{}{
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU0"},
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU1"},
},
},
"/redfish/v1/Systems/1/GraphicsControllers/GPU0": map[string]interface{}{
"Id": "GPU0",
"Name": "H100-PCIE-80G",
"Model": "H100-PCIE-80G",
"Manufacturer": "NVIDIA",
"SerialNumber": "N/A",
},
"/redfish/v1/Systems/1/GraphicsControllers/GPU1": map[string]interface{}{
"Id": "GPU1",
"Name": "H100-PCIE-80G",
"Model": "H100-PCIE-80G",
"Manufacturer": "NVIDIA",
"SerialNumber": "N/A",
},
}}
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil)
if len(got) != 2 {
t.Fatalf("expected both GPUs to be kept by unique redfish path, got %d", len(got))
}
}
func TestParseGPU_UsesNestedOemSerialNumber(t *testing.T) {
doc := map[string]interface{}{
"Id": "GPU4",
"Name": "H100-PCIE-80G",
"Model": "H100-PCIE-80G",
"Manufacturer": "NVIDIA",
"SerialNumber": "N/A",
"Oem": map[string]interface{}{
"SerialNumber": "1794024010533",
},
}
got := parseGPU(doc, nil, 1)
if got.SerialNumber != "1794024010533" {
t.Fatalf("expected nested OEM serial number, got %q", got.SerialNumber)
}
}
func TestParseBoardInfoWithFallback_UsesFRU(t *testing.T) {
system := map[string]interface{}{
"Manufacturer": "NULL",
@@ -769,6 +950,49 @@ func TestReplayCollectGPUs_DropsModelOnlyPlaceholderWhenConcreteDiscoveredLater(
}
}
func TestReplayCollectGPUs_MergesGraphicsSerialIntoConcretePCIeGPU(t *testing.T) {
r := redfishSnapshotReader{tree: map[string]interface{}{
"/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{
"Members": []interface{}{
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU4"},
},
},
"/redfish/v1/Systems/1/GraphicsControllers/GPU4": map[string]interface{}{
"Id": "4",
"Name": "H100-PCIE-80G",
"Model": "H100-PCIE-80G",
"Manufacturer": "NVIDIA",
"Oem": map[string]interface{}{
"SerialNumber": "1794024010533",
},
},
"/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{
"Members": []interface{}{
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/8"},
},
},
"/redfish/v1/Chassis/1/PCIeDevices/8": map[string]interface{}{
"Id": "8",
"Name": "PCIeCard8",
"Model": "H100-PCIE-80G",
"Manufacturer": "NVIDIA",
"SerialNumber": "N/A",
"BDF": "0000:b1:00.0",
},
}}
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"})
if len(got) != 1 {
t.Fatalf("expected merged single GPU row, got %d", len(got))
}
if got[0].Slot != "PCIeCard8" {
t.Fatalf("expected concrete PCIe slot, got %q", got[0].Slot)
}
if got[0].SerialNumber != "1794024010533" {
t.Fatalf("expected merged serial from graphics controller, got %q", got[0].SerialNumber)
}
}
func TestShouldCrawlPath_MemorySubresourcesAreSkipped(t *testing.T) {
if !shouldCrawlPath("/redfish/v1/Systems/1/Memory/CPU0_C0D0") {
t.Fatalf("expected direct DIMM resource to be crawlable")