feat: adaptive BMC readiness check + ghost NIC dedup fix + empty collection plan-B retry
BMC readiness after power-on (waitForStablePoweredOnHost): - After initial 1m stabilization, poll BMC inventory readiness before collecting - Ready if MemorySummary.TotalSystemMemoryGiB > 0 OR PCIeDevices.Members non-empty - On failure: wait +60s, retry; on second failure: wait +120s, retry; then warn and proceed - Configurable via LOGPILE_REDFISH_BMC_READY_WAITS (default: 60s,120s) Empty critical collection plan-B retry (EnableEmptyCriticalCollectionRetry): - Hardware inventory collections that returned Members=[] are now re-probed in plan-B - Covers PCIeDevices, NetworkAdapters, Processors, Drives, Storage, EthernetInterfaces - Enabled by default in generic profile (applies to all vendors) Ghost NIC dedup fix (enrichNICsFromNetworkInterfaces): - NetworkInterface entries (e.g. Id=2) that don't match existing NIC slots are now resolved via Links.NetworkAdapter cross-reference to the real Chassis NIC - Prevents duplicate ghost entries (slot=2 "Network Device View") from appearing alongside real NICs (slot="RISER 5 slot 1 (7)") with the same MAC addresses Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,16 @@ func (r redfishSnapshotReader) enrichNICsFromNetworkInterfaces(nics *[]models.Ne
|
||||
continue
|
||||
}
|
||||
idx, ok := bySlot[strings.ToLower(strings.TrimSpace(slot))]
|
||||
if !ok {
|
||||
// The NetworkInterface Id (e.g. "2") may not match the display slot of
|
||||
// the real NIC that came from Chassis/NetworkAdapters (e.g. "RISER 5
|
||||
// slot 1 (7)"). Try to find the real NIC via the Links.NetworkAdapter
|
||||
// cross-reference before creating a ghost entry.
|
||||
if linkedIdx := r.findNICIndexByLinkedNetworkAdapter(iface, bySlot); linkedIdx >= 0 {
|
||||
idx = linkedIdx
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
*nics = append(*nics, models.NetworkAdapter{
|
||||
Slot: slot,
|
||||
@@ -178,6 +188,35 @@ func (r redfishSnapshotReader) collectBMCMAC(managerPaths []string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// findNICIndexByLinkedNetworkAdapter resolves a NetworkInterface document to an
|
||||
// existing NIC in bySlot by following Links.NetworkAdapter → the Chassis
|
||||
// NetworkAdapter doc → its slot label. Returns -1 if no match is found.
|
||||
func (r redfishSnapshotReader) findNICIndexByLinkedNetworkAdapter(iface map[string]interface{}, bySlot map[string]int) int {
|
||||
links, ok := iface["Links"].(map[string]interface{})
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
adapterRef, ok := links["NetworkAdapter"].(map[string]interface{})
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
adapterPath := normalizeRedfishPath(asString(adapterRef["@odata.id"]))
|
||||
if adapterPath == "" {
|
||||
return -1
|
||||
}
|
||||
adapterDoc, err := r.getJSON(adapterPath)
|
||||
if err != nil || len(adapterDoc) == 0 {
|
||||
return -1
|
||||
}
|
||||
adapterNIC := parseNIC(adapterDoc)
|
||||
if slot := strings.ToLower(strings.TrimSpace(adapterNIC.Slot)); slot != "" {
|
||||
if idx, ok := bySlot[slot]; ok {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// enrichNICMACsFromNetworkDeviceFunctions reads the NetworkDeviceFunctions
|
||||
// collection linked from a NetworkAdapter document and populates the NIC's
|
||||
// MACAddresses from each function's Ethernet.PermanentMACAddress / MACAddress.
|
||||
|
||||
Reference in New Issue
Block a user