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>
123 lines
3.6 KiB
Go
123 lines
3.6 KiB
Go
package redfishprofile
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/models"
|
|
)
|
|
|
|
const (
|
|
ModeMatched = "matched"
|
|
ModeFallback = "fallback"
|
|
)
|
|
|
|
func MatchProfiles(signals MatchSignals) MatchResult {
|
|
type scored struct {
|
|
profile Profile
|
|
score int
|
|
}
|
|
builtins := BuiltinProfiles()
|
|
candidates := make([]scored, 0, len(builtins))
|
|
allScores := make([]ProfileScore, 0, len(builtins))
|
|
for _, profile := range builtins {
|
|
score := profile.Match(signals)
|
|
allScores = append(allScores, ProfileScore{
|
|
Name: profile.Name(),
|
|
Score: score,
|
|
Priority: profile.Priority(),
|
|
})
|
|
if score <= 0 {
|
|
continue
|
|
}
|
|
candidates = append(candidates, scored{profile: profile, score: score})
|
|
}
|
|
sort.Slice(allScores, func(i, j int) bool {
|
|
if allScores[i].Score == allScores[j].Score {
|
|
if allScores[i].Priority == allScores[j].Priority {
|
|
return allScores[i].Name < allScores[j].Name
|
|
}
|
|
return allScores[i].Priority < allScores[j].Priority
|
|
}
|
|
return allScores[i].Score > allScores[j].Score
|
|
})
|
|
sort.Slice(candidates, func(i, j int) bool {
|
|
if candidates[i].score == candidates[j].score {
|
|
return candidates[i].profile.Priority() < candidates[j].profile.Priority()
|
|
}
|
|
return candidates[i].score > candidates[j].score
|
|
})
|
|
if len(candidates) == 0 || candidates[0].score < 60 {
|
|
profiles := make([]Profile, 0, len(builtins))
|
|
active := make(map[string]struct{}, len(builtins))
|
|
for _, profile := range builtins {
|
|
if profile.SafeForFallback() {
|
|
profiles = append(profiles, profile)
|
|
active[profile.Name()] = struct{}{}
|
|
}
|
|
}
|
|
sortProfiles(profiles)
|
|
for i := range allScores {
|
|
_, ok := active[allScores[i].Name]
|
|
allScores[i].Active = ok
|
|
}
|
|
return MatchResult{Mode: ModeFallback, Profiles: profiles, Scores: allScores}
|
|
}
|
|
profiles := make([]Profile, 0, len(candidates))
|
|
seen := make(map[string]struct{}, len(candidates))
|
|
for _, candidate := range candidates {
|
|
name := candidate.profile.Name()
|
|
if _, ok := seen[name]; ok {
|
|
continue
|
|
}
|
|
seen[name] = struct{}{}
|
|
profiles = append(profiles, candidate.profile)
|
|
}
|
|
sortProfiles(profiles)
|
|
for i := range allScores {
|
|
_, ok := seen[allScores[i].Name]
|
|
allScores[i].Active = ok
|
|
}
|
|
return MatchResult{Mode: ModeMatched, Profiles: profiles, Scores: allScores}
|
|
}
|
|
|
|
func BuildAcquisitionPlan(signals MatchSignals) AcquisitionPlan {
|
|
match := MatchProfiles(signals)
|
|
plan := AcquisitionPlan{Mode: match.Mode}
|
|
for _, profile := range match.Profiles {
|
|
plan.Profiles = append(plan.Profiles, profile.Name())
|
|
profile.ExtendAcquisitionPlan(&plan, signals)
|
|
}
|
|
plan.Profiles = dedupeSorted(plan.Profiles)
|
|
plan.SeedPaths = dedupeSorted(plan.SeedPaths)
|
|
plan.CriticalPaths = dedupeSorted(plan.CriticalPaths)
|
|
plan.PlanBPaths = dedupeSorted(plan.PlanBPaths)
|
|
plan.Notes = dedupeSorted(plan.Notes)
|
|
if plan.Mode == ModeFallback {
|
|
ensureSnapshotMaxDocuments(&plan, 180000)
|
|
ensurePrefetchEnabled(&plan, true)
|
|
addPlanNote(&plan, "fallback acquisition expands safe profile probes")
|
|
}
|
|
return plan
|
|
}
|
|
|
|
func ApplyAnalysisProfiles(result *models.AnalysisResult, snapshot map[string]interface{}, signals MatchSignals) MatchResult {
|
|
match := MatchProfiles(signals)
|
|
for _, profile := range match.Profiles {
|
|
profile.PostAnalyze(result, snapshot, signals)
|
|
}
|
|
return match
|
|
}
|
|
|
|
func BuildAnalysisDirectives(match MatchResult) AnalysisDirectives {
|
|
return ResolveAnalysisPlan(match, nil, DiscoveredResources{}, MatchSignals{}).Directives
|
|
}
|
|
|
|
func sortProfiles(profiles []Profile) {
|
|
sort.Slice(profiles, func(i, j int) bool {
|
|
if profiles[i].Priority() == profiles[j].Priority() {
|
|
return profiles[i].Name() < profiles[j].Name()
|
|
}
|
|
return profiles[i].Priority() < profiles[j].Priority()
|
|
})
|
|
}
|