176 lines
6.5 KiB
Go
176 lines
6.5 KiB
Go
package redfishprofile
|
|
|
|
import "strings"
|
|
|
|
func lenovoProfile() Profile {
|
|
return staticProfile{
|
|
name: "lenovo",
|
|
priority: 20,
|
|
safeForFallback: true,
|
|
matchFn: func(s MatchSignals) int {
|
|
score := 0
|
|
if containsFold(s.SystemManufacturer, "lenovo") ||
|
|
containsFold(s.ChassisManufacturer, "lenovo") {
|
|
score += 80
|
|
}
|
|
for _, ns := range s.OEMNamespaces {
|
|
if containsFold(ns, "lenovo") {
|
|
score += 30
|
|
break
|
|
}
|
|
}
|
|
// Lenovo XClarity Controller (XCC) is the BMC product line.
|
|
if containsFold(s.ServiceRootProduct, "xclarity") ||
|
|
containsFold(s.ServiceRootProduct, "xcc") {
|
|
score += 30
|
|
}
|
|
return min(score, 100)
|
|
},
|
|
extendAcquisition: func(plan *AcquisitionPlan, _ MatchSignals) {
|
|
// Lenovo XCC BMC exposes Chassis/1/Sensors with hundreds of individual
|
|
// sensor member documents (e.g. Chassis/1/Sensors/101L1). These are
|
|
// not used by any LOGPile parser — thermal/power data is read from
|
|
// the aggregate Chassis/*/Thermal and Chassis/*/Power endpoints. On
|
|
// a real server they largely return errors, wasting many minutes.
|
|
// 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/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
|
|
// first few requests, and give the ETA estimator a realistic baseline.
|
|
ensureRatePolicy(plan, AcquisitionRatePolicy{
|
|
TargetP95LatencyMS: 2000,
|
|
ThrottleP95LatencyMS: 4000,
|
|
MinSnapshotWorkers: 2,
|
|
MinPrefetchWorkers: 1,
|
|
DisablePrefetchOnErrors: true,
|
|
})
|
|
ensureETABaseline(plan, AcquisitionETABaseline{
|
|
DiscoverySeconds: 15,
|
|
SnapshotSeconds: 120,
|
|
PrefetchSeconds: 30,
|
|
CriticalPlanBSeconds: 40,
|
|
ProfilePlanBSeconds: 20,
|
|
})
|
|
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
|
|
}
|