feat: HPE iLO support — profile, post-probe hang fix, replay parser fixes, AHS parser
- Add HPE iLO Redfish profile (priority 20): matches on manufacturer/OEM/iLO signals, adds SmartStorage/SmartStorageConfig to critical paths, sets realistic ETA baseline and rate policy for iLO's known slowness - Fix post-probe hang on HPE iLO: skip numeric probing of collections where Members@odata.count == len(Members); add 4s postProbeClient timeout as safety net - Exclude /WorkloadPerformanceAdvisor from crawl paths - Fix replay parser: skip absent CPU sockets, absent DIMM slots, absent drive bays - Filter N/A version entries from firmware inventory - Remove drive firmware from general firmware list (already in Storage[].Firmware) - Add HPE AHS (.ahs) archive parser with hybrid SMBIOS/Redfish extraction Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1606,6 +1606,7 @@ func (c *RedfishConnector) collectRawRedfishTree(ctx context.Context, client *ht
|
||||
crawlStart := time.Now()
|
||||
memoryClient := c.httpClientWithTimeout(req, redfishSnapshotMemoryRequestTimeout())
|
||||
memoryGate := make(chan struct{}, redfishSnapshotMemoryConcurrency())
|
||||
postProbeClient := c.httpClientWithTimeout(req, redfishSnapshotPostProbeRequestTimeout())
|
||||
branchLimiter := newRedfishSnapshotBranchLimiter(redfishSnapshotBranchConcurrency())
|
||||
branchRetryPause := redfishSnapshotBranchRequeueBackoff()
|
||||
timings := newRedfishPathTimingCollector(4)
|
||||
@@ -1908,7 +1909,7 @@ func (c *RedfishConnector) collectRawRedfishTree(ctx context.Context, client *ht
|
||||
ETASeconds: int(estimateProgressETA(postProbeStart, i, len(postProbeCollections), 3*time.Second).Seconds()),
|
||||
})
|
||||
}
|
||||
for childPath, doc := range c.probeDirectRedfishCollectionChildren(ctx, client, req, baseURL, path) {
|
||||
for childPath, doc := range c.probeDirectRedfishCollectionChildren(ctx, postProbeClient, req, baseURL, path) {
|
||||
if _, exists := out[childPath]; exists {
|
||||
continue
|
||||
}
|
||||
@@ -2158,6 +2159,12 @@ func shouldAdaptivePostProbeCollectionPath(path string, collectionDoc map[string
|
||||
if len(memberRefs) == 0 {
|
||||
return true
|
||||
}
|
||||
// If the collection reports an explicit non-zero member count that already
|
||||
// matches the number of discovered member refs, every member is accounted
|
||||
// for and numeric probing cannot find anything new.
|
||||
if odataCount := asInt(collectionDoc["Members@odata.count"]); odataCount > 0 && odataCount == len(memberRefs) {
|
||||
return false
|
||||
}
|
||||
return redfishCollectionHasNumericMemberRefs(memberRefs)
|
||||
}
|
||||
|
||||
@@ -2350,6 +2357,18 @@ func redfishSnapshotRequestTimeout() time.Duration {
|
||||
return 12 * time.Second
|
||||
}
|
||||
|
||||
func redfishSnapshotPostProbeRequestTimeout() time.Duration {
|
||||
if v := strings.TrimSpace(os.Getenv("LOGPILE_REDFISH_POSTPROBE_TIMEOUT")); v != "" {
|
||||
if d, err := time.ParseDuration(v); err == nil && d > 0 {
|
||||
return d
|
||||
}
|
||||
}
|
||||
// Post-probe probes non-existent numeric paths expecting fast 404s.
|
||||
// A short timeout prevents BMCs that hang on unknown paths from stalling
|
||||
// the entire collection for minutes (e.g. HPE iLO on NetworkAdapters Ports).
|
||||
return 4 * time.Second
|
||||
}
|
||||
|
||||
func redfishSnapshotWorkers(tuning redfishprofile.AcquisitionTuning) int {
|
||||
if tuning.SnapshotWorkers >= 1 && tuning.SnapshotWorkers <= 16 {
|
||||
return tuning.SnapshotWorkers
|
||||
@@ -2852,6 +2871,8 @@ func shouldCrawlPath(path string) bool {
|
||||
"/GetServerAllUSBStatus",
|
||||
"/Oem/Public/KVM",
|
||||
"/SecureBoot/SecureBootDatabases",
|
||||
// HPE iLO WorkloadPerformanceAdvisor — operational/advisory data, not inventory.
|
||||
"/WorkloadPerformanceAdvisor",
|
||||
} {
|
||||
if strings.Contains(normalized, part) {
|
||||
return false
|
||||
@@ -5047,6 +5068,16 @@ func isVirtualStorageDrive(doc map[string]interface{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// isAbsentDriveDoc returns true when the drive document represents an empty bay
|
||||
// with no installed media (Status.State == "Absent"). These should be excluded
|
||||
// from the storage inventory.
|
||||
func isAbsentDriveDoc(doc map[string]interface{}) bool {
|
||||
if status, ok := doc["Status"].(map[string]interface{}); ok {
|
||||
return strings.EqualFold(asString(status["State"]), "Absent")
|
||||
}
|
||||
return strings.EqualFold(asString(doc["Status"]), "Absent")
|
||||
}
|
||||
|
||||
func looksLikeDrive(doc map[string]interface{}) bool {
|
||||
if asString(doc["MediaType"]) != "" {
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user