Files
logpile/internal/collector/redfishprofile/signals.go
Mikhail Chusavitin d650a6ba1c refactor: unified ingest pipeline + modular Redfish profile framework
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>
2026-03-18 08:48:58 +03:00

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
}