fix(inspur): read RESTful FRU info and float fans_power from component.log
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
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ab8636da04
commit
2fa0f78f94
+114
@@ -0,0 +1,114 @@
|
||||
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 ""
|
||||
}
|
||||
Reference in New Issue
Block a user