dell: strip MAC from model names; fix device-bound firmware in dell/inspur
- Dell NICView: strip " - XX:XX:XX:XX:XX:XX" suffix from ProductName (Dell TSR embeds MAC in this field for every NIC port) - Dell SoftwareIdentity: same strip applied to ElementName; store FQDD in FirmwareInfo.Description so exporter can filter device-bound entries - Exporter: add isDeviceBoundFirmwareFQDD() to filter firmware entries whose Description matches NIC./PSU./Disk./RAID.Backplane./GPU. FQDD prefixes (prevents device firmware from appearing in hardware.firmware) - Exporter: extend isDeviceBoundFirmwareName() to filter HGX GPU/NVSwitch firmware inventory IDs (_fw_gpu_, _fw_nvswitch_, _inforom_gpu_) - Inspur: remove HDD firmware from Hardware.Firmware — already present in Storage.Firmware, duplicating it violates ADL-016 - bible-local/06-parsers.md: document firmware and MAC stripping rules - bible-local/10-decisions.md: add ADL-016 (device-bound firmware) and ADL-017 (vendor-embedded MAC in model name fields) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ All registrations are collected in `internal/parser/vendors/vendors.go`:
|
|||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
_ "git.mchus.pro/mchus/logpile/internal/parser/vendors/inspur"
|
_ "git.mchus.pro/mchus/logpile/internal/parser/vendors/inspur"
|
||||||
_ "git.mchus.pro/mchus/logpile/internal/parser/vendors/supermicro"
|
_ "git.mchus.pro/mchus/logpile/internal/parser/vendors/dell"
|
||||||
// etc.
|
// etc.
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@@ -53,6 +53,42 @@ version produced a given result.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Parser data quality rules
|
||||||
|
|
||||||
|
### FirmwareInfo — system-level only
|
||||||
|
|
||||||
|
`Hardware.Firmware` must contain **only system-level firmware**: BIOS, BMC/iDRAC,
|
||||||
|
Lifecycle Controller, CPLD, storage controllers, BOSS adapters.
|
||||||
|
|
||||||
|
**Device-bound firmware** (NIC, GPU, PSU, disk, backplane) **must NOT be added to
|
||||||
|
`Hardware.Firmware`**. It belongs to the device's own `Firmware` field and is already
|
||||||
|
present there. Duplicating it in `Hardware.Firmware` causes double entries in Reanimator.
|
||||||
|
|
||||||
|
The Reanimator exporter filters by `FirmwareInfo.DeviceName` prefix and by
|
||||||
|
`FirmwareInfo.Description` (FQDD prefix). Parsers must cooperate:
|
||||||
|
|
||||||
|
- Store the device's FQDD (or equivalent slot identifier) in `FirmwareInfo.Description`
|
||||||
|
for all firmware entries that come from a per-device inventory source (e.g. Dell
|
||||||
|
`DCIM_SoftwareIdentity`).
|
||||||
|
- FQDD prefixes that are device-bound: `NIC.`, `PSU.`, `Disk.`, `RAID.Backplane.`, `GPU.`
|
||||||
|
|
||||||
|
### NIC/device model names — strip embedded MAC addresses
|
||||||
|
|
||||||
|
Some vendors (confirmed: Dell TSR) embed the MAC address in the device model name field,
|
||||||
|
e.g. `ProductName = "NVIDIA ConnectX-6 Lx 2x 25G SFP28 OCP3.0 SFF - C4:70:BD:DB:56:08"`.
|
||||||
|
|
||||||
|
**Rule:** Strip any ` - XX:XX:XX:XX:XX:XX` suffix from model/name strings before storing
|
||||||
|
them in `FirmwareInfo.DeviceName`, `NetworkAdapter.Model`, or any other model field.
|
||||||
|
|
||||||
|
Use `nicMACInModelRE` (defined in the Dell parser) or an equivalent regex:
|
||||||
|
```
|
||||||
|
\s+-\s+([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$
|
||||||
|
```
|
||||||
|
|
||||||
|
This applies to **all** string fields used as device names or model identifiers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Vendor parsers
|
## Vendor parsers
|
||||||
|
|
||||||
### Inspur / Kaytus (`inspur`)
|
### Inspur / Kaytus (`inspur`)
|
||||||
@@ -86,29 +122,26 @@ inspur/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Supermicro (`supermicro`)
|
### Dell TSR (`dell`)
|
||||||
|
|
||||||
**Status:** Ready (v1.0.0). Tested on SYS-821GE-TNHR crash dumps.
|
**Status:** Ready (v3.0). Tested on nested TSR archives with embedded `*.pl.zip`.
|
||||||
|
|
||||||
**Archive format:** `.tgz` / `.tar.gz` / `.tar`
|
**Archive format:** `.zip` (outer archive + nested `*.pl.zip`)
|
||||||
|
|
||||||
**Primary source file:** `CDump.txt` — JSON crashdump file
|
**Primary source files:**
|
||||||
|
- `tsr/metadata.json`
|
||||||
**Confidence:** +80 when `CDump.txt` contains `crash_data`, `METADATA`, `bmc_fw_ver` markers.
|
- `tsr/hardware/sysinfo/inventory/sysinfo_DCIM_View.xml`
|
||||||
|
- `tsr/hardware/sysinfo/inventory/sysinfo_DCIM_SoftwareIdentity.xml`
|
||||||
|
- `tsr/hardware/sysinfo/inventory/sysinfo_CIM_Sensor.xml`
|
||||||
|
- `tsr/hardware/sysinfo/lcfiles/curr_lclog.xml`
|
||||||
|
|
||||||
**Extracted data:**
|
**Extracted data:**
|
||||||
- CPUs: CPUID, core count, manufacturer (Intel), microcode version (as firmware field)
|
- Board/system identity and BIOS/iDRAC firmware
|
||||||
- FRU: BMC firmware version, BIOS version, ME firmware version, CPU PPIN
|
- CPU, memory, physical disks, virtual disks, PSU, NIC, PCIe
|
||||||
- Events: crashdump collection event + MCA errors
|
- GPU inventory (`DCIM_VideoView`) + GPU sensor enrichment (`DCIM_GPUSensor`)
|
||||||
|
- Controller/backplane inventory (`DCIM_ControllerView`, `DCIM_EnclosureView`)
|
||||||
**MCA error detection:**
|
- Sensor readings (temperature/voltage/current/power/fan/utilization)
|
||||||
- Bit 63 (Valid), Bit 61 (UC — uncorrected), Bit 60 (EN — enabled)
|
- Lifecycle events (`curr_lclog.xml`)
|
||||||
- Corrected MCA errors → `Warning` severity
|
|
||||||
- Uncorrected MCA errors → `Critical` severity
|
|
||||||
|
|
||||||
**Known limitations:**
|
|
||||||
- TOR dump and extended MCA register data not yet parsed.
|
|
||||||
- No CPU model name (only CPUID hex code available in crashdump format).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -272,8 +305,8 @@ with content markers (e.g. `Unraid kernel build`, parity data markers).
|
|||||||
|
|
||||||
| Vendor | ID | Status | Tested on |
|
| Vendor | ID | Status | Tested on |
|
||||||
|--------|----|--------|-----------|
|
|--------|----|--------|-----------|
|
||||||
|
| Dell TSR | `dell` | Ready | TSR nested zip archives |
|
||||||
| Inspur / Kaytus | `inspur` | Ready | KR4268X2 onekeylog |
|
| Inspur / Kaytus | `inspur` | Ready | KR4268X2 onekeylog |
|
||||||
| Supermicro | `supermicro` | Ready | SYS-821GE-TNHR crashdump |
|
|
||||||
| NVIDIA HGX Field Diag | `nvidia` | Ready | Various HGX servers |
|
| NVIDIA HGX Field Diag | `nvidia` | Ready | Various HGX servers |
|
||||||
| NVIDIA Bug Report | `nvidia_bug_report` | Ready | H100 systems |
|
| NVIDIA Bug Report | `nvidia_bug_report` | Ready | H100 systems |
|
||||||
| Unraid | `unraid` | Ready | Unraid diagnostics archives |
|
| Unraid | `unraid` | Ready | Unraid diagnostics archives |
|
||||||
|
|||||||
@@ -201,4 +201,56 @@ the server; frontend only renders status/flags returned by the API.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## ADL-015 — Supermicro crashdump archive parser removed from active registry
|
||||||
|
|
||||||
|
**Date:** 2026-03-01
|
||||||
|
**Context:** The Supermicro crashdump parser (`SMC Crash Dump Parser`) produced low-value
|
||||||
|
results for current workflows and was explicitly rejected as a supported archive path.
|
||||||
|
**Decision:** Remove `supermicro` vendor parser from active registration and project source.
|
||||||
|
Do not include it in `/api/parsers` output or parser documentation matrix.
|
||||||
|
**Consequences:**
|
||||||
|
- Supermicro crashdump archives (`CDump.txt` format) are no longer parsed by a dedicated vendor parser.
|
||||||
|
- Such archives fall back to other matching parsers (typically `generic`) unless a new replacement parser is added.
|
||||||
|
- Reintroduction requires a new parser package and an explicit registry import in `vendors/vendors.go`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADL-016 — Device-bound firmware must not appear in hardware.firmware
|
||||||
|
|
||||||
|
**Date:** 2026-03-01
|
||||||
|
**Context:** Dell TSR `DCIM_SoftwareIdentity` lists firmware for every component (NICs,
|
||||||
|
PSUs, disks, backplanes) in addition to system-level firmware. Naively importing all entries
|
||||||
|
into `Hardware.Firmware` caused device firmware to appear twice in Reanimator: once in the
|
||||||
|
device's own record and again in the top-level firmware list.
|
||||||
|
**Decision:**
|
||||||
|
- `Hardware.Firmware` contains only system-level firmware (BIOS, BMC/iDRAC, CPLD,
|
||||||
|
Lifecycle Controller, storage controllers, BOSS).
|
||||||
|
- Device-bound entries (NIC, PSU, Disk, Backplane, GPU) must not be added to
|
||||||
|
`Hardware.Firmware`.
|
||||||
|
- Parsers must store the FQDD (or equivalent slot identifier) in `FirmwareInfo.Description`
|
||||||
|
so the Reanimator exporter can filter by FQDD prefix.
|
||||||
|
- The exporter's `isDeviceBoundFirmwareFQDD()` function performs this filter.
|
||||||
|
**Consequences:**
|
||||||
|
- Any new parser that ingests a per-device firmware inventory must follow the same rule.
|
||||||
|
- Device firmware is accessible only via the device's own record, not the firmware list.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADL-017 — Vendor-embedded MAC addresses must be stripped from model name fields
|
||||||
|
|
||||||
|
**Date:** 2026-03-01
|
||||||
|
**Context:** Dell TSR embeds MAC addresses directly in `ProductName` and `ElementName`
|
||||||
|
fields (e.g. `"NVIDIA ConnectX-6 Lx 2x 25G SFP28 OCP3.0 SFF - C4:70:BD:DB:56:08"`).
|
||||||
|
This caused model names to contain MAC addresses in NIC model, NIC firmware device name,
|
||||||
|
and potentially other fields.
|
||||||
|
**Decision:** Strip any ` - XX:XX:XX:XX:XX:XX` suffix from all model/name string fields
|
||||||
|
at parse time before storing in any model struct. Use the regex
|
||||||
|
`\s+-\s+([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$`.
|
||||||
|
**Consequences:**
|
||||||
|
- Model names are clean and consistent across all devices.
|
||||||
|
- All parsers must apply this stripping to any field used as a device name or model.
|
||||||
|
- Confirmed affected fields in Dell: `DCIM_NICView.ProductName`, `DCIM_SoftwareIdentity.ElementName`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
<!-- Add new decisions below this line using the format above -->
|
<!-- Add new decisions below this line using the format above -->
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ func convertFirmware(firmware []models.FirmwareInfo) []ReanimatorFirmware {
|
|||||||
|
|
||||||
result := make([]ReanimatorFirmware, 0, len(firmware))
|
result := make([]ReanimatorFirmware, 0, len(firmware))
|
||||||
for _, fw := range firmware {
|
for _, fw := range firmware {
|
||||||
if isDeviceBoundFirmwareName(fw.DeviceName) {
|
if isDeviceBoundFirmwareName(fw.DeviceName) || isDeviceBoundFirmwareFQDD(fw.Description) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, ReanimatorFirmware{
|
result = append(result, ReanimatorFirmware{
|
||||||
@@ -690,13 +690,34 @@ func isDeviceBoundFirmwareName(name string) bool {
|
|||||||
strings.HasPrefix(n, "hdd ") ||
|
strings.HasPrefix(n, "hdd ") ||
|
||||||
strings.HasPrefix(n, "ssd ") ||
|
strings.HasPrefix(n, "ssd ") ||
|
||||||
strings.HasPrefix(n, "nvme ") ||
|
strings.HasPrefix(n, "nvme ") ||
|
||||||
strings.HasPrefix(n, "psu") {
|
strings.HasPrefix(n, "psu") ||
|
||||||
|
// HGX baseboard firmware inventory IDs for device-bound components
|
||||||
|
strings.Contains(n, "_fw_gpu_") ||
|
||||||
|
strings.Contains(n, "_fw_nvswitch_") ||
|
||||||
|
strings.Contains(n, "_inforom_gpu_") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return cpuMicrocodeFirmwareRegex.MatchString(strings.TrimSpace(name))
|
return cpuMicrocodeFirmwareRegex.MatchString(strings.TrimSpace(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isDeviceBoundFirmwareFQDD returns true if the description looks like a device-bound FQDD
|
||||||
|
// (e.g. NIC.Integrated.1-1-1, PSU.Slot.1, Disk.Bay.0:..., RAID.Backplane.Firmware.0).
|
||||||
|
// These firmware entries are already embedded in the device itself and must not appear
|
||||||
|
// in hardware.firmware.
|
||||||
|
func isDeviceBoundFirmwareFQDD(desc string) bool {
|
||||||
|
d := strings.ToLower(strings.TrimSpace(desc))
|
||||||
|
if d == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, prefix := range []string{"nic.", "psu.", "disk.", "raid.backplane.", "gpu."} {
|
||||||
|
if strings.HasPrefix(d, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// convertCPUs converts CPU information to Reanimator format
|
// convertCPUs converts CPU information to Reanimator format
|
||||||
func convertCPUs(cpus []models.CPU, collectedAt string) []ReanimatorCPU {
|
func convertCPUs(cpus []models.CPU, collectedAt string) []ReanimatorCPU {
|
||||||
if len(cpus) == 0 {
|
if len(cpus) == 0 {
|
||||||
|
|||||||
1460
internal/parser/vendors/dell/parser.go
vendored
Normal file
1460
internal/parser/vendors/dell/parser.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
224
internal/parser/vendors/dell/parser_test.go
vendored
Normal file
224
internal/parser/vendors/dell/parser_test.go
vendored
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
package dell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.mchus.pro/mchus/logpile/internal/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDetectNestedTSRZip(t *testing.T) {
|
||||||
|
inner := makeZipArchive(t, map[string][]byte{
|
||||||
|
"tsr/metadata.json": []byte(`{"Make":"Dell Inc.","Model":"PowerEdge R750","ServiceTag":"G37Q064"}`),
|
||||||
|
"tsr/hardware/sysinfo/inventory/sysinfo_DCIM_View.xml": []byte(`<CIM><MESSAGE><SIMPLEREQ/></MESSAGE></CIM>`),
|
||||||
|
})
|
||||||
|
|
||||||
|
p := &Parser{}
|
||||||
|
score := p.Detect([]parser.ExtractedFile{
|
||||||
|
{Path: "signature", Content: []byte("ok")},
|
||||||
|
{Path: "TSR20241119143901_G37Q064.pl.zip", Content: inner},
|
||||||
|
})
|
||||||
|
if score < 80 {
|
||||||
|
t.Fatalf("expected high detect score for nested TSR zip, got %d", score)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseNestedTSRZip(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>G37Q064</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="BIOSVersionString"><VALUE>2.19.1</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="LifecycleControllerVersion"><VALUE>7.00.30.00</VALUE></PROPERTY>
|
||||||
|
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||||
|
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_CPUView">
|
||||||
|
<PROPERTY NAME="FQDD"><VALUE>CPU.Socket.1</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="Model"><VALUE>Intel(R) Xeon(R) Gold 6330</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="Manufacturer"><VALUE>Intel</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="NumberOfEnabledCores"><VALUE>28</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="NumberOfEnabledThreads"><VALUE>56</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="CurrentClockSpeed"><VALUE>2000</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="MaxClockSpeed"><VALUE>3100</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="PPIN"><VALUE>ABCD</VALUE></PROPERTY>
|
||||||
|
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||||
|
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_NICView">
|
||||||
|
<PROPERTY NAME="FQDD"><VALUE>NIC.Slot.1-1-1</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="ProductName"><VALUE>Broadcom 57414 Dual Port 10/25GbE SFP28 Adapter</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="VendorName"><VALUE>Broadcom</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="CurrentMACAddress"><VALUE>00:11:22:33:44:55</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="SerialNumber"><VALUE>NICSERIAL1</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="FamilyVersion"><VALUE>22.80.17</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="PCIVendorID"><VALUE>0x14e4</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="PCIDeviceID"><VALUE>0x16d7</VALUE></PROPERTY>
|
||||||
|
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||||
|
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_PowerSupplyView">
|
||||||
|
<PROPERTY NAME="FQDD"><VALUE>PSU.Slot.1</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="Model"><VALUE>D1400E-S0</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="Manufacturer"><VALUE>Dell</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="SerialNumber"><VALUE>PSUSERIAL1</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="FirmwareVersion"><VALUE>00.1A</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="TotalOutputPower"><VALUE>1400</VALUE></PROPERTY>
|
||||||
|
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||||
|
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_VideoView">
|
||||||
|
<PROPERTY NAME="FQDD"><VALUE>Video.Slot.38-1</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="MarketingName"><VALUE>NVIDIA H100 PCIe</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="Description"><VALUE>GH100 [H100 PCIe]</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="Manufacturer"><VALUE>NVIDIA Corporation</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="PCIVendorID"><VALUE>10DE</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="PCIDeviceID"><VALUE>2331</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="BusNumber"><VALUE>74</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="DeviceNumber"><VALUE>0</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="FunctionNumber"><VALUE>0</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="SerialNumber"><VALUE>1793924039808</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="FirmwareVersion"><VALUE>96.00.AF.00.01</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="GPUGUID"><VALUE>bc681a6d4785dde08c21f49c46c05cc3</VALUE></PROPERTY>
|
||||||
|
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||||
|
</SIMPLEREQ></MESSAGE></CIM>`
|
||||||
|
|
||||||
|
const swXML = `<CIM><MESSAGE><SIMPLEREQ>
|
||||||
|
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_SoftwareIdentity">
|
||||||
|
<PROPERTY NAME="ElementName"><VALUE>NIC.Slot.1-1-1</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="VersionString"><VALUE>22.80.17</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="ComponentType"><VALUE>Network</VALUE></PROPERTY>
|
||||||
|
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||||
|
</SIMPLEREQ></MESSAGE></CIM>`
|
||||||
|
|
||||||
|
const eventsXML = `<Log>
|
||||||
|
<Event AgentID="Lifecycle Controller" Category="System Health" Severity="Warning" Timestamp="2024-11-19T14:39:01-0800">
|
||||||
|
<MessageID>SYS1001</MessageID>
|
||||||
|
<Message>Link is down</Message>
|
||||||
|
<FQDD>NIC.Slot.1-1-1</FQDD>
|
||||||
|
</Event>
|
||||||
|
</Log>`
|
||||||
|
|
||||||
|
const cimSensorXML = `<CIM><MESSAGE><SIMPLEREQ>
|
||||||
|
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="DCIM_GPUSensor">
|
||||||
|
<PROPERTY NAME="DeviceID"><VALUE>Video.Slot.38-1</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="PrimaryGPUTemperature"><VALUE>290</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="MemoryTemperature"><VALUE>440</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="PowerConsumption"><VALUE>295</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="ThermalAlertStatus"><VALUE>5</VALUE></PROPERTY>
|
||||||
|
</INSTANCE></VALUE.NAMEDINSTANCE>
|
||||||
|
<VALUE.NAMEDINSTANCE><INSTANCE CLASSNAME="CIM_NumericSensor">
|
||||||
|
<PROPERTY NAME="ElementName"><VALUE>PS1 Voltage 1</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="CurrentReading"><VALUE>224.0</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="BaseUnits"><VALUE>5</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="UnitModifier"><VALUE>0</VALUE></PROPERTY>
|
||||||
|
<PROPERTY NAME="PrimaryStatus"><VALUE>5</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":"G37Q064",
|
||||||
|
"FirmwareVersion":"7.00.30.00",
|
||||||
|
"CollectionDateTime":"2024-11-19 14:39:01.000-0800"
|
||||||
|
}`),
|
||||||
|
"tsr/hardware/sysinfo/inventory/sysinfo_DCIM_View.xml": []byte(viewXML),
|
||||||
|
"tsr/hardware/sysinfo/inventory/sysinfo_DCIM_SoftwareIdentity.xml": []byte(swXML),
|
||||||
|
"tsr/hardware/sysinfo/inventory/sysinfo_CIM_Sensor.xml": []byte(cimSensorXML),
|
||||||
|
"tsr/hardware/sysinfo/lcfiles/curr_lclog.xml": []byte(eventsXML),
|
||||||
|
})
|
||||||
|
|
||||||
|
p := &Parser{}
|
||||||
|
result, err := p.Parse([]parser.ExtractedFile{
|
||||||
|
{Path: "signature", Content: []byte("ok")},
|
||||||
|
{Path: "TSR20241119143901_G37Q064.pl.zip", Content: inner},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse failed: %v", err)
|
||||||
|
}
|
||||||
|
if result.Hardware == nil {
|
||||||
|
t.Fatalf("expected hardware section")
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := result.Hardware.BoardInfo.Manufacturer; got != "Dell Inc." {
|
||||||
|
t.Fatalf("unexpected board manufacturer: %q", got)
|
||||||
|
}
|
||||||
|
if got := result.Hardware.BoardInfo.ProductName; got != "PowerEdge R750" {
|
||||||
|
t.Fatalf("unexpected board product: %q", got)
|
||||||
|
}
|
||||||
|
if got := result.Hardware.BoardInfo.SerialNumber; got != "G37Q064" {
|
||||||
|
t.Fatalf("unexpected service tag: %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Hardware.CPUs) != 1 {
|
||||||
|
t.Fatalf("expected 1 cpu, got %d", len(result.Hardware.CPUs))
|
||||||
|
}
|
||||||
|
if got := result.Hardware.CPUs[0].Model; got != "Intel(R) Xeon(R) Gold 6330" {
|
||||||
|
t.Fatalf("unexpected cpu model: %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Hardware.NetworkAdapters) != 1 {
|
||||||
|
t.Fatalf("expected 1 network adapter, got %d", len(result.Hardware.NetworkAdapters))
|
||||||
|
}
|
||||||
|
adapter := result.Hardware.NetworkAdapters[0]
|
||||||
|
if adapter.Vendor != "Broadcom" {
|
||||||
|
t.Fatalf("unexpected nic vendor: %q", adapter.Vendor)
|
||||||
|
}
|
||||||
|
if adapter.Firmware != "22.80.17" {
|
||||||
|
t.Fatalf("unexpected nic firmware: %q", adapter.Firmware)
|
||||||
|
}
|
||||||
|
if adapter.SerialNumber != "NICSERIAL1" {
|
||||||
|
t.Fatalf("unexpected nic serial: %q", adapter.SerialNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Hardware.PowerSupply) != 1 {
|
||||||
|
t.Fatalf("expected 1 psu, got %d", len(result.Hardware.PowerSupply))
|
||||||
|
}
|
||||||
|
psu := result.Hardware.PowerSupply[0]
|
||||||
|
if psu.Model != "D1400E-S0" {
|
||||||
|
t.Fatalf("unexpected psu model: %q", psu.Model)
|
||||||
|
}
|
||||||
|
if psu.Firmware != "00.1A" {
|
||||||
|
t.Fatalf("unexpected psu firmware: %q", psu.Firmware)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Hardware.Firmware) == 0 {
|
||||||
|
t.Fatalf("expected firmware entries")
|
||||||
|
}
|
||||||
|
if len(result.Hardware.GPUs) != 1 {
|
||||||
|
t.Fatalf("expected 1 gpu, got %d", len(result.Hardware.GPUs))
|
||||||
|
}
|
||||||
|
if got := result.Hardware.GPUs[0].Model; got != "NVIDIA H100 PCIe" {
|
||||||
|
t.Fatalf("unexpected gpu model: %q", got)
|
||||||
|
}
|
||||||
|
if got := result.Hardware.GPUs[0].SerialNumber; got != "1793924039808" {
|
||||||
|
t.Fatalf("unexpected gpu serial: %q", got)
|
||||||
|
}
|
||||||
|
if got := result.Hardware.GPUs[0].Temperature; got != 29 {
|
||||||
|
t.Fatalf("unexpected gpu temperature: %d", got)
|
||||||
|
}
|
||||||
|
if len(result.Sensors) == 0 {
|
||||||
|
t.Fatalf("expected sensors from CIM_Sensor")
|
||||||
|
}
|
||||||
|
if len(result.Events) != 1 {
|
||||||
|
t.Fatalf("expected one lifecycle event, got %d", len(result.Events))
|
||||||
|
}
|
||||||
|
if got := string(result.Events[0].Severity); got != "warning" {
|
||||||
|
t.Fatalf("unexpected event severity: %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeZipArchive(t *testing.T, files map[string][]byte) []byte {
|
||||||
|
t.Helper()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
zw := zip.NewWriter(&buf)
|
||||||
|
for name, content := range files {
|
||||||
|
w, err := zw.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create zip entry %s: %v", name, err)
|
||||||
|
}
|
||||||
|
if _, err := w.Write(content); err != nil {
|
||||||
|
t.Fatalf("write zip entry %s: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := zw.Close(); err != nil {
|
||||||
|
t.Fatalf("close zip: %v", err)
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
17
internal/parser/vendors/inspur/asset.go
vendored
17
internal/parser/vendors/inspur/asset.go
vendored
@@ -162,7 +162,6 @@ func ParseAssetJSON(content []byte) (*models.HardwareConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse storage info
|
// Parse storage info
|
||||||
seenHDDFW := make(map[string]bool)
|
|
||||||
for _, hdd := range asset.HddInfo {
|
for _, hdd := range asset.HddInfo {
|
||||||
slot := normalizeAssetHDDSlot(hdd.LocationString, hdd.Location, hdd.DiskInterfaceType)
|
slot := normalizeAssetHDDSlot(hdd.LocationString, hdd.Location, hdd.DiskInterfaceType)
|
||||||
modelName := strings.TrimSpace(hdd.ModelName)
|
modelName := strings.TrimSpace(hdd.ModelName)
|
||||||
@@ -198,21 +197,7 @@ func ParseAssetJSON(content []byte) (*models.HardwareConfig, error) {
|
|||||||
Present: present,
|
Present: present,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add HDD firmware to firmware list (deduplicated by model+version)
|
// Disk firmware is already stored in Storage.Firmware — do not duplicate in Hardware.Firmware.
|
||||||
if hdd.FirmwareVersion != "" {
|
|
||||||
fwKey := modelName + ":" + hdd.FirmwareVersion
|
|
||||||
if !seenHDDFW[fwKey] {
|
|
||||||
fwSlot := slot
|
|
||||||
if fwSlot == "" {
|
|
||||||
fwSlot = fmt.Sprintf("%s %dGB", storageType, hdd.Capacity)
|
|
||||||
}
|
|
||||||
config.Firmware = append(config.Firmware, models.FirmwareInfo{
|
|
||||||
DeviceName: fmt.Sprintf("%s (%s)", modelName, fwSlot),
|
|
||||||
Version: hdd.FirmwareVersion,
|
|
||||||
})
|
|
||||||
seenHDDFW[fwKey] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse PCIe info
|
// Parse PCIe info
|
||||||
|
|||||||
Reference in New Issue
Block a user