fix(inspur): read RESTful FRU info and float fans_power from component.log

Diffing an NF5280M6 BMC dump against its BEE-SP live-CD bundle found two
blind spots in the combined-component.log onekeylog layout (no
devicefrusdr.log / asset.json):

- board manufacturer/product/part/uuid empty and stats.fru 0: the
  "RESTful FRU info:" JSON block was never parsed. New component_fru.go
  (ParseComponentLogFRU) flattens it to []models.FRUInfo, prefers the
  product-area system serial over the board PCB serial, and sets
  BoardInfo.UUID from system_uuid. Wired as a fallback only when
  result.FRU is still empty.
- zero fan sensors: FanRESTInfo.FansPower was int but this firmware
  writes "fans_power": 12.000000, so json.Unmarshal of the whole fan
  block failed. Changed to float64.

Also included: SOL smartd SCSI/SAS device-line parsing and diagnose.go
gofmt from concurrent work on the same live-CD-diff task. See ADL-064.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LffAvostt3uMkiUbVUiyM
This commit is contained in:
Mikhail Chusavitin
2026-09-01 11:58:55 +03:00
co-authored by Claude Sonnet 5
parent ab8636da04
commit 2fa0f78f94
9 changed files with 780 additions and 99 deletions
+63
View File
@@ -36,6 +36,69 @@ func TestParseSOLSmartdDevices_Dedup(t *testing.T) {
}
}
// SAS drives (and SATA drives behind a SAS HBA in SCSI mode) print a different
// smartd line shape: no [SAT] tag, a bracketed SCSI INQUIRY triple, "lu id",
// spaced "S/N: ", and no firmware field.
const solSmartdSCSISample = `
[ 21.481033] smartd[2015]: Device: /dev/sdb, [SEAGATE ST6000NM005B K0A1], lu id: 0x5000c500ee6e11f7, S/N: WX004FCC0000E22967PY, 6.00 TB
[ 21.481212] smartd[2015]: Device: /dev/sdd, [SEAGATE ST6000NM005B K0A1], lu id: 0x5000c500ee6f5ebf, S/N: WX004TKE0000E233A32A, 6.00 TB
[ 21.481235] smartd[2015]: Device: /dev/sdd, is SMART capable. Adding to "monitor" list.
`
func TestParseSOLSmartdDevices_SCSI(t *testing.T) {
devices := parseSOLSmartdDevices([]byte(solSmartdSCSISample))
if len(devices) != 2 {
t.Fatalf("expected 2 SCSI devices, got %d: %v", len(devices), devices)
}
d := devices[0]
if d.Serial != "WX004FCC0000E22967PY" {
t.Errorf("serial: got %q", d.Serial)
}
if d.Model != "SEAGATE ST6000NM005B" {
t.Errorf("model: got %q, want %q", d.Model, "SEAGATE ST6000NM005B")
}
if d.SizeGB != 6000 {
t.Errorf("size: got %d, want 6000", d.SizeGB)
}
if d.Interface != "SAS" {
t.Errorf("interface: got %q, want SAS", d.Interface)
}
if d.Firmware != "" {
t.Errorf("firmware: got %q, want empty (not reported for SCSI)", d.Firmware)
}
}
func TestParseSOLSmartdDevices_MixedSATAandSCSI(t *testing.T) {
devices := parseSOLSmartdDevices([]byte(solSmartdSample + solSmartdSCSISample))
if len(devices) != 6 {
t.Fatalf("expected 4 SAT + 2 SCSI = 6 devices, got %d", len(devices))
}
got := map[string]string{}
for _, d := range devices {
got[d.Serial] = d.Interface
}
if got["2310400DC7E3"] != "SATA" {
t.Errorf("SAT device interface: got %q", got["2310400DC7E3"])
}
if got["WX004FCC0000E22967PY"] != "SAS" {
t.Errorf("SCSI device interface: got %q", got["WX004FCC0000E22967PY"])
}
}
func TestScsiInquiryModel(t *testing.T) {
cases := []struct{ raw, want string }{
{"SEAGATE ST6000NM005B K0A1", "SEAGATE ST6000NM005B"},
{"ATA Micron_5400_MTFD K0A1", "Micron_5400_MTFD"},
{"SEAGATE ST6000NM005B", "SEAGATE ST6000NM005B"},
{"BareModel", "BareModel"},
}
for _, c := range cases {
if got := scsiInquiryModel(c.raw); got != c.want {
t.Errorf("scsiInquiryModel(%q) = %q, want %q", c.raw, got, c.want)
}
}
}
func TestParseSOLSmartdDevices_SkipsNonInfoLines(t *testing.T) {
content := `
[ 17.886177] smartd[3321]: Device: /dev/sda [SAT], state written to /var/lib/smartmontools/smartd.foo.ata.state