feat(inspur): source-PRI timezone timeline, PPIN serial, NVMe fault storage status

Event ingestion now uses the source syslog PRI and an explicit-offset
timezone timeline instead of assuming host-local time. CPU PPIN is
exported as the source-backed CPU serial, and an active NVMe fault SEL
event promotes the matching drive's storage status.

See ADL-056, ADL-057.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-08-27 17:05:45 +03:00
co-authored by Claude Sonnet 5
parent 07c270cda2
commit e7b8a8badc
13 changed files with 671 additions and 48 deletions
+41 -6
View File
@@ -5,20 +5,26 @@ import (
"time"
"git.mchus.pro/mchus/logpile/internal/models"
"git.mchus.pro/mchus/logpile/internal/parser"
)
func TestDedupSELEvents(t *testing.T) {
ts := time.Date(2026, 7, 27, 4, 52, 55, 0, time.UTC)
events := []models.Event{
{Source: "SEL", Timestamp: ts, EventType: "Asserted", Description: "PSU0 fail"},
{Source: "SEL", Timestamp: ts, EventType: "Asserted", Description: "PSU0 fail"},
{Source: "BMC", Timestamp: ts, EventType: "Assert", Description: "recurring alarm"},
{Source: "BMC", Timestamp: ts, EventType: "Assert", Description: "recurring alarm"},
{Source: "BMC", Timestamp: ts, EventType: "Assert", Severity: models.SeverityCritical, Description: "Sys_Health Transition to Critical from less severe"},
{Source: "SEL", Timestamp: ts, EventType: "Transition to Critical from less severe", Severity: models.SeverityCritical, Description: "Transition to Critical from less severe", RawData: "Asserted"},
{Source: "SEL", Timestamp: ts.Add(time.Second), EventType: "Asserted", Description: "PSU0 fail"},
{Source: "SEL", Timestamp: ts.Add(time.Second), EventType: "Asserted", Description: "PSU0 fail"},
{Source: "BMC", Timestamp: ts.Add(2 * time.Second), EventType: "Assert", Description: "recurring alarm"},
{Source: "BMC", Timestamp: ts.Add(2 * time.Second), EventType: "Assert", Description: "recurring alarm"},
}
out := dedupSELEvents(events)
if len(out) != 3 {
t.Fatalf("expected 3 events (1 SEL dup removed, BMC untouched), got %d: %+v", len(out), out)
if len(out) != 4 {
t.Fatalf("expected IDL/SEL copy and exact SEL copy removed, recurring BMC alarms retained; got %d: %+v", len(out), out)
}
if out[0].Source != "BMC" {
t.Fatalf("expected richer IDL/BMC event to be retained, got %+v", out[0])
}
}
@@ -48,3 +54,32 @@ func TestParseTimezoneConfigLocation(t *testing.T) {
t.Fatalf("unexpected timezone: %q", got)
}
}
func TestSELTimezoneResolverUsesExplicitOffsetTimeline(t *testing.T) {
files := []parser.ExtractedFile{
{Path: "onekeylog/configuration/conf/timezone.conf", Content: []byte("[TimeZoneConfig]\ntimezone=Asia/Shanghai\n")},
{Path: "onekeylog/log/syslog/idl.log", Content: []byte(
"|2026-01-30T14:25:14+08:00|BMC|Assert|Info|1|old timezone|\n" +
"|2026-07-24T02:20:38+03:00|BMC|Assert|Critical|2|new timezone|\n")},
}
fallback := inferInspurArchiveLocation(files)
resolver := inferInspurTimezoneResolver(files, fallback)
events := parseSELListWithResolver([]byte("sel elist:\n"+
"1,01/30/2026,14:25:14,Chassis Sys_Health,old timezone,Asserted\n"+
"2,07/24/2026,02:20:38,Chassis Sys_Health,new timezone,Asserted\n"), resolver)
if len(events) != 2 {
t.Fatalf("expected 2 events, got %d", len(events))
}
wantOld := time.Date(2026, 1, 30, 6, 25, 14, 0, time.UTC)
wantNew := time.Date(2026, 7, 23, 23, 20, 38, 0, time.UTC)
if !events[0].Timestamp.UTC().Equal(wantOld) || !events[1].Timestamp.UTC().Equal(wantNew) {
t.Fatalf("unexpected resolved timestamps: got %s and %s", events[0].Timestamp.UTC(), events[1].Timestamp.UTC())
}
}
func TestDetermineSELSeverityDeassertedFaultIsInfo(t *testing.T) {
got := determineSELSeverity("Power Supply PSU0", "Failure detected", "Deasserted")
if got != models.SeverityInfo {
t.Fatalf("expected cleared fault to be info, got %q", got)
}
}