export: align reanimator and enrich redfish metrics
This commit is contained in:
81
internal/parser/vendors/dell/parser.go
vendored
81
internal/parser/vendors/dell/parser.go
vendored
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
"git.mchus.pro/mchus/logpile/internal/parser"
|
||||
"git.mchus.pro/mchus/logpile/internal/parser/vendors/pciids"
|
||||
)
|
||||
|
||||
const parserVersion = "3.0"
|
||||
@@ -199,7 +200,7 @@ func parseDCIMViewXML(content []byte, result *models.AnalysisResult) {
|
||||
parsePowerSupplyView(props, result)
|
||||
case "DCIM_PCIDeviceView":
|
||||
parsePCIeDeviceView(props, result)
|
||||
case "DCIM_NICView":
|
||||
case "DCIM_NICView", "DCIM_InfiniBandView":
|
||||
parseNICView(props, result)
|
||||
case "DCIM_VideoView":
|
||||
parseVideoView(props, result)
|
||||
@@ -374,6 +375,10 @@ func parsePhysicalDiskView(props map[string]string, result *models.AnalysisResul
|
||||
Location: strings.TrimSpace(props["devicedescription"]),
|
||||
Status: normalizeStatus(firstNonEmpty(props["raidstatus"], props["primarystatus"])),
|
||||
}
|
||||
if v := strings.TrimSpace(props["remainingratedwriteendurance"]); v != "" {
|
||||
n := parseIntLoose(v)
|
||||
st.RemainingEndurancePct = &n
|
||||
}
|
||||
result.Hardware.Storage = append(result.Hardware.Storage, st)
|
||||
}
|
||||
|
||||
@@ -437,11 +442,18 @@ var pcieFQDDNoisePrefix = []string{
|
||||
"SMBus.Embedded.",
|
||||
"AHCI.Embedded.",
|
||||
"Video.Embedded.",
|
||||
"NIC.Embedded.",
|
||||
// All NIC FQDD classes are parsed from DCIM_NICView / DCIM_InfiniBandView into
|
||||
// NetworkAdapters with model, MAC, firmware, and VendorID/DeviceID. The
|
||||
// DCIM_PCIDeviceView duplicate carries only DataBusWidth ("Unknown", "16x or x16")
|
||||
// and no useful extra data, so suppress it here.
|
||||
"NIC.",
|
||||
"InfiniBand.",
|
||||
}
|
||||
|
||||
func parsePCIeDeviceView(props map[string]string, result *models.AnalysisResult) {
|
||||
desc := strings.TrimSpace(firstNonEmpty(props["devicedescription"], props["description"]))
|
||||
// "description" is the chip/device model (e.g. "MT28908 Family [ConnectX-6]"); prefer
|
||||
// it over "devicedescription" which is the location string ("InfiniBand in Slot 1 Port 1").
|
||||
desc := strings.TrimSpace(firstNonEmpty(props["description"], props["devicedescription"]))
|
||||
fqdd := strings.TrimSpace(firstNonEmpty(props["fqdd"], props["instanceid"]))
|
||||
if desc == "" && fqdd == "" {
|
||||
return
|
||||
@@ -451,14 +463,26 @@ func parsePCIeDeviceView(props map[string]string, result *models.AnalysisResult)
|
||||
return
|
||||
}
|
||||
}
|
||||
vendorID := parseHexOrDec(firstNonEmpty(props["pcivendorid"], props["vendorid"]))
|
||||
deviceID := parseHexOrDec(firstNonEmpty(props["pcideviceid"], props["deviceid"]))
|
||||
manufacturer := strings.TrimSpace(props["manufacturer"])
|
||||
|
||||
// General rule: if chip model not found in logs but PCI IDs are known, resolve from pci.ids
|
||||
if desc == "" && vendorID != 0 && deviceID != 0 {
|
||||
desc = pciids.DeviceName(vendorID, deviceID)
|
||||
}
|
||||
if manufacturer == "" && vendorID != 0 {
|
||||
manufacturer = pciids.VendorName(vendorID)
|
||||
}
|
||||
|
||||
p := models.PCIeDevice{
|
||||
Slot: fqdd,
|
||||
Description: desc,
|
||||
VendorID: parseHexOrDec(firstNonEmpty(props["pcivendorid"], props["vendorid"])),
|
||||
DeviceID: parseHexOrDec(firstNonEmpty(props["pcideviceid"], props["deviceid"])),
|
||||
VendorID: vendorID,
|
||||
DeviceID: deviceID,
|
||||
BDF: formatBDF(props["busnumber"], props["devicenumber"], props["functionnumber"]),
|
||||
DeviceClass: strings.TrimSpace(props["databuswidth"]),
|
||||
Manufacturer: strings.TrimSpace(props["manufacturer"]),
|
||||
Manufacturer: manufacturer,
|
||||
NUMANode: parseIntLoose(props["cpuaffinity"]),
|
||||
Status: normalizeStatus(props["primarystatus"]),
|
||||
}
|
||||
result.Hardware.PCIeDevices = append(result.Hardware.PCIeDevices, p)
|
||||
@@ -471,15 +495,31 @@ func parseNICView(props map[string]string, result *models.AnalysisResult) {
|
||||
return
|
||||
}
|
||||
mac := strings.TrimSpace(firstNonEmpty(props["currentmacaddress"], props["permanentmacaddress"]))
|
||||
vendorID := parseHexOrDec(firstNonEmpty(props["pcivendorid"], props["vendorid"]))
|
||||
deviceID := parseHexOrDec(firstNonEmpty(props["pcideviceid"], props["deviceid"]))
|
||||
vendor := strings.TrimSpace(firstNonEmpty(props["vendorname"], props["manufacturer"]))
|
||||
|
||||
// Prefer pci.ids chip model over generic ProductName when PCI IDs are available.
|
||||
// Dell TSR often reports a marketing name (e.g. "Mellanox Network Adapter") while
|
||||
// pci.ids has the precise chip identifier (e.g. "MT28908 Family [ConnectX-6]").
|
||||
if vendorID != 0 && deviceID != 0 {
|
||||
if chipModel := pciids.DeviceName(vendorID, deviceID); chipModel != "" {
|
||||
model = chipModel
|
||||
}
|
||||
if vendor == "" {
|
||||
vendor = pciids.VendorName(vendorID)
|
||||
}
|
||||
}
|
||||
|
||||
n := models.NetworkAdapter{
|
||||
Slot: fqdd,
|
||||
Location: strings.TrimSpace(firstNonEmpty(props["devicedescription"], fqdd)),
|
||||
Present: true,
|
||||
Model: model,
|
||||
Description: strings.TrimSpace(props["protocol"]),
|
||||
Vendor: strings.TrimSpace(firstNonEmpty(props["vendorname"], props["manufacturer"])),
|
||||
VendorID: parseHexOrDec(firstNonEmpty(props["pcivendorid"], props["vendorid"])),
|
||||
DeviceID: parseHexOrDec(firstNonEmpty(props["pcideviceid"], props["deviceid"])),
|
||||
Vendor: vendor,
|
||||
VendorID: vendorID,
|
||||
DeviceID: deviceID,
|
||||
SerialNumber: strings.TrimSpace(props["serialnumber"]),
|
||||
PartNumber: strings.TrimSpace(props["partnumber"]),
|
||||
Firmware: strings.TrimSpace(firstNonEmpty(
|
||||
@@ -489,6 +529,7 @@ func parseNICView(props map[string]string, result *models.AnalysisResult) {
|
||||
props["controllerbiosversion"],
|
||||
)),
|
||||
PortCount: inferPortCountFromFQDD(fqdd),
|
||||
NUMANode: parseIntLoose(props["cpuaffinity"]),
|
||||
Status: normalizeStatus(props["primarystatus"]),
|
||||
}
|
||||
if mac != "" {
|
||||
@@ -542,10 +583,11 @@ func parseControllerView(props map[string]string, result *models.AnalysisResult)
|
||||
DeviceClass: "storage-controller",
|
||||
Manufacturer: strings.TrimSpace(firstNonEmpty(props["devicecardmanufacturer"], props["manufacturer"])),
|
||||
PartNumber: strings.TrimSpace(firstNonEmpty(props["ppid"], props["boardpartnumber"])),
|
||||
NUMANode: parseIntLoose(props["cpuaffinity"]),
|
||||
Status: normalizeStatus(props["primarystatus"]),
|
||||
})
|
||||
|
||||
addFirmware(result, firstNonEmpty(name, fqdd), props["controllerfirmwareversion"], "storage controller")
|
||||
addFirmware(result, firstNonEmpty(name, fqdd), props["controllerfirmwareversion"], firstNonEmpty(fqdd, "storage controller"))
|
||||
}
|
||||
|
||||
func parseControllerBatteryView(props map[string]string, result *models.AnalysisResult) {
|
||||
@@ -1131,6 +1173,7 @@ func mergeStorage(dst *models.Storage, src models.Storage) {
|
||||
}
|
||||
setIfEmpty(&dst.Location, src.Location)
|
||||
setIfEmpty(&dst.Status, src.Status)
|
||||
dst.Details = mergeDellDetails(dst.Details, src.Details)
|
||||
}
|
||||
|
||||
func dedupeVolumes(items []models.StorageVolume) []models.StorageVolume {
|
||||
@@ -1202,6 +1245,22 @@ func mergePSU(dst *models.PSU, src models.PSU) {
|
||||
dst.InputVoltage = src.InputVoltage
|
||||
}
|
||||
setIfEmpty(&dst.InputType, src.InputType)
|
||||
dst.Details = mergeDellDetails(dst.Details, src.Details)
|
||||
}
|
||||
|
||||
func mergeDellDetails(primary, secondary map[string]any) map[string]any {
|
||||
if len(secondary) == 0 {
|
||||
return primary
|
||||
}
|
||||
if primary == nil {
|
||||
primary = make(map[string]any, len(secondary))
|
||||
}
|
||||
for key, value := range secondary {
|
||||
if _, ok := primary[key]; !ok {
|
||||
primary[key] = value
|
||||
}
|
||||
}
|
||||
return primary
|
||||
}
|
||||
|
||||
func dedupeNetworkAdapters(items []models.NetworkAdapter) []models.NetworkAdapter {
|
||||
|
||||
256
internal/parser/vendors/dell/parser_test.go
vendored
256
internal/parser/vendors/dell/parser_test.go
vendored
@@ -204,6 +204,262 @@ func TestParseNestedTSRZip(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseDellPhysicalDiskEndurance verifies that RemainingRatedWriteEndurance from
|
||||
// DCIM_PhysicalDiskView is parsed into Storage.RemainingEndurancePct.
|
||||
func TestParseDellPhysicalDiskEndurance(t *testing.T) {
|
||||
const viewXML = `<CIM><MESSAGE><SIMPLEREQ>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_SystemView">
|
||||
<PROPERTY NAME="Manufacturer"><VALUE>Dell Inc.</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Model"><VALUE>PowerEdge R6625</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="ServiceTag"><VALUE>8VS2LG4</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_PhysicalDiskView">
|
||||
<PROPERTY NAME="FQDD"><VALUE>Disk.Bay.0:Enclosure.Internal.0-1:RAID.SL.3-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Slot"><VALUE>0</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Model"><VALUE>HFS480G3H2X069N</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="SerialNumber"><VALUE>ESEAN5254I030B26B</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="SizeInBytes"><VALUE>479559942144</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="MediaType"><VALUE>Solid State Drive</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="BusProtocol"><VALUE>SATA</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Revision"><VALUE>DZ03</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="RemainingRatedWriteEndurance"><VALUE>100</VALUE><DisplayValue>100 %</DisplayValue></PROPERTY>
|
||||
<PROPERTY NAME="PrimaryStatus"><VALUE>1</VALUE><DisplayValue>OK</DisplayValue></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_PhysicalDiskView">
|
||||
<PROPERTY NAME="FQDD"><VALUE>Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.3-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Slot"><VALUE>1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Model"><VALUE>TOSHIBA MG08ADA800E</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="SerialNumber"><VALUE>X1G0A0YXFVVG</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="SizeInBytes"><VALUE>8001563222016</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="MediaType"><VALUE>Hard Disk Drive</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="BusProtocol"><VALUE>SAS</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Revision"><VALUE>0104</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
</SIMPLEREQ></MESSAGE></CIM>`
|
||||
|
||||
inner := makeZipArchive(t, map[string][]byte{
|
||||
"tsr/metadata.json": []byte(`{"Make":"Dell Inc.","Model":"PowerEdge R6625","ServiceTag":"8VS2LG4"}`),
|
||||
"tsr/hardware/sysinfo/inventory/sysinfo_DCIM_View.xml": []byte(viewXML),
|
||||
})
|
||||
|
||||
p := &Parser{}
|
||||
result, err := p.Parse([]parser.ExtractedFile{
|
||||
{Path: "signature", Content: []byte("ok")},
|
||||
{Path: "TSR20260306141852_8VS2LG4.pl.zip", Content: inner},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("parse failed: %v", err)
|
||||
}
|
||||
if len(result.Hardware.Storage) != 2 {
|
||||
t.Fatalf("expected 2 storage devices, got %d", len(result.Hardware.Storage))
|
||||
}
|
||||
|
||||
ssd := result.Hardware.Storage[0]
|
||||
if ssd.RemainingEndurancePct == nil {
|
||||
t.Fatalf("SSD slot 0: expected RemainingEndurancePct to be set")
|
||||
}
|
||||
if *ssd.RemainingEndurancePct != 100 {
|
||||
t.Errorf("SSD slot 0: expected RemainingEndurancePct=100, got %d", *ssd.RemainingEndurancePct)
|
||||
}
|
||||
|
||||
hdd := result.Hardware.Storage[1]
|
||||
if hdd.RemainingEndurancePct != nil {
|
||||
t.Errorf("HDD slot 1: expected RemainingEndurancePct absent, got %d", *hdd.RemainingEndurancePct)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseDellInfiniBandView verifies that DCIM_InfiniBandView entries are parsed as
|
||||
// NetworkAdapters (not PCIe devices) and that the corresponding SoftwareIdentity firmware
|
||||
// entry with FQDD "InfiniBand.Slot.*" does not leak into hardware.firmware.
|
||||
//
|
||||
// Regression guard: PowerEdge R6625 (8VS2LG4) — "Mellanox Network Adapter" version
|
||||
// "20.39.35.60" appeared in hardware.firmware because DCIM_InfiniBandView was ignored
|
||||
// (device ended up only in PCIeDevices with model "16x or x16") and SoftwareIdentity
|
||||
// FQDD "InfiniBand.Slot.1-1" was not filtered. (2026-03-15)
|
||||
func TestParseDellInfiniBandView(t *testing.T) {
|
||||
const viewXML = `<CIM><MESSAGE><SIMPLEREQ>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_SystemView">
|
||||
<PROPERTY NAME="Manufacturer"><VALUE>Dell Inc.</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Model"><VALUE>PowerEdge R6625</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="ServiceTag"><VALUE>8VS2LG4</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_InfiniBandView">
|
||||
<PROPERTY NAME="FQDD"><VALUE>InfiniBand.Slot.1-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="DeviceDescription"><VALUE>InfiniBand in Slot 1 Port 1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="CurrentMACAddress"><VALUE>00:1C:FD:D7:5A:E6</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="FamilyVersion"><VALUE>20.39.35.60</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="EFIVersion"><VALUE>14.32.17</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="PCIVendorID"><VALUE>15B3</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="PCIDeviceID"><VALUE>101B</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="PrimaryStatus"><VALUE>0</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_PCIDeviceView">
|
||||
<PROPERTY NAME="FQDD"><VALUE>InfiniBand.Slot.1-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Description"><VALUE>MT28908 Family [ConnectX-6]</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="DeviceDescription"><VALUE>InfiniBand in Slot 1 Port 1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Manufacturer"><VALUE>Mellanox Technologies</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="PCIVendorID"><VALUE>15B3</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="PCIDeviceID"><VALUE>101B</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="DataBusWidth"><DisplayValue>16x or x16</DisplayValue></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_ControllerView">
|
||||
<PROPERTY NAME="FQDD"><VALUE>RAID.SL.3-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="ProductName"><VALUE>PERC H755 Front</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="ControllerFirmwareVersion"><VALUE>52.30.0-6115</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="PrimaryStatus"><VALUE>0</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
</SIMPLEREQ></MESSAGE></CIM>`
|
||||
|
||||
const swXML = `<CIM><MESSAGE><SIMPLEREQ>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_SoftwareIdentity">
|
||||
<PROPERTY NAME="ElementName"><VALUE>Mellanox Network Adapter - 00:1C:FD:D7:5A:E6</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="FQDD"><VALUE>InfiniBand.Slot.1-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="VersionString"><VALUE>20.39.35.60</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_SoftwareIdentity">
|
||||
<PROPERTY NAME="ElementName"><VALUE>PERC H755 Front</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="FQDD"><VALUE>RAID.SL.3-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="VersionString"><VALUE>52.30.0-6115</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_SoftwareIdentity">
|
||||
<PROPERTY NAME="ElementName"><VALUE>BIOS</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="FQDD"><VALUE>BIOS.Setup.1-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="VersionString"><VALUE>1.15.3</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
</SIMPLEREQ></MESSAGE></CIM>`
|
||||
|
||||
inner := makeZipArchive(t, map[string][]byte{
|
||||
"tsr/metadata.json": []byte(`{"Make":"Dell Inc.","Model":"PowerEdge R6625","ServiceTag":"8VS2LG4"}`),
|
||||
"tsr/hardware/sysinfo/inventory/sysinfo_DCIM_View.xml": []byte(viewXML),
|
||||
"tsr/hardware/sysinfo/inventory/sysinfo_DCIM_SoftwareIdentity.xml": []byte(swXML),
|
||||
})
|
||||
|
||||
p := &Parser{}
|
||||
result, err := p.Parse([]parser.ExtractedFile{
|
||||
{Path: "signature", Content: []byte("ok")},
|
||||
{Path: "TSR20260306141852_8VS2LG4.pl.zip", Content: inner},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("parse failed: %v", err)
|
||||
}
|
||||
|
||||
// InfiniBand adapter must appear as a NetworkAdapter, not a PCIe device.
|
||||
if len(result.Hardware.NetworkAdapters) != 1 {
|
||||
t.Fatalf("expected 1 network adapter, got %d", len(result.Hardware.NetworkAdapters))
|
||||
}
|
||||
nic := result.Hardware.NetworkAdapters[0]
|
||||
if nic.Slot != "InfiniBand.Slot.1-1" {
|
||||
t.Errorf("unexpected NIC slot: %q", nic.Slot)
|
||||
}
|
||||
if nic.Firmware != "20.39.35.60" {
|
||||
t.Errorf("unexpected NIC firmware: %q", nic.Firmware)
|
||||
}
|
||||
if len(nic.MACAddresses) == 0 || nic.MACAddresses[0] != "00:1C:FD:D7:5A:E6" {
|
||||
t.Errorf("unexpected NIC MAC: %v", nic.MACAddresses)
|
||||
}
|
||||
// pci.ids enrichment: VendorID=0x15B3, DeviceID=0x101B → chip model + vendor name.
|
||||
if nic.Model != "MT28908 Family [ConnectX-6]" {
|
||||
t.Errorf("NIC model = %q, want MT28908 Family [ConnectX-6] (from pci.ids)", nic.Model)
|
||||
}
|
||||
if nic.Vendor != "Mellanox Technologies" {
|
||||
t.Errorf("NIC vendor = %q, want Mellanox Technologies (from pci.ids)", nic.Vendor)
|
||||
}
|
||||
|
||||
// InfiniBand FQDD must NOT appear in PCIe devices.
|
||||
for _, pcie := range result.Hardware.PCIeDevices {
|
||||
if pcie.Slot == "InfiniBand.Slot.1-1" {
|
||||
t.Errorf("InfiniBand.Slot.1-1 must not appear in PCIeDevices")
|
||||
}
|
||||
}
|
||||
|
||||
// Firmware entries from SoftwareIdentity and parseControllerView must carry the FQDD
|
||||
// as their Description so the exporter's isDeviceBoundFirmwareFQDD filter can remove them.
|
||||
fqddByName := make(map[string]string)
|
||||
for _, fw := range result.Hardware.Firmware {
|
||||
fqddByName[fw.DeviceName] = fw.Description
|
||||
}
|
||||
if desc := fqddByName["Mellanox Network Adapter"]; desc != "InfiniBand.Slot.1-1" {
|
||||
t.Errorf("Mellanox firmware Description = %q, want InfiniBand.Slot.1-1 for FQDD filter", desc)
|
||||
}
|
||||
if desc := fqddByName["PERC H755 Front"]; desc != "RAID.SL.3-1" {
|
||||
t.Errorf("PERC H755 Front firmware Description = %q, want RAID.SL.3-1 for FQDD filter", desc)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseDellCPUAffinity verifies that CPUAffinity is parsed into NUMANode for
|
||||
// NIC, PCIe, and controller views. "Not Applicable" must result in NUMANode=0.
|
||||
func TestParseDellCPUAffinity(t *testing.T) {
|
||||
const viewXML = `<CIM><MESSAGE><SIMPLEREQ>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_SystemView">
|
||||
<PROPERTY NAME="Manufacturer"><VALUE>Dell Inc.</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Model"><VALUE>PowerEdge R750</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="ServiceTag"><VALUE>TESTST1</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_NICView">
|
||||
<PROPERTY NAME="FQDD"><VALUE>NIC.Slot.2-1-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="ProductName"><VALUE>Some NIC</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="CPUAffinity"><VALUE>1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="PrimaryStatus"><VALUE>0</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_InfiniBandView">
|
||||
<PROPERTY NAME="FQDD"><VALUE>InfiniBand.Slot.1-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="DeviceDescription"><VALUE>InfiniBand in Slot 1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="CPUAffinity"><VALUE>2</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="PrimaryStatus"><VALUE>0</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_ControllerView">
|
||||
<PROPERTY NAME="FQDD"><VALUE>RAID.Slot.1-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="ProductName"><VALUE>PERC H755</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="CPUAffinity"><VALUE>Not Applicable</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="PrimaryStatus"><VALUE>0</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_PCIDeviceView">
|
||||
<PROPERTY NAME="FQDD"><VALUE>Slot.7-1</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="Description"><VALUE>Some PCIe Card</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="CPUAffinity"><VALUE>2</VALUE></PROPERTY>
|
||||
<PROPERTY NAME="PrimaryStatus"><VALUE>0</VALUE></PROPERTY>
|
||||
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||
</SIMPLEREQ></MESSAGE></CIM>`
|
||||
|
||||
inner := makeZipArchive(t, map[string][]byte{
|
||||
"tsr/metadata.json": []byte(`{"Make":"Dell Inc.","Model":"PowerEdge R750","ServiceTag":"TESTST1"}`),
|
||||
"tsr/hardware/sysinfo/inventory/sysinfo_DCIM_View.xml": []byte(viewXML),
|
||||
})
|
||||
|
||||
p := &Parser{}
|
||||
result, err := p.Parse([]parser.ExtractedFile{
|
||||
{Path: "signature", Content: []byte("ok")},
|
||||
{Path: "TSR_TESTST1.pl.zip", Content: inner},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("parse failed: %v", err)
|
||||
}
|
||||
|
||||
// NIC CPUAffinity=1 → NUMANode=1
|
||||
nicBySlot := make(map[string]int)
|
||||
for _, nic := range result.Hardware.NetworkAdapters {
|
||||
nicBySlot[nic.Slot] = nic.NUMANode
|
||||
}
|
||||
if nicBySlot["NIC.Slot.2-1-1"] != 1 {
|
||||
t.Errorf("NIC.Slot.2-1-1 NUMANode = %d, want 1", nicBySlot["NIC.Slot.2-1-1"])
|
||||
}
|
||||
if nicBySlot["InfiniBand.Slot.1-1"] != 2 {
|
||||
t.Errorf("InfiniBand.Slot.1-1 NUMANode = %d, want 2", nicBySlot["InfiniBand.Slot.1-1"])
|
||||
}
|
||||
|
||||
// PCIe device CPUAffinity=2 → NUMANode=2; controller CPUAffinity="Not Applicable" → NUMANode=0
|
||||
pcieBySlot := make(map[string]int)
|
||||
for _, pcie := range result.Hardware.PCIeDevices {
|
||||
pcieBySlot[pcie.Slot] = pcie.NUMANode
|
||||
}
|
||||
if pcieBySlot["Slot.7-1"] != 2 {
|
||||
t.Errorf("Slot.7-1 NUMANode = %d, want 2", pcieBySlot["Slot.7-1"])
|
||||
}
|
||||
if pcieBySlot["RAID.Slot.1-1"] != 0 {
|
||||
t.Errorf("RAID.Slot.1-1 NUMANode = %d, want 0 (Not Applicable)", pcieBySlot["RAID.Slot.1-1"])
|
||||
}
|
||||
}
|
||||
|
||||
func makeZipArchive(t *testing.T, files map[string][]byte) []byte {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
|
||||
Reference in New Issue
Block a user