diff --git a/bible-local/decisions/2026-03-15-component-address-contract.md b/bible-local/decisions/2026-03-15-component-address-contract.md new file mode 100644 index 0000000..53263a0 --- /dev/null +++ b/bible-local/decisions/2026-03-15-component-address-contract.md @@ -0,0 +1,7 @@ +# 2026-03-15 - Canonical Component Address Contract + +- Date: `2026-03-15` +- Decision: Component payloads use `slot` as the single canonical installation address. `location` is removed from component payloads. For `pcie_devices`, canonical `slot` is the PCI Bus:Device.Function value; legacy input field `bdf` remains accepted during transition and is normalized into `slot`. +- Context: Hardware analyzers were emitting mixed `slot` and `location` semantics, and PCIe payloads exposed both `slot` and `bdf` as competing addresses. This created inconsistent ingest behavior and ambiguous downstream timeline/installations state. +- Consequences: Component contracts and examples must stop documenting `location`. PCIe examples must show BDF in `slot`. Ingest normalization must prefer `bdf` over legacy PCIe `slot` so runtime `installations.slot_name` stays canonical and stable for PCIe devices. +- Supersedes: Implicit mixed `slot`/`location` component addressing in `bible-local/docs/hardware-ingest-contract.md`. diff --git a/bible-local/docs/hardware-ingest-contract.md b/bible-local/docs/hardware-ingest-contract.md index 9fc5b5e..f0217ce 100644 --- a/bible-local/docs/hardware-ingest-contract.md +++ b/bible-local/docs/hardware-ingest-contract.md @@ -1,6 +1,6 @@ --- title: Hardware Ingest JSON Contract -version: "2.1" +version: "2.5" updated: "2026-03-15" maintainer: Reanimator Core audience: external-integrators, ai-agents @@ -9,7 +9,7 @@ language: ru # Интеграция с Reanimator: контракт JSON-импорта аппаратного обеспечения -Версия: **2.1** · Дата: **2026-03-15** +Версия: **2.5** · Дата: **2026-03-15** Документ описывает формат JSON для передачи данных об аппаратном обеспечении серверов в систему **Reanimator** (управление жизненным циклом аппаратного обеспечения). Предназначен для разработчиков смежных систем (Redfish-коллекторов, агентов мониторинга, CMDB-экспортёров) и может быть включён в документацию интегрируемых проектов. @@ -22,6 +22,7 @@ language: ru | Версия | Дата | Изменения | |--------|------|-----------| +| 2.5 | 2026-03-15 | Добавлено общее необязательное поле `manufactured_year_week` для компонентных секций (`YYYY-Www`) | | 2.4 | 2026-03-15 | Добавлена первая волна component telemetry: health/life поля для `cpus`, `memory`, `storage`, `pcie_devices`, `power_supplies` | | 2.3 | 2026-03-15 | Добавлены component telemetry поля: `pcie_devices.temperature_c`, `pcie_devices.power_w`, `power_supplies.temperature_c` | | 2.2 | 2026-03-15 | Добавлено поле `numa_node` у `pcie_devices` для topology/affinity | @@ -157,6 +158,7 @@ GET /ingest/hardware/jobs/{job_id} | `status_changed_at` | string RFC3339 | Время последнего изменения статуса | | `status_history` | array | История переходов статусов (см. ниже) | | `error_description` | string | Текст ошибки/диагностики | +| `manufactured_year_week` | string | Дата производства в формате `YYYY-Www`, например `2024-W07` | **Объект `status_history[]`:** @@ -178,6 +180,7 @@ GET /ingest/hardware/jobs/{job_id} - Если источник хранит историю — передавайте `status_history` отсортированным по `changed_at` по возрастанию. - Не включайте записи `status_history` без `changed_at`. - Все даты — RFC3339, рекомендуется UTC (`Z`). +- `manufactured_year_week` используйте, когда источник знает только год и неделю производства, без точной календарной даты. --- @@ -282,7 +285,6 @@ GET /ingest/hardware/jobs/{job_id} | Поле | Тип | Обязательно | Описание | |------|-----|-------------|----------| | `slot` | string | нет | Идентификатор слота | -| `location` | string | нет | Физическое расположение | | `present` | bool | нет | Наличие модуля (по умолчанию `true`) | | `serial_number` | string | нет | Серийный номер | | `part_number` | string | нет | Партномер (используется как модель) | @@ -328,7 +330,7 @@ GET /ingest/hardware/jobs/{job_id} | Поле | Тип | Обязательно | Описание | |------|-----|-------------|----------| -| `slot` | string | нет | Идентификатор слота | +| `slot` | string | нет | Канонический адрес установки PCIe-устройства; передавайте BDF (`0000:18:00.0`) | | `serial_number` | string | нет | Серийный номер | | `model` | string | нет | Модель | | `manufacturer` | string | нет | Производитель | @@ -404,7 +406,7 @@ GET /ingest/hardware/jobs/{job_id} | `sfp_rx_power_dbm` | float | нет | RX optical power, dBm | | `sfp_voltage_v` | float | нет | Напряжение SFP, В | | `sfp_bias_ma` | float | нет | Bias current SFP, мА | -| `bdf` | string | нет | Bus:Device.Function, например `0000:18:00.0` | +| `bdf` | string | нет | Deprecated alias для `slot`; при наличии ingest нормализует его в `slot` | | `device_class` | string | нет | Класс устройства (см. список ниже) | | `manufacturer` | string | нет | Производитель | | `model` | string | нет | Модель | @@ -421,7 +423,9 @@ GET /ingest/hardware/jobs/{job_id} `numa_node` передавайте для NIC / InfiniBand / RAID / GPU, когда источник знает CPU/NUMA affinity. Поле сохраняется в snapshot-атрибутах PCIe-компонента и дублируется в telemetry для topology use cases. Поля `temperature_c` и `power_w` используйте для device-level telemetry GPU / accelerator / smart PCIe devices. Они не влияют на идентификацию компонента. -**Генерация serial_number при отсутствии или `"N/A"`:** `{board_serial}-PCIE-{slot}` +**Генерация serial_number при отсутствии или `"N/A"`:** `{board_serial}-PCIE-{slot}`, где `slot` для PCIe равен BDF. + +`slot` — единственный канонический адрес компонента. Для PCIe в `slot` передавайте BDF. Поле `bdf` сохраняется только как переходный alias на входе и не должно использоваться как отдельная координата рядом со `slot`. **Значения `device_class`:** @@ -441,7 +445,7 @@ GET /ingest/hardware/jobs/{job_id} ```json "pcie_devices": [ { - "slot": "PCIeCard2", + "slot": "0000:3b:00.0", "vendor_id": 5555, "device_id": 4401, "numa_node": 0, @@ -450,7 +454,6 @@ GET /ingest/hardware/jobs/{job_id} "sfp_temperature_c": 36.2, "sfp_tx_power_dbm": -1.8, "sfp_rx_power_dbm": -2.1, - "bdf": "0000:3b:00.0", "device_class": "EthernetController", "manufacturer": "Intel", "model": "X710 10GbE", @@ -611,7 +614,7 @@ PSU без `serial_number` игнорируется. | Тип | Поведение | |-----|-----------| | CPU | Генерируется: `{board_serial}-CPU-{socket}` | -| PCIe | Генерируется: `{board_serial}-PCIE-{slot}` (если serial = `"N/A"` или пустой) | +| PCIe | Генерируется: `{board_serial}-PCIE-{slot}` (если serial = `"N/A"` или пустой; `slot` для PCIe = BDF) | | Memory | Компонент игнорируется | | Storage | Компонент игнорируется | | PSU | Компонент игнорируется | @@ -687,7 +690,7 @@ PSU без `serial_number` игнорируется. ], "pcie_devices": [ { - "slot": "PCIeCard1", + "slot": "0000:18:00.0", "device_class": "EthernetController", "manufacturer": "Intel", "model": "X710 10GbE", diff --git a/bible-local/docs/import-example-full.json b/bible-local/docs/import-example-full.json index 4bbbe3b..95cb62f 100644 --- a/bible-local/docs/import-example-full.json +++ b/bible-local/docs/import-example-full.json @@ -58,7 +58,6 @@ "memory": [ { "slot": "CPU0_C0D0", - "location": "CPU0_C0D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -77,7 +76,6 @@ }, { "slot": "CPU0_C1D0", - "location": "CPU0_C1D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -90,7 +88,6 @@ }, { "slot": "CPU0_C2D0", - "location": "CPU0_C2D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -103,7 +100,6 @@ }, { "slot": "CPU0_C3D0", - "location": "CPU0_C3D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -116,7 +112,6 @@ }, { "slot": "CPU1_C0D0", - "location": "CPU1_C0D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -129,7 +124,6 @@ }, { "slot": "CPU1_C1D0", - "location": "CPU1_C1D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -142,7 +136,6 @@ }, { "slot": "CPU1_C2D0", - "location": "CPU1_C2D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -155,7 +148,6 @@ }, { "slot": "CPU1_C3D0", - "location": "CPU1_C3D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -168,7 +160,6 @@ }, { "slot": "CPU0_C4D0", - "location": "CPU0_C4D0", "present": false, "size_mb": 0, "type": null, @@ -181,7 +172,6 @@ }, { "slot": "CPU1_C4D0", - "location": "CPU1_C4D0", "present": false, "size_mb": 0, "type": null, @@ -317,11 +307,10 @@ ], "pcie_devices": [ { - "slot": "PCIeCard1", + "slot": "0000:18:00.0", "vendor_id": 32902, "device_id": 2912, "numa_node": 0, - "bdf": "0000:18:00.0", "device_class": "MassStorageController", "manufacturer": "Intel", "model": "RAID Controller RSP3DD080F", @@ -335,7 +324,7 @@ "status_checked_at": "2026-02-10T15:28:00Z" }, { - "slot": "PCIeCard2", + "slot": "0000:3b:00.0", "vendor_id": 5555, "device_id": 4401, "numa_node": 0, @@ -346,7 +335,6 @@ "sfp_rx_power_dbm": -2.1, "sfp_voltage_v": 3.29, "sfp_bias_ma": 6.8, - "bdf": "0000:3b:00.0", "device_class": "NetworkController", "manufacturer": "Mellanox", "model": "ConnectX-5", @@ -360,13 +348,12 @@ "status": "OK" }, { - "slot": "PCIeCard3", + "slot": "0000:5e:00.0", "vendor_id": 5555, "device_id": 4401, "numa_node": 1, "temperature_c": 35.0, "power_w": 18.4, - "bdf": "0000:86:00.0", "device_class": "NetworkController", "manufacturer": "Mellanox", "model": "ConnectX-5", @@ -380,10 +367,9 @@ "status": "OK" }, { - "slot": "PCIeCard4", + "slot": "0000:86:00.0", "vendor_id": 4318, "device_id": 43690, - "bdf": "0000:d8:00.0", "device_class": "DisplayController", "manufacturer": "ASPEED", "model": "AST2600 VGA", @@ -396,7 +382,7 @@ "status": "OK" }, { - "slot": "PCIeCard5", + "slot": "0000:af:00.0", "vendor_id": 4318, "device_id": 1824, "numa_node": 0, @@ -406,7 +392,6 @@ "ecc_uncorrected_total": 0, "hw_slowdown": false, "life_remaining_pct": 96.0, - "bdf": "0000:41:00.0", "device_class": "VideoController", "manufacturer": "NVIDIA", "model": "Tesla T4", @@ -419,7 +404,7 @@ "status": "OK" }, { - "slot": "PCIeCard6", + "slot": "0000:d8:00.0", "vendor_id": 4318, "device_id": 9482, "numa_node": 1, @@ -429,7 +414,6 @@ "ecc_uncorrected_total": 0, "hw_slowdown": false, "life_remaining_pct": 94.0, - "bdf": "0000:61:00.0", "device_class": "ProcessingAccelerator", "manufacturer": "NVIDIA", "model": "A100 SXM4 80GB", @@ -442,11 +426,10 @@ "status": "OK" }, { - "slot": "PCIeCard7", + "slot": "0000:18:00.1", "vendor_id": 32902, "device_id": 5528, "numa_node": 0, - "bdf": "0000:19:00.0", "device_class": "EthernetController", "manufacturer": "Intel", "model": "X710 10GbE", @@ -460,11 +443,10 @@ "status": "OK" }, { - "slot": "PCIeCard8", + "slot": "0000:3b:00.1", "vendor_id": 4096, "device_id": 8200, "numa_node": 1, - "bdf": "0000:21:00.0", "device_class": "FibreChannelController", "manufacturer": "Marvell", "model": "QLE2742 32Gb FC", @@ -477,11 +459,10 @@ "status": "OK" }, { - "slot": "PCIeCard9", + "slot": "0000:5e:00.1", "vendor_id": 4358, "device_id": 1617, "numa_node": 0, - "bdf": "0000:22:00.0", "device_class": "StorageController", "manufacturer": "Broadcom", "model": "SAS 9400-8i", diff --git a/internal/api/import-example-full.json b/internal/api/import-example-full.json index 068ebcb..11e6c2d 100644 --- a/internal/api/import-example-full.json +++ b/internal/api/import-example-full.json @@ -58,7 +58,6 @@ "memory": [ { "slot": "CPU0_C0D0", - "location": "CPU0_C0D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -77,7 +76,6 @@ }, { "slot": "CPU0_C1D0", - "location": "CPU0_C1D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -90,7 +88,6 @@ }, { "slot": "CPU0_C2D0", - "location": "CPU0_C2D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -103,7 +100,6 @@ }, { "slot": "CPU0_C3D0", - "location": "CPU0_C3D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -116,7 +112,6 @@ }, { "slot": "CPU1_C0D0", - "location": "CPU1_C0D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -129,7 +124,6 @@ }, { "slot": "CPU1_C1D0", - "location": "CPU1_C1D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -142,7 +136,6 @@ }, { "slot": "CPU1_C2D0", - "location": "CPU1_C2D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -155,7 +148,6 @@ }, { "slot": "CPU1_C3D0", - "location": "CPU1_C3D0", "present": true, "size_mb": 32768, "type": "DDR5", @@ -168,7 +160,6 @@ }, { "slot": "CPU0_C4D0", - "location": "CPU0_C4D0", "present": false, "size_mb": 0, "type": null, @@ -181,7 +172,6 @@ }, { "slot": "CPU1_C4D0", - "location": "CPU1_C4D0", "present": false, "size_mb": 0, "type": null, @@ -317,11 +307,10 @@ ], "pcie_devices": [ { - "slot": "PCIeCard1", + "slot": "0000:18:00.0", "vendor_id": 32902, "device_id": 2912, "numa_node": 0, - "bdf": "0000:18:00.0", "device_class": "MassStorageController", "manufacturer": "Intel", "model": "RAID Controller RSP3DD080F", @@ -335,7 +324,7 @@ "status_checked_at": "2026-02-10T15:28:00Z" }, { - "slot": "PCIeCard2", + "slot": "0000:3b:00.0", "vendor_id": 5555, "device_id": 4401, "numa_node": 0, @@ -346,7 +335,6 @@ "sfp_rx_power_dbm": -2.1, "sfp_voltage_v": 3.29, "sfp_bias_ma": 6.8, - "bdf": "0000:3b:00.0", "device_class": "NetworkController", "manufacturer": "Mellanox", "model": "ConnectX-5", @@ -359,13 +347,12 @@ "status": "OK" }, { - "slot": "PCIeCard3", + "slot": "0000:5e:00.0", "vendor_id": 5555, "device_id": 4401, "numa_node": 1, "temperature_c": 35.0, "power_w": 18.4, - "bdf": "0000:86:00.0", "device_class": "NetworkController", "manufacturer": "Mellanox", "model": "ConnectX-5", @@ -378,10 +365,9 @@ "status": "OK" }, { - "slot": "PCIeCard4", + "slot": "0000:86:00.0", "vendor_id": 4318, "device_id": 43690, - "bdf": "0000:d8:00.0", "device_class": "DisplayController", "manufacturer": "ASPEED", "model": "AST2600 VGA", @@ -394,7 +380,7 @@ "status": "OK" }, { - "slot": "PCIeCard5", + "slot": "0000:af:00.0", "vendor_id": 4318, "device_id": 1824, "numa_node": 0, @@ -404,7 +390,6 @@ "ecc_uncorrected_total": 0, "hw_slowdown": false, "life_remaining_pct": 96.0, - "bdf": "0000:41:00.0", "device_class": "VideoController", "manufacturer": "NVIDIA", "model": "Tesla T4", @@ -417,7 +402,7 @@ "status": "OK" }, { - "slot": "PCIeCard6", + "slot": "0000:d8:00.0", "vendor_id": 4318, "device_id": 9482, "numa_node": 1, @@ -427,7 +412,6 @@ "ecc_uncorrected_total": 0, "hw_slowdown": false, "life_remaining_pct": 94.0, - "bdf": "0000:61:00.0", "device_class": "ProcessingAccelerator", "manufacturer": "NVIDIA", "model": "A100 SXM4 80GB", @@ -440,11 +424,10 @@ "status": "OK" }, { - "slot": "PCIeCard7", + "slot": "0000:18:00.1", "vendor_id": 32902, "device_id": 5528, "numa_node": 0, - "bdf": "0000:19:00.0", "device_class": "EthernetController", "manufacturer": "Intel", "model": "X710 10GbE", @@ -458,11 +441,10 @@ "status": "OK" }, { - "slot": "PCIeCard8", + "slot": "0000:3b:00.1", "vendor_id": 4096, "device_id": 8200, "numa_node": 1, - "bdf": "0000:21:00.0", "device_class": "FibreChannelController", "manufacturer": "Marvell", "model": "QLE2742 32Gb FC", @@ -475,11 +457,10 @@ "status": "OK" }, { - "slot": "PCIeCard9", + "slot": "0000:5e:00.1", "vendor_id": 4358, "device_id": 1617, "numa_node": 0, - "bdf": "0000:22:00.0", "device_class": "StorageController", "manufacturer": "Broadcom", "model": "SAS 9400-8i", diff --git a/internal/ingest/parser_hardware.go b/internal/ingest/parser_hardware.go index e38a1a3..6748a01 100644 --- a/internal/ingest/parser_hardware.go +++ b/internal/ingest/parser_hardware.go @@ -105,6 +105,7 @@ type HardwareCPU struct { StatusChangedAt *string `json:"status_changed_at"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description"` + ManufacturedYearWeek *string `json:"manufactured_year_week,omitempty"` Present *bool `json:"present"` SerialNumber *string `json:"serial_number"` Firmware *string `json:"firmware"` @@ -116,7 +117,6 @@ type HardwareCPU struct { type HardwareMemory struct { Slot *string `json:"slot"` - Location *string `json:"location"` Present *bool `json:"present"` SizeMB *int `json:"size_mb"` Type *string `json:"type"` @@ -138,6 +138,7 @@ type HardwareMemory struct { StatusChangedAt *string `json:"status_changed_at"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description"` + ManufacturedYearWeek *string `json:"manufactured_year_week,omitempty"` } type HardwareStorage struct { @@ -169,6 +170,7 @@ type HardwareStorage struct { StatusChangedAt *string `json:"status_changed_at"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description"` + ManufacturedYearWeek *string `json:"manufactured_year_week,omitempty"` } type HardwarePCIeDevice struct { @@ -210,6 +212,7 @@ type HardwarePCIeDevice struct { StatusChangedAt *string `json:"status_changed_at"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description"` + ManufacturedYearWeek *string `json:"manufactured_year_week,omitempty"` } type HardwarePowerSupply struct { @@ -226,6 +229,7 @@ type HardwarePowerSupply struct { StatusChangedAt *string `json:"status_changed_at"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description"` + ManufacturedYearWeek *string `json:"manufactured_year_week,omitempty"` InputType *string `json:"input_type"` InputPowerW *float64 `json:"input_power_w"` OutputPowerW *float64 `json:"output_power_w"` @@ -397,9 +401,10 @@ func flattenPCIe(boardSerial string, items []HardwarePCIeDevice) []HardwareCompo if !present || status == statusEmpty { continue } + slot := normalizePCIeSlot(item.Slot, item.BDF) serial := normalizeSerial(item.SerialNumber) if serial == "" { - serial = generatePCIEVendorSerial(boardSerial, item.Slot) + serial = generatePCIEVendorSerial(boardSerial, slot) } if serial == "" { continue @@ -416,7 +421,7 @@ func flattenPCIe(boardSerial string, items []HardwarePCIeDevice) []HardwareCompo StatusHistory: normalizeStatusHistory(item.StatusHistory), ErrorDescription: normalizeString(item.ErrorDescription), Present: boolPtr(present), - Slot: normalizeString(item.Slot), + Slot: slot, Attributes: structToMap(item), Telemetry: extractTelemetry(structToMap(item), []string{"vendor_id", "device_id", "numa_node", "temperature_c", "power_w", "life_remaining_pct", "life_used_pct", "ecc_corrected_total", "ecc_uncorrected_total", "hw_slowdown", "battery_charge_pct", "battery_health_pct", "battery_temperature_c", "battery_voltage_v", "battery_replace_required", "sfp_temperature_c", "sfp_tx_power_dbm", "sfp_rx_power_dbm", "sfp_voltage_v", "sfp_bias_ma"}), } @@ -491,6 +496,13 @@ func generatePCIEVendorSerial(boardSerial string, slot *string) string { return fmt.Sprintf("%s-PCIE-%s", boardSerial, trimmed) } +func normalizePCIeSlot(slot, bdf *string) *string { + if normalizedBDF := normalizeString(bdf); normalizedBDF != nil { + return normalizedBDF + } + return normalizeString(slot) +} + func resolvePresent(value *bool, defaultValue bool) bool { if value == nil { return defaultValue diff --git a/internal/ingest/parser_hardware_test.go b/internal/ingest/parser_hardware_test.go index c0210cf..a7299fd 100644 --- a/internal/ingest/parser_hardware_test.go +++ b/internal/ingest/parser_hardware_test.go @@ -25,6 +25,7 @@ func TestFlattenHardwareComponentsUsesCanonicalTypeKeys(t *testing.T) { pcieHWSlowdown := true psuTemperature := 42.25 psuLifeRemaining := 97.0 + manufacturedYearWeek := "2024-W07" input := HardwareSnapshot{ Board: HardwareBoard{SerialNumber: boardSerial}, CPUs: []HardwareCPU{ @@ -35,6 +36,7 @@ func TestFlattenHardwareComponentsUsesCanonicalTypeKeys(t *testing.T) { Throttled: &cpuThrottled, CorrectableErrorCount: &cpuCorrectableErrors, LifeRemainingPct: &cpuLifeRemaining, + ManufacturedYearWeek: &manufacturedYearWeek, Status: strPtr("OK"), }, }, @@ -63,6 +65,7 @@ func TestFlattenHardwareComponentsUsesCanonicalTypeKeys(t *testing.T) { PCIeDevices: []HardwarePCIeDevice{ { Slot: strPtr("PCIe.Slot.1"), + BDF: strPtr("0000:3b:00.0"), NUMANode: &numaNode, TemperatureC: &pcieTemperature, PowerW: &pciePower, @@ -97,6 +100,9 @@ func TestFlattenHardwareComponentsUsesCanonicalTypeKeys(t *testing.T) { switch component.ComponentType { case "cpu": foundCPU = true + if got := component.Attributes["manufactured_year_week"]; got != manufacturedYearWeek { + t.Fatalf("expected cpu attributes.manufactured_year_week=%q, got %#v", manufacturedYearWeek, got) + } if got := component.Telemetry["temperature_c"]; got != cpuTemperature { t.Fatalf("expected cpu telemetry.temperature_c=%v, got %#v", cpuTemperature, got) } @@ -142,6 +148,12 @@ func TestFlattenHardwareComponentsUsesCanonicalTypeKeys(t *testing.T) { } case "pcie_device": foundPCIe = true + if component.Slot == nil || *component.Slot != "0000:3b:00.0" { + t.Fatalf("expected pcie_device slot to normalize from bdf, got %#v", component.Slot) + } + if component.VendorSerial != "BOARD-001-PCIE-0000:3b:00.0" { + t.Fatalf("expected pcie_device fallback serial to use canonical bdf slot, got %q", component.VendorSerial) + } if got := component.Telemetry["numa_node"]; got != float64(numaNode) { t.Fatalf("expected pcie_device telemetry.numa_node=%d, got %#v", numaNode, got) }