The RAID controller (XFusion XC170-M-8i / Broadcom SAS3808) never appeared in inventory at all: it isn't listed in any Chassis/Systems PCIeDevices collection on this BMC, and its dedicated Board resource link 404s (id contains parentheses, same class of bug as the OCP NIC fixed earlier). Its full identity -- model, firmware, BDF, vendor/device IDs -- was sitting unread in the Storage resource's embedded StorageControllers[] array the whole time. Added parseStorageControllerPCIeDevice + collectStorageControllers to read that array and surface the controller as a PCIeDevice entry, merged into the existing pcie_devices list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
213 lines
7.8 KiB
Go
213 lines
7.8 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")
|
|
supplementalDocs = append(supplementalDocs, r.getLinkedPCIeFunctions(driveDoc)...)
|
|
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")
|
|
supplementalDocs = append(supplementalDocs, r.getLinkedPCIeFunctions(driveDoc)...)
|
|
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")
|
|
supplementalDocs = append(supplementalDocs, r.getLinkedPCIeFunctions(driveDoc)...)
|
|
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if looksLikeDrive(member) {
|
|
if isAbsentDriveDoc(member) || isVirtualStorageDrive(member) {
|
|
continue
|
|
}
|
|
supplementalDocs := r.getLinkedSupplementalDocs(member, "DriveMetrics", "EnvironmentMetrics", "Metrics")
|
|
supplementalDocs = append(supplementalDocs, r.getLinkedPCIeFunctions(member)...)
|
|
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")
|
|
supplementalDocs = append(supplementalDocs, r.getLinkedPCIeFunctions(driveDoc)...)
|
|
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")
|
|
supplementalDocs = append(supplementalDocs, r.getLinkedPCIeFunctions(driveDoc)...)
|
|
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)
|
|
}
|
|
|
|
// collectStorageControllers surfaces RAID/HBA controller cards from the
|
|
// Storage.StorageControllers[] embedded array as PCIeDevice inventory
|
|
// entries. Some BMCs (e.g. xFusion) never expose the controller as its own
|
|
// PCIeDevice or Board resource -- the Board link can even 404 on IDs
|
|
// containing parentheses -- so without reading this array the RAID
|
|
// controller never appears in inventory even though its model/firmware/BDF
|
|
// identity is fully present here.
|
|
func (r redfishSnapshotReader) collectStorageControllers(systemPath string) []models.PCIeDevice {
|
|
var out []models.PCIeDevice
|
|
storageMembers, _ := r.getCollectionMembers(joinPath(systemPath, "/Storage"))
|
|
seen := make(map[string]struct{})
|
|
for _, member := range storageMembers {
|
|
controllers, ok := member["StorageControllers"].([]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, ctrlAny := range controllers {
|
|
ctrl, ok := ctrlAny.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
dev := parseStorageControllerPCIeDevice(ctrl)
|
|
if isUnidentifiablePCIeDevice(dev) {
|
|
continue
|
|
}
|
|
key := firstNonEmpty(dev.SerialNumber, dev.BDF, dev.Slot)
|
|
if key == "" {
|
|
continue
|
|
}
|
|
if _, dup := seen[key]; dup {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
out = append(out, dev)
|
|
}
|
|
}
|
|
return 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)
|
|
}
|