package collector import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" ) 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 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 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 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") } }