Add PCI Vendor ID / Device ID to hardware.storage[] per the updated Reanimator ingest contract, mirroring the existing pcie_devices[] fields. Populated for Redfish-collected NVMe drives (live + TSR replay, via linked PCIeFunctions) and for Inspur (from asset.json's own PcieInfo[], joined by PcieSlot). Also fixes canonicalDevicesForExport dropping the fields when converting Storage into the canonical HardwareDevice list used by the actual export path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
174 lines
6.6 KiB
Go
174 lines
6.6 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)
|
|
}
|
|
|
|
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)
|
|
}
|