feat(exporter): collect and export storage.vendor_id/device_id (contract v2.13)

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>
This commit is contained in:
Mikhail Chusavitin
2026-08-11 16:04:54 +03:00
co-authored by Claude Sonnet 5
parent 063587958e
commit 399eca5f49
11 changed files with 221 additions and 4 deletions
+24
View File
@@ -813,6 +813,7 @@ func (c *RedfishConnector) collectStorage(ctx context.Context, client *http.Clie
continue
}
supplementalDocs := c.getLinkedSupplementalDocs(ctx, client, req, baseURL, driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, c.getLinkedPCIeFunctions(ctx, client, req, baseURL, driveDoc)...)
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
}
if len(driveDocs) == 0 {
@@ -821,6 +822,7 @@ func (c *RedfishConnector) collectStorage(ctx context.Context, client *http.Clie
continue
}
supplementalDocs := c.getLinkedSupplementalDocs(ctx, client, req, baseURL, driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, c.getLinkedPCIeFunctions(ctx, client, req, baseURL, driveDoc)...)
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
}
}
@@ -846,6 +848,7 @@ func (c *RedfishConnector) collectStorage(ctx context.Context, client *http.Clie
continue
}
supplementalDocs := c.getLinkedSupplementalDocs(ctx, client, req, baseURL, driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, c.getLinkedPCIeFunctions(ctx, client, req, baseURL, driveDoc)...)
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
}
continue
@@ -857,6 +860,7 @@ func (c *RedfishConnector) collectStorage(ctx context.Context, client *http.Clie
continue
}
supplementalDocs := c.getLinkedSupplementalDocs(ctx, client, req, baseURL, member, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, c.getLinkedPCIeFunctions(ctx, client, req, baseURL, member)...)
out = append(out, parseDriveWithSupplementalDocs(member, supplementalDocs...))
}
@@ -868,6 +872,7 @@ func (c *RedfishConnector) collectStorage(ctx context.Context, client *http.Clie
for _, driveDoc := range driveDocs {
if looksLikeDrive(driveDoc) && !isVirtualStorageDrive(driveDoc) {
supplementalDocs := c.getLinkedSupplementalDocs(ctx, client, req, baseURL, driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, c.getLinkedPCIeFunctions(ctx, client, req, baseURL, driveDoc)...)
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
}
}
@@ -890,6 +895,7 @@ func (c *RedfishConnector) collectStorage(ctx context.Context, client *http.Clie
}) {
if looksLikeDrive(driveDoc) && !isVirtualStorageDrive(driveDoc) {
supplementalDocs := c.getLinkedSupplementalDocs(ctx, client, req, baseURL, driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, c.getLinkedPCIeFunctions(ctx, client, req, baseURL, driveDoc)...)
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
}
}
@@ -922,6 +928,7 @@ func (c *RedfishConnector) collectStorage(ctx context.Context, client *http.Clie
continue
}
supplementalDocs := c.getLinkedSupplementalDocs(ctx, client, req, baseURL, driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, c.getLinkedPCIeFunctions(ctx, client, req, baseURL, driveDoc)...)
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
}
}
@@ -934,6 +941,7 @@ func (c *RedfishConnector) collectStorage(ctx context.Context, client *http.Clie
continue
}
supplementalDocs := c.getLinkedSupplementalDocs(ctx, client, req, baseURL, driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, c.getLinkedPCIeFunctions(ctx, client, req, baseURL, driveDoc)...)
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
}
}
@@ -3495,11 +3503,27 @@ func parseDriveWithSupplementalDocs(doc map[string]interface{}, supplementalDocs
storageType := classifyStorageType(doc)
// Drive resources rarely carry PCI Vendor/Device IDs directly; a linked
// PCIeFunction document (passed here as part of supplementalDocs, e.g. via
// getLinkedPCIeFunctions) usually does for NVMe drives.
vendorID := asHexOrInt(doc["VendorId"])
deviceID := asHexOrInt(doc["DeviceId"])
for _, sd := range supplementalDocs {
if vendorID == 0 {
vendorID = asHexOrInt(sd["VendorId"])
}
if deviceID == 0 {
deviceID = asHexOrInt(sd["DeviceId"])
}
}
slot := normalizeRAIDDriveSlot(firstNonEmpty(asString(doc["Id"]), asString(doc["Name"])))
return models.Storage{
Slot: slot,
Type: storageType,
Model: firstNonEmpty(asString(doc["Model"]), asString(doc["Name"])),
VendorID: vendorID,
DeviceID: deviceID,
SizeGB: sizeGB,
SerialNumber: findFirstNormalizedStringByKeys(doc, "SerialNumber"),
Manufacturer: asString(doc["Manufacturer"]),
@@ -16,6 +16,7 @@ func (r redfishSnapshotReader) collectStorage(systemPath string, plan redfishpro
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...))
}
}
@@ -25,6 +26,7 @@ func (r redfishSnapshotReader) collectStorage(systemPath string, plan redfishpro
continue
}
supplementalDocs := r.getLinkedSupplementalDocs(driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, r.getLinkedPCIeFunctions(driveDoc)...)
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
}
}
@@ -48,6 +50,7 @@ func (r redfishSnapshotReader) collectStorage(systemPath string, plan redfishpro
}
if !isAbsentDriveDoc(driveDoc) && !isVirtualStorageDrive(driveDoc) {
supplementalDocs := r.getLinkedSupplementalDocs(driveDoc, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, r.getLinkedPCIeFunctions(driveDoc)...)
out = append(out, parseDriveWithSupplementalDocs(driveDoc, supplementalDocs...))
}
}
@@ -58,6 +61,7 @@ func (r redfishSnapshotReader) collectStorage(systemPath string, plan redfishpro
continue
}
supplementalDocs := r.getLinkedSupplementalDocs(member, "DriveMetrics", "EnvironmentMetrics", "Metrics")
supplementalDocs = append(supplementalDocs, r.getLinkedPCIeFunctions(member)...)
out = append(out, parseDriveWithSupplementalDocs(member, supplementalDocs...))
}
@@ -68,6 +72,7 @@ func (r redfishSnapshotReader) collectStorage(systemPath string, plan redfishpro
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...))
}
}
@@ -88,6 +93,7 @@ func (r redfishSnapshotReader) collectStorage(systemPath string, plan redfishpro
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...))
}
}
+14
View File
@@ -1933,6 +1933,20 @@ func TestParseComponentDetails_UseLinkedSupplementalMetrics(t *testing.T) {
t.Fatalf("expected drive details from supplemental metrics, got %#v", drive.Details)
}
driveWithPCIeFn := parseDriveWithSupplementalDocs(
map[string]interface{}{
"Id": "Drive1",
"SerialNumber": "NVME-001",
},
map[string]interface{}{
"VendorId": "0x144D",
"DeviceId": "0xA808",
},
)
if driveWithPCIeFn.VendorID != 0x144D || driveWithPCIeFn.DeviceID != 0xA808 {
t.Fatalf("expected vendor/device id from linked PCIeFunction doc, got vendor=%#x device=%#x", driveWithPCIeFn.VendorID, driveWithPCIeFn.DeviceID)
}
psu := parsePSUWithSupplementalDocs(
map[string]interface{}{
"MemberId": "PSU0",