Files
Mikhail ChusavitinandClaude Sonnet 5 3311bafd8e feat(inspur_legacy): new parser for pre-Kaytus Inspur onekeylog
Older AMI-BMC Inspur onekeylog archives (NF5466M5 / NF5280M5 generation)
opened to an empty result: they carry none of the files the inspur parser
keys on (no asset.json, devicefrusdr.log, selelist.csv or component.log).

New package internal/parser/vendors/inspur_legacy (vendor id inspur_legacy),
separate from inspur:
- binary IPMI FRU decode (FRU.bin) -> board identity
- Inspur_AssetInfoInventory.log -> CPU / memory / PCIe / PSU inventory
- events from Inspur_<model>_<serial>_IDL, sel.log, blackbox.log,
  MegaRAID raid0.log, and the flat AMI <severity>.log files
- no live sensors in this archive class -> recorded as a collection error
- BMC clock timestamps before 2010 dropped as un-set (1970 / ~2005 RTC)

Registry: add optional PrioritizedParser { DetectPriority() int } so a
confidence tie is broken by specificity. inspur_legacy returns 10 and also
declines (Detect 0) when modern Kaytus markers are present, so the two
Inspur parsers never fight over a newer dump.

Docs: ADL-065, 06-parsers.md, releases/v1.32.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VL8wLGD6Lnp6hZCpqT6cZ
2026-09-02 12:51:53 +03:00

105 lines
3.2 KiB
Go

package inspurlegacy
import (
"testing"
"git.mchus.pro/mchus/logpile/internal/models"
)
// buildFRU assembles a minimal but spec-shaped IPMI FRU image with a chassis,
// board and product area. Single-char fields are encoded as 0xC1 (type 3,
// length 1) exactly as the real Inspur image does.
func buildFRU() []byte {
tl3 := func(s string) []byte { return append([]byte{byte(0xC0 | len(s))}, []byte(s)...) }
chassis := []byte{0x01, 0x00, 0x17} // version, len placeholder, Rack Mount
chassis = append(chassis, tl3("0")...)
chassis = append(chassis, tl3("0")...)
chassis = pad8(chassis)
chassis[1] = byte(len(chassis) / 8)
board := []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00} // v, len, lang, 3-byte date
board = append(board, tl3("Inspur")...)
board = append(board, tl3("NF5280M5")...)
board = append(board, tl3("MBL911S22243C30")...)
board = append(board, tl3("YZMB-00882-104")...)
board = pad8(board)
board[1] = byte(len(board) / 8)
product := []byte{0x01, 0x00, 0x00} // v, len, lang
product = append(product, tl3("Inspur")...)
product = append(product, tl3("NF5466M5")...)
product = append(product, tl3("0")...) // part placeholder
product = append(product, tl3("0")...) // version placeholder
product = append(product, tl3("221353113")...)
product = append(product, tl3("0")...) // asset tag placeholder
product = pad8(product)
product[1] = byte(len(product) / 8)
header := make([]byte, 8)
header[0] = 0x01
header[2] = 1 // chassis at 8
header[3] = byte((8 + len(chassis)) / 8) // board
header[4] = byte((8 + len(chassis) + len(board)) / 8)
out := append(header, chassis...)
out = append(out, board...)
out = append(out, product...)
return out
}
func pad8(b []byte) []byte {
for len(b)%8 != 0 {
b = append(b, 0x00)
}
return b
}
func TestDecodeBinaryFRU_Identity(t *testing.T) {
fru, ok := DecodeBinaryFRU(buildFRU())
if !ok {
t.Fatal("decode failed")
}
if fru.ProductName != "NF5466M5" {
t.Errorf("product name = %q", fru.ProductName)
}
if fru.ProductSerial != "221353113" {
t.Errorf("product serial = %q", fru.ProductSerial)
}
if fru.BoardPart != "YZMB-00882-104" {
t.Errorf("board part = %q", fru.BoardPart)
}
if fru.ChassisType != "Rack Mount Chassis" {
t.Errorf("chassis type = %q", fru.ChassisType)
}
}
func TestBinaryFRU_ApplyToBoardInfo_PrefersProductSerialAndBoardPart(t *testing.T) {
fru, _ := DecodeBinaryFRU(buildFRU())
var b models.BoardInfo
fru.applyToBoardInfo(&b)
if b.SerialNumber != "221353113" {
t.Errorf("board serial = %q, want operator-facing product serial", b.SerialNumber)
}
if b.PartNumber != "YZMB-00882-104" {
t.Errorf("board part = %q, want board part (product part is placeholder)", b.PartNumber)
}
if b.Manufacturer != "Inspur" {
t.Errorf("board manufacturer = %q", b.Manufacturer)
}
// An already-populated field is never overwritten.
pre := models.BoardInfo{SerialNumber: "EXISTING"}
fru.applyToBoardInfo(&pre)
if pre.SerialNumber != "EXISTING" {
t.Errorf("existing serial overwritten: %q", pre.SerialNumber)
}
}
func TestDecodeBinaryFRU_RejectsGarbage(t *testing.T) {
if _, ok := DecodeBinaryFRU([]byte{0x00, 0x00}); ok {
t.Error("expected decode to fail on short/garbage input")
}
}