unraid: parse dimm/nic/pcie and annotate duplicate serials
This commit is contained in:
@@ -300,7 +300,7 @@ func BuildHardwareDevices(hw *models.HardwareConfig) []models.HardwareDevice {
|
||||
})
|
||||
}
|
||||
|
||||
return dedupeDevices(all)
|
||||
return annotateDuplicateSerials(dedupeDevices(all))
|
||||
}
|
||||
|
||||
func isEmptyPCIeDevice(p models.PCIeDevice) bool {
|
||||
@@ -443,9 +443,22 @@ func shouldMergeDevices(a, b models.HardwareDevice) bool {
|
||||
bSN := strings.ToLower(normalizedSerial(b.SerialNumber))
|
||||
aBDF := strings.ToLower(strings.TrimSpace(a.BDF))
|
||||
bBDF := strings.ToLower(strings.TrimSpace(b.BDF))
|
||||
aSlot := normalizeSlot(a.Slot)
|
||||
bSlot := normalizeSlot(b.Slot)
|
||||
|
||||
// Memory DIMMs can legitimately share serial number in some dumps.
|
||||
// Never merge DIMMs with different slots.
|
||||
if a.Kind == models.DeviceKindMemory && b.Kind == models.DeviceKindMemory {
|
||||
if aSlot != "" && bSlot != "" && aSlot != bSlot {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Hard conflicts.
|
||||
if aSN != "" && bSN != "" && aSN == bSN {
|
||||
if a.Kind == models.DeviceKindMemory && b.Kind == models.DeviceKindMemory {
|
||||
return aSlot != "" && bSlot != "" && aSlot == bSlot
|
||||
}
|
||||
return true
|
||||
}
|
||||
if aSN != "" && bSN != "" && aSN != bSN {
|
||||
@@ -465,7 +478,7 @@ func shouldMergeDevices(a, b models.HardwareDevice) bool {
|
||||
if hasMACOverlap(a.MACAddresses, b.MACAddresses) {
|
||||
return true
|
||||
}
|
||||
if normalizeSlot(a.Slot) != "" && normalizeSlot(a.Slot) == normalizeSlot(b.Slot) {
|
||||
if aSlot != "" && aSlot == bSlot {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -481,7 +494,7 @@ func shouldMergeDevices(a, b models.HardwareDevice) bool {
|
||||
if sameManufacturer(a, b) {
|
||||
score += 2
|
||||
}
|
||||
if normalizeSlot(a.Slot) != "" && normalizeSlot(a.Slot) == normalizeSlot(b.Slot) {
|
||||
if aSlot != "" && aSlot == bSlot {
|
||||
score += 2
|
||||
}
|
||||
if hasMACOverlap(a.MACAddresses, b.MACAddresses) {
|
||||
@@ -732,3 +745,35 @@ func buildFirmwareBySlot(firmware []models.FirmwareInfo) map[string]slotFirmware
|
||||
func normalizeSlotKey(slot string) string {
|
||||
return strings.ToLower(strings.TrimSpace(slot))
|
||||
}
|
||||
|
||||
func annotateDuplicateSerials(items []models.HardwareDevice) []models.HardwareDevice {
|
||||
if len(items) < 2 {
|
||||
return items
|
||||
}
|
||||
|
||||
countByKindSerial := make(map[string]int)
|
||||
for _, d := range items {
|
||||
serial := normalizedSerial(d.SerialNumber)
|
||||
if serial == "" {
|
||||
continue
|
||||
}
|
||||
key := d.Kind + "|" + strings.ToLower(serial)
|
||||
countByKindSerial[key]++
|
||||
}
|
||||
|
||||
seenByKindSerial := make(map[string]int)
|
||||
for i := range items {
|
||||
serial := normalizedSerial(items[i].SerialNumber)
|
||||
if serial == "" {
|
||||
continue
|
||||
}
|
||||
key := items[i].Kind + "|" + strings.ToLower(serial)
|
||||
if countByKindSerial[key] < 2 {
|
||||
continue
|
||||
}
|
||||
seenByKindSerial[key]++
|
||||
items[i].SerialNumber = serial + " (DUP#" + strconv.Itoa(seenByKindSerial[key]) + ")"
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -65,6 +65,54 @@ func TestBuildHardwareDevices_SkipsEmptyMemorySlots(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHardwareDevices_MemorySameSerialDifferentSlots_NotDeduped(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
Memory: []models.MemoryDIMM{
|
||||
{Slot: "Node0_Dimm1", Location: "Node0_Bank0", Present: true, SizeMB: 16384, SerialNumber: "238F7649", PartNumber: "M393B2G70BH0-"},
|
||||
{Slot: "Node0_Dimm3", Location: "Node0_Bank0", Present: true, SizeMB: 16384, SerialNumber: "238F7649", PartNumber: "M393B2G70BH0-"},
|
||||
},
|
||||
}
|
||||
|
||||
devices := BuildHardwareDevices(hw)
|
||||
memorySlots := make(map[string]bool)
|
||||
for _, d := range devices {
|
||||
if d.Kind != models.DeviceKindMemory {
|
||||
continue
|
||||
}
|
||||
memorySlots[d.Slot] = true
|
||||
}
|
||||
|
||||
if len(memorySlots) != 2 {
|
||||
t.Fatalf("expected 2 memory devices, got %d", len(memorySlots))
|
||||
}
|
||||
if !memorySlots["Node0_Dimm1"] || !memorySlots["Node0_Dimm3"] {
|
||||
t.Fatalf("expected both Node0_Dimm1 and Node0_Dimm3 to remain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHardwareDevices_DuplicateSerials_AreAnnotated(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
Memory: []models.MemoryDIMM{
|
||||
{Slot: "A1", Location: "BANK0", Present: true, SizeMB: 16384, SerialNumber: "SN-1"},
|
||||
{Slot: "A2", Location: "BANK1", Present: true, SizeMB: 16384, SerialNumber: "SN-1"},
|
||||
},
|
||||
}
|
||||
|
||||
devices := BuildHardwareDevices(hw)
|
||||
var serials []string
|
||||
for _, d := range devices {
|
||||
if d.Kind == models.DeviceKindMemory {
|
||||
serials = append(serials, d.SerialNumber)
|
||||
}
|
||||
}
|
||||
if len(serials) != 2 {
|
||||
t.Fatalf("expected 2 memory devices, got %d", len(serials))
|
||||
}
|
||||
if serials[0] != "SN-1 (DUP#1)" || serials[1] != "SN-1 (DUP#2)" {
|
||||
t.Fatalf("unexpected annotated serials: %+v", serials)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHardwareDevices_DedupCrossKindByBDF(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
PCIeDevices: []models.PCIeDevice{
|
||||
|
||||
Reference in New Issue
Block a user