collector/redfish: improve GPU SN/model fallback and warnings
This commit is contained in:
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user