# Contract: Hardware Ingest JSON Version: 2.10 Source: `bee/bible-local/docs/hardware-ingest-contract.md` (canonical upstream) Стандартный JSON-контракт для передачи данных об аппаратном обеспечении серверов в Reanimator. Используется в `bee`, `logpile`, `core` и внешних интеграторах (Redfish-коллекторы, CMDB-экспортёры). > Актуальная версия: https://git.mchus.pro/reanimator/core/src/branch/main/bible-local/docs/hardware-ingest-contract.md ## Принципы 1. **Snapshot** — JSON описывает состояние сервера на момент сбора. Может включать историю изменений статуса. 2. **Идемпотентность** — повторная отправка идентичного payload не создаёт дублей (дедупликация по хешу). 3. **Частичность** — можно передавать только те секции, данные по которым доступны. Пустой массив и отсутствие секции эквивалентны. 4. **Строгая схема** — endpoint использует строгий JSON-декодер; неизвестные поля приводят к `400 Bad Request`. 5. **Event-driven** — импорт создаёт события в timeline (LOG_COLLECTED, INSTALLED, REMOVED, FIRMWARE_CHANGED и др.). 6. **Без синтеза** — сборщик передаёт только фактически собранные значения. Запрещено придумывать `serial_number`, `component_ref`, `message`, `message_id` или иные идентификаторы, если источник их не предоставил. ## Endpoint ``` POST /ingest/hardware Content-Type: application/json ``` Ответ `202 Accepted` с `job_id`. Результат: `GET /ingest/hardware/jobs/{job_id}`. ## Структура верхнего уровня ```json { "filename": "redfish://10.10.10.103", "source_type": "api", "protocol": "redfish", "target_host": "10.10.10.103", "collected_at": "2026-02-10T15:30:00Z", "hardware": { "board": { ... }, "firmware": [ ... ], "cpus": [ ... ], "memory": [ ... ], "storage": [ ... ], "pcie_devices": [ ... ], "power_supplies": [ ... ], "sensors": { ... }, "event_logs": [ ... ], "platform_config": { ... } } } ``` | Поле | Тип | Обязательно | Описание | |------|-----|-------------|----------| | `collected_at` | string RFC3339 | **да** | Время сбора данных | | `hardware` | object | **да** | Аппаратный снапшот | | `hardware.board.serial_number` | string | **да** | Серийный номер платы/сервера | | `target_host` | string | нет | IP или hostname | | `source_type` | string | нет | `api`, `logfile`, `manual` | | `protocol` | string | нет | `redfish`, `ipmi`, `snmp`, `ssh` | | `filename` | string | нет | Идентификатор источника | ## Общие поля статуса компонентов Применяются ко всем компонентным секциям (`cpus`, `memory`, `storage`, `pcie_devices`, `power_supplies`). | Поле | Тип | Описание | |------|-----|----------| | `status` | string | `OK`, `Warning`, `Critical`, `Unknown`, `Empty` | | `status_checked_at` | string RFC3339 | Время последней проверки | | `status_changed_at` | string RFC3339 | Время последнего изменения | | `status_history` | array | История переходов (`status`, `changed_at` обязательны) | | `error_description` | string | Текст ошибки/диагностики | | `manufactured_year_week` | string | Дата производства `YYYY-Www`, например `2024-W07` | Правила статуса: - Не включайте записи `status_history` без `changed_at`. - `status_history` сортировать по `changed_at` по возрастанию. - Все даты — RFC3339, рекомендуется UTC (`Z`). | Статус | Поведение | |--------|-----------| | `OK` | Нормальная обработка | | `Warning` | Создаётся событие `COMPONENT_WARNING` | | `Critical` | Создаётся событие `COMPONENT_FAILED` + запись в `failure_events` | | `Unknown` | Компонент считается рабочим, создаётся событие `COMPONENT_UNKNOWN` | | `Empty` | Компонент не создаётся/не обновляется | ## Секции hardware ### board (обязательная) | Поле | Тип | Обязательно | Описание | |------|-----|-------------|----------| | `serial_number` | string | **да** | Серийный номер (ключ идентификации Asset) | | `manufacturer` | string | нет | Производитель | | `product_name` | string | нет | Модель | | `part_number` | string | нет | Партномер | | `uuid` | string | нет | UUID системы | Значения `"NULL"` в строковых полях трактуются как отсутствие данных. ### firmware | Поле | Тип | Обязательно | |------|-----|-------------| | `device_name` | string | **да** | | `version` | string | **да** | Записи с пустым `device_name` или `version` игнорируются. Изменение версии создаёт событие `FIRMWARE_CHANGED`. ### cpus | Поле | Тип | Описание | |------|-----|----------| | `socket` | int | **обязательно**; используется для генерации serial | | `model` | string | Модель процессора | | `manufacturer` | string | | | `cores` / `threads` | int | | | `frequency_mhz` / `max_frequency_mhz` | int | | | `temperature_c` | float | Telemetry, °C | | `power_w` | float | Telemetry, Вт | | `throttled` | bool | Thermal/power throttling | | `correctable_error_count` / `uncorrectable_error_count` | int | | | `life_remaining_pct` / `life_used_pct` | float | Health/wear, % | | `serial_number` | string | Если доступен | | `firmware` | string | Версия микрокода (Microcode level — передавать как есть) | | `present` | bool | По умолчанию `true` | Генерация serial при отсутствии: `{board_serial}-CPU-{socket}` ### memory | Поле | Тип | Описание | |------|-----|----------| | `slot` | string | | | `present` | bool | По умолчанию `true` | | `serial_number` | string | Обязателен для создания записи | | `part_number` | string | Используется как модель | | `manufacturer` | string | | | `size_mb` | int | | | `type` | string | `DDR3`, `DDR4`, `DDR5` | | `max_speed_mhz` / `current_speed_mhz` | int | | | `temperature_c` | float | Telemetry | | `correctable_ecc_error_count` / `uncorrectable_ecc_error_count` | int | | | `life_remaining_pct` / `life_used_pct` / `spare_blocks_remaining_pct` | float | | | `performance_degraded` / `data_loss_detected` | bool | | Модуль без `serial_number`, с `present=false` или `status=Empty` игнорируется. ### storage | Поле | Тип | Описание | |------|-----|----------| | `slot` | string | BDF (`0000:18:00.0`) для PCIe-подключённых | | `serial_number` | string | Обязателен для создания записи | | `model` / `manufacturer` | string | | | `type` | string | `NVMe`, `SSD`, `HDD` | | `interface` | string | `NVMe`, `SATA`, `SAS` | | `size_gb` | int | | | `logical_block_size_bytes` | int64 | Логический размер блока, например `512` или `4096` | | `physical_block_size_bytes` | int64 | Физический размер блока | | `metadata_bytes_per_block` | int64 | Metadata/protection bytes на блок, например `0` или `8` | | `temperature_c` | float | Telemetry | | `power_on_hours` / `power_cycles` / `unsafe_shutdowns` | int64 | | | `media_errors` / `error_log_entries` | int64 | | | `written_bytes` / `read_bytes` | int64 | | | `life_used_pct` / `life_remaining_pct` / `available_spare_pct` | float | | | `reallocated_sectors` / `current_pending_sectors` / `offline_uncorrectable` | int64 | | | `firmware` | string | Изменение создаёт `FIRMWARE_CHANGED` | | `present` | bool | По умолчанию `true` | Формат `512+8` не передаётся строкой — только через `logical_block_size_bytes` + `metadata_bytes_per_block`. Диск без `serial_number` игнорируется. ### pcie_devices | Поле | Тип | Описание | |------|-----|----------| | `slot` | string | Канонический адрес (BDF). `bdf` — deprecated alias, нормализуется при ingest | | `vendor_id` / `device_id` | int | PCI ID (decimal) | | `numa_node` | int | NUMA/CPU affinity | | `device_class` | string | `MassStorageController`, `StorageController`, `NetworkController`, `EthernetController`, `FibreChannelController`, `VideoController`, `ProcessingAccelerator`, `DisplayController` (список открытый) | | `manufacturer` / `model` / `serial_number` / `firmware` | string | | | `link_width` / `max_link_width` | int | | | `link_speed` / `max_link_speed` | string | `Gen3`, `Gen4`, `Gen5` | | `mac_addresses` | string[] | MAC-адреса портов | | `temperature_c` / `power_w` | float | Device-level telemetry | | `life_remaining_pct` / `life_used_pct` | float | | | `ecc_corrected_total` / `ecc_uncorrected_total` | int64 | | | `hw_slowdown` | bool | | | `battery_charge_pct` / `battery_health_pct` / `battery_temperature_c` / `battery_voltage_v` | float | | | `battery_replace_required` | bool | | | `sfp_temperature_c` / `sfp_tx_power_dbm` / `sfp_rx_power_dbm` / `sfp_voltage_v` / `sfp_bias_ma` | float | Optical telemetry | | `present` | bool | По умолчанию `true` | Генерация serial при отсутствии или `"N/A"`: `{board_serial}-PCIE-{slot}` ### power_supplies | Поле | Тип | Описание | |------|-----|----------| | `slot` | string | | | `present` | bool | По умолчанию `true` | | `serial_number` | string | Обязателен для создания записи | | `part_number` / `model` / `vendor` | string | | | `wattage_w` | int | | | `firmware` | string | | | `input_type` | string | Например `ACWideRange` | | `input_voltage` / `input_power_w` / `output_power_w` / `temperature_c` | float | Telemetry | | `life_remaining_pct` / `life_used_pct` | float | | PSU без `serial_number` игнорируется. ### sensors (опционально) Данные хранятся как last-known-value на уровне Asset. Идентификатор: `(sensor_type, name)`. Поле `location` передавать не нужно — игнорируется. Сенсоры без `name` игнорируются. ```json "sensors": { "fans": [{ "name": "FAN1", "rpm": 4200, "status": "OK" }], "power": [{ "name": "12V Rail", "voltage_v": 12.06, "status": "OK" }], "temperatures": [{ "name": "CPU0 Temp", "celsius": 46.0, "threshold_warning_celsius": 80.0, "threshold_critical_celsius": 95.0, "status": "OK" }], "other": [{ "name": "System Humidity", "value": 38.5, "unit": "%" }] } ``` ### event_logs (опционально) Нормализованные операционные логи. Не попадают в history timeline. Дедуплицируются по `(asset, source, fingerprint)`. | Поле | Тип | Обязательно | Описание | |------|-----|-------------|----------| | `source` | string | **да** | `host`, `bmc`, `redfish` | | `message` | string | **да** | Нормализованный текст события | | `event_time` | string RFC3339 | нет | | | `severity` | string | нет | `OK`, `Info`, `Warning`, `Critical`, `Unknown` | | `message_id` | string | нет | Код события источника | | `component_ref` | string | нет | Ссылка на компонент/слот | | `fingerprint` | string | нет | Внешний dedup-key; если нет — система вычисляет свой | | `is_active` | bool | нет | Событие всё ещё активно | | `raw_payload` | object | нет | Сырой vendor-specific payload | Запрещено синтезировать `message`, `message_id`, `component_ref`, serial/device identifiers. ### platform_config (опционально) Произвольный объект с настройками платформы (BIOS/Redfish/IPMI) как есть из источника. При каждом импорте хранится latest-snapshot per machine. ## Обработка отсутствующих serial_number Интегратор не подставляет вымышленные значения, хеши или placeholder-идентификаторы. Разрешены только server-side fallback-правила: | Тип | Поведение | |-----|-----------| | CPU | Генерируется: `{board_serial}-CPU-{socket}` | | PCIe | Генерируется: `{board_serial}-PCIE-{slot}` | | Memory | Компонент игнорируется | | Storage | Компонент игнорируется | | PSU | Компонент игнорируется | Если `serial_number` не уникален внутри payload для того же `model`: первое вхождение — оригинальный serial, дубли получают `NO_SN-XXXXXXXX`. ## Минимальный валидный пример ```json { "collected_at": "2026-02-10T15:30:00Z", "target_host": "192.168.1.100", "hardware": { "board": { "serial_number": "SRV-001" } } } ``` ## Changelog | Версия | Дата | Изменения | |--------|------|-----------| | 2.10 | 2026-04-29 | `hardware.storage[]`: добавлены `logical_block_size_bytes`, `physical_block_size_bytes`, `metadata_bytes_per_block` | | 2.9 | 2026-03-19 | Добавлена секция `hardware.platform_config` | | 2.8 | 2026-03-15 | Поле `location` удалено из всех `sensors.*` | | 2.7 | 2026-03-15 | Явно запрещён синтез данных в `event_logs` | | 2.6 | 2026-03-15 | Добавлена секция `event_logs` | | 2.5 | 2026-03-15 | Добавлено `manufactured_year_week` для всех компонентов | | 2.4 | 2026-03-15 | Component telemetry: health/life поля для всех секций | | 2.0 | 2026-02-01 | `status_history`, `status_changed_at`; async job response | | 1.0 | 2026-01-01 | Начальная версия |