package collector import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" "time" "git.mchus.pro/mchus/logpile/internal/models" ) func TestRedfishConnectorCollect(t *testing.T) { mux := http.NewServeMux() register := func(path string, payload interface{}) { mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(payload) }) } register("/redfish/v1", map[string]interface{}{"Name": "ServiceRoot"}) register("/redfish/v1/Systems/1", map[string]interface{}{ "Manufacturer": "Supermicro", "Model": "SYS-TEST", "SerialNumber": "SYS123", "BiosVersion": "2.1a", }) register("/redfish/v1/Systems/1/Bios", map[string]interface{}{"Version": "2.1a"}) register("/redfish/v1/Systems/1/SecureBoot", map[string]interface{}{"SecureBootCurrentBoot": "Enabled"}) register("/redfish/v1/Systems/1/Processors", map[string]interface{}{ "Members": []map[string]string{ {"@odata.id": "/redfish/v1/Systems/1/Processors/CPU1"}, }, }) register("/redfish/v1/Systems/1/Processors/CPU1", map[string]interface{}{ "Name": "CPU1", "Model": "Xeon Gold", "TotalCores": 32, "TotalThreads": 64, "MaxSpeedMHz": 3600, }) register("/redfish/v1/Systems/1/Memory", map[string]interface{}{ "Members": []map[string]string{ {"@odata.id": "/redfish/v1/Systems/1/Memory/DIMM1"}, }, }) register("/redfish/v1/Systems/1/Memory/DIMM1", map[string]interface{}{ "Name": "DIMM A1", "CapacityMiB": 32768, "MemoryDeviceType": "DDR5", "OperatingSpeedMhz": 4800, "Status": map[string]interface{}{ "Health": "OK", }, }) register("/redfish/v1/Systems/1/Storage", map[string]interface{}{ "Members": []map[string]string{ {"@odata.id": "/redfish/v1/Systems/1/Storage/1"}, }, }) register("/redfish/v1/Systems/1/Storage/1", map[string]interface{}{ "Drives": []map[string]string{ {"@odata.id": "/redfish/v1/Systems/1/Storage/1/Drives/1"}, }, }) register("/redfish/v1/Systems/1/Storage/1/Drives/1", map[string]interface{}{ "Name": "Drive1", "Model": "NVMe Test", "MediaType": "SSD", "Protocol": "NVMe", "CapacityGB": 960, "SerialNumber": "SN123", }) register("/redfish/v1/Systems/1/PCIeDevices", map[string]interface{}{ "Members": []map[string]string{ {"@odata.id": "/redfish/v1/Systems/1/PCIeDevices/GPU1"}, }, }) register("/redfish/v1/Systems/1/PCIeDevices/GPU1", map[string]interface{}{ "Id": "GPU1", "Name": "NVIDIA H100", "Model": "NVIDIA H100 PCIe", "Manufacturer": "NVIDIA", "SerialNumber": "GPU-SN-001", "PCIeFunctions": map[string]interface{}{ "@odata.id": "/redfish/v1/Systems/1/PCIeDevices/GPU1/PCIeFunctions", }, }) register("/redfish/v1/Systems/1/PCIeDevices/GPU1/PCIeFunctions", map[string]interface{}{ "Members": []map[string]string{ {"@odata.id": "/redfish/v1/Systems/1/PCIeFunctions/GPU1F0"}, }, }) register("/redfish/v1/Systems/1/PCIeFunctions/GPU1F0", map[string]interface{}{ "FunctionId": "0000:65:00.0", "VendorId": "0x10DE", "DeviceId": "0x2331", "ClassCode": "0x030200", "CurrentLinkWidth": 16, "CurrentLinkSpeed": "16.0 GT/s", "MaxLinkWidth": 16, "MaxLinkSpeed": "16.0 GT/s", }) register("/redfish/v1/Chassis/1/NetworkAdapters", map[string]interface{}{ "Members": []map[string]string{ {"@odata.id": "/redfish/v1/Chassis/1/NetworkAdapters/1"}, }, }) register("/redfish/v1/Chassis/1/Power", map[string]interface{}{ "PowerSupplies": []map[string]interface{}{ { "MemberId": "PSU1", "Name": "PSU Slot 1", "Model": "PWS-2K01A-1R", "Manufacturer": "Delta", "PowerCapacityWatts": 2000, "PowerInputWatts": 1600, "LastPowerOutputWatts": 1200, "LineInputVoltage": 230, "Status": map[string]interface{}{ "Health": "OK", "State": "Enabled", }, }, }, }) register("/redfish/v1/Chassis/1/NetworkAdapters/1", map[string]interface{}{ "Name": "Mellanox", "Model": "ConnectX-6", "SerialNumber": "NIC123", }) register("/redfish/v1/Managers/1", map[string]interface{}{ "FirmwareVersion": "1.25", }) register("/redfish/v1/Managers/1/NetworkProtocol", map[string]interface{}{ "Id": "NetworkProtocol", }) ts := httptest.NewServer(mux) defer ts.Close() c := NewRedfishConnector() result, err := c.Collect(context.Background(), Request{ Host: ts.URL, Port: 443, Protocol: "redfish", Username: "admin", AuthType: "password", Password: "secret", TLSMode: "strict", }, nil) if err != nil { t.Fatalf("collect failed: %v", err) } if result.Hardware == nil { t.Fatalf("expected hardware config") } if result.Hardware.BoardInfo.ProductName != "SYS-TEST" { t.Fatalf("unexpected board model: %q", result.Hardware.BoardInfo.ProductName) } if len(result.Hardware.CPUs) != 1 { t.Fatalf("expected one CPU, got %d", len(result.Hardware.CPUs)) } if len(result.Hardware.Memory) != 1 { t.Fatalf("expected one DIMM, got %d", len(result.Hardware.Memory)) } if len(result.Hardware.Storage) != 1 { t.Fatalf("expected one drive, got %d", len(result.Hardware.Storage)) } if len(result.Hardware.NetworkAdapters) != 1 { t.Fatalf("expected one nic, got %d", len(result.Hardware.NetworkAdapters)) } if len(result.Hardware.GPUs) != 1 { t.Fatalf("expected one gpu, got %d", len(result.Hardware.GPUs)) } if result.Hardware.GPUs[0].BDF != "0000:65:00.0" { t.Fatalf("unexpected gpu BDF: %q", result.Hardware.GPUs[0].BDF) } if len(result.Hardware.PCIeDevices) != 1 { t.Fatalf("expected one pcie device, got %d", len(result.Hardware.PCIeDevices)) } if len(result.Hardware.PowerSupply) != 1 { t.Fatalf("expected one psu, got %d", len(result.Hardware.PowerSupply)) } if result.Hardware.PowerSupply[0].WattageW != 2000 { t.Fatalf("unexpected psu wattage: %d", result.Hardware.PowerSupply[0].WattageW) } if len(result.Hardware.Firmware) == 0 { t.Fatalf("expected firmware entries") } if result.RawPayloads == nil { t.Fatalf("expected raw payloads") } treeAny, ok := result.RawPayloads["redfish_tree"] if !ok { t.Fatalf("expected redfish_tree in raw payloads") } tree, ok := treeAny.(map[string]interface{}) if !ok || len(tree) == 0 { t.Fatalf("expected non-empty redfish_tree, got %#v", treeAny) } } func TestParsePCIeDeviceSlot_FromNestedRedfishSlotLocation(t *testing.T) { doc := map[string]interface{}{ "Id": "NIC1", "Slot": map[string]interface{}{ "Lanes": 16, "Location": map[string]interface{}{ "PartLocation": map[string]interface{}{ "LocationOrdinalValue": 1, "LocationType": "Slot", "ServiceLabel": "PCIe Slot 1 (1)", }, }, "PCIeType": "Gen5", "SlotType": "FullLength", }, } got := parsePCIeDevice(doc, nil) if got.Slot != "PCIe Slot 1 (1)" { t.Fatalf("unexpected slot: %q", got.Slot) } } func TestParsePCIeDeviceSlot_EmptyMapFallsBackToID(t *testing.T) { doc := map[string]interface{}{ "Id": "NIC42", "Slot": map[string]interface{}{}, } got := parsePCIeDevice(doc, nil) if got.Slot != "NIC42" { t.Fatalf("unexpected slot fallback: %q", got.Slot) } if got.Slot == "map[]" { t.Fatalf("slot should not stringify empty map") } } func TestReplayRedfishFromRawPayloads_FallbackCollectionMembersByPrefix(t *testing.T) { raw := map[string]any{ "redfish_tree": map[string]interface{}{ "/redfish/v1": map[string]interface{}{ "Systems": map[string]interface{}{"@odata.id": "/redfish/v1/Systems"}, "Chassis": map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"}, "Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"}, }, "/redfish/v1/Systems": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"}, }, }, "/redfish/v1/Systems/1": map[string]interface{}{ "Manufacturer": "Supermicro", "Model": "SYS-TEST", "SerialNumber": "SYS123", }, // Intentionally missing /redfish/v1/Systems/1/Processors collection. "/redfish/v1/Systems/1/Processors/CPU1": map[string]interface{}{ "Id": "CPU1", "Model": "Xeon Gold", "TotalCores": 32, "TotalThreads": 64, }, "/redfish/v1/Chassis": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"}, }, }, "/redfish/v1/Chassis/1": map[string]interface{}{ "Id": "1", }, "/redfish/v1/Managers": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"}, }, }, "/redfish/v1/Managers/1": map[string]interface{}{ "Id": "1", }, }, "redfish_fetch_errors": []map[string]interface{}{ {"path": "/redfish/v1/Systems/1/Processors", "error": "status 500"}, }, } got, err := ReplayRedfishFromRawPayloads(raw, nil) if err != nil { t.Fatalf("replay failed: %v", err) } if got.Hardware == nil { t.Fatalf("expected hardware") } if len(got.Hardware.CPUs) != 1 { t.Fatalf("expected one CPU via prefix fallback, got %d", len(got.Hardware.CPUs)) } if _, ok := got.RawPayloads["redfish_fetch_errors"]; !ok { t.Fatalf("expected raw payloads to preserve redfish_fetch_errors") } } func TestReplayRedfishFromRawPayloads_PreservesSourceTimezoneAndUTCCollectedAt(t *testing.T) { raw := map[string]any{ "redfish_tree": map[string]interface{}{ "/redfish/v1": map[string]interface{}{ "Systems": map[string]interface{}{"@odata.id": "/redfish/v1/Systems"}, "Chassis": map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"}, "Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"}, }, "/redfish/v1/Systems": map[string]interface{}{ "Members": []interface{}{map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"}}, }, "/redfish/v1/Systems/1": map[string]interface{}{ "Manufacturer": "Inspur", "Model": "NF5688M7", "SerialNumber": "23E100051", }, "/redfish/v1/Chassis": map[string]interface{}{ "Members": []interface{}{map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"}}, }, "/redfish/v1/Chassis/1": map[string]interface{}{"Id": "1"}, "/redfish/v1/Managers": map[string]interface{}{ "Members": []interface{}{map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"}}, }, "/redfish/v1/Managers/1": map[string]interface{}{ "DateTime": "2026-02-28T04:18:18+08:00", "DateTimeLocalOffset": "+08:00", }, }, } got, err := ReplayRedfishFromRawPayloads(raw, nil) if err != nil { t.Fatalf("replay failed: %v", err) } if got.SourceTimezone != "+08:00" { t.Fatalf("expected source_timezone +08:00, got %q", got.SourceTimezone) } wantCollectedAt := time.Date(2026, 2, 27, 20, 18, 18, 0, time.UTC) if !got.CollectedAt.Equal(wantCollectedAt) { t.Fatalf("expected collected_at %s, got %s", wantCollectedAt, got.CollectedAt) } if got.RawPayloads["source_timezone"] != "+08:00" { t.Fatalf("expected source_timezone in raw payloads, got %#v", got.RawPayloads["source_timezone"]) } } func TestReplayRedfishFromRawPayloads_ParsesInlineThresholdAndDiscreteSensors(t *testing.T) { raw := map[string]any{ "redfish_tree": map[string]interface{}{ "/redfish/v1": map[string]interface{}{ "Systems": map[string]interface{}{"@odata.id": "/redfish/v1/Systems"}, "Chassis": map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"}, "Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"}, }, "/redfish/v1/Systems": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"}, }, }, "/redfish/v1/Systems/1": map[string]interface{}{ "Id": "1", "Manufacturer": "Inspur", "Model": "NF5688M7", "SerialNumber": "23E100051", }, "/redfish/v1/Chassis": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"}, }, }, "/redfish/v1/Chassis/1": map[string]interface{}{ "Id": "1", }, "/redfish/v1/Managers": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"}, }, }, "/redfish/v1/Managers/1": map[string]interface{}{ "Id": "1", }, "/redfish/v1/Chassis/1/ThresholdSensors": map[string]interface{}{ "Sensors": []interface{}{ map[string]interface{}{ "Name": "Inlet_Temp", "Reading": 16, "ReadingUnits": "deg_c", "State": "Enabled", }, }, }, "/redfish/v1/Chassis/1/DiscreteSensors": map[string]interface{}{ "Sensors": []interface{}{ map[string]interface{}{ "Name": "PSU_Redundant", "State": "Disabled", }, }, }, }, } got, err := ReplayRedfishFromRawPayloads(raw, nil) if err != nil { t.Fatalf("replay failed: %v", err) } if len(got.Sensors) == 0 { t.Fatalf("expected sensors from inline ThresholdSensors") } foundSensor := false for _, s := range got.Sensors { if s.Name == "Inlet_Temp" { foundSensor = true break } } if !foundSensor { t.Fatalf("expected Inlet_Temp sensor in replay output") } foundEvent := false for _, ev := range got.Events { if ev.EventType == "Discrete Sensor Status" && ev.SensorName == "PSU_Redundant" { foundEvent = true break } } if !foundEvent { t.Fatalf("expected discrete sensor warning event from inline DiscreteSensors") } } func TestReplayRedfishFromRawPayloads_CollectsThermalAndPowerSensors(t *testing.T) { raw := map[string]any{ "redfish_tree": map[string]interface{}{ "/redfish/v1": map[string]interface{}{ "Systems": map[string]interface{}{"@odata.id": "/redfish/v1/Systems"}, "Chassis": map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"}, "Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"}, }, "/redfish/v1/Systems": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"}, }, }, "/redfish/v1/Systems/1": map[string]interface{}{ "Id": "1", "Manufacturer": "Inspur", "Model": "NF5688M7", "SerialNumber": "23E100051", }, "/redfish/v1/Chassis": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"}, }, }, "/redfish/v1/Chassis/1": map[string]interface{}{ "Id": "1", }, "/redfish/v1/Managers": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"}, }, }, "/redfish/v1/Managers/1": map[string]interface{}{ "Id": "1", }, "/redfish/v1/Chassis/1/Thermal": map[string]interface{}{ "Fans": []interface{}{ map[string]interface{}{ "Name": "FAN0_F_Speed", "Reading": 9279, "ReadingUnits": "RPM", "Status": map[string]interface{}{ "Health": "OK", "State": "Enabled", }, }, }, "Temperatures": []interface{}{ map[string]interface{}{ "Name": "CPU0_Temp", "ReadingCelsius": 44, "Status": map[string]interface{}{ "Health": "OK", "State": "Enabled", }, }, }, }, "/redfish/v1/Chassis/1/Power": map[string]interface{}{ "Oem": map[string]interface{}{ "Public": map[string]interface{}{ "TotalPower": 1836, "CurrentCPUPowerWatts": 304, "CurrentMemoryPowerWatts": 75, "CurrentFANPowerWatts": 180, }, }, "PowerControl": []interface{}{ map[string]interface{}{ "Name": "System Power Control 1", "PowerConsumedWatts": 1836, "Status": map[string]interface{}{ "Health": "OK", "State": "Enabled", }, }, }, "PowerSupplies": []interface{}{ map[string]interface{}{ "Name": "Power Supply 1", "PowerInputWatts": 180, "LastPowerOutputWatts": 155, "LineInputVoltage": 223.25, "Status": map[string]interface{}{ "Health": "OK", "State": "Enabled", }, }, }, }, }, } got, err := ReplayRedfishFromRawPayloads(raw, nil) if err != nil { t.Fatalf("replay failed: %v", err) } if len(got.Sensors) == 0 { t.Fatalf("expected non-empty sensors") } expected := map[string]bool{ "FAN0_F_Speed": false, "CPU0_Temp": false, "Total_Power": false, "System Power Control 1_Consumed": false, "Power Supply 1_InputPower": false, } for _, s := range got.Sensors { if _, ok := expected[s.Name]; ok { expected[s.Name] = true } } for name, found := range expected { if !found { t.Fatalf("expected sensor %q in replay output", name) } } } func TestEnrichNICFromPCIeFunctions(t *testing.T) { nic := parseNIC(map[string]interface{}{ "Id": "1", "Model": "MCX75310AAS-NEAT", "Manufacturer": "Supermicro", "SerialNumber": "NIC-SN-1", "Controllers": []interface{}{ map[string]interface{}{ "Links": map[string]interface{}{ "PCIeDevices": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/NIC1"}, }, }, "Location": map[string]interface{}{ "PartLocation": map[string]interface{}{"ServiceLabel": "PCIe Slot 1 (1)"}, }, }, }, }) pcieDoc := map[string]interface{}{ "Id": "NIC1", "PCIeFunctions": map[string]interface{}{ "@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/NIC1/PCIeFunctions", }, } functionDocs := []map[string]interface{}{ { "VendorId": "0x15b3", "DeviceId": "0x1021", }, } enrichNICFromPCIe(&nic, pcieDoc, functionDocs) if nic.VendorID != 0x15b3 || nic.DeviceID != 0x1021 { t.Fatalf("unexpected NIC IDs: vendor=%#x device=%#x", nic.VendorID, nic.DeviceID) } if nic.Location != "PCIe Slot 1 (1)" { t.Fatalf("unexpected NIC location: %q", nic.Location) } } func TestParseNIC_PortCountFromControllerCapabilities(t *testing.T) { nic := parseNIC(map[string]interface{}{ "Id": "1", "Controllers": []interface{}{ map[string]interface{}{ "ControllerCapabilities": map[string]interface{}{ "NetworkPortCount": 2, }, }, }, }) if nic.PortCount != 2 { t.Fatalf("expected port_count=2, got %d", nic.PortCount) } } func TestParseNIC_DropsUnrealisticPortCount(t *testing.T) { nic := parseNIC(map[string]interface{}{ "Id": "1", "Controllers": []interface{}{ map[string]interface{}{ "ControllerCapabilities": map[string]interface{}{ "NetworkPortCount": 825307750, }, }, }, }) if nic.PortCount != 0 { t.Fatalf("expected unrealistic port count to be dropped, got %d", nic.PortCount) } } func TestParsePCIeDevice_PrefersFunctionClassOverDeviceType(t *testing.T) { doc := map[string]interface{}{ "Id": "NIC1", "DeviceType": "SingleFunction", "Model": "MCX75310AAS-NEAT", "PartNumber": "MCX75310AAS-NEAT", } functionDocs := []map[string]interface{}{ { "DeviceClass": "NetworkController", "VendorId": "0x15b3", "DeviceId": "0x1021", }, } got := parsePCIeDevice(doc, functionDocs) if got.DeviceClass == "SingleFunction" { t.Fatalf("device class should not keep generic redfish DeviceType") } if got.DeviceClass == "" { t.Fatalf("device class should be resolved") } } 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 TestRecoverCriticalRedfishDocsPlanB_RetriesMembersFromSystemMemoryCollection(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 systemPath = "/redfish/v1/Systems/1" const memoryPath = "/redfish/v1/Systems/1/Memory" const dimmPath = "/redfish/v1/Systems/1/Memory/CPU1_C1D1" mux := http.NewServeMux() mux.HandleFunc(dimmPath, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]interface{}{ "Id": "CPU1_C1D1", "Name": "CPU1_C1D1", "DeviceLocator": "CPU1_C1D1", "CapacityMiB": 65536, "MemoryDeviceType": "DDR5", "Status": map[string]interface{}{"State": "Enabled", "Health": "OK"}, "SerialNumber": "DIMM-SN-001", "PartNumber": "DIMM-PN-001", }) }) ts := httptest.NewServer(mux) defer ts.Close() rawTree := map[string]interface{}{ memoryPath: map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": dimmPath}, }, }, } fetchErrs := map[string]string{ dimmPath: `Get "https://example/redfish/v1/Systems/1/Memory/CPU1_C1D1": context deadline exceeded (Client.Timeout exceeded while awaiting headers)`, } criticalPaths := redfishCriticalEndpoints([]string{systemPath}, nil, nil) hasMemoryPath := false for _, p := range criticalPaths { if p == memoryPath { hasMemoryPath = true break } } if !hasMemoryPath { t.Fatalf("expected critical endpoints to include %s", memoryPath) } c := NewRedfishConnector() recovered := c.recoverCriticalRedfishDocsPlanB( context.Background(), ts.Client(), Request{}, ts.URL, criticalPaths, rawTree, fetchErrs, nil, ) if recovered == 0 { t.Fatalf("expected plan-B to recover at least one DIMM document") } if _, ok := rawTree[dimmPath]; !ok { t.Fatalf("expected recovered DIMM doc for %s", dimmPath) } if _, ok := fetchErrs[dimmPath]; ok { t.Fatalf("expected DIMM fetch error for %s to be cleared", dimmPath) } } func TestReplayCollectStorage_ProbesSupermicroNVMeDiskBayWhenCollectionEmpty(t *testing.T) { r := redfishSnapshotReader{tree: map[string]interface{}{ "/redfish/v1/Systems": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"}, }, }, "/redfish/v1/Systems/1/Storage": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Storage/NVMeSSD"}, }, }, "/redfish/v1/Systems/1/Storage/NVMeSSD": map[string]interface{}{ "Drives": map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Storage/NVMeSSD/Drives"}, }, "/redfish/v1/Systems/1/Storage/NVMeSSD/Drives": map[string]interface{}{ "Members": []interface{}{}, }, "/redfish/v1/Chassis": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane"}, }, }, "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane": map[string]interface{}{ "Drives": map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane/Drives"}, }, "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane/Drives": map[string]interface{}{ "Members@odata.count": 0, "Members": []interface{}{}, }, "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane/Drives/Disk.Bay.0": map[string]interface{}{ "Id": "Disk.Bay.0", "Name": "Disk.Bay.0", "Manufacturer": "INTEL", "SerialNumber": "BTLJ035203XT1P0FGN", "Model": "INTEL SSDPE2KX010T8", "CapacityBytes": int64(1000204886016), "Protocol": "NVMe", "MediaType": "SSD", "Status": map[string]interface{}{"State": "Enabled", "Health": "OK"}, }, }} got := r.collectStorage("/redfish/v1/Systems/1") if len(got) != 1 { t.Fatalf("expected one drive from direct Disk.Bay probe, got %d", len(got)) } if got[0].SerialNumber != "BTLJ035203XT1P0FGN" { t.Fatalf("unexpected serial: %q", got[0].SerialNumber) } if got[0].SizeGB == 0 { t.Fatalf("expected size to be parsed from CapacityBytes") } } func TestReplayCollectGPUs_DoesNotCollapseOnPlaceholderSerialAndSkipsNIC(t *testing.T) { r := redfishSnapshotReader{tree: map[string]interface{}{ "/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/3"}, map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/9"}, map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/7"}, }, }, "/redfish/v1/Chassis/1/PCIeDevices/3": map[string]interface{}{ "Id": "3", "Name": "PCIeCard3", "Model": "H200-SXM5-141G", "Manufacturer": "NVIDIA", "SerialNumber": "N/A", "Oem": map[string]interface{}{ "Public": map[string]interface{}{ "DeviceClass": "DisplayController", }, }, }, "/redfish/v1/Chassis/1/PCIeDevices/9": map[string]interface{}{ "Id": "9", "Name": "PCIeCard9", "Model": "H200-SXM5-141G", "Manufacturer": "NVIDIA", "SerialNumber": "N/A", "Oem": map[string]interface{}{ "Public": map[string]interface{}{ "DeviceClass": "DisplayController", }, }, }, "/redfish/v1/Chassis/1/PCIeDevices/7": map[string]interface{}{ "Id": "7", "Name": "PCIeCard7", "Model": "MCX631102AN-ADAT", "Manufacturer": "NVIDIA", "SerialNumber": "MT2538J00CZE", "Oem": map[string]interface{}{ "Public": map[string]interface{}{ "DeviceClass": "NetworkController", }, }, }, }} got := r.collectGPUs(nil, []string{"/redfish/v1/Chassis/1"}) if len(got) != 2 { t.Fatalf("expected 2 GPUs (two H200 cards), got %d", len(got)) } for _, gpu := range got { if gpu.Model == "MCX631102AN-ADAT" { t.Fatalf("network adapter should not be classified as GPU") } } } func TestParseBoardInfo_NormalizesNullPlaceholders(t *testing.T) { got := parseBoardInfo(map[string]interface{}{ "Manufacturer": "NULL", "Model": "NULL", "SerialNumber": "23E100051", "PartNumber": "0 ", "UUID": "fa403f6f-2ee9-11f0-bab9-346f1104085a", }) if got.Manufacturer != "" { t.Fatalf("expected empty manufacturer, got %q", got.Manufacturer) } if got.ProductName != "" { t.Fatalf("expected empty product name, got %q", got.ProductName) } if got.PartNumber != "" { t.Fatalf("expected empty part number, got %q", got.PartNumber) } if got.SerialNumber != "23E100051" { t.Fatalf("unexpected serial number: %q", got.SerialNumber) } } func TestShouldCrawlPath_SkipsJsonSchemas(t *testing.T) { if shouldCrawlPath("/redfish/v1/JsonSchemas") { t.Fatalf("expected /JsonSchemas to be skipped") } if shouldCrawlPath("/redfish/v1/JsonSchemas/ComputerSystem.v1_8_0") { t.Fatalf("expected JsonSchemas members to be skipped") } if !shouldCrawlPath("/redfish/v1/Systems/1") { t.Fatalf("expected normal hardware path to be crawled") } } func TestReplayCollectGPUs_FromGraphicsControllers(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": "GPU0", "Model": "H200-SXM5-141G", "Manufacturer": "NVIDIA", "SerialNumber": "1654225094493", "Status": map[string]interface{}{"State": "Enabled", "Health": "OK"}, }, "/redfish/v1/Systems/1/GraphicsControllers/GPU1": map[string]interface{}{ "Id": "GPU1", "Name": "GPU1", "Model": "H200-SXM5-141G", "Manufacturer": "NVIDIA", "SerialNumber": "1654425002635", "Status": map[string]interface{}{"State": "Enabled", "Health": "OK"}, }, }} got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil) if len(got) != 2 { t.Fatalf("expected 2 GPUs from GraphicsControllers, got %d", len(got)) } if got[0].SerialNumber == "" || got[1].SerialNumber == "" { t.Fatalf("expected GPU serial numbers from GraphicsControllers") } } 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", "Model": "NULL", "SerialNumber": "23E100051", "PartNumber": "0", } chassis := map[string]interface{}{ "Manufacturer": "NULL", "Model": "NULL", } fru := map[string]interface{}{ "FRUInfo": map[string]interface{}{ "Board": map[string]interface{}{ "Manufacturer": "Kaytus", "ProductName": "KR4268X2", }, }, } got := parseBoardInfoWithFallback(system, chassis, fru) if got.ProductName != "KR4268X2" { t.Fatalf("expected product from FRU, got %q", got.ProductName) } if got.Manufacturer != "Kaytus" { t.Fatalf("expected manufacturer from FRU, got %q", got.Manufacturer) } if got.SerialNumber != "23E100051" { t.Fatalf("expected serial from system, got %q", got.SerialNumber) } } func TestReplayRedfishFromRawPayloads_AddsMissingServerModelWarning(t *testing.T) { raw := map[string]any{ "redfish_tree": map[string]interface{}{ "/redfish/v1": map[string]interface{}{ "Systems": map[string]interface{}{"@odata.id": "/redfish/v1/Systems"}, "Chassis": map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"}, "Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"}, }, "/redfish/v1/Systems": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"}, }, }, "/redfish/v1/Systems/1": map[string]interface{}{ "Manufacturer": "NULL", "Model": "NULL", "SerialNumber": "23E100051", }, "/redfish/v1/Chassis": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"}, }, }, "/redfish/v1/Chassis/1": map[string]interface{}{ "Id": "1", "Manufacturer": "NULL", "Model": "NULL", }, "/redfish/v1/Managers": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"}, }, }, "/redfish/v1/Managers/1": map[string]interface{}{ "Id": "1", }, }, "redfish_fetch_errors": []map[string]interface{}{ {"path": "/redfish/v1/Systems/1/Oem/Public/FRU", "error": "status 500"}, }, } got, err := ReplayRedfishFromRawPayloads(raw, nil) if err != nil { t.Fatalf("replay failed: %v", err) } if got.Hardware == nil { t.Fatalf("expected hardware") } if got.Hardware.BoardInfo.ProductName != "" { t.Fatalf("expected empty model for warning test, got %q", got.Hardware.BoardInfo.ProductName) } found := false for _, ev := range got.Events { if ev.Source == "Redfish" && ev.EventType == "Collection Warning" { found = true break } } if !found { t.Fatalf("expected collection warning event about missing server model") } } func TestReplayRedfishFromRawPayloads_AddsDriveFetchWarning(t *testing.T) { raw := map[string]any{ "redfish_tree": map[string]interface{}{ "/redfish/v1": map[string]interface{}{ "Systems": map[string]interface{}{"@odata.id": "/redfish/v1/Systems"}, "Chassis": map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"}, "Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"}, }, "/redfish/v1/Systems": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"}, }, }, "/redfish/v1/Systems/1": map[string]interface{}{ "Manufacturer": "Inspur", "Model": "NF5688M7", "SerialNumber": "23E100051", }, "/redfish/v1/Chassis": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"}, }, }, "/redfish/v1/Chassis/1": map[string]interface{}{ "Id": "1", "Manufacturer": "Inspur", "Model": "NF5688M7", }, "/redfish/v1/Managers": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"}, }, }, "/redfish/v1/Managers/1": map[string]interface{}{ "Id": "1", }, }, "redfish_fetch_errors": []map[string]interface{}{ { "path": "/redfish/v1/Chassis/1/Drives/FP00HDD00", "error": `Get "...": context deadline exceeded (Client.Timeout exceeded while awaiting headers)`, }, }, } got, err := ReplayRedfishFromRawPayloads(raw, nil) if err != nil { t.Fatalf("replay failed: %v", err) } found := false for _, ev := range got.Events { if ev.Source == "Redfish" && ev.EventType == "Collection Warning" && strings.Contains(strings.ToLower(ev.Description), "drive documents were unavailable") { found = true break } } if !found { t.Fatalf("expected collection warning event for drive fetch errors") } } func TestReplayCollectGPUs_SkipsModelOnlyDuplicateFromGraphicsControllers(t *testing.T) { r := redfishSnapshotReader{tree: map[string]interface{}{ "/redfish/v1/Systems/1/PCIeDevices": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/PCIeDevices/3"}, map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/PCIeDevices/9"}, }, }, "/redfish/v1/Systems/1/PCIeDevices/3": map[string]interface{}{ "Id": "3", "Name": "PCIeCard3", "Model": "H200-SXM5-141G", "Manufacturer": "NVIDIA", "SerialNumber": "1654225094493", }, "/redfish/v1/Systems/1/PCIeDevices/9": map[string]interface{}{ "Id": "9", "Name": "PCIeCard9", "Model": "H200-SXM5-141G", "Manufacturer": "NVIDIA", "SerialNumber": "1654425002635", }, "/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU0"}, }, }, "/redfish/v1/Systems/1/GraphicsControllers/GPU0": map[string]interface{}{ "Id": "GPU0", "Name": "H200-SXM5-141G", "Model": "H200-SXM5-141G", "Manufacturer": "NVIDIA", "SerialNumber": "N/A", }, }} got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil) if len(got) != 2 { t.Fatalf("expected 2 GPUs without generic duplicate, got %d", len(got)) } for _, gpu := range got { if gpu.Slot == "H200-SXM5-141G" { t.Fatalf("unexpected model-only duplicate GPU row") } } } func TestApplyBoardInfoFallbackFromDocs_SkipsComponentProductNames(t *testing.T) { board := models.BoardInfo{ SerialNumber: "23E100051", } docs := []map[string]interface{}{ { "Model": "DDR5 DIMM", "Manufacturer": "DELTA", "SerialNumber": "802C1A2507D284B001", }, { "PlatformId": "NF5688M7", "Manufacturer": "Inspur", "PartNumber": "YZMB-00001", }, } applyBoardInfoFallbackFromDocs(&board, docs) if board.ProductName != "NF5688M7" { t.Fatalf("expected server model from fallback docs, got %q", board.ProductName) } if board.Manufacturer != "Inspur" { t.Fatalf("expected manufacturer from server fallback doc, got %q", board.Manufacturer) } } func TestDedupeStorage_IgnoresPlaceholderSerial(t *testing.T) { in := []models.Storage{ {Slot: "OB01", Model: "N/A", SerialNumber: "N/A"}, {Slot: "OB02", Model: "N/A", SerialNumber: "N/A"}, {Slot: "OB03", Model: "N/A", SerialNumber: "N/A"}, {Slot: "OB04", Model: "N/A", SerialNumber: "N/A"}, } out := dedupeStorage(in) if len(out) != 4 { t.Fatalf("expected all placeholder-serial NVMe drives to be kept by slot key, got %d", len(out)) } } func TestDedupeStorage_MergesPlaceholderSlotsWithRichDrivesByOrder(t *testing.T) { in := []models.Storage{ {Slot: "PCIe8_RAID_Disk_1:0", Type: "SSD", Model: "SOLIDIGM SSDSC2K", SizeGB: 1787, SerialNumber: "S1", Present: true}, {Slot: "PCIe8_RAID_Disk_1:1", Type: "SSD", Model: "SOLIDIGM SSDSC2K", SizeGB: 1787, SerialNumber: "S2", Present: true}, {Slot: "PCIe8_RAID_Disk_1:2", Type: "SSD", Model: "SOLIDIGM SSDSC2K", SizeGB: 1787, SerialNumber: "S3", Present: true}, {Slot: "OB01", Type: "NVMe", Model: "N/A", SerialNumber: "N/A", Present: true}, {Slot: "OB02", Type: "NVMe", Model: "N/A", SerialNumber: "N/A", Present: true}, {Slot: "OB03", Type: "NVMe", Model: "N/A", SerialNumber: "N/A", Present: true}, {Slot: "OB04", Type: "NVMe", Model: "N/A", SerialNumber: "N/A", Present: true}, {Slot: "FP00HDD00", Type: "NVMe", Model: "INTEL SSDPE2KE032T8", SizeGB: 2980, SerialNumber: "N1", Present: true}, {Slot: "FP00HDD02", Type: "NVMe", Model: "INTEL SSDPE2KE032T8", SizeGB: 2980, SerialNumber: "N2", Present: true}, {Slot: "FP00HDD04", Type: "NVMe", Model: "INTEL SSDPE2KE032T8", SizeGB: 2980, SerialNumber: "N3", Present: true}, {Slot: "FP00HDD06", Type: "NVMe", Model: "INTEL SSDPE2KE032T8", SizeGB: 2980, SerialNumber: "N4", Present: true}, } out := dedupeStorage(in) if len(out) != 7 { t.Fatalf("expected 7 rows after placeholder merge, got %d", len(out)) } bySlot := make(map[string]models.Storage, len(out)) for _, d := range out { bySlot[d.Slot] = d if strings.HasPrefix(d.Slot, "FP00HDD") { t.Fatalf("expected FP donor slot %q to be absorbed by placeholder slot", d.Slot) } } if bySlot["OB01"].SerialNumber != "N1" || bySlot["OB02"].SerialNumber != "N2" || bySlot["OB03"].SerialNumber != "N3" || bySlot["OB04"].SerialNumber != "N4" { t.Fatalf("expected OB slots to be enriched in order, got OB01=%q OB02=%q OB03=%q OB04=%q", bySlot["OB01"].SerialNumber, bySlot["OB02"].SerialNumber, bySlot["OB03"].SerialNumber, bySlot["OB04"].SerialNumber) } if bySlot["OB01"].Model != "INTEL SSDPE2KE032T8" || bySlot["OB01"].SizeGB != 2980 { t.Fatalf("expected OB01 to inherit rich model/size, got model=%q size=%d", bySlot["OB01"].Model, bySlot["OB01"].SizeGB) } } func TestDedupeNetworkAdapters_MergesBySlotAndKeepsRicherData(t *testing.T) { in := []models.NetworkAdapter{ { Slot: "NIC-A", Model: "N/A", Vendor: "", Present: true, }, { Slot: "NIC-A", Model: "ConnectX-7", Vendor: "NVIDIA", SerialNumber: "NICSN001", Firmware: "28.41.2020", PortCount: 2, MACAddresses: []string{"00:11:22:33:44:55"}, Present: true, }, } out := dedupeNetworkAdapters(in) if len(out) != 1 { t.Fatalf("expected merged single NIC row, got %d", len(out)) } if out[0].SerialNumber != "NICSN001" || out[0].Model != "ConnectX-7" || out[0].Vendor != "NVIDIA" { t.Fatalf("expected richer NIC fields preserved, got %+v", out[0]) } } func TestDedupePCIeDevices_MergesByLooseKeyAndKeepsBDF(t *testing.T) { in := []models.PCIeDevice{ { Slot: "PCIe Slot 3", DeviceClass: "Network Controller", PartNumber: "MCX75310AAS-NEAT", }, { Slot: "PCIe Slot 3", DeviceClass: "Network Controller", PartNumber: "MCX75310AAS-NEAT", BDF: "0000:af:00.0", VendorID: 0x15b3, DeviceID: 0x1021, SerialNumber: "MT000123", }, } out := dedupePCIeDevices(in) if len(out) != 1 { t.Fatalf("expected merged single PCIe row, got %d", len(out)) } if out[0].BDF != "0000:af:00.0" || out[0].SerialNumber != "MT000123" || out[0].VendorID == 0 || out[0].DeviceID == 0 { t.Fatalf("expected richer PCIe fields preserved, got %+v", out[0]) } } func TestAppendPSU_MergesRicherDuplicate(t *testing.T) { var out []models.PSU seen := make(map[string]int) idx := 1 idx = appendPSU(&out, seen, models.PSU{ Slot: "PSU1", Model: "N/A", Present: true, }, idx) _ = appendPSU(&out, seen, models.PSU{ Slot: "PSU1", Model: "DLG2700BW54C31", SerialNumber: "DGPLV2515025L", WattageW: 2700, Firmware: "00.01.04", Present: true, }, idx) if len(out) != 1 { t.Fatalf("expected PSU duplicate merge, got %d rows", len(out)) } if out[0].SerialNumber != "DGPLV2515025L" || out[0].WattageW != 2700 || out[0].Model != "DLG2700BW54C31" { t.Fatalf("expected richer PSU fields preserved, got %+v", out[0]) } } func TestReplayCollectGPUs_DropsModelOnlyPlaceholderWhenConcreteDiscoveredLater(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"}, }, }, "/redfish/v1/Systems/1/GraphicsControllers/GPU0": map[string]interface{}{ "Id": "GPU0", "Name": "H200-SXM5-141G", "Model": "H200-SXM5-141G", }, "/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/4"}, }, }, "/redfish/v1/Chassis/1/PCIeDevices/4": map[string]interface{}{ "Id": "4", "Name": "PCIeCard4", "Model": "H200-SXM5-141G", "Manufacturer": "NVIDIA", "BDF": "0000:0f:00.0", }, }} got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"}) if len(got) != 1 { t.Fatalf("expected generic graphics placeholder to be dropped, got %d GPUs", len(got)) } if got[0].Slot != "PCIeCard4" { t.Fatalf("expected concrete PCIe GPU to remain, got slot=%q", got[0].Slot) } } 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 TestReplayCollectGPUs_MergesAmbiguousSameModelByOrder(t *testing.T) { tree := map[string]interface{}{ "/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{ "Members": []interface{}{}, }, "/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{ "Members": []interface{}{}, }, } pcieIDs := []int{4, 8, 12, 14, 20, 23, 26, 30} serials := []string{ "1654425002361", "1654425004310", "1654425004204", "1654225097289", "1654225095717", "1654425002114", "1654425002714", "1654425002991", } for i := 0; i < len(pcieIDs); i++ { gpuPath := fmt.Sprintf("/redfish/v1/Systems/1/GraphicsControllers/GPU%d", i+1) pciePath := fmt.Sprintf("/redfish/v1/Chassis/1/PCIeDevices/%d", pcieIDs[i]) tree["/redfish/v1/Systems/1/GraphicsControllers"].(map[string]interface{})["Members"] = append(tree["/redfish/v1/Systems/1/GraphicsControllers"].(map[string]interface{})["Members"].([]interface{}), map[string]interface{}{"@odata.id": gpuPath}) tree["/redfish/v1/Chassis/1/PCIeDevices"].(map[string]interface{})["Members"] = append(tree["/redfish/v1/Chassis/1/PCIeDevices"].(map[string]interface{})["Members"].([]interface{}), map[string]interface{}{"@odata.id": pciePath}) tree[gpuPath] = map[string]interface{}{ "Id": fmt.Sprintf("GPU%d", i+1), "Name": "H200-SXM5-141G", "Model": "H200-SXM5-141G", "Manufacturer": "NVIDIA", "SerialNumber": serials[i], } tree[pciePath] = map[string]interface{}{ "Id": fmt.Sprintf("%d", pcieIDs[i]), "Name": fmt.Sprintf("PCIeCard%d", pcieIDs[i]), "Model": "H200-SXM5-141G", "Manufacturer": "NVIDIA", "BDF": fmt.Sprintf("0000:%02x:00.0", i+1), } } r := redfishSnapshotReader{tree: tree} got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"}) if len(got) != len(pcieIDs) { t.Fatalf("expected %d merged GPUs, got %d", len(pcieIDs), len(got)) } bySlot := make(map[string]models.GPU, len(got)) for _, gpu := range got { bySlot[gpu.Slot] = gpu if strings.EqualFold(strings.TrimSpace(gpu.Slot), strings.TrimSpace(gpu.Model)) { t.Fatalf("expected model-only placeholder to be dropped, got slot=%q", gpu.Slot) } } for i, id := range pcieIDs { slot := fmt.Sprintf("PCIeCard%d", id) gpu, ok := bySlot[slot] if !ok { t.Fatalf("expected concrete slot %q in output", slot) } if gpu.SerialNumber != serials[i] { t.Fatalf("expected slot %s serial %s, got %s", slot, serials[i], gpu.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") } if shouldCrawlPath("/redfish/v1/Systems/1/Memory/CPU0_C0D0/Assembly") { t.Fatalf("expected DIMM assembly subresource to be skipped") } if shouldCrawlPath("/redfish/v1/Systems/1/Memory/CPU0_C0D0/MemoryMetrics") { t.Fatalf("expected DIMM metrics subresource to be skipped") } if shouldCrawlPath("/redfish/v1/Chassis/1/PCIeDevices/0/PCIeFunctions/1") { t.Fatalf("expected noisy chassis pciefunctions branch to be skipped") } } func TestIsRedfishMemoryMemberPath(t *testing.T) { cases := []struct { path string want bool }{ {path: "/redfish/v1/Systems/1/Memory", want: false}, {path: "/redfish/v1/Systems/1/Memory/CPU0_C0D0", want: true}, {path: "/redfish/v1/Systems/1/Memory/CPU0_C0D0/Assembly", want: false}, {path: "/redfish/v1/Systems/1/Memory/CPU0_C0D0/MemoryMetrics", want: false}, {path: "/redfish/v1/Chassis/1/Memory/CPU0_C0D0", want: false}, } for _, tc := range cases { got := isRedfishMemoryMemberPath(tc.path) if got != tc.want { t.Fatalf("isRedfishMemoryMemberPath(%q) = %v, want %v", tc.path, got, tc.want) } } } func TestRedfishSnapshotBranchKey(t *testing.T) { cases := []struct { path string want string }{ {path: "", want: ""}, {path: "/redfish/v1", want: ""}, {path: "/redfish/v1/Systems", want: "/redfish/v1/Systems"}, {path: "/redfish/v1/Systems/1", want: "/redfish/v1/Systems/1"}, {path: "/redfish/v1/Systems/1/Memory", want: "/redfish/v1/Systems/1/Memory"}, {path: "/redfish/v1/Systems/1/Memory/CPU0_C0D0", want: "/redfish/v1/Systems/1/Memory"}, {path: "/redfish/v1/Systems/1/PCIeDevices/GPU1", want: "/redfish/v1/Systems/1/PCIeDevices"}, {path: "/redfish/v1/Chassis/1/Sensors/1", want: "/redfish/v1/Chassis/1/Sensors"}, {path: "/redfish/v1/UpdateService/FirmwareInventory/BIOS", want: "/redfish/v1/UpdateService/FirmwareInventory"}, } for _, tc := range cases { got := redfishSnapshotBranchKey(tc.path) if got != tc.want { t.Fatalf("redfishSnapshotBranchKey(%q) = %q, want %q", tc.path, got, tc.want) } } } func TestShouldPostProbeCollectionPath(t *testing.T) { if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Sensors") { t.Fatalf("expected sensors collection to be skipped by default") } t.Setenv("LOGPILE_REDFISH_SENSOR_POSTPROBE", "1") if !shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Sensors") { t.Fatalf("expected sensors collection to be post-probed when enabled") } if !shouldPostProbeCollectionPath("/redfish/v1/Systems/1/Storage/RAID/Drives") { t.Fatalf("expected drives collection to be post-probed") } if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Boards/BOARD1") { t.Fatalf("expected board member resource to be skipped from post-probe") } if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Assembly/Oem/COMMONb/COMMONbAssembly/1") { t.Fatalf("expected assembly member resource to be skipped from post-probe") } } func TestShouldAdaptivePostProbeCollectionPath(t *testing.T) { withExplicitNamedMembers := map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/NIC-0-0"}, map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/NIC-0-1"}, }, } if shouldAdaptivePostProbeCollectionPath("/redfish/v1/Systems/1/EthernetInterfaces", withExplicitNamedMembers) { t.Fatalf("expected explicit non-numeric members to skip adaptive post-probe") } withNumericMembers := map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/1"}, map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/2"}, }, } if !shouldAdaptivePostProbeCollectionPath("/redfish/v1/Chassis/1/PCIeDevices", withNumericMembers) { t.Fatalf("expected numeric members to allow adaptive post-probe") } withoutMembers := map[string]interface{}{"Name": "Drives"} if !shouldAdaptivePostProbeCollectionPath("/redfish/v1/Chassis/1/Drives", withoutMembers) { t.Fatalf("expected missing members to allow adaptive post-probe") } } func TestShouldAdaptiveNVMeProbe(t *testing.T) { withMembers := map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/Drives/OB01"}, }, } if shouldAdaptiveNVMeProbe(withMembers) { t.Fatalf("expected drives collection with explicit members to skip NVMe probe") } withoutMembers := map[string]interface{}{"Name": "Drives"} if !shouldAdaptiveNVMeProbe(withoutMembers) { t.Fatalf("expected drives collection without members to allow NVMe probe") } } func TestRedfishAdaptivePrefetchTargets(t *testing.T) { candidates := []string{ "/redfish/v1/Systems/1/Memory", "/redfish/v1/Systems/1/Processors", "/redfish/v1/Systems/1/Storage", } rawTree := map[string]interface{}{ "/redfish/v1/Systems/1/Memory": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Memory/DIMM1"}, }, }, "/redfish/v1/Systems/1/Storage": map[string]interface{}{ "Members": []interface{}{ map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Storage/1"}, }, }, } fetchErrs := map[string]string{ "/redfish/v1/Systems/1/Memory/DIMM1": "Get \"https://bmc/redfish/v1/Systems/1/Memory/DIMM1\": context deadline exceeded", "/redfish/v1/Systems/1/Storage/1": "status 404 from /redfish/v1/Systems/1/Storage/1: not found", "/redfish/v1/Systems/1/Processors": "Get \"https://bmc/redfish/v1/Systems/1/Processors\": context deadline exceeded", "/redfish/v1/Systems/1/Storage/Volumes": "status 404 from /redfish/v1/Systems/1/Storage/Volumes: not found", } got := redfishAdaptivePrefetchTargets(candidates, rawTree, fetchErrs) joined := strings.Join(got, "\n") for _, wanted := range []string{ "/redfish/v1/Systems/1/Memory", "/redfish/v1/Systems/1/Processors", } { if !strings.Contains(joined, wanted) { t.Fatalf("expected adaptive prefetch target %q", wanted) } } if strings.Contains(joined, "/redfish/v1/Systems/1/Storage") { t.Fatalf("expected storage with only non-retryable missing members to be skipped") } } func TestRedfishSnapshotPrioritySeeds_DefaultSkipsNoisyBranches(t *testing.T) { seeds := redfishSnapshotPrioritySeeds( []string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"}, []string{"/redfish/v1/Managers/1"}, ) joined := strings.Join(seeds, "\n") for _, noisy := range []string{ "/redfish/v1/Fabrics", "/redfish/v1/Chassis/1/Backplanes", "/redfish/v1/Chassis/1/Boards", "/redfish/v1/Chassis/1/Sensors", "/redfish/v1/Managers/1/LogServices", } { if strings.Contains(joined, noisy) { t.Fatalf("unexpected noisy seed %q", noisy) } } for _, wanted := range []string{ "/redfish/v1/Systems/1/Memory", "/redfish/v1/Systems/1/PCIeDevices", "/redfish/v1/Chassis/1/Drives", "/redfish/v1/Chassis/1/NetworkAdapters", "/redfish/v1/Managers/1/NetworkProtocol", } { if !strings.Contains(joined, wanted) { t.Fatalf("expected seed %q", wanted) } } } func TestRedfishPrefetchTargets_FilterNoisyBranches(t *testing.T) { critical := []string{ "/redfish/v1/Systems/1", "/redfish/v1/Systems/1/Memory", "/redfish/v1/Systems/1/Oem/Public/FRU", "/redfish/v1/Chassis/1/Drives", "/redfish/v1/Chassis/1/Backplanes", "/redfish/v1/Chassis/1/Sensors", "/redfish/v1/Managers/1/LogServices", "/redfish/v1/Managers/1/NetworkProtocol", } got := redfishPrefetchTargets(critical) joined := strings.Join(got, "\n") for _, wanted := range []string{ "/redfish/v1/Systems/1", "/redfish/v1/Systems/1/Memory", "/redfish/v1/Systems/1/Oem/Public/FRU", "/redfish/v1/Chassis/1/Drives", "/redfish/v1/Managers/1/NetworkProtocol", } { if !strings.Contains(joined, wanted) { t.Fatalf("expected prefetch target %q", wanted) } } for _, noisy := range []string{ "/redfish/v1/Chassis/1/Backplanes", "/redfish/v1/Chassis/1/Sensors", "/redfish/v1/Managers/1/LogServices", } { if strings.Contains(joined, noisy) { t.Fatalf("unexpected noisy prefetch target %q", noisy) } } }