Fix NVIDIA GPU/NVSwitch parsing and Reanimator export statuses

This commit is contained in:
2026-02-15 21:00:30 +03:00
parent 0af3cee9b6
commit c7b2a7ab29
12 changed files with 695 additions and 92 deletions

View File

@@ -27,8 +27,6 @@ func ConvertToReanimator(result *models.AnalysisResult) (*ReanimatorExport, erro
// Determine target host (optional field)
targetHost := inferTargetHost(result.TargetHost, result.Filename)
boardSerial := result.Hardware.BoardInfo.SerialNumber
export := &ReanimatorExport{
Filename: result.Filename,
SourceType: normalizeSourceType(result.SourceType),
@@ -41,7 +39,7 @@ func ConvertToReanimator(result *models.AnalysisResult) (*ReanimatorExport, erro
CPUs: convertCPUs(result.Hardware.CPUs),
Memory: convertMemory(result.Hardware.Memory),
Storage: convertStorage(result.Hardware.Storage),
PCIeDevices: convertPCIeDevices(result.Hardware, boardSerial),
PCIeDevices: convertPCIeDevices(result.Hardware),
PowerSupplies: convertPowerSupplies(result.Hardware.PowerSupply),
},
}
@@ -174,16 +172,12 @@ func convertStorage(storage []models.Storage) []ReanimatorStorage {
}
// convertPCIeDevices converts PCIe devices, GPUs, and network adapters to Reanimator format
func convertPCIeDevices(hw *models.HardwareConfig, boardSerial string) []ReanimatorPCIe {
func convertPCIeDevices(hw *models.HardwareConfig) []ReanimatorPCIe {
result := make([]ReanimatorPCIe, 0)
// Convert regular PCIe devices
for _, pcie := range hw.PCIeDevices {
serialNumber := pcie.SerialNumber
if serialNumber == "" || serialNumber == "N/A" {
// Generate serial number
serialNumber = generatePCIeSerialNumber(boardSerial, pcie.Slot, pcie.BDF)
}
serialNumber := normalizedSerial(pcie.SerialNumber)
// Determine model (prefer PartNumber, fallback to DeviceClass)
model := pcie.PartNumber
@@ -211,11 +205,7 @@ func convertPCIeDevices(hw *models.HardwareConfig, boardSerial string) []Reanima
// Convert GPUs as PCIe devices
for _, gpu := range hw.GPUs {
serialNumber := gpu.SerialNumber
if serialNumber == "" {
// Generate serial number
serialNumber = generatePCIeSerialNumber(boardSerial, gpu.Slot, gpu.BDF)
}
serialNumber := normalizedSerial(gpu.SerialNumber)
// Determine device class
deviceClass := "DisplayController"
@@ -244,11 +234,7 @@ func convertPCIeDevices(hw *models.HardwareConfig, boardSerial string) []Reanima
continue
}
serialNumber := nic.SerialNumber
if serialNumber == "" {
// Generate serial number
serialNumber = generatePCIeSerialNumber(boardSerial, nic.Slot, "")
}
serialNumber := normalizedSerial(nic.SerialNumber)
result = append(result, ReanimatorPCIe{
Slot: nic.Slot,
@@ -339,17 +325,17 @@ func inferCPUManufacturer(model string) string {
return ""
}
// generatePCIeSerialNumber generates a serial number for PCIe device
func generatePCIeSerialNumber(boardSerial, slot, bdf string) string {
if slot != "" {
return fmt.Sprintf("%s-PCIE-%s", boardSerial, slot)
func normalizedSerial(serial string) string {
s := strings.TrimSpace(serial)
if s == "" {
return ""
}
if bdf != "" {
// Use BDF as identifier (e.g., "0000:18:00.0" -> "0000-18-00-0")
safeBDF := strings.ReplaceAll(strings.ReplaceAll(bdf, ":", "-"), ".", "-")
return fmt.Sprintf("%s-PCIE-%s", boardSerial, safeBDF)
switch strings.ToUpper(s) {
case "N/A", "NA", "NONE", "NULL", "UNKNOWN", "-":
return ""
default:
return s
}
return fmt.Sprintf("%s-PCIE-UNKNOWN", boardSerial)
}
// inferStorageStatus determines storage device status
@@ -392,10 +378,14 @@ func normalizeStatus(status string, allowEmpty bool) string {
switch strings.ToLower(strings.TrimSpace(status)) {
case "ok":
return "OK"
case "pass":
return "OK"
case "warning":
return "Warning"
case "critical":
return "Critical"
case "fail":
return "Critical"
case "unknown":
return "Unknown"
case "empty":

View File

@@ -111,42 +111,39 @@ func TestInferCPUManufacturer(t *testing.T) {
}
}
func TestGeneratePCIeSerialNumber(t *testing.T) {
func TestNormalizedSerial(t *testing.T) {
tests := []struct {
name string
boardSerial string
slot string
bdf string
want string
name string
in string
want string
}{
{
name: "with slot",
boardSerial: "TEST123",
slot: "PCIeCard1",
bdf: "0000:18:00.0",
want: "TEST123-PCIE-PCIeCard1",
name: "empty",
in: "",
want: "",
},
{
name: "without slot, with bdf",
boardSerial: "TEST123",
slot: "",
bdf: "0000:18:00.0",
want: "TEST123-PCIE-0000-18-00-0",
name: "n_a",
in: "N/A",
want: "",
},
{
name: "without slot and bdf",
boardSerial: "TEST123",
slot: "",
bdf: "",
want: "TEST123-PCIE-UNKNOWN",
name: "unknown",
in: "unknown",
want: "",
},
{
name: "normal",
in: "SN123",
want: "SN123",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := generatePCIeSerialNumber(tt.boardSerial, tt.slot, tt.bdf)
got := normalizedSerial(tt.in)
if got != tt.want {
t.Errorf("generatePCIeSerialNumber() = %q, want %q", got, tt.want)
t.Errorf("normalizedSerial() = %q, want %q", got, tt.want)
}
})
}
@@ -184,6 +181,15 @@ func TestInferStorageStatus(t *testing.T) {
}
}
func TestNormalizeStatus_PassFail(t *testing.T) {
if got := normalizeStatus("PASS", false); got != "OK" {
t.Fatalf("expected PASS -> OK, got %q", got)
}
if got := normalizeStatus("FAIL", false); got != "Critical" {
t.Fatalf("expected FAIL -> Critical, got %q", got)
}
}
func TestConvertCPUs(t *testing.T) {
cpus := []models.CPU{
{
@@ -323,17 +329,16 @@ func TestConvertPCIeDevices(t *testing.T) {
},
}
boardSerial := "TEST123"
result := convertPCIeDevices(hw, boardSerial)
result := convertPCIeDevices(hw)
// Should have: 2 PCIe devices + 1 GPU + 1 NIC = 4 total
if len(result) != 4 {
t.Fatalf("expected 4 PCIe devices total, got %d", len(result))
}
// Check that serial was generated for second PCIe device
if result[1].SerialNumber != "TEST123-PCIE-PCIeCard2" {
t.Errorf("expected generated serial TEST123-PCIE-PCIeCard2, got %q", result[1].SerialNumber)
// Check that serial is empty for second PCIe device (no auto-generation)
if result[1].SerialNumber != "" {
t.Errorf("expected empty serial for missing device serial, got %q", result[1].SerialNumber)
}
// Check GPU was included
@@ -352,6 +357,29 @@ func TestConvertPCIeDevices(t *testing.T) {
}
}
func TestConvertPCIeDevices_NVSwitchWithoutSerialRemainsEmpty(t *testing.T) {
hw := &models.HardwareConfig{
PCIeDevices: []models.PCIeDevice{
{
Slot: "NVSWITCH1",
DeviceClass: "NVSwitch",
BDF: "0000:06:00.0",
// SerialNumber empty on purpose; should remain empty.
},
},
}
result := convertPCIeDevices(hw)
if len(result) != 1 {
t.Fatalf("expected 1 PCIe device, got %d", len(result))
}
if result[0].SerialNumber != "" {
t.Fatalf("expected empty NVSwitch serial, got %q", result[0].SerialNumber)
}
}
func TestConvertPowerSupplies(t *testing.T) {
psus := []models.PSU{
{