Trim noisy Lenovo Redfish collection paths

This commit is contained in:
Mikhail Chusavitin
2026-04-29 17:02:40 +03:00
parent 3887df6547
commit 1162ccd22e
2 changed files with 167 additions and 9 deletions

View File

@@ -352,7 +352,15 @@ func TestMatchProfiles_LenovoXCCSelectsMatchedModeAndExcludesSensors(t *testing.
ChassisManufacturer: "Lenovo",
OEMNamespaces: []string{"Lenovo"},
})
wantExcluded := []string{"/Sensors/", "/Oem/Lenovo/LEDs/", "/Oem/Lenovo/Slots/"}
wantExcluded := []string{
"/Sensors/",
"/Oem/Lenovo/LEDs/",
"/Oem/Lenovo/Slots/",
"/Oem/Lenovo/Configuration",
"/NetworkProtocol/Oem/Lenovo/",
"/VirtualMedia/",
"/ThermalSubsystem/Fans/",
}
for _, want := range wantExcluded {
found := false
for _, ex := range plan.Tuning.SnapshotExcludeContains {
@@ -367,6 +375,46 @@ func TestMatchProfiles_LenovoXCCSelectsMatchedModeAndExcludesSensors(t *testing.
}
}
func TestResolveAcquisitionPlan_LenovoFiltersNonInventoryChassisBranches(t *testing.T) {
signals := MatchSignals{
SystemManufacturer: "Lenovo",
ChassisManufacturer: "Lenovo",
OEMNamespaces: []string{"Lenovo"},
ResourceHints: []string{
"/redfish/v1/Chassis/1/Power",
"/redfish/v1/Chassis/1/Thermal",
"/redfish/v1/Chassis/1/NetworkAdapters",
"/redfish/v1/Chassis/3",
"/redfish/v1/Chassis/IO_Board",
},
}
match := MatchProfiles(signals)
plan := BuildAcquisitionPlan(signals)
resolved := ResolveAcquisitionPlan(match, plan, DiscoveredResources{
ChassisPaths: []string{
"/redfish/v1/Chassis/1",
"/redfish/v1/Chassis/3",
"/redfish/v1/Chassis/IO_Board",
},
}, signals)
if !containsString(resolved.CriticalPaths, "/redfish/v1/Chassis/1/Power") {
t.Fatal("expected primary Lenovo chassis power path to remain critical")
}
if containsString(resolved.CriticalPaths, "/redfish/v1/Chassis/3/Power") {
t.Fatal("did not expect non-inventory Lenovo backplane chassis power path")
}
if containsString(resolved.CriticalPaths, "/redfish/v1/Chassis/IO_Board/Assembly") {
t.Fatal("did not expect IO board assembly path without inventory hints")
}
if containsString(resolved.Plan.PlanBPaths, "/redfish/v1/Chassis/3/Assembly") {
t.Fatal("did not expect non-inventory Lenovo chassis plan-b target")
}
if !containsString(resolved.CriticalPaths, "/redfish/v1/Chassis/3") {
t.Fatal("expected chassis root to remain discoverable even when suffixes are filtered")
}
}
func TestMatchProfiles_OrderingIsDeterministic(t *testing.T) {
signals := MatchSignals{
SystemManufacturer: "Micro-Star International Co., Ltd.",

View File

@@ -1,5 +1,7 @@
package redfishprofile
import "strings"
func lenovoProfile() Profile {
return staticProfile{
name: "lenovo",
@@ -33,14 +35,30 @@ func lenovoProfile() Profile {
// Lenovo OEM subtrees under Oem/Lenovo/LEDs and Oem/Lenovo/Slots also
// enumerate dozens of individual documents not relevant to inventory.
ensureSnapshotExcludeContains(plan,
"/Sensors/", // individual sensor docs (Chassis/1/Sensors/NNN)
"/Oem/Lenovo/LEDs/", // individual LED status entries (~47 per server)
"/Oem/Lenovo/Slots/", // individual slot detail entries (~26 per server)
"/Oem/Lenovo/Metrics/", // operational metrics, not inventory
"/Oem/Lenovo/History", // historical telemetry
"/Oem/Lenovo/ScheduledPower", // power scheduling config
"/Oem/Lenovo/BootSettings/BootOrder", // individual boot order lists
"/PortForwardingMap/", // network port forwarding config
"/Sensors/", // individual sensor docs (Chassis/1/Sensors/NNN)
"/Oem/Lenovo/LEDs/", // individual LED status entries (~47 per server)
"/Oem/Lenovo/Slots/", // individual slot detail entries (~26 per server)
"/Oem/Lenovo/Metrics/", // operational metrics, not inventory
"/Oem/Lenovo/History", // historical telemetry
"/Oem/Lenovo/Configuration", // BMC config service, not inventory
"/Oem/Lenovo/DateTimeService", // BMC time service config
"/Oem/Lenovo/GroupService", // XCC fleet/group management state
"/Oem/Lenovo/Recipients", // alert recipient config
"/Oem/Lenovo/RemoteControl", // remote-media/session management
"/Oem/Lenovo/RemoteMap", // remote-media mapping config
"/Oem/Lenovo/SecureKeyLifecycleService", // key lifecycle/cert config
"/Oem/Lenovo/ServerProfile", // profile export/import config
"/Oem/Lenovo/ServiceData", // support/service metadata
"/Oem/Lenovo/SsoCertificates", // SSO certificate config
"/Oem/Lenovo/SystemGuard", // snapshot/history service
"/Oem/Lenovo/Watchdogs", // watchdog config
"/Oem/Lenovo/ScheduledPower", // power scheduling config
"/Oem/Lenovo/BootSettings/BootOrder", // individual boot order lists
"/NetworkProtocol/Oem/Lenovo/", // DNS/LDAP/SMTP/SNMP manager config
"/PortForwardingMap/", // network port forwarding config
"/VirtualMedia/", // virtual media inventory/config, not hardware
"/Boot/Certificates", // secure boot certificate stores, not inventory
"/ThermalSubsystem/Fans/", // per-fan member docs; replay uses aggregate Thermal only
)
// Lenovo XCC BMC is typically slow (p95 latency often 3-5s even under
// normal load). Set rate thresholds that don't over-throttle on the
@@ -61,5 +79,97 @@ func lenovoProfile() Profile {
})
addPlanNote(plan, "lenovo xcc acquisition extensions enabled: noisy sensor/oem paths excluded from snapshot")
},
refineAcquisition: func(resolved *ResolvedAcquisitionPlan, discovered DiscoveredResources, signals MatchSignals) {
allowedChassis := lenovoAllowedInventoryChassis(discovered.ChassisPaths, signals.ResourceHints)
resolved.SeedPaths = filterLenovoChassisInventoryPaths(resolved.SeedPaths, allowedChassis)
resolved.CriticalPaths = filterLenovoChassisInventoryPaths(resolved.CriticalPaths, allowedChassis)
resolved.Plan.SeedPaths = filterLenovoChassisInventoryPaths(resolved.Plan.SeedPaths, allowedChassis)
resolved.Plan.CriticalPaths = filterLenovoChassisInventoryPaths(resolved.Plan.CriticalPaths, allowedChassis)
resolved.Plan.PlanBPaths = filterLenovoChassisInventoryPaths(resolved.Plan.PlanBPaths, allowedChassis)
},
}
}
func lenovoAllowedInventoryChassis(chassisPaths, resourceHints []string) map[string]struct{} {
allowed := make(map[string]struct{}, len(chassisPaths))
for _, chassisPath := range chassisPaths {
normalized := normalizePath(chassisPath)
if normalized == "" {
continue
}
if normalized == "/redfish/v1/Chassis/1" {
allowed[normalized] = struct{}{}
continue
}
for _, hint := range resourceHints {
hint = normalizePath(hint)
if !strings.HasPrefix(hint, normalized+"/") {
continue
}
if lenovoHintLooksLikeChassisInventory(hint) {
allowed[normalized] = struct{}{}
break
}
}
}
return allowed
}
func lenovoHintLooksLikeChassisInventory(path string) bool {
for _, suffix := range []string{
"/Power",
"/PowerSubsystem",
"/PowerSubsystem/PowerSupplies",
"/Thermal",
"/ThresholdSensors",
"/DiscreteSensors",
"/SensorsList",
"/NetworkAdapters",
"/PCIeDevices",
"/Drives",
"/Assembly",
} {
if strings.HasSuffix(path, suffix) || strings.Contains(path, suffix+"/") {
return true
}
}
return false
}
func filterLenovoChassisInventoryPaths(paths []string, allowedChassis map[string]struct{}) []string {
if len(paths) == 0 {
return nil
}
out := make([]string, 0, len(paths))
for _, path := range paths {
normalized := normalizePath(path)
chassis := lenovoPathChassisRoot(normalized)
if chassis == "" {
out = append(out, normalized)
continue
}
if normalized == chassis {
out = append(out, normalized)
continue
}
if _, ok := allowedChassis[chassis]; ok {
out = append(out, normalized)
}
}
return dedupeSorted(out)
}
func lenovoPathChassisRoot(path string) string {
const prefix = "/redfish/v1/Chassis/"
if !strings.HasPrefix(path, prefix) {
return ""
}
rest := strings.TrimPrefix(path, prefix)
if rest == "" {
return ""
}
if idx := strings.IndexByte(rest, '/'); idx >= 0 {
return prefix + rest[:idx]
}
return prefix + rest
}