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
192 lines
6.0 KiB
Go
192 lines
6.0 KiB
Go
// Package inspurlegacy parses the legacy Inspur "onekeylog" BMC diagnostic
|
|
// archive produced by older AMI-based Inspur BMCs (e.g. NF5466M5 / NF5280M5
|
|
// generation). That format predates the Kaytus-style onekeylog handled by the
|
|
// internal/parser/vendors/inspur package: it has no asset.json, no
|
|
// devicefrusdr.log, no selelist.csv and no component.log. Instead hardware
|
|
// inventory lives in Inspur_AssetInfoInventory.log, FRU data in a binary
|
|
// FRU.bin, the event log in an Inspur_<model>_<serial>_IDL text file plus a
|
|
// pipe-delimited sel.log, and BMC syslog in flat <sev>.log files at the archive
|
|
// root.
|
|
//
|
|
// IMPORTANT: bump parserVersion on any behavior change (see module-versioning).
|
|
package inspurlegacy
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/models"
|
|
"git.mchus.pro/mchus/logpile/internal/parser"
|
|
)
|
|
|
|
// parserVersion follows the N.M module-versioning scheme.
|
|
const parserVersion = "1.0"
|
|
|
|
func init() {
|
|
parser.Register(&Parser{})
|
|
}
|
|
|
|
// Parser implements parser.VendorParser for the legacy Inspur onekeylog format.
|
|
type Parser struct{}
|
|
|
|
// Name returns a human-readable parser name.
|
|
func (p *Parser) Name() string { return "Inspur Legacy onekeylog BMC Parser" }
|
|
|
|
// Vendor returns the parser identifier.
|
|
func (p *Parser) Vendor() string { return "inspur_legacy" }
|
|
|
|
// Version returns the parser module version.
|
|
func (p *Parser) Version() string { return parserVersion }
|
|
|
|
// DetectPriority makes this parser win a confidence tie against the broader
|
|
// Kaytus-style Inspur parser, which also matches anything under onekeylog/.
|
|
func (p *Parser) DetectPriority() int { return 10 }
|
|
|
|
// modernMarkers are files that only appear in the newer Kaytus-style onekeylog.
|
|
// Their presence means the other Inspur parser should handle the archive.
|
|
var modernMarkers = []string{
|
|
"asset.json",
|
|
"devicefrusdr.log",
|
|
"selelist.csv",
|
|
"component.log",
|
|
"onekeylog_dreport.log",
|
|
}
|
|
|
|
// Detect returns a confidence score for the legacy onekeylog format.
|
|
func (p *Parser) Detect(files []parser.ExtractedFile) int {
|
|
confidence := 0
|
|
for _, f := range files {
|
|
base := strings.ToLower(baseName(f.Path))
|
|
for _, m := range modernMarkers {
|
|
if base == m {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case base == "inspur_assetinfoinventory.log":
|
|
confidence += 40
|
|
case isLegacyIDLName(base):
|
|
confidence += 30
|
|
case base == "fru.bin":
|
|
confidence += 15
|
|
case base == "blackbox.log":
|
|
confidence += 10
|
|
case base == "eepromdata.log":
|
|
confidence += 10
|
|
case base == "sdr.dat":
|
|
confidence += 10
|
|
case base == "sadtadreg.dat":
|
|
confidence += 5
|
|
}
|
|
}
|
|
|
|
if confidence > 100 {
|
|
return 100
|
|
}
|
|
return confidence
|
|
}
|
|
|
|
// Parse extracts events, FRU and hardware inventory from a legacy onekeylog.
|
|
func (p *Parser) Parse(files []parser.ExtractedFile) (*models.AnalysisResult, error) {
|
|
result := &models.AnalysisResult{
|
|
Events: make([]models.Event, 0),
|
|
FRU: make([]models.FRUInfo, 0),
|
|
Sensors: make([]models.SensorReading, 0),
|
|
}
|
|
hw := &models.HardwareConfig{}
|
|
result.Hardware = hw
|
|
|
|
if f := parser.FindFileByName(files, "FRU.bin"); f != nil {
|
|
if fru, ok := DecodeBinaryFRU(f.Content); ok {
|
|
result.FRU = append(result.FRU, fru.toFRUInfo())
|
|
fru.applyToBoardInfo(&hw.BoardInfo)
|
|
}
|
|
}
|
|
|
|
if f := parser.FindFileByName(files, "Inspur_AssetInfoInventory.log"); f != nil {
|
|
ParseAssetInfoInventory(f.Content, hw)
|
|
}
|
|
|
|
// Event sources, most structured first.
|
|
for _, f := range findLegacyIDLFiles(files) {
|
|
result.Events = append(result.Events, ParseIDLEvents(f.Content)...)
|
|
}
|
|
if f := parser.FindFileByName(files, "sel.log"); f != nil {
|
|
result.Events = append(result.Events, ParseSELLog(f.Content)...)
|
|
}
|
|
if f := parser.FindFileByName(files, "blackbox.log"); f != nil {
|
|
result.Events = append(result.Events, ParseBlackbox(f.Content)...)
|
|
}
|
|
for _, f := range findRAIDLogFiles(files) {
|
|
result.Events = append(result.Events, ParseMegaRAIDLog(f.Content, baseName(f.Path))...)
|
|
}
|
|
for _, f := range findSyslogFiles(files) {
|
|
result.Events = append(result.Events, ParseAMISyslog(f.Content, baseName(f.Path))...)
|
|
}
|
|
|
|
result.Events = dedupeEvents(result.Events)
|
|
sortEvents(result.Events)
|
|
|
|
// This archive class carries no live sensor readings: SDR.dat holds only
|
|
// sensor definitions, and there is no "sensor list" capture. Record why the
|
|
// sensor set is empty instead of leaving it silently unexplained.
|
|
result.CollectionErrors = append(result.CollectionErrors, models.CollectionError{
|
|
Section: "sensors",
|
|
Message: "legacy onekeylog carries no live sensor readings (SDR.dat holds definitions only, no sensor-list capture)",
|
|
})
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func baseName(path string) string {
|
|
path = strings.ReplaceAll(path, "\\", "/")
|
|
if i := strings.LastIndex(path, "/"); i >= 0 {
|
|
return path[i+1:]
|
|
}
|
|
return path
|
|
}
|
|
|
|
// isLegacyIDLName matches the "Inspur_<model>_<serial>_IDL" event-log filename
|
|
// (no extension), lower-cased.
|
|
func isLegacyIDLName(base string) bool {
|
|
return strings.HasPrefix(base, "inspur_") && strings.HasSuffix(base, "_idl")
|
|
}
|
|
|
|
func findLegacyIDLFiles(files []parser.ExtractedFile) []parser.ExtractedFile {
|
|
var out []parser.ExtractedFile
|
|
for _, f := range files {
|
|
if isLegacyIDLName(strings.ToLower(baseName(f.Path))) {
|
|
out = append(out, f)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func findRAIDLogFiles(files []parser.ExtractedFile) []parser.ExtractedFile {
|
|
var out []parser.ExtractedFile
|
|
for _, f := range files {
|
|
base := strings.ToLower(baseName(f.Path))
|
|
if strings.HasPrefix(base, "raid") && strings.HasSuffix(base, ".log") {
|
|
out = append(out, f)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// syslogFileNames are the flat AMI BMC severity logs at the archive root that
|
|
// carry fault-relevant records. debug.log and audit.log are intentionally left
|
|
// out: they are high-volume housekeeping noise, not diagnostics.
|
|
var syslogFileNames = map[string]struct{}{
|
|
"emerg.log": {}, "alert.log": {}, "crit.log": {}, "err.log": {}, "warning.log": {},
|
|
}
|
|
|
|
func findSyslogFiles(files []parser.ExtractedFile) []parser.ExtractedFile {
|
|
var out []parser.ExtractedFile
|
|
for _, f := range files {
|
|
if _, ok := syslogFileNames[strings.ToLower(baseName(f.Path))]; ok {
|
|
out = append(out, f)
|
|
}
|
|
}
|
|
return out
|
|
}
|