Diffing an NF5280M6 BMC dump against its BEE-SP live-CD bundle found two blind spots in the combined-component.log onekeylog layout (no devicefrusdr.log / asset.json): - board manufacturer/product/part/uuid empty and stats.fru 0: the "RESTful FRU info:" JSON block was never parsed. New component_fru.go (ParseComponentLogFRU) flattens it to []models.FRUInfo, prefers the product-area system serial over the board PCB serial, and sets BoardInfo.UUID from system_uuid. Wired as a fallback only when result.FRU is still empty. - zero fan sensors: FanRESTInfo.FansPower was int but this firmware writes "fans_power": 12.000000, so json.Unmarshal of the whole fan block failed. Changed to float64. Also included: SOL smartd SCSI/SAS device-line parsing and diagnose.go gofmt from concurrent work on the same live-CD-diff task. See ADL-064. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011LffAvostt3uMkiUbVUiyM
115 lines
3.8 KiB
Go
115 lines
3.8 KiB
Go
package inspur
|
|
|
|
import (
|
|
"encoding/json"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/models"
|
|
)
|
|
|
|
// componentLogFRURegex isolates the JSON array that follows "RESTful FRU info:"
|
|
// in a combined component.log. FRU areas carry no nested arrays, so the lazy
|
|
// match stops at the array's own closing bracket.
|
|
var componentLogFRURegex = regexp.MustCompile(`RESTful FRU info:\s*(\[[\s\S]*?\])`)
|
|
|
|
type componentFRUArea struct {
|
|
Version int `json:"version"`
|
|
Type string `json:"type"`
|
|
Date string `json:"date"`
|
|
Manufacturer string `json:"manufacturer"`
|
|
ProductName string `json:"product_name"`
|
|
SerialNumber string `json:"serial_number"`
|
|
PartNumber string `json:"part_number"`
|
|
AssetTag string `json:"asset_tag"`
|
|
}
|
|
|
|
type componentFRUDevice struct {
|
|
Device struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
SystemUUID string `json:"system_uuid"`
|
|
} `json:"device"`
|
|
Chassis componentFRUArea `json:"chassis"`
|
|
Board componentFRUArea `json:"board"`
|
|
Product componentFRUArea `json:"product"`
|
|
}
|
|
|
|
// ParseComponentLogFRU parses the "RESTful FRU info:" JSON block from a combined
|
|
// component.log. Some onekeylog dumps ship this block instead of a
|
|
// devicefrusdr.log / asset.json, so it is the only source of board manufacturer,
|
|
// product name, part number and system UUID for that dump class. It also sets
|
|
// hw.BoardInfo.UUID directly, which extractBoardInfo does not populate.
|
|
func ParseComponentLogFRU(content []byte, hw *models.HardwareConfig) []models.FRUInfo {
|
|
m := componentLogFRURegex.FindSubmatch(content)
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
|
|
var devices []componentFRUDevice
|
|
if err := json.Unmarshal(m[1], &devices); err != nil {
|
|
return nil
|
|
}
|
|
|
|
var out []models.FRUInfo
|
|
for _, d := range devices {
|
|
fru := componentFRUToModel(d)
|
|
if fru.Manufacturer == "" && fru.ProductName == "" && fru.SerialNumber == "" && fru.PartNumber == "" {
|
|
continue
|
|
}
|
|
out = append(out, fru)
|
|
|
|
if hw != nil && hw.BoardInfo.UUID == "" && isSystemFRUDevice(d.Device.Name) {
|
|
if u := fruAreaValue(d.Device.SystemUUID); u != "" {
|
|
hw.BoardInfo.UUID = u
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func isSystemFRUDevice(name string) bool {
|
|
n := strings.ToUpper(strings.TrimSpace(name))
|
|
return n == "BMC_FRU" || n == "MB_FRU" || strings.HasPrefix(n, "MAINBOARD")
|
|
}
|
|
|
|
// componentFRUToModel flattens a FRU device into one models.FRUInfo. The product
|
|
// area (system serial / asset tag the operator knows, e.g. "24C319579") is
|
|
// preferred over the board area (physical PCB serial) for identity, matching the
|
|
// BEE-SP live-CD inventory and the Reanimator board contract.
|
|
func componentFRUToModel(d componentFRUDevice) models.FRUInfo {
|
|
desc := strings.TrimSpace(d.Device.Name)
|
|
if desc == "" {
|
|
desc = "FRU Device"
|
|
}
|
|
// extractBoardInfo only lifts manufacturer from an entry it recognises as the
|
|
// main board; tag the system FRU so it does.
|
|
if isSystemFRUDevice(d.Device.Name) {
|
|
desc += " (builtin board)"
|
|
}
|
|
|
|
return models.FRUInfo{
|
|
Description: desc,
|
|
ChassisType: fruAreaValue(d.Chassis.Type),
|
|
Manufacturer: fruAreaValue(d.Product.Manufacturer, d.Board.Manufacturer),
|
|
ProductName: fruAreaValue(d.Product.ProductName, d.Board.ProductName),
|
|
SerialNumber: fruAreaValue(d.Product.SerialNumber, d.Board.SerialNumber),
|
|
PartNumber: fruAreaValue(d.Product.PartNumber, d.Board.PartNumber, d.Chassis.PartNumber),
|
|
MfgDate: fruAreaValue(d.Board.Date),
|
|
AssetTag: fruAreaValue(d.Product.AssetTag),
|
|
}
|
|
}
|
|
|
|
// fruAreaValue returns the first argument that is a real value. iBMC uses "0",
|
|
// "NULL" and whitespace-padded blanks as placeholders in unused FRU areas.
|
|
func fruAreaValue(values ...string) string {
|
|
for _, v := range values {
|
|
t := strings.TrimSpace(v)
|
|
if t == "" || t == "0" || strings.EqualFold(t, "null") || strings.EqualFold(t, "n/a") {
|
|
continue
|
|
}
|
|
return t
|
|
}
|
|
return ""
|
|
}
|