package collector import ( "strings" "git.mchus.pro/mchus/logpile/internal/models" ) func (r redfishSnapshotReader) collectBoardFallbackDocs(systemPaths, chassisPaths []string) []map[string]interface{} { out := make([]map[string]interface{}, 0) for _, chassisPath := range chassisPaths { for _, suffix := range []string{"/Boards", "/Backplanes"} { path := joinPath(chassisPath, suffix) if docs, err := r.getCollectionMembers(path); err == nil && len(docs) > 0 { out = append(out, docs...) continue } if doc, err := r.getJSON(path); err == nil && len(doc) > 0 { out = append(out, doc) } } } for _, path := range append(append([]string{}, systemPaths...), chassisPaths...) { for _, suffix := range []string{"/Oem/Public", "/Oem/Public/ThermalConfig", "/ThermalConfig"} { docPath := joinPath(path, suffix) if doc, err := r.getJSON(docPath); err == nil && len(doc) > 0 { out = append(out, doc) } } } return out } func applyBoardInfoFallbackFromDocs(board *models.BoardInfo, docs []map[string]interface{}) { if board == nil || len(docs) == 0 { return } for _, doc := range docs { candidate := parseBoardInfoFromFRUDoc(doc) if !isLikelyServerProductName(candidate.ProductName) { continue } if board.Manufacturer == "" { board.Manufacturer = candidate.Manufacturer } if board.ProductName == "" { board.ProductName = candidate.ProductName } if board.SerialNumber == "" { board.SerialNumber = candidate.SerialNumber } if board.PartNumber == "" { board.PartNumber = candidate.PartNumber } if board.Manufacturer != "" && board.ProductName != "" && board.SerialNumber != "" && board.PartNumber != "" { return } } } func isLikelyServerProductName(v string) bool { v = strings.TrimSpace(v) if v == "" { return false } n := strings.ToUpper(v) if strings.Contains(n, "NULL") { return false } componentTokens := []string{ "DIMM", "DDR", "NVME", "SSD", "HDD", "GPU", "NIC", "RAID", "PSU", "FAN", "BACKPLANE", "FRU", } for _, token := range componentTokens { if strings.Contains(n, strings.ToUpper(token)) { return false } } return true } // collectAssemblyFRU reads Chassis/*/Assembly documents and returns FRU entries // for subcomponents (backplanes, PSUs, DIMMs, etc.) that carry meaningful // serial or part numbers. Entries already present in dedicated collections // (PSUs, DIMMs) are included here as well so that all FRU data is available // in one place; deduplication by serial is performed. func (r redfishSnapshotReader) collectAssemblyFRU(chassisPaths []string) []models.FRUInfo { seen := make(map[string]struct{}) var out []models.FRUInfo add := func(fru models.FRUInfo) { key := strings.ToUpper(strings.TrimSpace(fru.SerialNumber)) if key == "" { key = strings.ToUpper(strings.TrimSpace(fru.Description + "|" + fru.PartNumber)) } if key == "" || key == "|" { return } if _, ok := seen[key]; ok { return } seen[key] = struct{}{} out = append(out, fru) } for _, chassisPath := range chassisPaths { doc, err := r.getJSON(joinPath(chassisPath, "/Assembly")) if err != nil || len(doc) == 0 { continue } assemblies, _ := doc["Assemblies"].([]interface{}) for _, aAny := range assemblies { a, ok := aAny.(map[string]interface{}) if !ok { continue } name := strings.TrimSpace(firstNonEmpty(asString(a["Name"]), asString(a["Description"]))) model := strings.TrimSpace(asString(a["Model"])) partNumber := strings.TrimSpace(asString(a["PartNumber"])) serial := extractAssemblySerial(a) if serial == "" && partNumber == "" { continue } add(models.FRUInfo{ Description: name, ProductName: model, SerialNumber: serial, PartNumber: partNumber, }) } } return out } // extractAssemblySerial tries to find a serial number in an Assembly entry. // Standard Redfish Assembly has no top-level SerialNumber; vendors put it in Oem. func extractAssemblySerial(a map[string]interface{}) string { if s := strings.TrimSpace(asString(a["SerialNumber"])); s != "" { return s } oem, _ := a["Oem"].(map[string]interface{}) for _, v := range oem { subtree, ok := v.(map[string]interface{}) if !ok { continue } for _, v2 := range subtree { node, ok := v2.(map[string]interface{}) if !ok { continue } if s := strings.TrimSpace(asString(node["SerialNumber"])); s != "" { return s } } } return "" }