Implement the full architectural plan: unified ingest.Service entry point for archive and Redfish payloads, modular redfishprofile package with composable profiles (generic, ami-family, msi, supermicro, dell, hgx-topology), score-based profile matching with fallback expansion mode, and profile-driven acquisition/analysis plans. Vendor-specific logic moved out of common executors and into profile hooks. GPU chassis lookup strategies and known storage recovery collections (IntelVROC/HA-RAID/MRVL) now live in ResolvedAnalysisPlan, populated by profiles at analysis time. Replay helpers read from the plan; no hardcoded path lists remain in generic code. Also splits redfish_replay.go into domain modules (gpu, storage, inventory, fru, profiles) and adds full fixture/matcher/directive test coverage including Dell, AMI, unknown-vendor fallback, and deterministic ordering. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
99 lines
2.6 KiB
Go
99 lines
2.6 KiB
Go
package redfishprofile
|
|
|
|
import "strings"
|
|
|
|
func CollectSignals(serviceRootDoc, systemDoc, chassisDoc, managerDoc map[string]interface{}, resourceHints []string) MatchSignals {
|
|
signals := MatchSignals{
|
|
ServiceRootVendor: lookupString(serviceRootDoc, "Vendor"),
|
|
ServiceRootProduct: lookupString(serviceRootDoc, "Product"),
|
|
SystemManufacturer: lookupString(systemDoc, "Manufacturer"),
|
|
SystemModel: lookupString(systemDoc, "Model"),
|
|
SystemSKU: lookupString(systemDoc, "SKU"),
|
|
ChassisManufacturer: lookupString(chassisDoc, "Manufacturer"),
|
|
ChassisModel: lookupString(chassisDoc, "Model"),
|
|
ManagerManufacturer: lookupString(managerDoc, "Manufacturer"),
|
|
ResourceHints: resourceHints,
|
|
}
|
|
signals.OEMNamespaces = dedupeSorted(append(
|
|
oemNamespaces(serviceRootDoc),
|
|
append(oemNamespaces(systemDoc), append(oemNamespaces(chassisDoc), oemNamespaces(managerDoc)...)...)...,
|
|
))
|
|
return normalizeSignals(signals)
|
|
}
|
|
|
|
func CollectSignalsFromTree(tree map[string]interface{}) MatchSignals {
|
|
getDoc := func(path string) map[string]interface{} {
|
|
if v, ok := tree[path]; ok {
|
|
if doc, ok := v.(map[string]interface{}); ok {
|
|
return doc
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
memberPath := func(collectionPath, fallbackPath string) string {
|
|
collection := getDoc(collectionPath)
|
|
if len(collection) != 0 {
|
|
if members, ok := collection["Members"].([]interface{}); ok && len(members) > 0 {
|
|
if ref, ok := members[0].(map[string]interface{}); ok {
|
|
if path := lookupString(ref, "@odata.id"); path != "" {
|
|
return path
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return fallbackPath
|
|
}
|
|
|
|
systemPath := memberPath("/redfish/v1/Systems", "/redfish/v1/Systems/1")
|
|
chassisPath := memberPath("/redfish/v1/Chassis", "/redfish/v1/Chassis/1")
|
|
managerPath := memberPath("/redfish/v1/Managers", "/redfish/v1/Managers/1")
|
|
|
|
resourceHints := make([]string, 0, len(tree))
|
|
for path := range tree {
|
|
path = strings.TrimSpace(path)
|
|
if path == "" {
|
|
continue
|
|
}
|
|
resourceHints = append(resourceHints, path)
|
|
}
|
|
|
|
return CollectSignals(
|
|
getDoc("/redfish/v1"),
|
|
getDoc(systemPath),
|
|
getDoc(chassisPath),
|
|
getDoc(managerPath),
|
|
resourceHints,
|
|
)
|
|
}
|
|
|
|
func lookupString(doc map[string]interface{}, key string) string {
|
|
if len(doc) == 0 {
|
|
return ""
|
|
}
|
|
value, _ := doc[key]
|
|
if s, ok := value.(string); ok {
|
|
return strings.TrimSpace(s)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func oemNamespaces(doc map[string]interface{}) []string {
|
|
if len(doc) == 0 {
|
|
return nil
|
|
}
|
|
oem, ok := doc["Oem"].(map[string]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
out := make([]string, 0, len(oem))
|
|
for key := range oem {
|
|
key = strings.TrimSpace(key)
|
|
if key == "" {
|
|
continue
|
|
}
|
|
out = append(out, key)
|
|
}
|
|
return out
|
|
}
|