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>
This commit is contained in:
@@ -11,9 +11,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/collector/redfishprofile"
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
)
|
||||
|
||||
func testAnalysisPlan(d redfishprofile.AnalysisDirectives) redfishprofile.ResolvedAnalysisPlan {
|
||||
return redfishprofile.ResolvedAnalysisPlan{Directives: d}
|
||||
}
|
||||
|
||||
func TestRedfishConnectorCollect(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
register := func(path string, payload interface{}) {
|
||||
@@ -1422,6 +1427,12 @@ func TestRecoverCriticalRedfishDocsPlanB_RetriesMembersFromExistingCollection(t
|
||||
[]string{"/redfish/v1/Chassis/1/Drives"},
|
||||
rawTree,
|
||||
fetchErrs,
|
||||
redfishprofile.AcquisitionTuning{
|
||||
RecoveryPolicy: redfishprofile.AcquisitionRecoveryPolicy{
|
||||
EnableCriticalCollectionMemberRetry: true,
|
||||
EnableCriticalSlowProbe: true,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if recovered == 0 {
|
||||
@@ -1474,7 +1485,12 @@ func TestRecoverCriticalRedfishDocsPlanB_RetriesMembersFromSystemMemoryCollectio
|
||||
dimmPath: `Get "https://example/redfish/v1/Systems/1/Memory/CPU1_C1D1": context deadline exceeded (Client.Timeout exceeded while awaiting headers)`,
|
||||
}
|
||||
|
||||
criticalPaths := redfishCriticalEndpoints([]string{systemPath}, nil, nil)
|
||||
plan := redfishprofile.BuildAcquisitionPlan(redfishprofile.MatchSignals{})
|
||||
match := redfishprofile.MatchProfiles(redfishprofile.MatchSignals{})
|
||||
resolved := redfishprofile.ResolveAcquisitionPlan(match, plan, redfishprofile.DiscoveredResources{
|
||||
SystemPaths: []string{systemPath},
|
||||
}, redfishprofile.MatchSignals{})
|
||||
criticalPaths := resolved.CriticalPaths
|
||||
hasMemoryPath := false
|
||||
for _, p := range criticalPaths {
|
||||
if p == memoryPath {
|
||||
@@ -1495,6 +1511,12 @@ func TestRecoverCriticalRedfishDocsPlanB_RetriesMembersFromSystemMemoryCollectio
|
||||
criticalPaths,
|
||||
rawTree,
|
||||
fetchErrs,
|
||||
redfishprofile.AcquisitionTuning{
|
||||
RecoveryPolicy: redfishprofile.AcquisitionRecoveryPolicy{
|
||||
EnableCriticalCollectionMemberRetry: true,
|
||||
EnableCriticalSlowProbe: true,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if recovered == 0 {
|
||||
@@ -1508,6 +1530,50 @@ func TestRecoverCriticalRedfishDocsPlanB_RetriesMembersFromSystemMemoryCollectio
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecoverCriticalRedfishDocsPlanB_SkipsMemberRetryWithoutRecoveryPolicy(t *testing.T) {
|
||||
t.Setenv("LOGPILE_REDFISH_CRITICAL_COOLDOWN", "0s")
|
||||
t.Setenv("LOGPILE_REDFISH_CRITICAL_SLOW_GAP", "0s")
|
||||
t.Setenv("LOGPILE_REDFISH_CRITICAL_PLANB_RETRIES", "1")
|
||||
t.Setenv("LOGPILE_REDFISH_CRITICAL_RETRIES", "1")
|
||||
t.Setenv("LOGPILE_REDFISH_CRITICAL_BACKOFF", "0s")
|
||||
|
||||
const memoryPath = "/redfish/v1/Systems/1/Memory"
|
||||
const dimmPath = "/redfish/v1/Systems/1/Memory/CPU1_C1D1"
|
||||
|
||||
rawTree := map[string]interface{}{
|
||||
memoryPath: map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": dimmPath},
|
||||
},
|
||||
},
|
||||
}
|
||||
fetchErrs := map[string]string{
|
||||
dimmPath: `Get "https://example/redfish/v1/Systems/1/Memory/CPU1_C1D1": context deadline exceeded (Client.Timeout exceeded while awaiting headers)`,
|
||||
}
|
||||
|
||||
c := NewRedfishConnector()
|
||||
recovered := c.recoverCriticalRedfishDocsPlanB(
|
||||
context.Background(),
|
||||
http.DefaultClient,
|
||||
Request{},
|
||||
"https://example",
|
||||
[]string{memoryPath},
|
||||
rawTree,
|
||||
fetchErrs,
|
||||
redfishprofile.AcquisitionTuning{},
|
||||
nil,
|
||||
)
|
||||
if recovered != 0 {
|
||||
t.Fatalf("expected no recovery without recovery policy, got %d", recovered)
|
||||
}
|
||||
if _, ok := rawTree[dimmPath]; ok {
|
||||
t.Fatalf("did not expect recovered DIMM doc for %s", dimmPath)
|
||||
}
|
||||
if _, ok := fetchErrs[dimmPath]; !ok {
|
||||
t.Fatalf("expected DIMM fetch error for %s to remain", dimmPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplayCollectStorage_ProbesSupermicroNVMeDiskBayWhenCollectionEmpty(t *testing.T) {
|
||||
r := redfishSnapshotReader{tree: map[string]interface{}{
|
||||
"/redfish/v1/Systems": map[string]interface{}{
|
||||
@@ -1551,7 +1617,7 @@ func TestReplayCollectStorage_ProbesSupermicroNVMeDiskBayWhenCollectionEmpty(t *
|
||||
},
|
||||
}}
|
||||
|
||||
got := r.collectStorage("/redfish/v1/Systems/1")
|
||||
got := r.collectStorage("/redfish/v1/Systems/1", testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableSupermicroNVMeBackplane: true}))
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("expected one drive from direct Disk.Bay probe, got %d", len(got))
|
||||
}
|
||||
@@ -1563,6 +1629,70 @@ func TestReplayCollectStorage_ProbesSupermicroNVMeDiskBayWhenCollectionEmpty(t *
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplayCollectStorage_SkipsEnclosureRecoveryWhenDirectiveDisabled(t *testing.T) {
|
||||
r := redfishSnapshotReader{tree: map[string]interface{}{
|
||||
"/redfish/v1/Systems/1/Storage": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Storage/1"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Systems/1/Storage/1": map[string]interface{}{
|
||||
"Links": map[string]interface{}{
|
||||
"Enclosures": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Enclosures/1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Enclosures/1/Drives": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Enclosures/1/Drives/Drive1"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Enclosures/1/Drives/Drive1": map[string]interface{}{
|
||||
"Id": "Drive1",
|
||||
"Name": "Drive1",
|
||||
"Model": "INTEL SSD",
|
||||
"SerialNumber": "ENCLOSURE-DRIVE-001",
|
||||
"Protocol": "SATA",
|
||||
"MediaType": "SSD",
|
||||
},
|
||||
}}
|
||||
|
||||
got := r.collectStorage("/redfish/v1/Systems/1", testAnalysisPlan(redfishprofile.AnalysisDirectives{}))
|
||||
if len(got) != 0 {
|
||||
t.Fatalf("expected no enclosure recovery when directive is off, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplayCollectStorage_UsesKnownControllerRecoveryWhenEnabled(t *testing.T) {
|
||||
r := redfishSnapshotReader{tree: map[string]interface{}{
|
||||
"/redfish/v1/Systems/1/Storage/IntelVROC/Drives": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Storage/IntelVROC/Drives/1"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Systems/1/Storage/IntelVROC/Drives/1": map[string]interface{}{
|
||||
"Id": "1",
|
||||
"Name": "Drive1",
|
||||
"Model": "VROC SSD",
|
||||
"SerialNumber": "VROC-001",
|
||||
"Protocol": "NVMe",
|
||||
"MediaType": "SSD",
|
||||
},
|
||||
}}
|
||||
|
||||
got := r.collectStorage("/redfish/v1/Systems/1", redfishprofile.ResolvedAnalysisPlan{
|
||||
Directives: redfishprofile.AnalysisDirectives{EnableKnownStorageControllerRecovery: true},
|
||||
KnownStorageDriveCollections: []string{"/Storage/IntelVROC/Drives"},
|
||||
})
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("expected one drive from known controller recovery, got %d", len(got))
|
||||
}
|
||||
if got[0].SerialNumber != "VROC-001" {
|
||||
t.Fatalf("unexpected serial %q", got[0].SerialNumber)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplayCollectGPUs_DoesNotCollapseOnPlaceholderSerialAndSkipsNIC(t *testing.T) {
|
||||
r := redfishSnapshotReader{tree: map[string]interface{}{
|
||||
"/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{
|
||||
@@ -1610,7 +1740,7 @@ func TestReplayCollectGPUs_DoesNotCollapseOnPlaceholderSerialAndSkipsNIC(t *test
|
||||
},
|
||||
}}
|
||||
|
||||
got := r.collectGPUs(nil, []string{"/redfish/v1/Chassis/1"})
|
||||
got := r.collectGPUs(nil, []string{"/redfish/v1/Chassis/1"}, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableGenericGraphicsControllerDedup: true}))
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("expected 2 GPUs (two H200 cards), got %d", len(got))
|
||||
}
|
||||
@@ -1681,7 +1811,7 @@ func TestReplayCollectGPUs_FromGraphicsControllers(t *testing.T) {
|
||||
},
|
||||
}}
|
||||
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil)
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableGenericGraphicsControllerDedup: true}))
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("expected 2 GPUs from GraphicsControllers, got %d", len(got))
|
||||
}
|
||||
@@ -1714,7 +1844,7 @@ func TestReplayCollectGPUs_DedupUsesRedfishPathBeforeHeuristics(t *testing.T) {
|
||||
},
|
||||
}}
|
||||
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil)
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableGenericGraphicsControllerDedup: true}))
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("expected both GPUs to be kept by unique redfish path, got %d", len(got))
|
||||
}
|
||||
@@ -1834,6 +1964,83 @@ func TestReplayRedfishFromRawPayloads_AddsMissingServerModelWarning(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplayRedfishFromRawPayloads_StoresAnalysisProfilesMetadata(t *testing.T) {
|
||||
raw := map[string]any{
|
||||
"redfish_tree": map[string]interface{}{
|
||||
"/redfish/v1": map[string]interface{}{
|
||||
"Vendor": "AMI",
|
||||
"Product": "AMI Redfish",
|
||||
"Systems": map[string]interface{}{"@odata.id": "/redfish/v1/Systems"},
|
||||
"Chassis": map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"},
|
||||
"Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"},
|
||||
},
|
||||
"/redfish/v1/Systems": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Systems/1": map[string]interface{}{
|
||||
"Manufacturer": "Micro-Star International Co., Ltd.",
|
||||
"Model": "CG290",
|
||||
},
|
||||
"/redfish/v1/Chassis": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Chassis/1": map[string]interface{}{
|
||||
"Manufacturer": "Micro-Star International Co., Ltd.",
|
||||
"Model": "CG290",
|
||||
},
|
||||
"/redfish/v1/Managers": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Managers/1": map[string]interface{}{
|
||||
"Id": "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := ReplayRedfishFromRawPayloads(raw, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("replay failed: %v", err)
|
||||
}
|
||||
meta, ok := got.RawPayloads["redfish_analysis_profiles"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected redfish_analysis_profiles metadata")
|
||||
}
|
||||
if meta["mode"] != redfishprofile.ModeMatched {
|
||||
t.Fatalf("expected matched mode, got %#v", meta["mode"])
|
||||
}
|
||||
profiles, ok := meta["profiles"].([]string)
|
||||
if !ok {
|
||||
t.Fatalf("expected []string profiles, got %T", meta["profiles"])
|
||||
}
|
||||
foundMSI := false
|
||||
for _, profile := range profiles {
|
||||
if profile == "msi" {
|
||||
foundMSI = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundMSI {
|
||||
t.Fatalf("expected msi in applied profiles, got %v", profiles)
|
||||
}
|
||||
planMeta, ok := got.RawPayloads["redfish_analysis_plan"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected redfish_analysis_plan metadata")
|
||||
}
|
||||
directives, ok := planMeta["directives"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected directives map in redfish_analysis_plan")
|
||||
}
|
||||
if directives["generic_graphics_controller_dedup"] != true {
|
||||
t.Fatalf("expected generic_graphics_controller_dedup directive, got %#v", directives["generic_graphics_controller_dedup"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplayRedfishFromRawPayloads_AddsDriveFetchWarning(t *testing.T) {
|
||||
raw := map[string]any{
|
||||
"redfish_tree": map[string]interface{}{
|
||||
@@ -1934,7 +2141,7 @@ func TestReplayCollectGPUs_SkipsModelOnlyDuplicateFromGraphicsControllers(t *tes
|
||||
},
|
||||
}}
|
||||
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil)
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableGenericGraphicsControllerDedup: true}))
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("expected 2 GPUs without generic duplicate, got %d", len(got))
|
||||
}
|
||||
@@ -1945,6 +2152,48 @@ func TestReplayCollectGPUs_SkipsModelOnlyDuplicateFromGraphicsControllers(t *tes
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplayCollectGPUs_KeepsModelOnlyGraphicsDuplicateWhenDirectiveDisabled(t *testing.T) {
|
||||
r := redfishSnapshotReader{tree: map[string]interface{}{
|
||||
"/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/4"},
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/9"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Chassis/1/PCIeDevices/4": map[string]interface{}{
|
||||
"Id": "4",
|
||||
"Name": "PCIeCard4",
|
||||
"Model": "H200-SXM5-141G",
|
||||
"Manufacturer": "NVIDIA",
|
||||
"SerialNumber": "1654225094493",
|
||||
},
|
||||
"/redfish/v1/Chassis/1/PCIeDevices/9": map[string]interface{}{
|
||||
"Id": "9",
|
||||
"Name": "PCIeCard9",
|
||||
"Model": "H200-SXM5-141G",
|
||||
"Manufacturer": "NVIDIA",
|
||||
"SerialNumber": "1654425002635",
|
||||
},
|
||||
"/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU0"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Systems/1/GraphicsControllers/GPU0": map[string]interface{}{
|
||||
"Id": "GPU0",
|
||||
"Name": "H200-SXM5-141G",
|
||||
"Model": "H200-SXM5-141G",
|
||||
"Manufacturer": "NVIDIA",
|
||||
"SerialNumber": "N/A",
|
||||
},
|
||||
}}
|
||||
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"}, testAnalysisPlan(redfishprofile.AnalysisDirectives{}))
|
||||
if len(got) != 3 {
|
||||
t.Fatalf("expected model-only graphics duplicate to remain when directive is off, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyBoardInfoFallbackFromDocs_SkipsComponentProductNames(t *testing.T) {
|
||||
board := models.BoardInfo{
|
||||
SerialNumber: "23E100051",
|
||||
@@ -2129,7 +2378,7 @@ func TestReplayCollectGPUs_DropsModelOnlyPlaceholderWhenConcreteDiscoveredLater(
|
||||
},
|
||||
}}
|
||||
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"})
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"}, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableGenericGraphicsControllerDedup: true}))
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("expected generic graphics placeholder to be dropped, got %d GPUs", len(got))
|
||||
}
|
||||
@@ -2169,7 +2418,7 @@ func TestReplayCollectGPUs_MergesGraphicsSerialIntoConcretePCIeGPU(t *testing.T)
|
||||
},
|
||||
}}
|
||||
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"})
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"}, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableGenericGraphicsControllerDedup: true}))
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("expected merged single GPU row, got %d", len(got))
|
||||
}
|
||||
@@ -2227,7 +2476,7 @@ func TestReplayCollectGPUs_MergesAmbiguousSameModelByOrder(t *testing.T) {
|
||||
}
|
||||
|
||||
r := redfishSnapshotReader{tree: tree}
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"})
|
||||
got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"}, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableGenericGraphicsControllerDedup: true}))
|
||||
if len(got) != len(pcieIDs) {
|
||||
t.Fatalf("expected %d merged GPUs, got %d", len(pcieIDs), len(got))
|
||||
}
|
||||
@@ -2358,8 +2607,8 @@ func TestCollectGPUsFromProcessors_SupermicroHGX(t *testing.T) {
|
||||
}
|
||||
systemPaths := []string{"/redfish/v1/Systems/HGX_Baseboard_0"}
|
||||
|
||||
gpus := r.collectGPUs(systemPaths, chassisPaths)
|
||||
gpus = r.collectGPUsFromProcessors(systemPaths, chassisPaths, gpus)
|
||||
gpus := r.collectGPUs(systemPaths, chassisPaths, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableGenericGraphicsControllerDedup: true}))
|
||||
gpus = r.collectGPUsFromProcessors(systemPaths, chassisPaths, gpus, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableProcessorGPUFallback: true}))
|
||||
|
||||
if len(gpus) != 2 {
|
||||
var slots []string
|
||||
@@ -2370,6 +2619,110 @@ func TestCollectGPUsFromProcessors_SupermicroHGX(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectGPUsFromProcessors_SupermicroHGXUsesChassisAliasSerial(t *testing.T) {
|
||||
tree := map[string]interface{}{
|
||||
"/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/GPU1"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Chassis/1/PCIeDevices/GPU1": map[string]interface{}{
|
||||
"Id": "GPU1",
|
||||
"Name": "GPU1",
|
||||
"Model": "NVIDIA H200",
|
||||
"Manufacturer": "NVIDIA",
|
||||
"SerialNumber": "SN-ALIAS-001",
|
||||
"PCIeFunctions": map[string]interface{}{
|
||||
"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/GPU1/PCIeFunctions",
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Chassis/1/PCIeDevices/GPU1/PCIeFunctions": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/GPU1/PCIeFunctions/1"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Chassis/1/PCIeDevices/GPU1/PCIeFunctions/1": map[string]interface{}{
|
||||
"FunctionId": "1",
|
||||
"ClassCode": "0x030200",
|
||||
},
|
||||
"/redfish/v1/Chassis/HGX_GPU_SXM_1": map[string]interface{}{
|
||||
"Id": "HGX_GPU_SXM_1",
|
||||
"SerialNumber": "SN-ALIAS-001",
|
||||
},
|
||||
"/redfish/v1/Systems/HGX_Baseboard_0/Processors": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/HGX_Baseboard_0/Processors/GPU_SXM_1"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Systems/HGX_Baseboard_0/Processors/GPU_SXM_1": map[string]interface{}{
|
||||
"Id": "GPU_SXM_1",
|
||||
"Name": "Processor",
|
||||
"ProcessorType": "GPU",
|
||||
"Model": "NVIDIA H200",
|
||||
"Manufacturer": "NVIDIA",
|
||||
},
|
||||
}
|
||||
|
||||
r := redfishSnapshotReader{tree: tree}
|
||||
chassisPaths := []string{
|
||||
"/redfish/v1/Chassis/1",
|
||||
"/redfish/v1/Chassis/HGX_GPU_SXM_1",
|
||||
}
|
||||
systemPaths := []string{"/redfish/v1/Systems/HGX_Baseboard_0"}
|
||||
|
||||
gpus := r.collectGPUs(systemPaths, chassisPaths, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableGenericGraphicsControllerDedup: true}))
|
||||
gpus = r.collectGPUsFromProcessors(systemPaths, chassisPaths, gpus, redfishprofile.ResolvedAnalysisPlan{
|
||||
Directives: redfishprofile.AnalysisDirectives{EnableProcessorGPUFallback: true, EnableProcessorGPUChassisAlias: true},
|
||||
ProcessorGPUChassisLookupModes: []string{"hgx-alias"},
|
||||
})
|
||||
|
||||
if len(gpus) != 1 {
|
||||
t.Fatalf("expected alias serial dedupe to keep 1 gpu, got %d", len(gpus))
|
||||
}
|
||||
if gpus[0].SerialNumber != "SN-ALIAS-001" {
|
||||
t.Fatalf("expected serial from aliased chassis, got %q", gpus[0].SerialNumber)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectGPUsFromProcessors_MSIUsesIndexedChassisLookup(t *testing.T) {
|
||||
tree := map[string]interface{}{
|
||||
"/redfish/v1/Chassis/GPU1": map[string]interface{}{
|
||||
"Id": "GPU1",
|
||||
"SerialNumber": "MSI-SN-001",
|
||||
},
|
||||
"/redfish/v1/Systems/1/Processors": map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Processors/GPU_SXM_1"},
|
||||
},
|
||||
},
|
||||
"/redfish/v1/Systems/1/Processors/GPU_SXM_1": map[string]interface{}{
|
||||
"Id": "GPU_SXM_1",
|
||||
"Name": "Processor",
|
||||
"ProcessorType": "GPU",
|
||||
"Model": "NVIDIA RTX PRO 6000 Blackwell",
|
||||
"Manufacturer": "NVIDIA",
|
||||
},
|
||||
}
|
||||
|
||||
r := redfishSnapshotReader{tree: tree}
|
||||
gpus := r.collectGPUsFromProcessors(
|
||||
[]string{"/redfish/v1/Systems/1"},
|
||||
[]string{"/redfish/v1/Chassis/GPU1"},
|
||||
nil,
|
||||
redfishprofile.ResolvedAnalysisPlan{
|
||||
Directives: redfishprofile.AnalysisDirectives{EnableProcessorGPUFallback: true, EnableMSIProcessorGPUChassisLookup: true},
|
||||
ProcessorGPUChassisLookupModes: []string{"msi-index"},
|
||||
},
|
||||
)
|
||||
|
||||
if len(gpus) != 1 {
|
||||
t.Fatalf("expected one gpu, got %d", len(gpus))
|
||||
}
|
||||
if gpus[0].SerialNumber != "MSI-SN-001" {
|
||||
t.Fatalf("expected serial from MSI indexed chassis lookup, got %q", gpus[0].SerialNumber)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReplayCollectGPUs_DedupCrossChassisSerial verifies that the same GPU
|
||||
// appearing under two Chassis PCIeDevice trees (e.g. Chassis/1/PCIeDevices/GPU1
|
||||
// and Chassis/HGX_GPU_SXM_1/PCIeDevices/GPU_SXM_1) is deduplicated to one entry
|
||||
@@ -2428,7 +2781,7 @@ func TestReplayCollectGPUs_DedupCrossChassisSerial(t *testing.T) {
|
||||
got := r.collectGPUs(nil, []string{
|
||||
"/redfish/v1/Chassis/1",
|
||||
"/redfish/v1/Chassis/HGX_GPU_SXM_1",
|
||||
})
|
||||
}, testAnalysisPlan(redfishprofile.AnalysisDirectives{EnableGenericGraphicsControllerDedup: true}))
|
||||
if len(got) != 1 {
|
||||
var slots []string
|
||||
for _, g := range got {
|
||||
@@ -2565,32 +2918,42 @@ func TestRedfishSnapshotBranchKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShouldPostProbeCollectionPath(t *testing.T) {
|
||||
if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Sensors") {
|
||||
var tuning redfishprofile.AcquisitionTuning
|
||||
if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Sensors", tuning) {
|
||||
t.Fatalf("expected sensors collection to be skipped by default")
|
||||
}
|
||||
if shouldPostProbeCollectionPath("/redfish/v1/Systems/1/Storage/RAID/Drives", tuning) {
|
||||
t.Fatalf("expected drives collection to be skipped without profile policy")
|
||||
}
|
||||
tuning.PostProbePolicy.EnableNumericCollectionProbe = true
|
||||
t.Setenv("LOGPILE_REDFISH_SENSOR_POSTPROBE", "1")
|
||||
if !shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Sensors") {
|
||||
if !shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Sensors", tuning) {
|
||||
t.Fatalf("expected sensors collection to be post-probed when enabled")
|
||||
}
|
||||
if !shouldPostProbeCollectionPath("/redfish/v1/Systems/1/Storage/RAID/Drives") {
|
||||
if !shouldPostProbeCollectionPath("/redfish/v1/Systems/1/Storage/RAID/Drives", tuning) {
|
||||
t.Fatalf("expected drives collection to be post-probed")
|
||||
}
|
||||
if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Boards/BOARD1") {
|
||||
if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Boards/BOARD1", tuning) {
|
||||
t.Fatalf("expected board member resource to be skipped from post-probe")
|
||||
}
|
||||
if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Assembly/Oem/COMMONb/COMMONbAssembly/1") {
|
||||
if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Assembly/Oem/COMMONb/COMMONbAssembly/1", tuning) {
|
||||
t.Fatalf("expected assembly member resource to be skipped from post-probe")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldAdaptivePostProbeCollectionPath(t *testing.T) {
|
||||
tuning := redfishprofile.AcquisitionTuning{
|
||||
PostProbePolicy: redfishprofile.AcquisitionPostProbePolicy{
|
||||
EnableNumericCollectionProbe: true,
|
||||
},
|
||||
}
|
||||
withExplicitNamedMembers := map[string]interface{}{
|
||||
"Members": []interface{}{
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/NIC-0-0"},
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/NIC-0-1"},
|
||||
},
|
||||
}
|
||||
if shouldAdaptivePostProbeCollectionPath("/redfish/v1/Systems/1/EthernetInterfaces", withExplicitNamedMembers) {
|
||||
if shouldAdaptivePostProbeCollectionPath("/redfish/v1/Systems/1/EthernetInterfaces", withExplicitNamedMembers, tuning) {
|
||||
t.Fatalf("expected explicit non-numeric members to skip adaptive post-probe")
|
||||
}
|
||||
|
||||
@@ -2600,14 +2963,18 @@ func TestShouldAdaptivePostProbeCollectionPath(t *testing.T) {
|
||||
map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/2"},
|
||||
},
|
||||
}
|
||||
if !shouldAdaptivePostProbeCollectionPath("/redfish/v1/Chassis/1/PCIeDevices", withNumericMembers) {
|
||||
if !shouldAdaptivePostProbeCollectionPath("/redfish/v1/Chassis/1/PCIeDevices", withNumericMembers, tuning) {
|
||||
t.Fatalf("expected numeric members to allow adaptive post-probe")
|
||||
}
|
||||
|
||||
withoutMembers := map[string]interface{}{"Name": "Drives"}
|
||||
if !shouldAdaptivePostProbeCollectionPath("/redfish/v1/Chassis/1/Drives", withoutMembers) {
|
||||
if !shouldAdaptivePostProbeCollectionPath("/redfish/v1/Chassis/1/Drives", withoutMembers, tuning) {
|
||||
t.Fatalf("expected missing members to allow adaptive post-probe")
|
||||
}
|
||||
|
||||
if shouldAdaptivePostProbeCollectionPath("/redfish/v1/Chassis/1/Drives", withoutMembers, redfishprofile.AcquisitionTuning{}) {
|
||||
t.Fatalf("expected post-probe to stay disabled without profile policy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldAdaptiveNVMeProbe(t *testing.T) {
|
||||
@@ -2627,6 +2994,15 @@ func TestShouldAdaptiveNVMeProbe(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRedfishAdaptivePrefetchTargets(t *testing.T) {
|
||||
tuning := redfishprofile.AcquisitionTuning{
|
||||
PrefetchPolicy: redfishprofile.AcquisitionPrefetchPolicy{
|
||||
IncludeSuffixes: []string{
|
||||
"/Memory",
|
||||
"/Processors",
|
||||
"/Storage",
|
||||
},
|
||||
},
|
||||
}
|
||||
candidates := []string{
|
||||
"/redfish/v1/Systems/1/Memory",
|
||||
"/redfish/v1/Systems/1/Processors",
|
||||
@@ -2651,7 +3027,7 @@ func TestRedfishAdaptivePrefetchTargets(t *testing.T) {
|
||||
"/redfish/v1/Systems/1/Storage/Volumes": "status 404 from /redfish/v1/Systems/1/Storage/Volumes: not found",
|
||||
}
|
||||
|
||||
got := redfishAdaptivePrefetchTargets(candidates, rawTree, fetchErrs)
|
||||
got := redfishAdaptivePrefetchTargets(redfishPrefetchTargets(candidates, tuning), rawTree, fetchErrs)
|
||||
joined := strings.Join(got, "\n")
|
||||
for _, wanted := range []string{
|
||||
"/redfish/v1/Systems/1/Memory",
|
||||
@@ -2666,12 +3042,16 @@ func TestRedfishAdaptivePrefetchTargets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedfishSnapshotPrioritySeeds_DefaultSkipsNoisyBranches(t *testing.T) {
|
||||
seeds := redfishSnapshotPrioritySeeds(
|
||||
[]string{"/redfish/v1/Systems/1"},
|
||||
[]string{"/redfish/v1/Chassis/1"},
|
||||
[]string{"/redfish/v1/Managers/1"},
|
||||
)
|
||||
func TestResolveAcquisitionPlan_DefaultSkipsNoisyBranches(t *testing.T) {
|
||||
signals := redfishprofile.MatchSignals{}
|
||||
match := redfishprofile.MatchProfiles(signals)
|
||||
plan := redfishprofile.BuildAcquisitionPlan(signals)
|
||||
resolved := redfishprofile.ResolveAcquisitionPlan(match, plan, redfishprofile.DiscoveredResources{
|
||||
SystemPaths: []string{"/redfish/v1/Systems/1"},
|
||||
ChassisPaths: []string{"/redfish/v1/Chassis/1"},
|
||||
ManagerPaths: []string{"/redfish/v1/Managers/1"},
|
||||
}, signals)
|
||||
seeds := resolved.SeedPaths
|
||||
joined := strings.Join(seeds, "\n")
|
||||
for _, noisy := range []string{
|
||||
"/redfish/v1/Fabrics",
|
||||
@@ -2697,7 +3077,43 @@ func TestRedfishSnapshotPrioritySeeds_DefaultSkipsNoisyBranches(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldPrefetchCriticalPath_UsesPrefetchPolicy(t *testing.T) {
|
||||
tuning := redfishprofile.AcquisitionTuning{
|
||||
PrefetchPolicy: redfishprofile.AcquisitionPrefetchPolicy{
|
||||
IncludeSuffixes: []string{"/Storage", "/Oem/Public"},
|
||||
ExcludeContains: []string{"/Assembly"},
|
||||
},
|
||||
}
|
||||
if !shouldPrefetchCriticalPath("/redfish/v1/Systems/1/Storage", tuning) {
|
||||
t.Fatal("expected storage path to be prefetched when included by policy")
|
||||
}
|
||||
if !shouldPrefetchCriticalPath("/redfish/v1/Systems/1/Oem/Public", tuning) {
|
||||
t.Fatal("expected OEM public path to be prefetched when included by policy")
|
||||
}
|
||||
if shouldPrefetchCriticalPath("/redfish/v1/Chassis/1/Assembly", tuning) {
|
||||
t.Fatal("expected excluded path to skip prefetch")
|
||||
}
|
||||
if shouldPrefetchCriticalPath("/redfish/v1/Chassis/1/Power", redfishprofile.AcquisitionTuning{}) {
|
||||
t.Fatal("expected empty prefetch policy to disable suffix-based prefetch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedfishPrefetchTargets_FilterNoisyBranches(t *testing.T) {
|
||||
tuning := redfishprofile.AcquisitionTuning{
|
||||
PrefetchPolicy: redfishprofile.AcquisitionPrefetchPolicy{
|
||||
IncludeSuffixes: []string{
|
||||
"/Memory",
|
||||
"/Oem/Public/FRU",
|
||||
"/Drives",
|
||||
"/NetworkProtocol",
|
||||
},
|
||||
ExcludeContains: []string{
|
||||
"/Backplanes",
|
||||
"/Sensors",
|
||||
"/LogServices",
|
||||
},
|
||||
},
|
||||
}
|
||||
critical := []string{
|
||||
"/redfish/v1/Systems/1",
|
||||
"/redfish/v1/Systems/1/Memory",
|
||||
@@ -2708,7 +3124,7 @@ func TestRedfishPrefetchTargets_FilterNoisyBranches(t *testing.T) {
|
||||
"/redfish/v1/Managers/1/LogServices",
|
||||
"/redfish/v1/Managers/1/NetworkProtocol",
|
||||
}
|
||||
got := redfishPrefetchTargets(critical)
|
||||
got := redfishPrefetchTargets(critical, tuning)
|
||||
joined := strings.Join(got, "\n")
|
||||
for _, wanted := range []string{
|
||||
"/redfish/v1/Systems/1",
|
||||
|
||||
Reference in New Issue
Block a user