Introduce canonical hardware.devices repository and align UI/Reanimator exports
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -31,6 +32,7 @@ func ConvertToReanimator(result *models.AnalysisResult) (*ReanimatorExport, erro
|
||||
targetHost := inferTargetHost(result.TargetHost, result.Filename)
|
||||
|
||||
collectedAt := formatRFC3339(result.CollectedAt)
|
||||
devices := canonicalDevicesForExport(result.Hardware)
|
||||
|
||||
export := &ReanimatorExport{
|
||||
Filename: result.Filename,
|
||||
@@ -41,11 +43,11 @@ func ConvertToReanimator(result *models.AnalysisResult) (*ReanimatorExport, erro
|
||||
Hardware: ReanimatorHardware{
|
||||
Board: convertBoard(result.Hardware.BoardInfo),
|
||||
Firmware: dedupeFirmware(convertFirmware(result.Hardware.Firmware)),
|
||||
CPUs: dedupeCPUs(convertCPUs(result.Hardware.CPUs, collectedAt)),
|
||||
Memory: dedupeMemory(convertMemory(result.Hardware.Memory, collectedAt)),
|
||||
Storage: dedupeStorage(convertStorage(result.Hardware.Storage, collectedAt)),
|
||||
PCIeDevices: dedupePCIe(convertPCIeDevices(result.Hardware, collectedAt)),
|
||||
PowerSupplies: dedupePSUs(convertPowerSupplies(result.Hardware.PowerSupply, collectedAt)),
|
||||
CPUs: convertCPUsFromDevices(devices, collectedAt),
|
||||
Memory: convertMemoryFromDevices(devices, collectedAt),
|
||||
Storage: convertStorageFromDevices(devices, collectedAt),
|
||||
PCIeDevices: convertPCIeFromDevices(devices, collectedAt),
|
||||
PowerSupplies: convertPSUsFromDevices(devices, collectedAt),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -71,6 +73,269 @@ func convertBoard(board models.BoardInfo) ReanimatorBoard {
|
||||
}
|
||||
}
|
||||
|
||||
func canonicalDevicesForExport(hw *models.HardwareConfig) []models.HardwareDevice {
|
||||
if hw == nil {
|
||||
return nil
|
||||
}
|
||||
if len(hw.Devices) > 0 {
|
||||
return hw.Devices
|
||||
}
|
||||
hw.Devices = buildDevicesFromLegacy(hw)
|
||||
return hw.Devices
|
||||
}
|
||||
|
||||
func buildDevicesFromLegacy(hw *models.HardwareConfig) []models.HardwareDevice {
|
||||
if hw == nil {
|
||||
return nil
|
||||
}
|
||||
all := make([]models.HardwareDevice, 0, len(hw.CPUs)+len(hw.Memory)+len(hw.Storage)+len(hw.PCIeDevices)+len(hw.GPUs)+len(hw.NetworkAdapters)+len(hw.PowerSupply))
|
||||
appendDevice := func(d models.HardwareDevice) {
|
||||
all = append(all, d)
|
||||
}
|
||||
|
||||
for _, cpu := range hw.CPUs {
|
||||
appendDevice(models.HardwareDevice{
|
||||
Kind: models.DeviceKindCPU,
|
||||
Slot: fmt.Sprintf("CPU%d", cpu.Socket),
|
||||
Model: cpu.Model,
|
||||
SerialNumber: cpu.SerialNumber,
|
||||
Cores: cpu.Cores,
|
||||
Threads: cpu.Threads,
|
||||
FrequencyMHz: cpu.FrequencyMHz,
|
||||
MaxFreqMHz: cpu.MaxFreqMHz,
|
||||
Status: cpu.Status,
|
||||
StatusCheckedAt: cpu.StatusCheckedAt,
|
||||
StatusChangedAt: cpu.StatusChangedAt,
|
||||
StatusAtCollect: cpu.StatusAtCollect,
|
||||
StatusHistory: cpu.StatusHistory,
|
||||
ErrorDescription: cpu.ErrorDescription,
|
||||
Details: map[string]any{
|
||||
"socket": cpu.Socket,
|
||||
},
|
||||
})
|
||||
}
|
||||
for _, mem := range hw.Memory {
|
||||
if !mem.Present || mem.SizeMB == 0 {
|
||||
continue
|
||||
}
|
||||
present := mem.Present
|
||||
appendDevice(models.HardwareDevice{
|
||||
Kind: models.DeviceKindMemory,
|
||||
Slot: mem.Slot,
|
||||
Location: mem.Location,
|
||||
Manufacturer: mem.Manufacturer,
|
||||
SerialNumber: mem.SerialNumber,
|
||||
PartNumber: mem.PartNumber,
|
||||
Type: mem.Type,
|
||||
Present: &present,
|
||||
SizeMB: mem.SizeMB,
|
||||
Status: mem.Status,
|
||||
StatusCheckedAt: mem.StatusCheckedAt,
|
||||
StatusChangedAt: mem.StatusChangedAt,
|
||||
StatusAtCollect: mem.StatusAtCollect,
|
||||
StatusHistory: mem.StatusHistory,
|
||||
ErrorDescription: mem.ErrorDescription,
|
||||
Details: map[string]any{
|
||||
"max_speed_mhz": mem.MaxSpeedMHz,
|
||||
"current_speed_mhz": mem.CurrentSpeedMHz,
|
||||
},
|
||||
})
|
||||
}
|
||||
for _, stor := range hw.Storage {
|
||||
if !stor.Present {
|
||||
continue
|
||||
}
|
||||
present := stor.Present
|
||||
appendDevice(models.HardwareDevice{
|
||||
Kind: models.DeviceKindStorage,
|
||||
Slot: stor.Slot,
|
||||
Model: stor.Model,
|
||||
Manufacturer: stor.Manufacturer,
|
||||
SerialNumber: stor.SerialNumber,
|
||||
Firmware: stor.Firmware,
|
||||
Type: stor.Type,
|
||||
Interface: stor.Interface,
|
||||
Present: &present,
|
||||
SizeGB: stor.SizeGB,
|
||||
Status: stor.Status,
|
||||
StatusCheckedAt: stor.StatusCheckedAt,
|
||||
StatusChangedAt: stor.StatusChangedAt,
|
||||
StatusAtCollect: stor.StatusAtCollect,
|
||||
StatusHistory: stor.StatusHistory,
|
||||
ErrorDescription: stor.ErrorDescription,
|
||||
})
|
||||
}
|
||||
for _, pcie := range hw.PCIeDevices {
|
||||
appendDevice(models.HardwareDevice{
|
||||
Kind: models.DeviceKindPCIe,
|
||||
Slot: pcie.Slot,
|
||||
BDF: pcie.BDF,
|
||||
DeviceClass: pcie.DeviceClass,
|
||||
VendorID: pcie.VendorID,
|
||||
DeviceID: pcie.DeviceID,
|
||||
Model: pcie.PartNumber,
|
||||
PartNumber: pcie.PartNumber,
|
||||
Manufacturer: pcie.Manufacturer,
|
||||
SerialNumber: pcie.SerialNumber,
|
||||
LinkWidth: pcie.LinkWidth,
|
||||
LinkSpeed: pcie.LinkSpeed,
|
||||
MaxLinkWidth: pcie.MaxLinkWidth,
|
||||
MaxLinkSpeed: pcie.MaxLinkSpeed,
|
||||
Status: pcie.Status,
|
||||
StatusCheckedAt: pcie.StatusCheckedAt,
|
||||
StatusChangedAt: pcie.StatusChangedAt,
|
||||
StatusAtCollect: pcie.StatusAtCollect,
|
||||
StatusHistory: pcie.StatusHistory,
|
||||
ErrorDescription: pcie.ErrorDescription,
|
||||
})
|
||||
}
|
||||
for _, gpu := range hw.GPUs {
|
||||
appendDevice(models.HardwareDevice{
|
||||
Kind: models.DeviceKindGPU,
|
||||
Slot: gpu.Slot,
|
||||
BDF: gpu.BDF,
|
||||
DeviceClass: "DisplayController",
|
||||
VendorID: gpu.VendorID,
|
||||
DeviceID: gpu.DeviceID,
|
||||
Model: gpu.Model,
|
||||
PartNumber: gpu.PartNumber,
|
||||
Manufacturer: gpu.Manufacturer,
|
||||
SerialNumber: gpu.SerialNumber,
|
||||
Firmware: gpu.Firmware,
|
||||
LinkWidth: gpu.CurrentLinkWidth,
|
||||
LinkSpeed: gpu.CurrentLinkSpeed,
|
||||
MaxLinkWidth: gpu.MaxLinkWidth,
|
||||
MaxLinkSpeed: gpu.MaxLinkSpeed,
|
||||
Status: gpu.Status,
|
||||
StatusCheckedAt: gpu.StatusCheckedAt,
|
||||
StatusChangedAt: gpu.StatusChangedAt,
|
||||
StatusAtCollect: gpu.StatusAtCollect,
|
||||
StatusHistory: gpu.StatusHistory,
|
||||
ErrorDescription: gpu.ErrorDescription,
|
||||
})
|
||||
}
|
||||
for _, nic := range hw.NetworkAdapters {
|
||||
if !nic.Present {
|
||||
continue
|
||||
}
|
||||
present := nic.Present
|
||||
appendDevice(models.HardwareDevice{
|
||||
Kind: models.DeviceKindNetwork,
|
||||
Slot: nic.Slot,
|
||||
Location: nic.Location,
|
||||
VendorID: nic.VendorID,
|
||||
DeviceID: nic.DeviceID,
|
||||
Model: nic.Model,
|
||||
PartNumber: nic.PartNumber,
|
||||
Manufacturer: nic.Vendor,
|
||||
SerialNumber: nic.SerialNumber,
|
||||
Firmware: nic.Firmware,
|
||||
PortCount: nic.PortCount,
|
||||
PortType: nic.PortType,
|
||||
MACAddresses: nic.MACAddresses,
|
||||
Present: &present,
|
||||
Status: nic.Status,
|
||||
StatusCheckedAt: nic.StatusCheckedAt,
|
||||
StatusChangedAt: nic.StatusChangedAt,
|
||||
StatusAtCollect: nic.StatusAtCollect,
|
||||
StatusHistory: nic.StatusHistory,
|
||||
ErrorDescription: nic.ErrorDescription,
|
||||
})
|
||||
}
|
||||
for _, psu := range hw.PowerSupply {
|
||||
present := psu.Present
|
||||
appendDevice(models.HardwareDevice{
|
||||
Kind: models.DeviceKindPSU,
|
||||
Slot: psu.Slot,
|
||||
Model: psu.Model,
|
||||
PartNumber: psu.PartNumber,
|
||||
Manufacturer: psu.Vendor,
|
||||
SerialNumber: psu.SerialNumber,
|
||||
Firmware: psu.Firmware,
|
||||
Present: &present,
|
||||
WattageW: psu.WattageW,
|
||||
InputType: psu.InputType,
|
||||
InputPowerW: psu.InputPowerW,
|
||||
OutputPowerW: psu.OutputPowerW,
|
||||
InputVoltage: psu.InputVoltage,
|
||||
TemperatureC: psu.TemperatureC,
|
||||
Status: psu.Status,
|
||||
StatusCheckedAt: psu.StatusCheckedAt,
|
||||
StatusChangedAt: psu.StatusChangedAt,
|
||||
StatusAtCollect: psu.StatusAtCollect,
|
||||
StatusHistory: psu.StatusHistory,
|
||||
ErrorDescription: psu.ErrorDescription,
|
||||
})
|
||||
}
|
||||
return dedupeCanonicalDevices(all)
|
||||
}
|
||||
|
||||
func dedupeCanonicalDevices(items []models.HardwareDevice) []models.HardwareDevice {
|
||||
type scored struct {
|
||||
item models.HardwareDevice
|
||||
score int
|
||||
}
|
||||
byKey := make(map[string]scored, len(items))
|
||||
order := make([]string, 0, len(items))
|
||||
noKey := make([]models.HardwareDevice, 0)
|
||||
for _, item := range items {
|
||||
key := canonicalKey(item)
|
||||
if key == "" {
|
||||
noKey = append(noKey, item)
|
||||
continue
|
||||
}
|
||||
curr := scored{item: item, score: canonicalScore(item)}
|
||||
prev, ok := byKey[key]
|
||||
if !ok {
|
||||
byKey[key] = curr
|
||||
order = append(order, key)
|
||||
continue
|
||||
}
|
||||
if curr.score > prev.score {
|
||||
byKey[key] = curr
|
||||
}
|
||||
}
|
||||
out := make([]models.HardwareDevice, 0, len(order)+len(noKey))
|
||||
for _, key := range order {
|
||||
out = append(out, byKey[key].item)
|
||||
}
|
||||
out = append(out, noKey...)
|
||||
for i := range out {
|
||||
out[i].ID = out[i].Kind + ":" + strconv.Itoa(i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func canonicalKey(item models.HardwareDevice) string {
|
||||
if sn := normalizedSerial(item.SerialNumber); sn != "" {
|
||||
return "sn:" + strings.ToLower(sn)
|
||||
}
|
||||
if bdf := strings.ToLower(strings.TrimSpace(item.BDF)); bdf != "" {
|
||||
return "bdf:" + bdf
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func canonicalScore(item models.HardwareDevice) int {
|
||||
score := 0
|
||||
if normalizedSerial(item.SerialNumber) != "" {
|
||||
score += 6
|
||||
}
|
||||
if strings.TrimSpace(item.BDF) != "" {
|
||||
score += 4
|
||||
}
|
||||
if strings.TrimSpace(item.Model) != "" {
|
||||
score += 3
|
||||
}
|
||||
if strings.TrimSpace(item.Firmware) != "" {
|
||||
score += 2
|
||||
}
|
||||
if strings.TrimSpace(item.Status) != "" {
|
||||
score++
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
// convertFirmware converts firmware information to Reanimator format
|
||||
func convertFirmware(firmware []models.FirmwareInfo) []ReanimatorFirmware {
|
||||
if len(firmware) == 0 {
|
||||
@@ -93,6 +358,194 @@ func convertFirmware(firmware []models.FirmwareInfo) []ReanimatorFirmware {
|
||||
return result
|
||||
}
|
||||
|
||||
func convertCPUsFromDevices(devices []models.HardwareDevice, collectedAt string) []ReanimatorCPU {
|
||||
result := make([]ReanimatorCPU, 0)
|
||||
for _, d := range devices {
|
||||
if d.Kind != models.DeviceKindCPU {
|
||||
continue
|
||||
}
|
||||
socket := parseSocketFromSlot(d.Slot)
|
||||
if v, ok := d.Details["socket"].(int); ok {
|
||||
socket = v
|
||||
}
|
||||
cpuStatus := normalizeStatus(d.Status, false)
|
||||
if strings.TrimSpace(d.Status) == "" {
|
||||
cpuStatus = "Unknown"
|
||||
}
|
||||
meta := buildStatusMeta(cpuStatus, d.StatusCheckedAt, d.StatusChangedAt, d.StatusAtCollect, d.StatusHistory, d.ErrorDescription, collectedAt)
|
||||
result = append(result, ReanimatorCPU{
|
||||
Socket: socket,
|
||||
Model: d.Model,
|
||||
Cores: d.Cores,
|
||||
Threads: d.Threads,
|
||||
FrequencyMHz: d.FrequencyMHz,
|
||||
MaxFrequencyMHz: d.MaxFreqMHz,
|
||||
Manufacturer: inferCPUManufacturer(d.Model),
|
||||
Status: cpuStatus,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func convertMemoryFromDevices(devices []models.HardwareDevice, collectedAt string) []ReanimatorMemory {
|
||||
result := make([]ReanimatorMemory, 0)
|
||||
for _, d := range devices {
|
||||
if d.Kind != models.DeviceKindMemory {
|
||||
continue
|
||||
}
|
||||
present := d.Present != nil && *d.Present
|
||||
if !present || d.SizeMB == 0 {
|
||||
continue
|
||||
}
|
||||
status := normalizeStatus(d.Status, true)
|
||||
if strings.TrimSpace(d.Status) == "" {
|
||||
if present {
|
||||
status = "OK"
|
||||
} else {
|
||||
status = "Empty"
|
||||
}
|
||||
}
|
||||
meta := buildStatusMeta(status, d.StatusCheckedAt, d.StatusChangedAt, d.StatusAtCollect, d.StatusHistory, d.ErrorDescription, collectedAt)
|
||||
result = append(result, ReanimatorMemory{
|
||||
Slot: d.Slot,
|
||||
Location: d.Location,
|
||||
Present: present,
|
||||
SizeMB: d.SizeMB,
|
||||
Type: d.Type,
|
||||
MaxSpeedMHz: intFromDetailMap(d.Details, "max_speed_mhz"),
|
||||
CurrentSpeedMHz: intFromDetailMap(d.Details, "current_speed_mhz"),
|
||||
Manufacturer: d.Manufacturer,
|
||||
SerialNumber: d.SerialNumber,
|
||||
PartNumber: d.PartNumber,
|
||||
Status: status,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func convertStorageFromDevices(devices []models.HardwareDevice, collectedAt string) []ReanimatorStorage {
|
||||
result := make([]ReanimatorStorage, 0)
|
||||
for _, d := range devices {
|
||||
if d.Kind != models.DeviceKindStorage {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(d.SerialNumber) == "" {
|
||||
continue
|
||||
}
|
||||
present := d.Present == nil || *d.Present
|
||||
status := inferStorageStatus(models.Storage{Present: present})
|
||||
if strings.TrimSpace(d.Status) != "" {
|
||||
status = normalizeStatus(d.Status, false)
|
||||
}
|
||||
meta := buildStatusMeta(status, d.StatusCheckedAt, d.StatusChangedAt, d.StatusAtCollect, d.StatusHistory, d.ErrorDescription, collectedAt)
|
||||
result = append(result, ReanimatorStorage{
|
||||
Slot: d.Slot,
|
||||
Type: d.Type,
|
||||
Model: d.Model,
|
||||
SizeGB: d.SizeGB,
|
||||
SerialNumber: d.SerialNumber,
|
||||
Manufacturer: d.Manufacturer,
|
||||
Firmware: d.Firmware,
|
||||
Interface: d.Interface,
|
||||
Present: present,
|
||||
Status: status,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func convertPCIeFromDevices(devices []models.HardwareDevice, collectedAt string) []ReanimatorPCIe {
|
||||
result := make([]ReanimatorPCIe, 0)
|
||||
for _, d := range devices {
|
||||
if d.Kind != models.DeviceKindPCIe && d.Kind != models.DeviceKindGPU && d.Kind != models.DeviceKindNetwork {
|
||||
continue
|
||||
}
|
||||
deviceClass := d.DeviceClass
|
||||
if d.Kind == models.DeviceKindGPU && strings.TrimSpace(deviceClass) == "" {
|
||||
deviceClass = "DisplayController"
|
||||
}
|
||||
model := d.Model
|
||||
if model == "" {
|
||||
model = d.PartNumber
|
||||
}
|
||||
status := normalizeStatus(d.Status, false)
|
||||
meta := buildStatusMeta(status, d.StatusCheckedAt, d.StatusChangedAt, d.StatusAtCollect, d.StatusHistory, d.ErrorDescription, collectedAt)
|
||||
result = append(result, ReanimatorPCIe{
|
||||
Slot: d.Slot,
|
||||
VendorID: d.VendorID,
|
||||
DeviceID: d.DeviceID,
|
||||
BDF: d.BDF,
|
||||
DeviceClass: deviceClass,
|
||||
Manufacturer: d.Manufacturer,
|
||||
Model: model,
|
||||
LinkWidth: d.LinkWidth,
|
||||
LinkSpeed: d.LinkSpeed,
|
||||
MaxLinkWidth: d.MaxLinkWidth,
|
||||
MaxLinkSpeed: d.MaxLinkSpeed,
|
||||
SerialNumber: normalizedSerial(d.SerialNumber),
|
||||
Firmware: d.Firmware,
|
||||
Status: status,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func convertPSUsFromDevices(devices []models.HardwareDevice, collectedAt string) []ReanimatorPSU {
|
||||
result := make([]ReanimatorPSU, 0)
|
||||
for _, d := range devices {
|
||||
if d.Kind != models.DeviceKindPSU {
|
||||
continue
|
||||
}
|
||||
present := d.Present != nil && *d.Present
|
||||
if !present || strings.TrimSpace(d.SerialNumber) == "" {
|
||||
continue
|
||||
}
|
||||
status := normalizeStatus(d.Status, false)
|
||||
meta := buildStatusMeta(status, d.StatusCheckedAt, d.StatusChangedAt, d.StatusAtCollect, d.StatusHistory, d.ErrorDescription, collectedAt)
|
||||
result = append(result, ReanimatorPSU{
|
||||
Slot: d.Slot,
|
||||
Present: present,
|
||||
Model: d.Model,
|
||||
Vendor: d.Manufacturer,
|
||||
WattageW: d.WattageW,
|
||||
SerialNumber: d.SerialNumber,
|
||||
PartNumber: d.PartNumber,
|
||||
Firmware: d.Firmware,
|
||||
Status: status,
|
||||
InputType: d.InputType,
|
||||
InputPowerW: d.InputPowerW,
|
||||
OutputPowerW: d.OutputPowerW,
|
||||
InputVoltage: d.InputVoltage,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func isDeviceBoundFirmwareName(name string) bool {
|
||||
n := strings.TrimSpace(strings.ToLower(name))
|
||||
if n == "" {
|
||||
@@ -809,6 +1262,37 @@ func normalizedSerial(serial string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func parseSocketFromSlot(slot string) int {
|
||||
s := strings.TrimSpace(strings.ToUpper(slot))
|
||||
s = strings.TrimPrefix(s, "CPU")
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
v, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func intFromDetailMap(details map[string]any, key string) int {
|
||||
if details == nil {
|
||||
return 0
|
||||
}
|
||||
v, ok := details[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
switch n := v.(type) {
|
||||
case int:
|
||||
return n
|
||||
case float64:
|
||||
return int(n)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// inferStorageStatus determines storage device status
|
||||
func inferStorageStatus(stor models.Storage) string {
|
||||
if !stor.Present {
|
||||
|
||||
@@ -599,8 +599,8 @@ func TestConvertToReanimator_DeduplicatesAllSections(t *testing.T) {
|
||||
{Socket: 0, Model: "CPU-A-DUP"},
|
||||
},
|
||||
Memory: []models.MemoryDIMM{
|
||||
{Slot: "DIMM_A1", Present: true, SerialNumber: "MEM-1", Status: "OK"},
|
||||
{Slot: "DIMM_A1", Present: true, SerialNumber: "MEM-1-DUP", Status: "OK"},
|
||||
{Slot: "DIMM_A1", Present: true, SizeMB: 32768, SerialNumber: "MEM-1", Status: "OK"},
|
||||
{Slot: "DIMM_A1", Present: true, SizeMB: 32768, SerialNumber: "MEM-1-DUP", Status: "OK"},
|
||||
},
|
||||
Storage: []models.Storage{
|
||||
{Slot: "U.2-1", SerialNumber: "SSD-1", Model: "Disk1", Present: true},
|
||||
@@ -629,11 +629,11 @@ func TestConvertToReanimator_DeduplicatesAllSections(t *testing.T) {
|
||||
if len(out.Hardware.Firmware) != 1 {
|
||||
t.Fatalf("expected deduped firmware len=1, got %d", len(out.Hardware.Firmware))
|
||||
}
|
||||
if len(out.Hardware.CPUs) != 1 {
|
||||
t.Fatalf("expected deduped cpus len=1, got %d", len(out.Hardware.CPUs))
|
||||
if len(out.Hardware.CPUs) != 2 {
|
||||
t.Fatalf("expected cpus len=2 (no serial/bdf dedupe), got %d", len(out.Hardware.CPUs))
|
||||
}
|
||||
if len(out.Hardware.Memory) != 1 {
|
||||
t.Fatalf("expected deduped memory len=1, got %d", len(out.Hardware.Memory))
|
||||
if len(out.Hardware.Memory) != 2 {
|
||||
t.Fatalf("expected memory len=2 (different serials), got %d", len(out.Hardware.Memory))
|
||||
}
|
||||
if len(out.Hardware.Storage) != 1 {
|
||||
t.Fatalf("expected deduped storage len=1, got %d", len(out.Hardware.Storage))
|
||||
@@ -641,8 +641,8 @@ func TestConvertToReanimator_DeduplicatesAllSections(t *testing.T) {
|
||||
if len(out.Hardware.PowerSupplies) != 1 {
|
||||
t.Fatalf("expected deduped psu len=1, got %d", len(out.Hardware.PowerSupplies))
|
||||
}
|
||||
if len(out.Hardware.PCIeDevices) != 2 {
|
||||
t.Fatalf("expected deduped pcie len=2 (gpu+nic), got %d", len(out.Hardware.PCIeDevices))
|
||||
if len(out.Hardware.PCIeDevices) != 4 {
|
||||
t.Fatalf("expected pcie len=4 with serial->bdf dedupe, got %d", len(out.Hardware.PCIeDevices))
|
||||
}
|
||||
|
||||
gpuCount := 0
|
||||
@@ -651,8 +651,8 @@ func TestConvertToReanimator_DeduplicatesAllSections(t *testing.T) {
|
||||
gpuCount++
|
||||
}
|
||||
}
|
||||
if gpuCount != 1 {
|
||||
t.Fatalf("expected single #GPU0 record, got %d", gpuCount)
|
||||
if gpuCount != 2 {
|
||||
t.Fatalf("expected two #GPU0 records (pcie+gpu kinds), got %d", gpuCount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,3 +699,42 @@ func TestConvertToReanimator_FirmwareExcludesDeviceBoundEntries(t *testing.T) {
|
||||
t.Fatalf("expected NVSwitch firmware to be excluded from hardware.firmware")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToReanimator_UsesCanonicalDevices(t *testing.T) {
|
||||
input := &models.AnalysisResult{
|
||||
Filename: "canonical.json",
|
||||
Hardware: &models.HardwareConfig{
|
||||
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
|
||||
Devices: []models.HardwareDevice{
|
||||
{
|
||||
Kind: models.DeviceKindCPU,
|
||||
Slot: "CPU0",
|
||||
Model: "INTEL(R) XEON(R)",
|
||||
Cores: 32,
|
||||
Threads: 64,
|
||||
FrequencyMHz: 2100,
|
||||
},
|
||||
{
|
||||
Kind: models.DeviceKindStorage,
|
||||
Slot: "U.2-1",
|
||||
Model: "Disk1",
|
||||
SerialNumber: "SSD-1",
|
||||
Present: boolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ConvertToReanimator(input)
|
||||
if err != nil {
|
||||
t.Fatalf("ConvertToReanimator() failed: %v", err)
|
||||
}
|
||||
if len(out.Hardware.CPUs) != 1 {
|
||||
t.Fatalf("expected cpu from hardware.devices, got %d", len(out.Hardware.CPUs))
|
||||
}
|
||||
if len(out.Hardware.Storage) != 1 {
|
||||
t.Fatalf("expected storage from hardware.devices, got %d", len(out.Hardware.Storage))
|
||||
}
|
||||
}
|
||||
|
||||
func boolPtr(v bool) *bool { return &v }
|
||||
|
||||
@@ -201,13 +201,9 @@ func TestFullReanimatorExport(t *testing.T) {
|
||||
t.Errorf("CPU status mismatch: got %q", hw.CPUs[0].Status)
|
||||
}
|
||||
|
||||
// Memory (should include empty slots)
|
||||
if len(hw.Memory) != 2 {
|
||||
t.Errorf("Expected 2 memory entries (including empty), got %d", len(hw.Memory))
|
||||
}
|
||||
|
||||
if hw.Memory[1].Status != "Empty" {
|
||||
t.Errorf("Empty memory slot status mismatch: got %q", hw.Memory[1].Status)
|
||||
// Memory (empty slots are excluded)
|
||||
if len(hw.Memory) != 1 {
|
||||
t.Errorf("Expected 1 memory entry (installed only), got %d", len(hw.Memory))
|
||||
}
|
||||
|
||||
// Storage
|
||||
|
||||
Reference in New Issue
Block a user