- Add HPE iLO Redfish profile (priority 20): matches on manufacturer/OEM/iLO signals, adds SmartStorage/SmartStorageConfig to critical paths, sets realistic ETA baseline and rate policy for iLO's known slowness - Fix post-probe hang on HPE iLO: skip numeric probing of collections where Members@odata.count == len(Members); add 4s postProbeClient timeout as safety net - Exclude /WorkloadPerformanceAdvisor from crawl paths - Fix replay parser: skip absent CPU sockets, absent DIMM slots, absent drive bays - Filter N/A version entries from firmware inventory - Remove drive firmware from general firmware list (already in Storage[].Firmware) - Add HPE AHS (.ahs) archive parser with hybrid SMBIOS/Redfish extraction Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
211 lines
6.6 KiB
Go
211 lines
6.6 KiB
Go
package hpe_ilo_ahs
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/binary"
|
|
"testing"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/parser"
|
|
)
|
|
|
|
func TestDetectAHS(t *testing.T) {
|
|
p := &Parser{}
|
|
score := p.Detect([]parser.ExtractedFile{{
|
|
Path: "HPE_CZ2D1X0GS3_20260330.ahs",
|
|
Content: makeAHSArchive(t, []ahsTestEntry{{Name: "CUST_INFO.DAT", Payload: []byte("x")}}),
|
|
}})
|
|
if score < 80 {
|
|
t.Fatalf("expected high confidence detect, got %d", score)
|
|
}
|
|
}
|
|
|
|
func TestParseAHSInventory(t *testing.T) {
|
|
p := &Parser{}
|
|
content := makeAHSArchive(t, []ahsTestEntry{
|
|
{Name: "CUST_INFO.DAT", Payload: make([]byte, 16)},
|
|
{Name: "0000088-2026-03-30.zbb", Payload: gzipBytes(t, []byte(sampleInventoryBlob()))},
|
|
})
|
|
|
|
result, err := p.Parse([]parser.ExtractedFile{{
|
|
Path: "HPE_CZ2D1X0GS3_20260330.ahs",
|
|
Content: content,
|
|
}})
|
|
if err != nil {
|
|
t.Fatalf("parse failed: %v", err)
|
|
}
|
|
if result.Hardware == nil {
|
|
t.Fatalf("expected hardware section")
|
|
}
|
|
|
|
board := result.Hardware.BoardInfo
|
|
if board.Manufacturer != "HPE" {
|
|
t.Fatalf("unexpected board manufacturer: %q", board.Manufacturer)
|
|
}
|
|
if board.ProductName != "ProLiant DL380 Gen11" {
|
|
t.Fatalf("unexpected board product: %q", board.ProductName)
|
|
}
|
|
if board.SerialNumber != "CZ2D1X0GS3" {
|
|
t.Fatalf("unexpected board serial: %q", board.SerialNumber)
|
|
}
|
|
if board.PartNumber != "P52560-421" {
|
|
t.Fatalf("unexpected board part number: %q", board.PartNumber)
|
|
}
|
|
|
|
if len(result.Hardware.CPUs) != 1 || result.Hardware.CPUs[0].Model != "Intel(R) Xeon(R) Gold 6444Y" {
|
|
t.Fatalf("unexpected CPUs: %+v", result.Hardware.CPUs)
|
|
}
|
|
if len(result.Hardware.Memory) != 1 {
|
|
t.Fatalf("expected one DIMM, got %d", len(result.Hardware.Memory))
|
|
}
|
|
if result.Hardware.Memory[0].PartNumber != "HMCG88AEBRA115N" {
|
|
t.Fatalf("unexpected DIMM part number: %q", result.Hardware.Memory[0].PartNumber)
|
|
}
|
|
|
|
if len(result.Hardware.NetworkAdapters) != 2 {
|
|
t.Fatalf("expected two network adapters, got %d", len(result.Hardware.NetworkAdapters))
|
|
}
|
|
if len(result.Hardware.PowerSupply) != 1 {
|
|
t.Fatalf("expected one PSU, got %d", len(result.Hardware.PowerSupply))
|
|
}
|
|
if result.Hardware.PowerSupply[0].SerialNumber != "5XUWB0C4DJG4BV" {
|
|
t.Fatalf("unexpected PSU serial: %q", result.Hardware.PowerSupply[0].SerialNumber)
|
|
}
|
|
|
|
if len(result.Hardware.Storage) != 1 {
|
|
t.Fatalf("expected one physical drive, got %d", len(result.Hardware.Storage))
|
|
}
|
|
drive := result.Hardware.Storage[0]
|
|
if drive.Model != "SAMSUNGMZ7L3480HCHQ-00A07" {
|
|
t.Fatalf("unexpected drive model: %q", drive.Model)
|
|
}
|
|
if drive.SerialNumber != "S664NC0Y502720" {
|
|
t.Fatalf("unexpected drive serial: %q", drive.SerialNumber)
|
|
}
|
|
if drive.SizeGB != 480 {
|
|
t.Fatalf("unexpected drive size: %d", drive.SizeGB)
|
|
}
|
|
|
|
if len(result.Hardware.Firmware) == 0 {
|
|
t.Fatalf("expected firmware inventory")
|
|
}
|
|
foundILO := false
|
|
foundControllerFW := false
|
|
for _, item := range result.Hardware.Firmware {
|
|
if item.DeviceName == "iLO 6" && item.Version == "v1.63p20" {
|
|
foundILO = true
|
|
}
|
|
if item.DeviceName == "HPE MR408i-o Gen11" && item.Version == "52.26.3-5379" {
|
|
foundControllerFW = true
|
|
}
|
|
}
|
|
if !foundILO {
|
|
t.Fatalf("expected iLO firmware entry")
|
|
}
|
|
if !foundControllerFW {
|
|
t.Fatalf("expected controller firmware entry")
|
|
}
|
|
|
|
if len(result.Hardware.Devices) < 6 {
|
|
t.Fatalf("expected canonical devices, got %d", len(result.Hardware.Devices))
|
|
}
|
|
if len(result.Events) == 0 {
|
|
t.Fatalf("expected parsed events")
|
|
}
|
|
}
|
|
|
|
type ahsTestEntry struct {
|
|
Name string
|
|
Payload []byte
|
|
Flag uint32
|
|
}
|
|
|
|
func makeAHSArchive(t *testing.T, entries []ahsTestEntry) []byte {
|
|
t.Helper()
|
|
|
|
var buf bytes.Buffer
|
|
for _, entry := range entries {
|
|
header := make([]byte, ahsHeaderSize)
|
|
copy(header[:4], []byte("ABJR"))
|
|
binary.LittleEndian.PutUint16(header[4:6], 0x0300)
|
|
binary.LittleEndian.PutUint16(header[6:8], 0x0002)
|
|
binary.LittleEndian.PutUint32(header[8:12], uint32(len(entry.Payload)))
|
|
flag := entry.Flag
|
|
if flag == 0 {
|
|
flag = 0x80000002
|
|
if len(entry.Payload) >= 2 && entry.Payload[0] == 0x1f && entry.Payload[1] == 0x8b {
|
|
flag = 0x80000001
|
|
}
|
|
}
|
|
binary.LittleEndian.PutUint32(header[16:20], flag)
|
|
copy(header[20:52], []byte(entry.Name))
|
|
buf.Write(header)
|
|
buf.Write(entry.Payload)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func gzipBytes(t *testing.T, payload []byte) []byte {
|
|
t.Helper()
|
|
|
|
var buf bytes.Buffer
|
|
zw := gzip.NewWriter(&buf)
|
|
if _, err := zw.Write(payload); err != nil {
|
|
t.Fatalf("gzip payload: %v", err)
|
|
}
|
|
if err := zw.Close(); err != nil {
|
|
t.Fatalf("close gzip writer: %v", err)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func sampleInventoryBlob() string {
|
|
return stringsJoin(
|
|
"iLO 6 v1.63p20 built on Sep 13 2024",
|
|
"HPE",
|
|
"ProLiant DL380 Gen11",
|
|
"CZ2D1X0GS3",
|
|
"P52560-421",
|
|
"Proc 1",
|
|
"Intel(R) Corporation",
|
|
"Intel(R) Xeon(R) Gold 6444Y",
|
|
"PROC 1 DIMM 3",
|
|
"Hynix",
|
|
"HMCG88AEBRA115N",
|
|
"2B5F92C6",
|
|
"Power Supply 1",
|
|
"5XUWB0C4DJG4BV",
|
|
"P03178-B21",
|
|
"PciRoot(0x1)/Pci(0x5,0x0)/Pci(0x0,0x0)",
|
|
"NIC.Slot.1.1",
|
|
"Network Controller",
|
|
"Slot 1",
|
|
"MCX512A-ACAT",
|
|
"MT2230478382",
|
|
"PciRoot(0x3)/Pci(0x1,0x0)/Pci(0x0,0x0)",
|
|
"OCP.Slot.15.1",
|
|
"Broadcom NetXtreme Gigabit Ethernet - NIC",
|
|
"OCP Slot 15",
|
|
"P51183-001",
|
|
"1CH0150001",
|
|
"20.28.41",
|
|
"System ROM",
|
|
"v2.22 (06/19/2024)",
|
|
"03/30/2026 09:47:33",
|
|
"iLO network link down.",
|
|
`{"@odata.id":"/redfish/v1/Systems/1/Storage/DE00A000/Controllers/0","@odata.type":"#StorageController.v1_7_0.StorageController","Id":"0","Name":"HPE MR408i-o Gen11","FirmwareVersion":"52.26.3-5379","Manufacturer":"HPE","Model":"HPE MR408i-o Gen11","PartNumber":"P58543-001","SKU":"P58335-B21","SerialNumber":"PXSFQ0BBIJY3B3","Status":{"State":"Enabled","Health":"OK"},"Location":{"PartLocation":{"ServiceLabel":"Slot=14","LocationType":"Slot","LocationOrdinalValue":14}},"PCIeInterface":{"PCIeType":"Gen4","LanesInUse":8}}`,
|
|
`{"@odata.id":"/redfish/v1/Chassis/DE00A000/Drives/0","@odata.type":"#Drive.v1_17_0.Drive","Id":"0","Name":"480GB 6G SATA SSD","Status":{"State":"StandbyOffline","Health":"OK"},"PhysicalLocation":{"PartLocation":{"ServiceLabel":"Slot=14:Port=1:Box=3:Bay=1","LocationType":"Bay","LocationOrdinalValue":1}},"CapacityBytes":480103981056,"MediaType":"SSD","Model":"SAMSUNGMZ7L3480HCHQ-00A07","Protocol":"SATA","Revision":"JXTC604Q","SerialNumber":"S664NC0Y502720","PredictedMediaLifeLeftPercent":100}`,
|
|
`{"@odata.id":"/redfish/v1/Chassis/DE00A000/Drives/64515","@odata.type":"#Drive.v1_17_0.Drive","Id":"64515","Name":"Empty Bay","Status":{"State":"Absent","Health":"OK"}}`,
|
|
)
|
|
}
|
|
|
|
func stringsJoin(parts ...string) string {
|
|
return string(bytes.Join(func() [][]byte {
|
|
out := make([][]byte, 0, len(parts))
|
|
for _, part := range parts {
|
|
out = append(out, []byte(part))
|
|
}
|
|
return out
|
|
}(), []byte{0}))
|
|
}
|