feat(models): resolve CPU serial from source PPIN as common fallback
Centralize CPU identity in models.ResolveCPUSerialNumber: an explicit source serial wins, otherwise a valid source PPIN is used, placeholders rejected. Dell, H3C and Redfish apply it while parsing; canonical-device and Reanimator conversion apply it again at the output boundary. No identity is synthesized from socket/model/board serial. See ADL-058. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
9d701885da
commit
07c270cda2
@@ -3311,6 +3311,7 @@ func parseCPUs(docs []map[string]interface{}) []models.CPU {
|
|||||||
if serial == "" && publicSerial == "" {
|
if serial == "" && publicSerial == "" {
|
||||||
serial = findFirstNormalizedStringByKeys(doc, "SerialNumber")
|
serial = findFirstNormalizedStringByKeys(doc, "SerialNumber")
|
||||||
}
|
}
|
||||||
|
ppin := firstNonEmpty(findFirstNormalizedStringByKeys(doc, "PPIN", "ProtectedIdentificationNumber"), publicSerial)
|
||||||
cpus = append(cpus, models.CPU{
|
cpus = append(cpus, models.CPU{
|
||||||
Socket: socket,
|
Socket: socket,
|
||||||
Model: firstNonEmpty(asString(doc["Model"]), asString(doc["Name"])),
|
Model: firstNonEmpty(asString(doc["Model"]), asString(doc["Name"])),
|
||||||
@@ -3318,8 +3319,8 @@ func parseCPUs(docs []map[string]interface{}) []models.CPU {
|
|||||||
Threads: asInt(doc["TotalThreads"]),
|
Threads: asInt(doc["TotalThreads"]),
|
||||||
FrequencyMHz: int(redfishFirstNumeric(doc, "OperatingSpeedMHz", "CurrentSpeedMHz", "FrequencyMHz")),
|
FrequencyMHz: int(redfishFirstNumeric(doc, "OperatingSpeedMHz", "CurrentSpeedMHz", "FrequencyMHz")),
|
||||||
MaxFreqMHz: int(redfishFirstNumeric(doc, "MaxSpeedMHz", "TurboEnableMaxSpeedMHz", "TurboDisableMaxSpeedMHz")),
|
MaxFreqMHz: int(redfishFirstNumeric(doc, "MaxSpeedMHz", "TurboEnableMaxSpeedMHz", "TurboDisableMaxSpeedMHz")),
|
||||||
PPIN: firstNonEmpty(findFirstNormalizedStringByKeys(doc, "PPIN", "ProtectedIdentificationNumber"), publicSerial),
|
PPIN: ppin,
|
||||||
SerialNumber: serial,
|
SerialNumber: models.ResolveCPUSerialNumber(serial, ppin),
|
||||||
L1CacheKB: l1,
|
L1CacheKB: l1,
|
||||||
L2CacheKB: l2,
|
L2CacheKB: l2,
|
||||||
L3CacheKB: l3,
|
L3CacheKB: l3,
|
||||||
|
|||||||
@@ -1557,8 +1557,8 @@ func TestParseCPU_UsesPublicSerialAsPPINAndCurrentSpeedMHz(t *testing.T) {
|
|||||||
if cpus[0].PPIN != "6FB5241E81CECDFD" {
|
if cpus[0].PPIN != "6FB5241E81CECDFD" {
|
||||||
t.Fatalf("expected PPIN from Oem.Public.SerialNumber, got %+v", cpus[0])
|
t.Fatalf("expected PPIN from Oem.Public.SerialNumber, got %+v", cpus[0])
|
||||||
}
|
}
|
||||||
if cpus[0].SerialNumber != "" {
|
if cpus[0].SerialNumber != "6FB5241E81CECDFD" {
|
||||||
t.Fatalf("expected empty CPU serial number when only Public serial exists, got %+v", cpus[0])
|
t.Fatalf("expected CPU serial number to fall back to PPIN, got %+v", cpus[0])
|
||||||
}
|
}
|
||||||
if cpus[0].FrequencyMHz != 2700 {
|
if cpus[0].FrequencyMHz != 2700 {
|
||||||
t.Fatalf("expected CPU frequency from Oem.Public.CurrentSpeedMHz, got %+v", cpus[0])
|
t.Fatalf("expected CPU frequency from Oem.Public.CurrentSpeedMHz, got %+v", cpus[0])
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestExportCSV_IncludesAllComponentTypesWithUsableSerials(t *testing.T) {
|
|||||||
Manufacturer: "Supermicro",
|
Manufacturer: "Supermicro",
|
||||||
},
|
},
|
||||||
CPUs: []models.CPU{
|
CPUs: []models.CPU{
|
||||||
{Socket: 0, Model: "Xeon", SerialNumber: "CPU-001"},
|
{Socket: 0, Model: "Xeon", PPIN: "D46E5D6B1D3E40E1"},
|
||||||
},
|
},
|
||||||
Memory: []models.MemoryDIMM{
|
Memory: []models.MemoryDIMM{
|
||||||
{Slot: "DIMM0", PartNumber: "MEM-PN", SerialNumber: "MEM-001", Manufacturer: "Samsung"},
|
{Slot: "DIMM0", PartNumber: "MEM-PN", SerialNumber: "MEM-001", Manufacturer: "Samsung"},
|
||||||
@@ -73,7 +73,7 @@ func TestExportCSV_IncludesAllComponentTypesWithUsableSerials(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
want := []string{"FRU-001", "BOARD-001", "CPU-001", "MEM-001", "SSD-001", "GPU-001", "PCIE-001", "NIC-001", "LNIC-001", "PSU-001"}
|
want := []string{"FRU-001", "BOARD-001", "D46E5D6B1D3E40E1", "MEM-001", "SSD-001", "GPU-001", "PCIE-001", "NIC-001", "LNIC-001", "PSU-001"}
|
||||||
for _, sn := range want {
|
for _, sn := range want {
|
||||||
if !serials[sn] {
|
if !serials[sn] {
|
||||||
t.Fatalf("expected serial %s in csv export", sn)
|
t.Fatalf("expected serial %s in csv export", sn)
|
||||||
|
|||||||
@@ -113,12 +113,13 @@ func buildDevicesFromLegacy(hw *models.HardwareConfig) []models.HardwareDevice {
|
|||||||
details := mergeDetailMaps(nil, cpu.Details)
|
details := mergeDetailMaps(nil, cpu.Details)
|
||||||
details = mergeDetailMaps(details, map[string]any{
|
details = mergeDetailMaps(details, map[string]any{
|
||||||
"socket": cpu.Socket,
|
"socket": cpu.Socket,
|
||||||
|
"ppin": cpu.PPIN,
|
||||||
})
|
})
|
||||||
appendDevice(models.HardwareDevice{
|
appendDevice(models.HardwareDevice{
|
||||||
Kind: models.DeviceKindCPU,
|
Kind: models.DeviceKindCPU,
|
||||||
Slot: fmt.Sprintf("CPU%d", cpu.Socket),
|
Slot: fmt.Sprintf("CPU%d", cpu.Socket),
|
||||||
Model: cpu.Model,
|
Model: cpu.Model,
|
||||||
SerialNumber: cpu.SerialNumber,
|
SerialNumber: models.ResolveCPUSerialNumber(cpu.SerialNumber, cpu.PPIN),
|
||||||
Cores: cpu.Cores,
|
Cores: cpu.Cores,
|
||||||
Threads: cpu.Threads,
|
Threads: cpu.Threads,
|
||||||
FrequencyMHz: cpu.FrequencyMHz,
|
FrequencyMHz: cpu.FrequencyMHz,
|
||||||
@@ -837,7 +838,10 @@ func convertCPUsFromDevices(devices []models.HardwareDevice, collectedAt, boardS
|
|||||||
UncorrectableErrorCount: int64FromDetailMap(d.Details, "uncorrectable_error_count"),
|
UncorrectableErrorCount: int64FromDetailMap(d.Details, "uncorrectable_error_count"),
|
||||||
LifeRemainingPct: floatFromDetailMap(d.Details, "life_remaining_pct"),
|
LifeRemainingPct: floatFromDetailMap(d.Details, "life_remaining_pct"),
|
||||||
LifeUsedPct: floatFromDetailMap(d.Details, "life_used_pct"),
|
LifeUsedPct: floatFromDetailMap(d.Details, "life_used_pct"),
|
||||||
SerialNumber: strings.TrimSpace(d.SerialNumber),
|
SerialNumber: models.ResolveCPUSerialNumber(
|
||||||
|
d.SerialNumber,
|
||||||
|
stringFromDetailMap(d.Details, "ppin"),
|
||||||
|
),
|
||||||
Firmware: firstNonEmptyString(
|
Firmware: firstNonEmptyString(
|
||||||
stringFromDetailMap(d.Details, "microcode"),
|
stringFromDetailMap(d.Details, "microcode"),
|
||||||
microcodeBySocket[socket],
|
microcodeBySocket[socket],
|
||||||
@@ -1529,7 +1533,7 @@ func convertCPUs(cpus []models.CPU, collectedAt string) []ReanimatorCPU {
|
|||||||
Threads: cpu.Threads,
|
Threads: cpu.Threads,
|
||||||
FrequencyMHz: cpu.FrequencyMHz,
|
FrequencyMHz: cpu.FrequencyMHz,
|
||||||
MaxFrequencyMHz: cpu.MaxFreqMHz,
|
MaxFrequencyMHz: cpu.MaxFreqMHz,
|
||||||
SerialNumber: strings.TrimSpace(cpu.SerialNumber),
|
SerialNumber: models.ResolveCPUSerialNumber(cpu.SerialNumber, cpu.PPIN),
|
||||||
Firmware: "",
|
Firmware: "",
|
||||||
Manufacturer: manufacturer,
|
Manufacturer: manufacturer,
|
||||||
Status: cpuStatus,
|
Status: cpuStatus,
|
||||||
@@ -2793,4 +2797,3 @@ func inferTargetHost(targetHost, filename string) string {
|
|||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -320,6 +320,31 @@ func TestConvertToReanimator_CPUSerialIsNotSynthesizedAndSocketIsDeduped(t *test
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConvertToReanimator_CPUSerialUsesPPINFallback(t *testing.T) {
|
||||||
|
input := &models.AnalysisResult{
|
||||||
|
Hardware: &models.HardwareConfig{
|
||||||
|
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
|
||||||
|
Devices: []models.HardwareDevice{{
|
||||||
|
Kind: models.DeviceKindCPU,
|
||||||
|
Slot: "CPU0",
|
||||||
|
Model: "Intel Xeon",
|
||||||
|
Details: map[string]any{
|
||||||
|
"socket": 0,
|
||||||
|
"ppin": "D46E5D6B1D3E40E1",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := ConvertToReanimator(input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ConvertToReanimator() failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(out.Hardware.CPUs) != 1 || out.Hardware.CPUs[0].SerialNumber != "D46E5D6B1D3E40E1" {
|
||||||
|
t.Fatalf("expected CPU serial fallback from PPIN, got %+v", out.Hardware.CPUs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConvertToReanimator_ExportsEventLogsAndOmitsPCIeBDFJSON(t *testing.T) {
|
func TestConvertToReanimator_ExportsEventLogsAndOmitsPCIeBDFJSON(t *testing.T) {
|
||||||
input := &models.AnalysisResult{
|
input := &models.AnalysisResult{
|
||||||
Filename: "events.json",
|
Filename: "events.json",
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// ResolveCPUSerialNumber returns a source-backed processor inventory identity.
|
||||||
|
// A vendor-provided serial number has priority; PPIN is the documented CPU
|
||||||
|
// serial identity and is used when a separate serial number is unavailable.
|
||||||
|
func ResolveCPUSerialNumber(serialNumber, ppin string) string {
|
||||||
|
if serial := validCPUIdentity(serialNumber); serial != "" {
|
||||||
|
return serial
|
||||||
|
}
|
||||||
|
return validCPUIdentity(ppin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validCPUIdentity(value string) string {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
switch strings.ToLower(value) {
|
||||||
|
case "", "-", "n/a", "na", "none", "null", "unknown", "not available", "to be filled by o.e.m.":
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestResolveCPUSerialNumber(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
serial string
|
||||||
|
ppin string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "serial has priority", serial: "CPU-SERIAL", ppin: "D46E5D6B1D3E40E1", want: "CPU-SERIAL"},
|
||||||
|
{name: "PPIN fallback", ppin: " D46E5D6B1D3E40E1 ", want: "D46E5D6B1D3E40E1"},
|
||||||
|
{name: "placeholder serial falls back", serial: "Unknown", ppin: "D44F8D6B9155EE0E", want: "D44F8D6B9155EE0E"},
|
||||||
|
{name: "placeholder PPIN rejected", ppin: "N/A", want: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := ResolveCPUSerialNumber(tt.serial, tt.ppin); got != tt.want {
|
||||||
|
t.Fatalf("ResolveCPUSerialNumber(%q, %q) = %q, want %q", tt.serial, tt.ppin, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-2
@@ -19,7 +19,7 @@ import (
|
|||||||
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
|
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
|
||||||
)
|
)
|
||||||
|
|
||||||
const parserVersion = "3.0"
|
const parserVersion = "3.1"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
parser.Register(&Parser{})
|
parser.Register(&Parser{})
|
||||||
@@ -323,6 +323,7 @@ func parseCPUView(props map[string]string, result *models.AnalysisResult) {
|
|||||||
if model == "" {
|
if model == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ppin := strings.TrimSpace(props["ppin"])
|
||||||
cpu := models.CPU{
|
cpu := models.CPU{
|
||||||
Socket: parseSocketFromFQDD(firstNonEmpty(props["fqdd"], props["instanceid"])),
|
Socket: parseSocketFromFQDD(firstNonEmpty(props["fqdd"], props["instanceid"])),
|
||||||
Model: model,
|
Model: model,
|
||||||
@@ -331,7 +332,8 @@ func parseCPUView(props map[string]string, result *models.AnalysisResult) {
|
|||||||
Threads: parseIntLoose(props["numberofenabledthreads"]),
|
Threads: parseIntLoose(props["numberofenabledthreads"]),
|
||||||
FrequencyMHz: parseIntLoose(props["currentclockspeed"]),
|
FrequencyMHz: parseIntLoose(props["currentclockspeed"]),
|
||||||
MaxFreqMHz: parseIntLoose(props["maxclockspeed"]),
|
MaxFreqMHz: parseIntLoose(props["maxclockspeed"]),
|
||||||
PPIN: strings.TrimSpace(props["ppin"]),
|
PPIN: ppin,
|
||||||
|
SerialNumber: models.ResolveCPUSerialNumber(props["serialnumber"], ppin),
|
||||||
Status: normalizeStatus(props["primarystatus"]),
|
Status: normalizeStatus(props["primarystatus"]),
|
||||||
}
|
}
|
||||||
result.Hardware.CPUs = append(result.Hardware.CPUs, cpu)
|
result.Hardware.CPUs = append(result.Hardware.CPUs, cpu)
|
||||||
|
|||||||
+3
@@ -152,6 +152,9 @@ func TestParseNestedTSRZip(t *testing.T) {
|
|||||||
if got := result.Hardware.CPUs[0].Model; got != "Intel(R) Xeon(R) Gold 6330" {
|
if got := result.Hardware.CPUs[0].Model; got != "Intel(R) Xeon(R) Gold 6330" {
|
||||||
t.Fatalf("unexpected cpu model: %q", got)
|
t.Fatalf("unexpected cpu model: %q", got)
|
||||||
}
|
}
|
||||||
|
if got := result.Hardware.CPUs[0].SerialNumber; got != "ABCD" {
|
||||||
|
t.Fatalf("expected CPU serial fallback from PPIN, got %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
if len(result.Hardware.NetworkAdapters) != 1 {
|
if len(result.Hardware.NetworkAdapters) != 1 {
|
||||||
t.Fatalf("expected 1 network adapter, got %d", len(result.Hardware.NetworkAdapters))
|
t.Fatalf("expected 1 network adapter, got %d", len(result.Hardware.NetworkAdapters))
|
||||||
|
|||||||
+9
-6
@@ -20,8 +20,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
parserVersionG5 = "2.1"
|
parserVersionG5 = "2.2"
|
||||||
parserVersionG6 = "2.1"
|
parserVersionG6 = "2.2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -506,6 +506,8 @@ func parseHardwareInfoNetworkAdapters(content []byte) []models.NetworkAdapter {
|
|||||||
func parseCPUFromHardwareInfoSection(sectionName string, section map[string]string) models.CPU {
|
func parseCPUFromHardwareInfoSection(sectionName string, section map[string]string) models.CPU {
|
||||||
socket := parseSectionIndex(sectionName, `(?i)processor\s+(\d+)`)
|
socket := parseSectionIndex(sectionName, `(?i)processor\s+(\d+)`)
|
||||||
statusRaw := getSectionValue(section, "Status")
|
statusRaw := getSectionValue(section, "Status")
|
||||||
|
ppin := getSectionValue(section, "CPU PPIN", "PPIN")
|
||||||
|
serial := getSectionValue(section, "Processor ID", "Serial Number")
|
||||||
|
|
||||||
return models.CPU{
|
return models.CPU{
|
||||||
Socket: socket,
|
Socket: socket,
|
||||||
@@ -516,8 +518,8 @@ func parseCPUFromHardwareInfoSection(sectionName string, section map[string]stri
|
|||||||
L1CacheKB: parseMaybeIntLoose(getSectionValue(section, "L1 Cache")),
|
L1CacheKB: parseMaybeIntLoose(getSectionValue(section, "L1 Cache")),
|
||||||
L2CacheKB: parseMaybeIntLoose(getSectionValue(section, "L2 Cache")),
|
L2CacheKB: parseMaybeIntLoose(getSectionValue(section, "L2 Cache")),
|
||||||
L3CacheKB: parseMaybeIntLoose(getSectionValue(section, "L3 Cache")),
|
L3CacheKB: parseMaybeIntLoose(getSectionValue(section, "L3 Cache")),
|
||||||
PPIN: getSectionValue(section, "CPU PPIN", "PPIN"),
|
PPIN: ppin,
|
||||||
SerialNumber: getSectionValue(section, "Processor ID", "Serial Number"),
|
SerialNumber: models.ResolveCPUSerialNumber(serial, ppin),
|
||||||
Status: normalizeComponentStatus(statusRaw),
|
Status: normalizeComponentStatus(statusRaw),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1503,6 +1505,7 @@ func parseCPUXML(content []byte, result *models.AnalysisResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
socket := parseSocketID(node.XMLName.Local)
|
socket := parseSocketID(node.XMLName.Local)
|
||||||
|
ppin := strings.TrimSpace(fields["PPIN"])
|
||||||
cpu := models.CPU{
|
cpu := models.CPU{
|
||||||
Socket: socket,
|
Socket: socket,
|
||||||
Model: model,
|
Model: model,
|
||||||
@@ -1511,8 +1514,8 @@ func parseCPUXML(content []byte, result *models.AnalysisResult) {
|
|||||||
Threads: parseMaybeInt(fields["TotalThreads"]),
|
Threads: parseMaybeInt(fields["TotalThreads"]),
|
||||||
FrequencyMHz: parseMaybeInt(fields["ProcessorSpeed"]),
|
FrequencyMHz: parseMaybeInt(fields["ProcessorSpeed"]),
|
||||||
MaxFreqMHz: parseMaybeInt(fields["ProcessorMaxSpeed"]),
|
MaxFreqMHz: parseMaybeInt(fields["ProcessorMaxSpeed"]),
|
||||||
SerialNumber: strings.TrimSpace(fields["SerialNumber"]),
|
SerialNumber: models.ResolveCPUSerialNumber(fields["SerialNumber"], ppin),
|
||||||
PPIN: strings.TrimSpace(fields["PPIN"]),
|
PPIN: ppin,
|
||||||
Status: normalizePresenceStatus(fields["Status"]),
|
Status: normalizePresenceStatus(fields["Status"]),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -831,6 +831,9 @@ Pre-Init,Info,0x0,Management Subsystem Health,Health,Assertion event,Pre-Init,"M
|
|||||||
if result.Hardware.CPUs[0].FrequencyMHz != 2800 {
|
if result.Hardware.CPUs[0].FrequencyMHz != 2800 {
|
||||||
t.Fatalf("expected CPU frequency 2800MHz, got %d", result.Hardware.CPUs[0].FrequencyMHz)
|
t.Fatalf("expected CPU frequency 2800MHz, got %d", result.Hardware.CPUs[0].FrequencyMHz)
|
||||||
}
|
}
|
||||||
|
if got := result.Hardware.CPUs[0].SerialNumber; got != "49-A9-50-C0-15-9F-2D-DC" {
|
||||||
|
t.Fatalf("expected CPU serial fallback from PPIN, got %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
if len(result.Hardware.Memory) != 2 {
|
if len(result.Hardware.Memory) != 2 {
|
||||||
t.Fatalf("expected 2 DIMMs from hardware_info.ini, got %d", len(result.Hardware.Memory))
|
t.Fatalf("expected 2 DIMMs from hardware_info.ini, got %d", len(result.Hardware.Memory))
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func BuildHardwareDevices(hw *models.HardwareConfig) []models.HardwareDevice {
|
|||||||
Source: "cpus",
|
Source: "cpus",
|
||||||
Slot: fmt.Sprintf("CPU%d", cpu.Socket),
|
Slot: fmt.Sprintf("CPU%d", cpu.Socket),
|
||||||
Model: cpu.Model,
|
Model: cpu.Model,
|
||||||
SerialNumber: cpu.SerialNumber,
|
SerialNumber: models.ResolveCPUSerialNumber(cpu.SerialNumber, cpu.PPIN),
|
||||||
Cores: cpu.Cores,
|
Cores: cpu.Cores,
|
||||||
Threads: cpu.Threads,
|
Threads: cpu.Threads,
|
||||||
FrequencyMHz: cpu.FrequencyMHz,
|
FrequencyMHz: cpu.FrequencyMHz,
|
||||||
|
|||||||
@@ -315,3 +315,18 @@ func TestHandleGetConfig_ReturnsCanonicalHardware(t *testing.T) {
|
|||||||
t.Fatalf("did not expect legacy hardware.cpus in config response")
|
t.Fatalf("did not expect legacy hardware.cpus in config response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildHardwareDevices_CPUUsesPPINAsSerialFallback(t *testing.T) {
|
||||||
|
hw := &models.HardwareConfig{CPUs: []models.CPU{{Socket: 0, Model: "Intel Xeon", PPIN: "D46E5D6B1D3E40E1"}}}
|
||||||
|
|
||||||
|
devices := BuildHardwareDevices(hw)
|
||||||
|
for _, device := range devices {
|
||||||
|
if device.Kind == models.DeviceKindCPU {
|
||||||
|
if device.SerialNumber != "D46E5D6B1D3E40E1" {
|
||||||
|
t.Fatalf("expected CPU serial fallback from PPIN, got %q", device.SerialNumber)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatal("CPU device was not built")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user