collector/redfish: improve GPU SN/model fallback and warnings
This commit is contained in:
@@ -421,3 +421,226 @@ func TestReplayCollectStorage_ProbesSupermicroNVMeDiskBayWhenCollectionEmpty(t *
|
||||
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 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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user