improve redfish collection progress and robust hardware dedup/serial parsing
This commit is contained in:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user