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
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
be37f1852b
commit
3311bafd8e
+191
@@ -0,0 +1,191 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user