collector/redfish: improve GPU SN/model fallback and warnings

This commit is contained in:
2026-02-28 12:52:22 +03:00
parent ddab93a5ee
commit 9aadf2f1e9
3 changed files with 436 additions and 11 deletions

View File

@@ -416,13 +416,15 @@ func redfishLinkedPath(doc map[string]interface{}, key string) string {
}
func (c *RedfishConnector) collectGPUs(ctx context.Context, client *http.Client, req Request, baseURL string, systemPaths, chassisPaths []string) []models.GPU {
collections := make([]string, 0, len(systemPaths)*2+len(chassisPaths))
collections := make([]string, 0, len(systemPaths)*3+len(chassisPaths)*2)
for _, systemPath := range systemPaths {
collections = append(collections, joinPath(systemPath, "/PCIeDevices"))
collections = append(collections, joinPath(systemPath, "/Accelerators"))
collections = append(collections, joinPath(systemPath, "/GraphicsControllers"))
}
for _, chassisPath := range chassisPaths {
collections = append(collections, joinPath(chassisPath, "/PCIeDevices"))
collections = append(collections, joinPath(chassisPath, "/Accelerators"))
}
var out []models.GPU
@@ -443,7 +445,7 @@ func (c *RedfishConnector) collectGPUs(ctx context.Context, client *http.Client,
gpu := parseGPU(doc, functionDocs, idx)
idx++
key := firstNonEmpty(gpu.SerialNumber, gpu.BDF, gpu.Slot+"|"+gpu.Model)
key := gpuDedupKey(gpu)
if key == "" {
continue
}
@@ -955,21 +957,26 @@ func redfishCriticalEndpoints(systemPaths, chassisPaths, managerPaths []string)
add(p)
add(joinPath(p, "/Bios"))
add(joinPath(p, "/SecureBoot"))
add(joinPath(p, "/Oem/Public/FRU"))
add(joinPath(p, "/Processors"))
add(joinPath(p, "/Memory"))
add(joinPath(p, "/Storage"))
add(joinPath(p, "/SimpleStorage"))
add(joinPath(p, "/PCIeDevices"))
add(joinPath(p, "/Accelerators"))
add(joinPath(p, "/GraphicsControllers"))
add(joinPath(p, "/EthernetInterfaces"))
add(joinPath(p, "/NetworkInterfaces"))
}
for _, p := range chassisPaths {
add(p)
add(joinPath(p, "/Oem/Public/FRU"))
add(joinPath(p, "/Power"))
add(joinPath(p, "/Thermal"))
add(joinPath(p, "/Sensors"))
add(joinPath(p, "/NetworkAdapters"))
add(joinPath(p, "/PCIeDevices"))
add(joinPath(p, "/Accelerators"))
add(joinPath(p, "/Drives"))
}
for _, p := range managerPaths {
@@ -1132,6 +1139,7 @@ func shouldCrawlPath(path string) bool {
return false
}
heavyParts := []string{
"/JsonSchemas",
"/LogServices/",
"/Entries/",
"/TelemetryService/",
@@ -1429,14 +1437,98 @@ func (c *RedfishConnector) recoverCriticalRedfishDocsPlanB(ctx context.Context,
func parseBoardInfo(system map[string]interface{}) models.BoardInfo {
return models.BoardInfo{
Manufacturer: asString(system["Manufacturer"]),
ProductName: firstNonEmpty(asString(system["Model"]), asString(system["Name"])),
SerialNumber: asString(system["SerialNumber"]),
PartNumber: asString(system["PartNumber"]),
UUID: asString(system["UUID"]),
Manufacturer: normalizeRedfishIdentityField(asString(system["Manufacturer"])),
ProductName: normalizeRedfishIdentityField(firstNonEmpty(
asString(system["Model"]),
asString(system["ProductName"]),
asString(system["Name"]),
)),
SerialNumber: normalizeRedfishIdentityField(asString(system["SerialNumber"])),
PartNumber: normalizeRedfishIdentityField(asString(system["PartNumber"])),
UUID: normalizeRedfishIdentityField(asString(system["UUID"])),
}
}
func parseBoardInfoWithFallback(system, chassis, fru map[string]interface{}) models.BoardInfo {
board := parseBoardInfo(system)
chassisBoard := parseBoardInfo(chassis)
fruBoard := parseBoardInfoFromFRUDoc(fru)
if board.Manufacturer == "" {
board.Manufacturer = firstNonEmpty(chassisBoard.Manufacturer, fruBoard.Manufacturer)
}
if board.ProductName == "" {
board.ProductName = firstNonEmpty(chassisBoard.ProductName, fruBoard.ProductName)
}
if board.SerialNumber == "" {
board.SerialNumber = firstNonEmpty(chassisBoard.SerialNumber, fruBoard.SerialNumber)
}
if board.PartNumber == "" {
board.PartNumber = firstNonEmpty(chassisBoard.PartNumber, fruBoard.PartNumber)
}
if board.UUID == "" {
board.UUID = chassisBoard.UUID
}
return board
}
func parseBoardInfoFromFRUDoc(doc map[string]interface{}) models.BoardInfo {
if len(doc) == 0 {
return models.BoardInfo{}
}
return models.BoardInfo{
Manufacturer: findFirstNormalizedStringByKeys(doc, "Manufacturer", "BoardManufacturer", "Vendor"),
ProductName: findFirstNormalizedStringByKeys(doc, "ProductName", "BoardName", "Model"),
SerialNumber: findFirstNormalizedStringByKeys(doc, "SerialNumber", "BoardSerialNumber"),
PartNumber: findFirstNormalizedStringByKeys(doc, "PartNumber", "BoardPartNumber", "ProductPartNumber"),
}
}
func findFirstNormalizedStringByKeys(doc map[string]interface{}, keys ...string) string {
if len(doc) == 0 || len(keys) == 0 {
return ""
}
keySet := make(map[string]struct{}, len(keys))
for _, key := range keys {
k := strings.ToLower(strings.TrimSpace(key))
if k != "" {
keySet[k] = struct{}{}
}
}
stack := []any{doc}
for len(stack) > 0 {
last := len(stack) - 1
node := stack[last]
stack = stack[:last]
switch v := node.(type) {
case map[string]interface{}:
for k, raw := range v {
if _, ok := keySet[strings.ToLower(strings.TrimSpace(k))]; ok {
if s, ok := raw.(string); ok {
if normalized := normalizeRedfishIdentityField(s); normalized != "" {
return normalized
}
}
}
switch nested := raw.(type) {
case map[string]interface{}, []interface{}:
stack = append(stack, nested)
}
}
case []interface{}:
for _, item := range v {
switch nested := item.(type) {
case map[string]interface{}, []interface{}:
stack = append(stack, nested)
}
}
}
}
return ""
}
func parseCPUs(docs []map[string]interface{}) []models.CPU {
cpus := make([]models.CPU, 0, len(docs))
for idx, doc := range docs {
@@ -1695,7 +1787,7 @@ func parseGPU(doc map[string]interface{}, functionDocs []map[string]interface{},
Location: firstNonEmpty(redfishLocationLabel(doc["Location"]), redfishLocationLabel(doc["PhysicalLocation"])),
Model: firstNonEmpty(asString(doc["Model"]), asString(doc["Name"])),
Manufacturer: asString(doc["Manufacturer"]),
SerialNumber: asString(doc["SerialNumber"]),
SerialNumber: strings.TrimSpace(asString(doc["SerialNumber"])),
PartNumber: asString(doc["PartNumber"]),
Firmware: asString(doc["FirmwareVersion"]),
Status: mapStatus(doc["Status"]),
@@ -1877,11 +1969,45 @@ func isGenericPCIeClassLabel(v string) bool {
}
}
func normalizeRedfishIdentityField(v string) string {
v = strings.TrimSpace(v)
if v == "" {
return ""
}
switch strings.ToLower(v) {
case "n/a", "na", "none", "null", "unknown", "0":
return ""
default:
return v
}
}
func gpuDedupKey(gpu models.GPU) string {
if serial := normalizeRedfishIdentityField(gpu.SerialNumber); serial != "" {
return serial
}
if bdf := strings.TrimSpace(gpu.BDF); bdf != "" {
return bdf
}
return firstNonEmpty(strings.TrimSpace(gpu.Slot)+"|"+strings.TrimSpace(gpu.Model), strings.TrimSpace(gpu.Slot))
}
func looksLikeGPU(doc map[string]interface{}, functionDocs []map[string]interface{}) bool {
deviceType := strings.ToLower(asString(doc["DeviceType"]))
if strings.Contains(deviceType, "gpu") || strings.Contains(deviceType, "graphics") || strings.Contains(deviceType, "accelerator") {
return true
}
if strings.Contains(deviceType, "network") {
return false
}
if oem, ok := doc["Oem"].(map[string]interface{}); ok {
if public, ok := oem["Public"].(map[string]interface{}); ok {
if dc := strings.ToLower(asString(public["DeviceClass"])); strings.Contains(dc, "network") {
return false
}
}
}
modelText := strings.ToLower(strings.Join([]string{
asString(doc["Name"]),
@@ -2378,6 +2504,7 @@ func redfishSnapshotPrioritySeeds(systemPaths, chassisPaths, managerPaths []stri
add(p)
add(joinPath(p, "/Bios"))
add(joinPath(p, "/SecureBoot"))
add(joinPath(p, "/Oem/Public/FRU"))
add(joinPath(p, "/Processors"))
add(joinPath(p, "/Memory"))
add(joinPath(p, "/EthernetInterfaces"))
@@ -2395,6 +2522,7 @@ func redfishSnapshotPrioritySeeds(systemPaths, chassisPaths, managerPaths []stri
}
for _, p := range chassisPaths {
add(p)
add(joinPath(p, "/Oem/Public/FRU"))
add(joinPath(p, "/Sensors"))
add(joinPath(p, "/Thermal"))
add(joinPath(p, "/EnvironmentMetrics"))