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>
406 lines
16 KiB
Go
406 lines
16 KiB
Go
package redfishprofile
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestBuildAcquisitionPlan_Fixture_MSI_CG480(t *testing.T) {
|
|
signals := loadProfileFixtureSignals(t, "msi-cg480.json")
|
|
match := MatchProfiles(signals)
|
|
plan := BuildAcquisitionPlan(signals)
|
|
resolved := ResolveAcquisitionPlan(match, plan, discoveredResourcesFromSignals(signals), signals)
|
|
|
|
if match.Mode != ModeMatched {
|
|
t.Fatalf("expected matched mode, got %q", match.Mode)
|
|
}
|
|
assertProfileSelected(t, match, "msi")
|
|
assertProfileSelected(t, match, "ami-family")
|
|
assertProfileNotSelected(t, match, "hgx-topology")
|
|
|
|
if plan.Tuning.PrefetchWorkers < 6 {
|
|
t.Fatalf("expected msi prefetch worker tuning, got %d", plan.Tuning.PrefetchWorkers)
|
|
}
|
|
if !containsString(resolved.SeedPaths, "/redfish/v1/Chassis/GPU1") {
|
|
t.Fatalf("expected MSI chassis GPU seed path")
|
|
}
|
|
if !containsString(resolved.CriticalPaths, "/redfish/v1/Chassis/GPU1/Sensors") {
|
|
t.Fatal("expected MSI GPU sensor critical path")
|
|
}
|
|
if !containsString(resolved.Plan.PlanBPaths, "/redfish/v1/Chassis/GPU1/Sensors") {
|
|
t.Fatal("expected MSI GPU sensor plan-b path")
|
|
}
|
|
if plan.Tuning.ETABaseline.SnapshotSeconds <= 0 {
|
|
t.Fatal("expected MSI snapshot eta baseline")
|
|
}
|
|
if !plan.Tuning.PostProbePolicy.EnableNumericCollectionProbe {
|
|
t.Fatal("expected MSI fixture to inherit generic numeric post-probe policy")
|
|
}
|
|
if !containsString(plan.ScopedPaths.SystemSeedSuffixes, "/SimpleStorage") {
|
|
t.Fatal("expected MSI fixture to inherit generic SimpleStorage scoped seed suffix")
|
|
}
|
|
if !containsString(plan.ScopedPaths.SystemCriticalSuffixes, "/Memory") {
|
|
t.Fatal("expected MSI fixture to inherit generic system critical suffixes")
|
|
}
|
|
if !containsString(plan.Tuning.PrefetchPolicy.IncludeSuffixes, "/Storage") {
|
|
t.Fatal("expected MSI fixture to inherit generic storage prefetch policy")
|
|
}
|
|
if !containsString(plan.CriticalPaths, "/redfish/v1/UpdateService") {
|
|
t.Fatal("expected MSI fixture to inherit generic top-level critical path")
|
|
}
|
|
if !plan.Tuning.RecoveryPolicy.EnableProfilePlanB {
|
|
t.Fatal("expected MSI fixture to enable profile plan-b")
|
|
}
|
|
}
|
|
|
|
func TestBuildAcquisitionPlan_Fixture_MSI_CG480_CopyMatchesSameProfiles(t *testing.T) {
|
|
originalSignals := loadProfileFixtureSignals(t, "msi-cg480.json")
|
|
copySignals := loadProfileFixtureSignals(t, "msi-cg480-copy.json")
|
|
originalMatch := MatchProfiles(originalSignals)
|
|
copyMatch := MatchProfiles(copySignals)
|
|
originalPlan := BuildAcquisitionPlan(originalSignals)
|
|
copyPlan := BuildAcquisitionPlan(copySignals)
|
|
originalResolved := ResolveAcquisitionPlan(originalMatch, originalPlan, discoveredResourcesFromSignals(originalSignals), originalSignals)
|
|
copyResolved := ResolveAcquisitionPlan(copyMatch, copyPlan, discoveredResourcesFromSignals(copySignals), copySignals)
|
|
|
|
assertSameProfileNames(t, originalMatch, copyMatch)
|
|
if originalPlan.Tuning.PrefetchWorkers != copyPlan.Tuning.PrefetchWorkers {
|
|
t.Fatalf("expected same MSI prefetch worker tuning, got %d vs %d", originalPlan.Tuning.PrefetchWorkers, copyPlan.Tuning.PrefetchWorkers)
|
|
}
|
|
if containsString(originalResolved.SeedPaths, "/redfish/v1/Chassis/GPU1") != containsString(copyResolved.SeedPaths, "/redfish/v1/Chassis/GPU1") {
|
|
t.Fatal("expected same MSI GPU chassis seed presence in both fixtures")
|
|
}
|
|
}
|
|
|
|
func TestBuildAcquisitionPlan_Fixture_MSI_CG290(t *testing.T) {
|
|
signals := loadProfileFixtureSignals(t, "msi-cg290.json")
|
|
match := MatchProfiles(signals)
|
|
plan := BuildAcquisitionPlan(signals)
|
|
resolved := ResolveAcquisitionPlan(match, plan, discoveredResourcesFromSignals(signals), signals)
|
|
|
|
if match.Mode != ModeMatched {
|
|
t.Fatalf("expected matched mode, got %q", match.Mode)
|
|
}
|
|
assertProfileSelected(t, match, "msi")
|
|
assertProfileSelected(t, match, "ami-family")
|
|
assertProfileNotSelected(t, match, "hgx-topology")
|
|
|
|
if plan.Tuning.PrefetchWorkers < 6 {
|
|
t.Fatalf("expected MSI prefetch worker tuning, got %d", plan.Tuning.PrefetchWorkers)
|
|
}
|
|
if !containsString(resolved.SeedPaths, "/redfish/v1/Chassis/GPU1") {
|
|
t.Fatalf("expected MSI chassis GPU seed path")
|
|
}
|
|
}
|
|
|
|
func TestBuildAcquisitionPlan_Fixture_Supermicro_HGX(t *testing.T) {
|
|
signals := loadProfileFixtureSignals(t, "supermicro-hgx.json")
|
|
match := MatchProfiles(signals)
|
|
plan := BuildAcquisitionPlan(signals)
|
|
discovered := discoveredResourcesFromSignals(signals)
|
|
discovered.SystemPaths = dedupeSorted(append(discovered.SystemPaths, "/redfish/v1/Systems/HGX_Baseboard_0"))
|
|
resolved := ResolveAcquisitionPlan(match, plan, discovered, signals)
|
|
|
|
if match.Mode != ModeMatched {
|
|
t.Fatalf("expected matched mode, got %q", match.Mode)
|
|
}
|
|
assertProfileSelected(t, match, "supermicro")
|
|
assertProfileSelected(t, match, "hgx-topology")
|
|
assertProfileNotSelected(t, match, "msi")
|
|
|
|
if plan.Tuning.SnapshotMaxDocuments < 180000 {
|
|
t.Fatalf("expected widened HGX snapshot cap, got %d", plan.Tuning.SnapshotMaxDocuments)
|
|
}
|
|
if plan.Tuning.NVMePostProbeEnabled == nil || *plan.Tuning.NVMePostProbeEnabled {
|
|
t.Fatal("expected HGX fixture to disable NVMe post-probe")
|
|
}
|
|
if !containsString(resolved.SeedPaths, "/redfish/v1/Systems/HGX_Baseboard_0/Processors") {
|
|
t.Fatal("expected HGX baseboard processors seed path")
|
|
}
|
|
if !containsString(resolved.CriticalPaths, "/redfish/v1/Systems/HGX_Baseboard_0/Processors") {
|
|
t.Fatal("expected HGX baseboard processors critical path")
|
|
}
|
|
if !containsString(resolved.Plan.PlanBPaths, "/redfish/v1/Systems/HGX_Baseboard_0/Processors") {
|
|
t.Fatal("expected HGX baseboard processors plan-b path")
|
|
}
|
|
if plan.Tuning.ETABaseline.SnapshotSeconds < 300 {
|
|
t.Fatalf("expected HGX snapshot eta baseline, got %d", plan.Tuning.ETABaseline.SnapshotSeconds)
|
|
}
|
|
if !plan.Tuning.PostProbePolicy.EnableDirectNVMEDiskBayProbe {
|
|
t.Fatal("expected HGX fixture to retain Supermicro direct NVMe disk bay probe policy")
|
|
}
|
|
if !containsString(plan.ScopedPaths.SystemCriticalSuffixes, "/Storage/IntelVROC/Drives") {
|
|
t.Fatal("expected HGX fixture to inherit generic IntelVROC scoped critical suffix")
|
|
}
|
|
if !containsString(plan.ScopedPaths.ChassisCriticalSuffixes, "/Assembly") {
|
|
t.Fatal("expected HGX fixture to inherit generic chassis critical suffixes")
|
|
}
|
|
if !containsString(plan.Tuning.PrefetchPolicy.ExcludeContains, "/Assembly") {
|
|
t.Fatal("expected HGX fixture to inherit generic assembly prefetch exclusion")
|
|
}
|
|
if !plan.Tuning.RecoveryPolicy.EnableProfilePlanB {
|
|
t.Fatal("expected HGX fixture to enable profile plan-b")
|
|
}
|
|
}
|
|
|
|
func TestBuildAcquisitionPlan_Fixture_Supermicro_OAM_NoHGX(t *testing.T) {
|
|
signals := loadProfileFixtureSignals(t, "supermicro-oam-amd.json")
|
|
match := MatchProfiles(signals)
|
|
plan := BuildAcquisitionPlan(signals)
|
|
resolved := ResolveAcquisitionPlan(match, plan, discoveredResourcesFromSignals(signals), signals)
|
|
|
|
if match.Mode != ModeMatched {
|
|
t.Fatalf("expected matched mode, got %q", match.Mode)
|
|
}
|
|
assertProfileSelected(t, match, "supermicro")
|
|
assertProfileNotSelected(t, match, "hgx-topology")
|
|
assertProfileNotSelected(t, match, "msi")
|
|
|
|
if containsString(resolved.SeedPaths, "/redfish/v1/Systems/HGX_Baseboard_0/Processors") {
|
|
t.Fatal("did not expect HGX baseboard processors seed path for OAM fixture")
|
|
}
|
|
if containsString(resolved.CriticalPaths, "/redfish/v1/Systems/HGX_Baseboard_0/Processors") {
|
|
t.Fatal("did not expect HGX baseboard processors critical path for OAM fixture")
|
|
}
|
|
if !containsString(resolved.CriticalPaths, "/redfish/v1/UpdateService/Oem/Supermicro/FirmwareInventory") {
|
|
t.Fatal("expected Supermicro firmware critical path")
|
|
}
|
|
if !containsString(resolved.Plan.PlanBPaths, "/redfish/v1/UpdateService/Oem/Supermicro/FirmwareInventory") {
|
|
t.Fatal("expected Supermicro firmware plan-b path")
|
|
}
|
|
if plan.Tuning.SnapshotMaxDocuments != 150000 {
|
|
t.Fatalf("expected generic supermicro snapshot cap, got %d", plan.Tuning.SnapshotMaxDocuments)
|
|
}
|
|
if plan.Tuning.NVMePostProbeEnabled != nil {
|
|
t.Fatal("did not expect HGX NVMe tuning for OAM fixture")
|
|
}
|
|
if plan.Tuning.ETABaseline.SnapshotSeconds < 180 {
|
|
t.Fatalf("expected Supermicro snapshot eta baseline, got %d", plan.Tuning.ETABaseline.SnapshotSeconds)
|
|
}
|
|
if !plan.Tuning.PostProbePolicy.EnableDirectNVMEDiskBayProbe {
|
|
t.Fatal("expected Supermicro OAM fixture to use direct NVMe disk bay probe policy")
|
|
}
|
|
if !plan.Tuning.PostProbePolicy.EnableNumericCollectionProbe {
|
|
t.Fatal("expected Supermicro OAM fixture to inherit generic numeric post-probe policy")
|
|
}
|
|
if !containsString(plan.ScopedPaths.SystemSeedSuffixes, "/Storage/IntelVROC") {
|
|
t.Fatal("expected Supermicro OAM fixture to inherit generic IntelVROC scoped seed suffix")
|
|
}
|
|
if !plan.Tuning.RecoveryPolicy.EnableProfilePlanB {
|
|
t.Fatal("expected Supermicro OAM fixture to enable profile plan-b")
|
|
}
|
|
}
|
|
|
|
func TestBuildAcquisitionPlan_Fixture_Dell_R750(t *testing.T) {
|
|
signals := loadProfileFixtureSignals(t, "dell-r750.json")
|
|
match := MatchProfiles(signals)
|
|
plan := BuildAcquisitionPlan(signals)
|
|
resolved := ResolveAcquisitionPlan(match, plan, DiscoveredResources{
|
|
SystemPaths: []string{"/redfish/v1/Systems/System.Embedded.1"},
|
|
ChassisPaths: []string{"/redfish/v1/Chassis/System.Embedded.1"},
|
|
ManagerPaths: []string{"/redfish/v1/Managers/1", "/redfish/v1/Managers/iDRAC.Embedded.1"},
|
|
}, signals)
|
|
|
|
if match.Mode != ModeMatched {
|
|
t.Fatalf("expected matched mode, got %q", match.Mode)
|
|
}
|
|
assertProfileSelected(t, match, "dell")
|
|
assertProfileNotSelected(t, match, "supermicro")
|
|
assertProfileNotSelected(t, match, "hgx-topology")
|
|
assertProfileNotSelected(t, match, "msi")
|
|
|
|
if !plan.Tuning.RecoveryPolicy.EnableProfilePlanB {
|
|
t.Fatal("expected dell fixture to enable profile plan-b")
|
|
}
|
|
if !containsString(resolved.SeedPaths, "/redfish/v1/Managers/iDRAC.Embedded.1") {
|
|
t.Fatal("expected Dell refinement to add iDRAC manager seed path")
|
|
}
|
|
if !containsString(resolved.CriticalPaths, "/redfish/v1/Managers/iDRAC.Embedded.1") {
|
|
t.Fatal("expected Dell refinement to add iDRAC manager critical path")
|
|
}
|
|
directives := ResolveAnalysisPlan(match, nil, DiscoveredResources{}, signals).Directives
|
|
if !directives.EnableGenericGraphicsControllerDedup {
|
|
t.Fatal("expected dell fixture to enable graphics controller dedup")
|
|
}
|
|
}
|
|
|
|
func TestBuildAcquisitionPlan_Fixture_AMI_Generic(t *testing.T) {
|
|
signals := loadProfileFixtureSignals(t, "ami-generic.json")
|
|
match := MatchProfiles(signals)
|
|
plan := BuildAcquisitionPlan(signals)
|
|
|
|
if match.Mode != ModeMatched {
|
|
t.Fatalf("expected matched mode, got %q", match.Mode)
|
|
}
|
|
assertProfileSelected(t, match, "ami-family")
|
|
assertProfileNotSelected(t, match, "msi")
|
|
assertProfileNotSelected(t, match, "supermicro")
|
|
assertProfileNotSelected(t, match, "dell")
|
|
assertProfileNotSelected(t, match, "hgx-topology")
|
|
|
|
if plan.Tuning.PrefetchEnabled == nil || !*plan.Tuning.PrefetchEnabled {
|
|
t.Fatal("expected ami-family fixture to force prefetch enabled")
|
|
}
|
|
if !containsString(plan.SeedPaths, "/redfish/v1/Oem/Ami") {
|
|
t.Fatal("expected ami-family fixture seed path /redfish/v1/Oem/Ami")
|
|
}
|
|
if !containsString(plan.SeedPaths, "/redfish/v1/Oem/Ami/InventoryData/Status") {
|
|
t.Fatal("expected ami-family fixture seed path /redfish/v1/Oem/Ami/InventoryData/Status")
|
|
}
|
|
if !containsString(plan.CriticalPaths, "/redfish/v1/UpdateService") {
|
|
t.Fatal("expected ami-family fixture to inherit generic critical path")
|
|
}
|
|
|
|
directives := ResolveAnalysisPlan(match, nil, DiscoveredResources{}, signals).Directives
|
|
if !directives.EnableGenericGraphicsControllerDedup {
|
|
t.Fatal("expected ami-family fixture to enable graphics controller dedup")
|
|
}
|
|
}
|
|
|
|
func TestBuildAcquisitionPlan_Fixture_UnknownVendor(t *testing.T) {
|
|
signals := loadProfileFixtureSignals(t, "unknown-vendor.json")
|
|
match := MatchProfiles(signals)
|
|
plan := BuildAcquisitionPlan(signals)
|
|
resolved := ResolveAcquisitionPlan(match, plan, DiscoveredResources{
|
|
SystemPaths: []string{"/redfish/v1/Systems/1"},
|
|
ChassisPaths: []string{"/redfish/v1/Chassis/1"},
|
|
ManagerPaths: []string{"/redfish/v1/Managers/1"},
|
|
}, signals)
|
|
|
|
if match.Mode != ModeFallback {
|
|
t.Fatalf("expected fallback mode for unknown vendor, got %q", match.Mode)
|
|
}
|
|
if len(match.Profiles) == 0 {
|
|
t.Fatal("expected fallback to aggregate profiles")
|
|
}
|
|
for _, profile := range match.Profiles {
|
|
if !profile.SafeForFallback() {
|
|
t.Fatalf("fallback mode included non-safe profile %q", profile.Name())
|
|
}
|
|
}
|
|
|
|
if plan.Tuning.SnapshotMaxDocuments < 180000 {
|
|
t.Fatalf("expected fallback to widen snapshot cap, got %d", plan.Tuning.SnapshotMaxDocuments)
|
|
}
|
|
if plan.Tuning.PrefetchEnabled == nil || !*plan.Tuning.PrefetchEnabled {
|
|
t.Fatal("expected fallback fixture to force prefetch enabled")
|
|
}
|
|
if !containsString(resolved.CriticalPaths, "/redfish/v1/Systems/1") {
|
|
t.Fatal("expected fallback resolved critical paths to include discovered system")
|
|
}
|
|
|
|
analysisPlan := ResolveAnalysisPlan(match, nil, DiscoveredResources{}, signals)
|
|
if !analysisPlan.Directives.EnableProcessorGPUFallback {
|
|
t.Fatal("expected fallback fixture to enable processor GPU fallback")
|
|
}
|
|
if !analysisPlan.Directives.EnableStorageEnclosureRecovery {
|
|
t.Fatal("expected fallback fixture to enable storage enclosure recovery")
|
|
}
|
|
if !analysisPlan.Directives.EnableGenericGraphicsControllerDedup {
|
|
t.Fatal("expected fallback fixture to enable graphics controller dedup")
|
|
}
|
|
}
|
|
|
|
func loadProfileFixtureSignals(t *testing.T, fixtureName string) MatchSignals {
|
|
t.Helper()
|
|
path := filepath.Join("testdata", fixtureName)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read fixture %s: %v", path, err)
|
|
}
|
|
var signals MatchSignals
|
|
if err := json.Unmarshal(data, &signals); err != nil {
|
|
t.Fatalf("decode fixture %s: %v", path, err)
|
|
}
|
|
return normalizeSignals(signals)
|
|
}
|
|
|
|
func assertProfileSelected(t *testing.T, match MatchResult, want string) {
|
|
t.Helper()
|
|
for _, profile := range match.Profiles {
|
|
if profile.Name() == want {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("expected profile %q in %v", want, profileNames(match))
|
|
}
|
|
|
|
func assertProfileNotSelected(t *testing.T, match MatchResult, want string) {
|
|
t.Helper()
|
|
for _, profile := range match.Profiles {
|
|
if profile.Name() == want {
|
|
t.Fatalf("did not expect profile %q in %v", want, profileNames(match))
|
|
}
|
|
}
|
|
}
|
|
|
|
func profileNames(match MatchResult) []string {
|
|
out := make([]string, 0, len(match.Profiles))
|
|
for _, profile := range match.Profiles {
|
|
out = append(out, profile.Name())
|
|
}
|
|
return out
|
|
}
|
|
|
|
func assertSameProfileNames(t *testing.T, left, right MatchResult) {
|
|
t.Helper()
|
|
leftNames := profileNames(left)
|
|
rightNames := profileNames(right)
|
|
if len(leftNames) != len(rightNames) {
|
|
t.Fatalf("profile stack size differs: %v vs %v", leftNames, rightNames)
|
|
}
|
|
for i := range leftNames {
|
|
if leftNames[i] != rightNames[i] {
|
|
t.Fatalf("profile stack differs: %v vs %v", leftNames, rightNames)
|
|
}
|
|
}
|
|
}
|
|
|
|
func containsString(items []string, want string) bool {
|
|
for _, item := range items {
|
|
if item == want {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func discoveredResourcesFromSignals(signals MatchSignals) DiscoveredResources {
|
|
var discovered DiscoveredResources
|
|
for _, hint := range signals.ResourceHints {
|
|
memberPath := discoveredMemberPath(hint)
|
|
switch {
|
|
case strings.HasPrefix(memberPath, "/redfish/v1/Systems/"):
|
|
discovered.SystemPaths = append(discovered.SystemPaths, memberPath)
|
|
case strings.HasPrefix(memberPath, "/redfish/v1/Chassis/"):
|
|
discovered.ChassisPaths = append(discovered.ChassisPaths, memberPath)
|
|
case strings.HasPrefix(memberPath, "/redfish/v1/Managers/"):
|
|
discovered.ManagerPaths = append(discovered.ManagerPaths, memberPath)
|
|
}
|
|
}
|
|
discovered.SystemPaths = dedupeSorted(discovered.SystemPaths)
|
|
discovered.ChassisPaths = dedupeSorted(discovered.ChassisPaths)
|
|
discovered.ManagerPaths = dedupeSorted(discovered.ManagerPaths)
|
|
return discovered
|
|
}
|
|
|
|
func discoveredMemberPath(path string) string {
|
|
path = strings.TrimSpace(path)
|
|
if path == "" {
|
|
return ""
|
|
}
|
|
parts := strings.Split(strings.Trim(path, "/"), "/")
|
|
if len(parts) < 4 || parts[0] != "redfish" || parts[1] != "v1" {
|
|
return ""
|
|
}
|
|
switch parts[2] {
|
|
case "Systems", "Chassis", "Managers":
|
|
return "/" + strings.Join(parts[:4], "/")
|
|
default:
|
|
return ""
|
|
}
|
|
}
|