- Collect hardware event logs (last 7 days) from Systems and Managers/SEL LogServices - Parse AMI raw IPMI dump messages into readable descriptions (Sensor_Type: Event_Type) - Filter out audit/journal/non-hardware log services; only SEL from Managers - MSI ghost GPU filter: exclude processor GPU entries with temperature=0 when host is powered on - Reanimator collected_at uses InventoryData/Status.LastModifiedTime (30-day fallback) - Invalidate Redfish inventory CRC groups before host power-on - Log inventory LastModifiedTime age in collection logs - Drop SecureBoot collection (SecureBootMode, SecureBootDatabases) — not hardware inventory - Add build version to UI footer via template - Add MSI Redfish API reference doc to bible-local/docs/ ADL-032–ADL-035 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
163 lines
4.4 KiB
Go
163 lines
4.4 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, "/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)
|
|
}
|