- 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>
168 lines
6.0 KiB
Go
168 lines
6.0 KiB
Go
package collector
|
|
|
|
import (
|
|
"git.mchus.pro/mchus/logpile/internal/collector/redfishprofile"
|
|
"git.mchus.pro/mchus/logpile/internal/models"
|
|
)
|
|
|
|
func (r redfishSnapshotReader) collectStorage(systemPath string, plan redfishprofile.ResolvedAnalysisPlan) []models.Storage {
|
|
var out []models.Storage
|
|
storageMembers, _ := r.getCollectionMembers(joinPath(systemPath, "/Storage"))
|
|
for _, member := range storageMembers {
|
|
if driveCollection, ok := member["Drives"].(map[string]interface{}); ok {
|
|
if driveCollectionPath := asString(driveCollection["@odata.id"]); driveCollectionPath != "" {
|
|
driveDocs, err := r.getCollectionMembers(driveCollectionPath)
|
|
if err == nil {
|
|
for _, driveDoc := range driveDocs {
|
|
if !isAbsentDriveDoc(driveDoc) && !isVirtualStorageDrive(driveDoc) {
|
|
supplementalDocs := r.getLinkedSupplementalDocs(driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
|
|
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
|
|
}
|
|
}
|
|
if len(driveDocs) == 0 {
|
|
for _, driveDoc := range r.probeDirectDiskBayChildren(driveCollectionPath) {
|
|
if isAbsentDriveDoc(driveDoc) {
|
|
continue
|
|
}
|
|
supplementalDocs := r.getLinkedSupplementalDocs(driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
|
|
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
|
|
}
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
if drives, ok := member["Drives"].([]interface{}); ok {
|
|
for _, driveAny := range drives {
|
|
driveRef, ok := driveAny.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
odata := asString(driveRef["@odata.id"])
|
|
if odata == "" {
|
|
continue
|
|
}
|
|
driveDoc, err := r.getJSON(odata)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if !isAbsentDriveDoc(driveDoc) && !isVirtualStorageDrive(driveDoc) {
|
|
supplementalDocs := r.getLinkedSupplementalDocs(driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
|
|
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if looksLikeDrive(member) {
|
|
if isAbsentDriveDoc(member) || isVirtualStorageDrive(member) {
|
|
continue
|
|
}
|
|
supplementalDocs := r.getLinkedSupplementalDocs(member, "DriveMetrics", "EnvironmentMetrics", "Metrics")
|
|
out = append(out, parseDriveWithSupplementalDocs(member, supplementalDocs...))
|
|
}
|
|
|
|
if plan.Directives.EnableStorageEnclosureRecovery {
|
|
for _, enclosurePath := range redfishLinkRefs(member, "Links", "Enclosures") {
|
|
driveDocs, err := r.getCollectionMembers(joinPath(enclosurePath, "/Drives"))
|
|
if err == nil {
|
|
for _, driveDoc := range driveDocs {
|
|
if looksLikeDrive(driveDoc) && !isAbsentDriveDoc(driveDoc) && !isVirtualStorageDrive(driveDoc) {
|
|
supplementalDocs := r.getLinkedSupplementalDocs(driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
|
|
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
|
|
}
|
|
}
|
|
if len(driveDocs) == 0 {
|
|
for _, driveDoc := range r.probeDirectDiskBayChildren(joinPath(enclosurePath, "/Drives")) {
|
|
if isAbsentDriveDoc(driveDoc) || isVirtualStorageDrive(driveDoc) {
|
|
continue
|
|
}
|
|
out = append(out, parseDrive(driveDoc))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(plan.KnownStorageDriveCollections) > 0 {
|
|
for _, driveDoc := range r.collectKnownStorageMembers(systemPath, plan.KnownStorageDriveCollections) {
|
|
if looksLikeDrive(driveDoc) && !isAbsentDriveDoc(driveDoc) && !isVirtualStorageDrive(driveDoc) {
|
|
supplementalDocs := r.getLinkedSupplementalDocs(driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
|
|
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
|
|
}
|
|
}
|
|
}
|
|
|
|
simpleStorageMembers, _ := r.getCollectionMembers(joinPath(systemPath, "/SimpleStorage"))
|
|
for _, member := range simpleStorageMembers {
|
|
devices, ok := member["Devices"].([]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, devAny := range devices {
|
|
devDoc, ok := devAny.(map[string]interface{})
|
|
if !ok || !looksLikeDrive(devDoc) || isAbsentDriveDoc(devDoc) || isVirtualStorageDrive(devDoc) {
|
|
continue
|
|
}
|
|
out = append(out, parseDrive(devDoc))
|
|
}
|
|
}
|
|
|
|
chassisPaths := r.discoverMemberPaths("/redfish/v1/Chassis", "/redfish/v1/Chassis/1")
|
|
for _, chassisPath := range chassisPaths {
|
|
driveDocs, err := r.getCollectionMembers(joinPath(chassisPath, "/Drives"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, driveDoc := range driveDocs {
|
|
if !looksLikeDrive(driveDoc) || isAbsentDriveDoc(driveDoc) || isVirtualStorageDrive(driveDoc) {
|
|
continue
|
|
}
|
|
out = append(out, parseDrive(driveDoc))
|
|
}
|
|
}
|
|
if plan.Directives.EnableSupermicroNVMeBackplane {
|
|
for _, chassisPath := range chassisPaths {
|
|
if !isSupermicroNVMeBackplanePath(chassisPath) {
|
|
continue
|
|
}
|
|
for _, driveDoc := range r.probeSupermicroNVMeDiskBays(chassisPath) {
|
|
if !looksLikeDrive(driveDoc) || isAbsentDriveDoc(driveDoc) || isVirtualStorageDrive(driveDoc) {
|
|
continue
|
|
}
|
|
out = append(out, parseDrive(driveDoc))
|
|
}
|
|
}
|
|
}
|
|
return dedupeStorage(out)
|
|
}
|
|
|
|
func (r redfishSnapshotReader) collectStorageVolumes(systemPath string, plan redfishprofile.ResolvedAnalysisPlan) []models.StorageVolume {
|
|
var out []models.StorageVolume
|
|
storageMembers, _ := r.getCollectionMembers(joinPath(systemPath, "/Storage"))
|
|
for _, member := range storageMembers {
|
|
controller := firstNonEmpty(asString(member["Id"]), asString(member["Name"]))
|
|
volumeCollectionPath := redfishLinkedPath(member, "Volumes")
|
|
if volumeCollectionPath == "" {
|
|
continue
|
|
}
|
|
volumeDocs, err := r.getCollectionMembers(volumeCollectionPath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, volDoc := range volumeDocs {
|
|
if looksLikeVolume(volDoc) {
|
|
out = append(out, parseStorageVolume(volDoc, controller))
|
|
}
|
|
}
|
|
}
|
|
if len(plan.KnownStorageVolumeCollections) > 0 {
|
|
for _, volDoc := range r.collectKnownStorageMembers(systemPath, plan.KnownStorageVolumeCollections) {
|
|
if looksLikeVolume(volDoc) {
|
|
out = append(out, parseStorageVolume(volDoc, storageControllerFromPath(asString(volDoc["@odata.id"]))))
|
|
}
|
|
}
|
|
}
|
|
return dedupeStorageVolumes(out)
|
|
}
|