fix: dedup GPUs across multiple chassis PCIeDevice trees in Redfish collector
Supermicro HGX exposes each GPU under both Chassis/1/PCIeDevices and a dedicated Chassis/HGX_GPU_SXM_N/PCIeDevices. gpuDocDedupKey was keying by @odata.id path, so identical GPUs with the same serial were not deduplicated across sources. Now stable identifiers (serial → BDF → slot+model) take priority over path. Also includes Inspur parser improvements: NVMe model/serial enrichment from devicefrusdr.log and audit.log, RAID drive slot normalization to BP notation, PSU slot normalization, BMC/CPLD/VR firmware from RESTful version info section, and parser version bump to 1.8. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
39
internal/parser/vendors/inspur/parser.go
vendored
39
internal/parser/vendors/inspur/parser.go
vendored
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
// parserVersion - version of this parser module
|
||||
// IMPORTANT: Increment this version when making changes to parser logic!
|
||||
const parserVersion = "1.5"
|
||||
const parserVersion = "1.8"
|
||||
|
||||
func init() {
|
||||
parser.Register(&Parser{})
|
||||
@@ -95,9 +95,41 @@ func (p *Parser) Parse(files []parser.ExtractedFile) (*models.AnalysisResult, er
|
||||
Sensors: make([]models.SensorReading, 0),
|
||||
}
|
||||
|
||||
// Pre-parse enrichment maps from devicefrusdr.log for use inside ParseAssetJSON.
|
||||
// BMC does not populate HddInfo.ModelName or SerialNumber for NVMe drives.
|
||||
var pcieSlotDeviceNames map[int]string
|
||||
var nvmeLocToSlot map[int]int
|
||||
if f := parser.FindFileByName(files, "devicefrusdr.log"); f != nil {
|
||||
pcieSlotDeviceNames = ParsePCIeSlotDeviceNames(f.Content)
|
||||
nvmeLocToSlot = ParsePCIeNVMeLocToSlot(f.Content)
|
||||
}
|
||||
|
||||
// Parse NVMe serial numbers from audit.log: every disk SN change is logged there.
|
||||
// Combine with the NVMe loc→slot mapping to build pcieSlot→serial map.
|
||||
// Also parse RAID disk serials by backplane slot key (e.g. "BP0:0").
|
||||
var pcieSlotSerials map[int]string
|
||||
var raidSlotSerials map[string]string
|
||||
if f := parser.FindFileByName(files, "audit.log"); f != nil {
|
||||
if len(nvmeLocToSlot) > 0 {
|
||||
nvmeDiskSerials := ParseAuditLogNVMeSerials(f.Content)
|
||||
if len(nvmeDiskSerials) > 0 {
|
||||
pcieSlotSerials = make(map[int]string, len(nvmeDiskSerials))
|
||||
for diskNum, serial := range nvmeDiskSerials {
|
||||
if slot, ok := nvmeLocToSlot[diskNum]; ok {
|
||||
pcieSlotSerials[slot] = serial
|
||||
}
|
||||
}
|
||||
if len(pcieSlotSerials) == 0 {
|
||||
pcieSlotSerials = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
raidSlotSerials = ParseAuditLogRAIDSerials(f.Content)
|
||||
}
|
||||
|
||||
// Parse asset.json first (base hardware info)
|
||||
if f := parser.FindFileByName(files, "asset.json"); f != nil {
|
||||
if hw, err := ParseAssetJSON(f.Content); err == nil {
|
||||
if hw, err := ParseAssetJSON(f.Content, pcieSlotDeviceNames, pcieSlotSerials); err == nil {
|
||||
result.Hardware = hw
|
||||
}
|
||||
}
|
||||
@@ -182,6 +214,9 @@ func (p *Parser) Parse(files []parser.ExtractedFile) (*models.AnalysisResult, er
|
||||
if result.Hardware != nil {
|
||||
applyGPUStatusFromEvents(result.Hardware, result.Events)
|
||||
enrichStorageFromSerialFallbackFiles(files, result.Hardware)
|
||||
// Apply RAID disk serials from audit.log (authoritative: last non-NULL SN change).
|
||||
// These override redis/component.log serials which may be stale after disk replacement.
|
||||
applyRAIDSlotSerials(result.Hardware, raidSlotSerials)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
Reference in New Issue
Block a user