Files
logpile/internal/parser/vendors/xfusion/parser.go
Mikhail Chusavitin 30409eef67 feat: add xFusion iBMC dump parser (tar.gz format)
Parses xFusion G5500 V7 iBMC diagnostic dump archives with:
- FRU info (board serial, product name, component inventory)
- IPMI sensor readings (temperature, voltage, power, fan, current)
- CPU inventory (model, cores, threads, cache, serial)
- Memory DIMMs (size, speed, type, serial, manufacturer)
- GPU inventory from card_manage/card_info (serial, firmware, ECC counts)
- OCP NIC detection (ConnectX-6 Lx with serial)
- PSU inventory (4x 3000W, serial, firmware, voltage)
- Storage: RAID controller firmware + physical drives (model, serial, endurance)
- iBMC maintenance log events with severity mapping
- Registers as vendor "xfusion" in the parser registry

All 11 fixture tests pass against real G5500 V7 dump archive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 15:31:28 +03:00

127 lines
3.9 KiB
Go

// Package xfusion provides parser for xFusion iBMC diagnostic dump archives.
// Tested with: xFusion G5500 V7 iBMC dump (tar.gz format, exported via iBMC UI)
//
// Archive structure: dump_info/AppDump/... and dump_info/LogDump/...
//
// IMPORTANT: Increment parserVersion when modifying parser logic!
package xfusion
import (
"strings"
"git.mchus.pro/mchus/logpile/internal/models"
"git.mchus.pro/mchus/logpile/internal/parser"
)
const parserVersion = "1.0"
func init() {
parser.Register(&Parser{})
}
// Parser implements VendorParser for xFusion iBMC dump archives.
type Parser struct{}
func (p *Parser) Name() string { return "xFusion iBMC Dump Parser" }
func (p *Parser) Vendor() string { return "xfusion" }
func (p *Parser) Version() string { return parserVersion }
// Detect checks if files match the xFusion iBMC dump format.
// Returns confidence score 0-100.
func (p *Parser) Detect(files []parser.ExtractedFile) int {
confidence := 0
for _, f := range files {
path := strings.ToLower(f.Path)
switch {
case strings.Contains(path, "appdump/frudata/fruinfo.txt"):
confidence += 60
case strings.Contains(path, "appdump/sensor_alarm/sensor_info.txt"):
confidence += 20
case strings.Contains(path, "appdump/card_manage/card_info"):
confidence += 20
}
if confidence >= 100 {
return 100
}
}
return confidence
}
// Parse parses xFusion iBMC dump and returns an analysis result.
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),
Hardware: &models.HardwareConfig{
CPUs: make([]models.CPU, 0),
Memory: make([]models.MemoryDIMM, 0),
Storage: make([]models.Storage, 0),
GPUs: make([]models.GPU, 0),
NetworkCards: make([]models.NIC, 0),
PowerSupply: make([]models.PSU, 0),
Firmware: make([]models.FirmwareInfo, 0),
},
}
if f := findByPath(files, "appdump/frudata/fruinfo.txt"); f != nil {
parseFRUInfo(f.Content, result)
}
if f := findByPath(files, "appdump/sensor_alarm/sensor_info.txt"); f != nil {
result.Sensors = parseSensorInfo(f.Content)
}
if f := findByPath(files, "appdump/cpumem/cpu_info"); f != nil {
result.Hardware.CPUs = parseCPUInfo(f.Content)
}
if f := findByPath(files, "appdump/cpumem/mem_info"); f != nil {
result.Hardware.Memory = parseMemInfo(f.Content)
}
if f := findByPath(files, "appdump/card_manage/card_info"); f != nil {
gpus, nics := parseCardInfo(f.Content)
result.Hardware.GPUs = gpus
result.Hardware.NetworkCards = nics
}
if f := findByPath(files, "appdump/bmc/psu_info.txt"); f != nil {
result.Hardware.PowerSupply = parsePSUInfo(f.Content)
}
if f := findByPath(files, "appdump/storagemgnt/raid_controller_info.txt"); f != nil {
parseStorageControllerInfo(f.Content, result)
}
for _, f := range findDiskInfoFiles(files) {
disk := parseDiskInfo(f.Content)
if disk != nil {
result.Hardware.Storage = append(result.Hardware.Storage, *disk)
}
}
if f := findByPath(files, "logdump/maintenance_log"); f != nil {
result.Events = parseMaintenanceLog(f.Content)
}
result.Protocol = "ipmi"
result.SourceType = models.SourceTypeArchive
return result, nil
}
// findByPath returns the first file whose lowercased path contains the given substring.
func findByPath(files []parser.ExtractedFile, substring string) *parser.ExtractedFile {
for i := range files {
if strings.Contains(strings.ToLower(files[i].Path), substring) {
return &files[i]
}
}
return nil
}
// findDiskInfoFiles returns all PhysicalDrivesInfo disk_info files.
func findDiskInfoFiles(files []parser.ExtractedFile) []parser.ExtractedFile {
var out []parser.ExtractedFile
for _, f := range files {
path := strings.ToLower(f.Path)
if strings.Contains(path, "physicaldrivesinfo/") && strings.HasSuffix(path, "/disk_info") {
out = append(out, f)
}
}
return out
}