feat: improve inspur parsing and pci.ids integration
This commit is contained in:
@@ -309,6 +309,23 @@ func (s *Server) handleGetSerials(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var serials []SerialEntry
|
||||
seenByLocationSerial := make(map[string]bool)
|
||||
markSeen := func(location, serial string) {
|
||||
loc := strings.ToLower(strings.TrimSpace(location))
|
||||
sn := strings.ToLower(strings.TrimSpace(serial))
|
||||
if loc == "" || sn == "" {
|
||||
return
|
||||
}
|
||||
seenByLocationSerial[loc+"|"+sn] = true
|
||||
}
|
||||
alreadySeen := func(location, serial string) bool {
|
||||
loc := strings.ToLower(strings.TrimSpace(location))
|
||||
sn := strings.ToLower(strings.TrimSpace(serial))
|
||||
if loc == "" || sn == "" {
|
||||
return false
|
||||
}
|
||||
return seenByLocationSerial[loc+"|"+sn]
|
||||
}
|
||||
|
||||
// From FRU
|
||||
for _, fru := range result.FRU {
|
||||
@@ -403,6 +420,7 @@ func (s *Server) handleGetSerials(w http.ResponseWriter, r *http.Request) {
|
||||
Manufacturer: gpu.Manufacturer,
|
||||
Category: "GPU",
|
||||
})
|
||||
markSeen(gpu.Slot, gpu.SerialNumber)
|
||||
}
|
||||
|
||||
// PCIe devices
|
||||
@@ -410,7 +428,10 @@ func (s *Server) handleGetSerials(w http.ResponseWriter, r *http.Request) {
|
||||
if !hasUsableSerial(pcie.SerialNumber) {
|
||||
continue
|
||||
}
|
||||
component := pcie.DeviceClass
|
||||
if alreadySeen(pcie.Slot, pcie.SerialNumber) {
|
||||
continue
|
||||
}
|
||||
component := normalizePCIeSerialComponentName(pcie)
|
||||
if strings.EqualFold(strings.TrimSpace(pcie.DeviceClass), "NVSwitch") && strings.TrimSpace(pcie.PartNumber) != "" {
|
||||
component = strings.TrimSpace(pcie.PartNumber)
|
||||
}
|
||||
@@ -422,6 +443,7 @@ func (s *Server) handleGetSerials(w http.ResponseWriter, r *http.Request) {
|
||||
PartNumber: pcie.PartNumber,
|
||||
Category: "PCIe",
|
||||
})
|
||||
markSeen(pcie.Slot, pcie.SerialNumber)
|
||||
}
|
||||
|
||||
// Network cards
|
||||
@@ -431,9 +453,11 @@ func (s *Server) handleGetSerials(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
serials = append(serials, SerialEntry{
|
||||
Component: nic.Model,
|
||||
Location: nic.Name,
|
||||
SerialNumber: strings.TrimSpace(nic.SerialNumber),
|
||||
Category: "Network",
|
||||
})
|
||||
markSeen(nic.Name, nic.SerialNumber)
|
||||
}
|
||||
|
||||
// Power supplies
|
||||
@@ -454,6 +478,28 @@ func (s *Server) handleGetSerials(w http.ResponseWriter, r *http.Request) {
|
||||
jsonResponse(w, serials)
|
||||
}
|
||||
|
||||
func normalizePCIeSerialComponentName(p models.PCIeDevice) string {
|
||||
className := strings.TrimSpace(p.DeviceClass)
|
||||
part := strings.TrimSpace(p.PartNumber)
|
||||
if part != "" && !strings.EqualFold(part, className) {
|
||||
return part
|
||||
}
|
||||
lowerClass := strings.ToLower(className)
|
||||
switch lowerClass {
|
||||
case "display", "display controller", "3d controller", "vga", "network", "network controller", "pcie device", "other", "unknown", "":
|
||||
if part != "" {
|
||||
return part
|
||||
}
|
||||
}
|
||||
if className != "" {
|
||||
return className
|
||||
}
|
||||
if part != "" {
|
||||
return part
|
||||
}
|
||||
return "PCIe device"
|
||||
}
|
||||
|
||||
func hasUsableSerial(serial string) bool {
|
||||
s := strings.TrimSpace(serial)
|
||||
if s == "" {
|
||||
@@ -474,33 +520,70 @@ func (s *Server) handleGetFirmware(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
jsonResponse(w, buildFirmwareEntries(result.Hardware))
|
||||
}
|
||||
|
||||
type firmwareEntry struct {
|
||||
Component string `json:"component"`
|
||||
Model string `json:"model"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func buildFirmwareEntries(hw *models.HardwareConfig) []firmwareEntry {
|
||||
if hw == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deduplicate firmware by extracting model name and version
|
||||
// E.g., "PSU0 (AP-CR3000F12BY)" and "PSU1 (AP-CR3000F12BY)" with same version -> one entry
|
||||
type FirmwareEntry struct {
|
||||
Component string `json:"component"`
|
||||
Model string `json:"model"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
var deduplicated []FirmwareEntry
|
||||
var deduplicated []firmwareEntry
|
||||
|
||||
for _, fw := range result.Hardware.Firmware {
|
||||
// Extract component type and model from device name
|
||||
component, model := extractFirmwareComponentAndModel(fw.DeviceName)
|
||||
key := component + "|" + model + "|" + fw.Version
|
||||
|
||||
if !seen[key] {
|
||||
seen[key] = true
|
||||
deduplicated = append(deduplicated, FirmwareEntry{
|
||||
Component: component,
|
||||
Model: model,
|
||||
Version: fw.Version,
|
||||
})
|
||||
appendEntry := func(component, model, version string) {
|
||||
component = strings.TrimSpace(component)
|
||||
model = strings.TrimSpace(model)
|
||||
version = strings.TrimSpace(version)
|
||||
if component == "" || version == "" {
|
||||
return
|
||||
}
|
||||
if model == "" {
|
||||
model = "-"
|
||||
}
|
||||
key := component + "|" + model + "|" + version
|
||||
if seen[key] {
|
||||
return
|
||||
}
|
||||
seen[key] = true
|
||||
deduplicated = append(deduplicated, firmwareEntry{
|
||||
Component: component,
|
||||
Model: model,
|
||||
Version: version,
|
||||
})
|
||||
}
|
||||
|
||||
jsonResponse(w, deduplicated)
|
||||
for _, fw := range hw.Firmware {
|
||||
component, model := extractFirmwareComponentAndModel(fw.DeviceName)
|
||||
appendEntry(component, model, fw.Version)
|
||||
}
|
||||
|
||||
// Fallback for parsers that fill GPU firmware on device inventory only
|
||||
// (e.g. runtime enrichment from redis/HGX) without explicit Hardware.Firmware entries.
|
||||
for _, gpu := range hw.GPUs {
|
||||
version := strings.TrimSpace(gpu.Firmware)
|
||||
if version == "" {
|
||||
continue
|
||||
}
|
||||
model := strings.TrimSpace(gpu.PartNumber)
|
||||
if model == "" {
|
||||
model = strings.TrimSpace(gpu.Model)
|
||||
}
|
||||
if model == "" {
|
||||
model = strings.TrimSpace(gpu.Slot)
|
||||
}
|
||||
appendEntry("GPU", model, version)
|
||||
}
|
||||
|
||||
return deduplicated
|
||||
}
|
||||
|
||||
// extractFirmwareComponentAndModel extracts the component type and model from firmware device name
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package server
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
)
|
||||
|
||||
func TestExtractFirmwareComponentAndModel_GPUUsesPartNumberFromParentheses(t *testing.T) {
|
||||
component, model := extractFirmwareComponentAndModel("GPU GPUSXM3 (692-2G520-0280-501)")
|
||||
@@ -21,3 +25,40 @@ func TestExtractFirmwareComponentAndModel_GPUFallbackWithoutParentheses(t *testi
|
||||
t.Fatalf("expected GPU model 692-2G520-0280-501, got %q", model)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFirmwareEntries_IncludesGPUFirmwareFallback(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
Firmware: []models.FirmwareInfo{
|
||||
{DeviceName: "BIOS", Version: "1.0.0"},
|
||||
},
|
||||
GPUs: []models.GPU{
|
||||
{
|
||||
Slot: "#CPU0_PCIE2",
|
||||
Model: "GH100 [H200 NVL]",
|
||||
PartNumber: "699-2G530-0200-501",
|
||||
Firmware: "96.00.B7.00.02",
|
||||
},
|
||||
{
|
||||
Slot: "#CPU0_PCIE1",
|
||||
Model: "GH100 [H200 NVL]",
|
||||
PartNumber: "699-2G530-0200-501",
|
||||
Firmware: "96.00.B7.00.02",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
entries := buildFirmwareEntries(hw)
|
||||
if len(entries) != 2 {
|
||||
t.Fatalf("expected 2 deduplicated firmware entries, got %d", len(entries))
|
||||
}
|
||||
|
||||
var hasGPU bool
|
||||
for _, e := range entries {
|
||||
if e.Component == "GPU" && e.Version == "96.00.B7.00.02" {
|
||||
hasGPU = true
|
||||
}
|
||||
}
|
||||
if !hasGPU {
|
||||
t.Fatalf("expected GPU firmware entry from hardware.gpus fallback")
|
||||
}
|
||||
}
|
||||
|
||||
20
internal/server/handlers_serials_test.go
Normal file
20
internal/server/handlers_serials_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
)
|
||||
|
||||
func TestNormalizePCIeSerialComponentName_PrefersPartOverGenericClass(t *testing.T) {
|
||||
got := normalizePCIeSerialComponentName(models.PCIeDevice{DeviceClass:"Display Controller", PartNumber:"GH100 [H200 NVL]"})
|
||||
if got != "GH100 [H200 NVL]" {
|
||||
t.Fatalf("expected part number, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePCIeSerialComponentName_UsesClassWhenSpecific(t *testing.T) {
|
||||
got := normalizePCIeSerialComponentName(models.PCIeDevice{DeviceClass:"I350 Gigabit Network Connection", PartNumber:"I350T4V2"})
|
||||
if got != "I350T4V2" {
|
||||
t.Fatalf("expected part number for readability, got %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user