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>
164 lines
4.5 KiB
Go
164 lines
4.5 KiB
Go
package redfishprofile
|
|
|
|
import "strings"
|
|
|
|
func ResolveAcquisitionPlan(match MatchResult, plan AcquisitionPlan, discovered DiscoveredResources, signals MatchSignals) ResolvedAcquisitionPlan {
|
|
seedGroups := [][]string{
|
|
baselineSeedPaths(discovered),
|
|
expandScopedSuffixes(discovered.SystemPaths, plan.ScopedPaths.SystemSeedSuffixes),
|
|
expandScopedSuffixes(discovered.ChassisPaths, plan.ScopedPaths.ChassisSeedSuffixes),
|
|
expandScopedSuffixes(discovered.ManagerPaths, plan.ScopedPaths.ManagerSeedSuffixes),
|
|
plan.SeedPaths,
|
|
}
|
|
if plan.Mode == ModeFallback {
|
|
seedGroups = append(seedGroups, plan.PlanBPaths)
|
|
}
|
|
|
|
criticalGroups := [][]string{
|
|
baselineCriticalPaths(discovered),
|
|
expandScopedSuffixes(discovered.SystemPaths, plan.ScopedPaths.SystemCriticalSuffixes),
|
|
expandScopedSuffixes(discovered.ChassisPaths, plan.ScopedPaths.ChassisCriticalSuffixes),
|
|
expandScopedSuffixes(discovered.ManagerPaths, plan.ScopedPaths.ManagerCriticalSuffixes),
|
|
plan.CriticalPaths,
|
|
}
|
|
|
|
resolved := ResolvedAcquisitionPlan{
|
|
Plan: plan,
|
|
SeedPaths: mergeResolvedPaths(seedGroups...),
|
|
CriticalPaths: mergeResolvedPaths(criticalGroups...),
|
|
}
|
|
for _, profile := range match.Profiles {
|
|
profile.RefineAcquisitionPlan(&resolved, discovered, signals)
|
|
}
|
|
resolved.SeedPaths = mergeResolvedPaths(resolved.SeedPaths)
|
|
resolved.CriticalPaths = mergeResolvedPaths(resolved.CriticalPaths, resolved.Plan.CriticalPaths)
|
|
resolved.Plan.SeedPaths = mergeResolvedPaths(resolved.Plan.SeedPaths)
|
|
resolved.Plan.CriticalPaths = mergeResolvedPaths(resolved.Plan.CriticalPaths)
|
|
resolved.Plan.PlanBPaths = mergeResolvedPaths(resolved.Plan.PlanBPaths)
|
|
return resolved
|
|
}
|
|
|
|
func baselineSeedPaths(discovered DiscoveredResources) []string {
|
|
var out []string
|
|
add := func(p string) {
|
|
if p = normalizePath(p); p != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
|
|
add("/redfish/v1/UpdateService")
|
|
add("/redfish/v1/UpdateService/FirmwareInventory")
|
|
|
|
for _, p := range discovered.SystemPaths {
|
|
add(p)
|
|
add(joinPath(p, "/Bios"))
|
|
add(joinPath(p, "/SecureBoot"))
|
|
add(joinPath(p, "/Oem/Public"))
|
|
add(joinPath(p, "/Oem/Public/FRU"))
|
|
add(joinPath(p, "/Processors"))
|
|
add(joinPath(p, "/Memory"))
|
|
add(joinPath(p, "/EthernetInterfaces"))
|
|
add(joinPath(p, "/NetworkInterfaces"))
|
|
add(joinPath(p, "/PCIeDevices"))
|
|
add(joinPath(p, "/PCIeFunctions"))
|
|
add(joinPath(p, "/Accelerators"))
|
|
add(joinPath(p, "/GraphicsControllers"))
|
|
add(joinPath(p, "/Storage"))
|
|
}
|
|
for _, p := range discovered.ChassisPaths {
|
|
add(p)
|
|
add(joinPath(p, "/Oem/Public"))
|
|
add(joinPath(p, "/Oem/Public/FRU"))
|
|
add(joinPath(p, "/PCIeDevices"))
|
|
add(joinPath(p, "/PCIeSlots"))
|
|
add(joinPath(p, "/NetworkAdapters"))
|
|
add(joinPath(p, "/Drives"))
|
|
add(joinPath(p, "/Power"))
|
|
}
|
|
for _, p := range discovered.ManagerPaths {
|
|
add(p)
|
|
add(joinPath(p, "/EthernetInterfaces"))
|
|
add(joinPath(p, "/NetworkProtocol"))
|
|
}
|
|
return mergeResolvedPaths(out)
|
|
}
|
|
|
|
func baselineCriticalPaths(discovered DiscoveredResources) []string {
|
|
var out []string
|
|
for _, group := range [][]string{
|
|
{"/redfish/v1"},
|
|
discovered.SystemPaths,
|
|
discovered.ChassisPaths,
|
|
discovered.ManagerPaths,
|
|
} {
|
|
out = append(out, group...)
|
|
}
|
|
return mergeResolvedPaths(out)
|
|
}
|
|
|
|
func expandScopedSuffixes(basePaths, suffixes []string) []string {
|
|
if len(basePaths) == 0 || len(suffixes) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]string, 0, len(basePaths)*len(suffixes))
|
|
for _, basePath := range basePaths {
|
|
basePath = normalizePath(basePath)
|
|
if basePath == "" {
|
|
continue
|
|
}
|
|
for _, suffix := range suffixes {
|
|
suffix = strings.TrimSpace(suffix)
|
|
if suffix == "" {
|
|
continue
|
|
}
|
|
out = append(out, joinPath(basePath, suffix))
|
|
}
|
|
}
|
|
return mergeResolvedPaths(out)
|
|
}
|
|
|
|
func mergeResolvedPaths(groups ...[]string) []string {
|
|
seen := make(map[string]struct{})
|
|
out := make([]string, 0)
|
|
for _, group := range groups {
|
|
for _, path := range group {
|
|
path = normalizePath(path)
|
|
if path == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[path]; ok {
|
|
continue
|
|
}
|
|
seen[path] = struct{}{}
|
|
out = append(out, path)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func normalizePath(path string) string {
|
|
path = strings.TrimSpace(path)
|
|
if path == "" {
|
|
return ""
|
|
}
|
|
if !strings.HasPrefix(path, "/") {
|
|
path = "/" + path
|
|
}
|
|
return strings.TrimRight(path, "/")
|
|
}
|
|
|
|
func joinPath(base, rel string) string {
|
|
base = normalizePath(base)
|
|
rel = strings.TrimSpace(rel)
|
|
if base == "" {
|
|
return normalizePath(rel)
|
|
}
|
|
if rel == "" {
|
|
return base
|
|
}
|
|
if !strings.HasPrefix(rel, "/") {
|
|
rel = "/" + rel
|
|
}
|
|
return normalizePath(base + rel)
|
|
}
|