Files
logpile/internal/parser/vendors/inspur/component_test.go
T

405 lines
12 KiB
Go

package inspur
import (
"strings"
"testing"
"git.mchus.pro/mchus/logpile/internal/models"
)
func TestParseNetworkAdapterInfo_ResolvesModelFromPCIIDsForRawHexModel(t *testing.T) {
text := `RESTful Network Adapter info:
{
"sys_adapters": [
{
"id": 1,
"name": "NIC1",
"Location": "#CPU0_PCIE4",
"present": 1,
"slot": 4,
"vendor_id": 32902,
"device_id": 5409,
"vendor": "",
"model": "0x1521",
"fw_ver": "",
"status": "OK",
"sn": "",
"pn": "",
"port_num": 4,
"port_type": "Base-T",
"ports": []
}
]
}
RESTful fan`
hw := &models.HardwareConfig{}
parseNetworkAdapterInfo(text, hw)
if len(hw.NetworkAdapters) != 1 {
t.Fatalf("expected 1 network adapter, got %d", len(hw.NetworkAdapters))
}
got := hw.NetworkAdapters[0]
if got.Model == "" {
t.Fatalf("expected NIC model resolved from pci.ids, got empty")
}
if !strings.Contains(strings.ToUpper(got.Model), "I350") {
t.Fatalf("expected I350 in model, got %q", got.Model)
}
if got.Vendor == "" {
t.Fatalf("expected NIC vendor resolved from pci.ids")
}
if got.NUMANode == nil || *got.NUMANode != 0 {
t.Fatalf("expected CPU affinity 0, got %v", got.NUMANode)
}
}
func TestParseComponentLog_AddsPCIeInventoryAndAffinity(t *testing.T) {
text := `RESTful PCIE Device info:
[{"id":1,"present":1,"vendor_id":6987,"vendor_name":"Marvell","device_id":37424,"device_name":"Marvell 9230 Raid","bus_num":101,"dev_num":0,"func_num":0,"max_link_width":2,"max_link_speed":2,"current_link_width":2,"current_link_speed":2,"location":"#CPU0_PE3_P4_R4_SL1","DeviceLocator":"CPU0_PE3_PCIE4","dev_type":1,"dev_subtype":6}]
RESTful Network Adapter info:
{"sys_adapters":[]}
RESTful fan info:
{"fans":[]}`
hw := &models.HardwareConfig{}
ParseComponentLog([]byte(text), hw)
if len(hw.PCIeDevices) != 1 {
t.Fatalf("expected 1 PCIe device, got %d", len(hw.PCIeDevices))
}
if got := hw.PCIeDevices[0].NUMANode; got == nil || *got != 0 {
t.Fatalf("expected CPU affinity 0, got %v", got)
}
}
func TestParseNetworkAdapterInfo_MergesIntoExistingInventory(t *testing.T) {
text := `RESTful Network Adapter info:
{
"sys_adapters": [
{
"id": 1,
"name": "NIC1",
"Location": "#CPU0_PCIE4",
"present": 1,
"slot": 4,
"vendor_id": 32902,
"device_id": 5409,
"vendor": "Mellanox",
"model": "ConnectX-6",
"fw_ver": "22.1.0",
"status": "OK",
"sn": "",
"pn": "",
"port_num": 2,
"port_type": "QSFP",
"ports": [
{ "id": 1, "mac_addr": "00:11:22:33:44:55" }
]
}
]
}
RESTful fan`
hw := &models.HardwareConfig{
NetworkAdapters: []models.NetworkAdapter{
{
Slot: "Slot 4",
BDF: "0000:17:00.0",
SerialNumber: "NIC-SN-1",
Present: true,
},
},
}
parseNetworkAdapterInfo(text, hw)
if len(hw.NetworkAdapters) != 1 {
t.Fatalf("expected merged single adapter, got %d", len(hw.NetworkAdapters))
}
got := hw.NetworkAdapters[0]
if got.BDF != "0000:17:00.0" {
t.Fatalf("expected existing BDF to survive merge, got %q", got.BDF)
}
if got.Model != "ConnectX-6" {
t.Fatalf("expected model from component log, got %q", got.Model)
}
if got.SerialNumber != "NIC-SN-1" {
t.Fatalf("expected serial from existing inventory to survive merge, got %q", got.SerialNumber)
}
if len(got.MACAddresses) != 1 || got.MACAddresses[0] != "00:11:22:33:44:55" {
t.Fatalf("expected MAC addresses from component log, got %#v", got.MACAddresses)
}
}
func TestParseNetworkAdapterInfo_PerFunctionFromBIOSPCIeTable(t *testing.T) {
text := `RESTful Network Adapter info:
{
"sys_adapters": [
{
"id": 0, "present": 1, "slot": 13,
"pcie_bus": 152, "pcie_dev": 0, "pcie_func": 0,
"vendor_id": 32902, "device_id": 5409,
"vendor": "Intel Corporation", "model": "ENFI1100-T4",
"status": "OK", "port_num": 4,
"ports": [
{ "id": 1, "mac_addr": "9C:C2:C4:65:5A:99" },
{ "id": 2, "mac_addr": "9C:C2:C4:65:5A:9A" },
{ "id": 3, "mac_addr": "9C:C2:C4:65:5A:9B" },
{ "id": 4, "mac_addr": "9C:C2:C4:65:5A:9C" }
]
}
]
}
RESTful fan
PCIE_2_INFO: Device B.D.F=0x98.0x0.0x0, RootPort B.D.F=0x97.0x2.0x0, VendorID:0x8086, DevieID:0x1521
PCIE_2_INFO: Device B.D.F=0x98.0x0.0x1, RootPort B.D.F=0x97.0x2.0x0, VendorID:0x8086, DevieID:0x1521
PCIE_2_INFO: Device B.D.F=0x98.0x0.0x2, RootPort B.D.F=0x97.0x2.0x0, VendorID:0x8086, DevieID:0x1521
PCIE_2_INFO: Device B.D.F=0x98.0x0.0x3, RootPort B.D.F=0x97.0x2.0x0, VendorID:0x8086, DevieID:0x1521
`
hw := &models.HardwareConfig{}
parseNetworkAdapterInfo(text, hw)
if len(hw.NetworkAdapters) != 4 {
t.Fatalf("expected 4 per-function NIC records, got %d: %+v", len(hw.NetworkAdapters), hw.NetworkAdapters)
}
want := map[string]string{
"0000:98:00.0": "9C:C2:C4:65:5A:99",
"0000:98:00.1": "9C:C2:C4:65:5A:9A",
"0000:98:00.2": "9C:C2:C4:65:5A:9B",
"0000:98:00.3": "9C:C2:C4:65:5A:9C",
}
for _, na := range hw.NetworkAdapters {
mac, ok := want[na.Slot]
if !ok {
t.Fatalf("unexpected slot %q", na.Slot)
}
if na.BDF != na.Slot {
t.Errorf("slot %q: BDF should equal slot, got %q", na.Slot, na.BDF)
}
if len(na.MACAddresses) != 1 || na.MACAddresses[0] != mac {
t.Errorf("slot %q: want MAC %s, got %v", na.Slot, mac, na.MACAddresses)
}
if na.DeviceID != 5409 {
t.Errorf("slot %q: device id not carried, got %d", na.Slot, na.DeviceID)
}
}
}
func TestParseNetworkAdapterInfo_EnrichesExistingPCIeRecords(t *testing.T) {
text := `RESTful Network Adapter info:
{
"sys_adapters": [
{
"id": 0, "present": 1, "slot": 13,
"pcie_bus": 101, "pcie_dev": 0, "pcie_func": 0,
"vendor_id": 5555, "device_id": 4119,
"vendor": "Mellanox Technologies", "model": "MCX512A-ACAT",
"fw_ver": "16.35.3006", "status": "OK", "port_num": 2,
"ports": [
{ "id": 1, "mac_addr": "58:A2:E1:7D:49:D4" },
{ "id": 2, "mac_addr": "58:A2:E1:7D:49:D5" }
]
}
]
}
RESTful fan`
hw := &models.HardwareConfig{
PCIeDevices: []models.PCIeDevice{
{BDF: "0000:65:00.0", VendorID: 5555, DeviceID: 4119, DeviceClass: "NetworkController"},
{BDF: "0000:65:00.1", VendorID: 5555, DeviceID: 4119, DeviceClass: "NetworkController"},
},
}
parseNetworkAdapterInfo(text, hw)
if len(hw.NetworkAdapters) != 0 {
t.Fatalf("expected no new NIC records (existing PCIe enriched in place), got %d", len(hw.NetworkAdapters))
}
for _, d := range hw.PCIeDevices {
if d.Model != "MCX512A-ACAT" {
t.Errorf("%s: model not enriched, got %q", d.BDF, d.Model)
}
if d.Firmware != "16.35.3006" {
t.Errorf("%s: firmware not enriched, got %q", d.BDF, d.Firmware)
}
}
}
func TestParseComponentLogSensors_ExtractsFanBackplaneAndPSUSummary(t *testing.T) {
text := `RESTful PSU info:
{
"power_supplies": [
{ "id": 0, "present": 1, "status": "OK", "ps_in_power": 123, "ps_out_power": 110, "psu_max_temperature": 41 }
],
"present_power_reading": 999
}
RESTful Network Adapter info:
{ "sys_adapters": [] }
RESTful fan info:
{
"fans": [
{ "id": 1, "fan_name": "FAN0_F_Speed", "present": "OK", "status": "OK", "status_str": "OK", "speed_rpm": 9200, "speed_percent": 35, "max_speed_rpm": 20000, "fan_model": "6056" }
],
"fans_power": 33
}
RESTful diskbackplane info:
[
{ "port_count": 8, "driver_count": 4, "front": 1, "backplane_index": 0, "present": 1, "cpld_version": "3.1", "temperature": 18 }
]
BMC`
sensors := ParseComponentLogSensors([]byte(text))
if len(sensors) == 0 {
t.Fatalf("expected sensors from component.log, got none")
}
has := func(name string) bool {
for _, s := range sensors {
if s.Name == name {
return true
}
}
return false
}
if !has("FAN0_F_Speed") {
t.Fatalf("expected FAN0_F_Speed sensor in parsed output")
}
if !has("Backplane0_Temp") {
t.Fatalf("expected Backplane0_Temp sensor in parsed output")
}
if !has("PSU_Present_Power_Reading") {
t.Fatalf("expected PSU_Present_Power_Reading sensor in parsed output")
}
}
func TestParseComponentLogEvents_FanCriticalStatus(t *testing.T) {
text := `RESTful fan info:
{
"fans": [
{ "id": 7, "fan_name": "FAN3_R_Speed", "present": "OK", "status": "Critical", "status_str": "Critical", "speed_rpm": 0, "speed_percent": 0, "max_speed_rpm": 20000, "fan_model": "6056" }
],
"fans_power": 0
}
RESTful diskbackplane info:
[]
BMC`
events := ParseComponentLogEvents([]byte(text))
if len(events) != 1 {
t.Fatalf("expected 1 fan event, got %d", len(events))
}
if events[0].EventType != "Fan Status" {
t.Fatalf("expected Fan Status event type, got %q", events[0].EventType)
}
if events[0].Severity != models.SeverityCritical {
t.Fatalf("expected critical severity, got %q", events[0].Severity)
}
}
// TestParseComponentLogSensors_FloatFansPower guards a regression where a
// float "fans_power" ("12.000000") made json.Unmarshal of the whole fan block
// fail, dropping every fan reading (0 fans vs the live-CD's 8 for the same host).
func TestParseComponentLogSensors_FloatFansPower(t *testing.T) {
text := `RESTful fan info:
{ "fans": [
{ "id": 0, "fan_name": "System Fan0 Front", "present": "OK", "status": "OK", "status_str": "OK", "speed_rpm": 2947, "speed_percent": 23, "max_speed_rpm": 20000, "fan_model": "8056" },
{ "id": 1, "fan_name": "System Fan0 Rear", "present": "OK", "status": "OK", "status_str": "OK", "speed_rpm": 2520, "speed_percent": 23, "max_speed_rpm": 20000, "fan_model": "8056" }
], "fans_power": 12.000000 }
RESTful diskbackplane info:
[]
BMC`
sensors := ParseComponentLogSensors([]byte(text))
var fans, fanPower int
for _, s := range sensors {
switch s.Name {
case "System Fan0 Front", "System Fan0 Rear":
fans++
case "Fans_Power":
fanPower++
if s.Value != 12 {
t.Errorf("Fans_Power value = %v, want 12", s.Value)
}
}
}
if fans != 2 {
t.Fatalf("expected 2 fan sensors, got %d (float fans_power broke the parse)", fans)
}
if fanPower != 1 {
t.Errorf("expected 1 Fans_Power sensor, got %d", fanPower)
}
}
// TestExtractComponentFirmware_StripsBuildStamp guards against the iBMC
// "RESTful version info" build-timestamp suffix leaking into hardware.firmware,
// which made BIOS/BMC versions churn against the bare live-CD values.
func TestExtractComponentFirmware_StripsBuildStamp(t *testing.T) {
text := `RESTful version info:
[ { "id": 0, "dev_name": "Activate(BMC0)", "dev_version": "7.11.02 (2024-01-05 16:01:47)" },
{ "id": 1, "dev_name": "Inactivate(BMC1)", "dev_version": "7.11.02 (2024-01-05 16:01:47)" },
{ "id": 2, "dev_name": "BIOS", "dev_version": "08.05.01 (02/21/2024 16:51:27)" },
{ "id": 3, "dev_name": "ME", "dev_version": "4.4.4.500" } ]
RESTful CPU info:
{ }`
hw := &models.HardwareConfig{}
extractComponentFirmware(text, hw)
got := map[string]string{}
for _, fw := range hw.Firmware {
got[fw.DeviceName] = fw.Version
}
if got["BIOS"] != "08.05.01" {
t.Errorf("BIOS version = %q, want 08.05.01", got["BIOS"])
}
if got["BMC"] != "7.11.02" {
t.Errorf("BMC version = %q, want 7.11.02", got["BMC"])
}
if got["ME"] != "4.4.4.500" {
t.Errorf("ME version = %q, want 4.4.4.500 (untouched)", got["ME"])
}
}
func TestParseHDDInfo_MergesIntoExistingStorage(t *testing.T) {
text := `RESTful HDD info:
[
{
"id": 1,
"present": 1,
"enable": 1,
"SN": "SER123",
"model": "Sample SSD",
"capacity": 1024,
"manufacture": "ACME",
"firmware": "1.0.0",
"locationstring": "OB01",
"capablespeed": 6
}
]
RESTful PSU`
hw := &models.HardwareConfig{
Storage: []models.Storage{
{
Slot: "OB01",
Type: "SSD",
},
},
}
parseHDDInfo(text, hw)
if len(hw.Storage) != 1 {
t.Fatalf("expected 1 storage item, got %d", len(hw.Storage))
}
if hw.Storage[0].SerialNumber != "SER123" {
t.Fatalf("expected serial from HDD section, got %q", hw.Storage[0].SerialNumber)
}
if hw.Storage[0].Model != "Sample SSD" {
t.Fatalf("expected model from HDD section, got %q", hw.Storage[0].Model)
}
if hw.Storage[0].Firmware != "1.0.0" {
t.Fatalf("expected firmware from HDD section, got %q", hw.Storage[0].Firmware)
}
}