Add hardware ingest flow and UI upload
This commit is contained in:
114
docs/plan/Plan #1.md
Normal file
114
docs/plan/Plan #1.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Реализация JSON hardware-ingest по INTEGRATION_GUIDE.md
|
||||
|
||||
## Summary
|
||||
Добавляем новый инструмент импорта hardware snapshot: `POST /ingest/hardware` + форму в ingest UI, без ломки текущего `POST /ingest/logbundle`.
|
||||
|
||||
Импорт должен:
|
||||
1. находить/создавать `asset` по `hardware.board.serial_number`,
|
||||
2. автоматически назначать новые asset в служебный customer/project (`Без заказчика` / `Без проекта`),
|
||||
3. создавать/обновлять компоненты + LOT,
|
||||
4. писать `observations` с `details JSON`,
|
||||
5. синхронизировать `installations`,
|
||||
6. генерировать timeline events (`LOG_COLLECTED`, `INSTALLED`, `REMOVED`, `FIRMWARE_CHANGED`, `COMPONENT_WARNING`, `COMPONENT_FAILED`),
|
||||
7. создавать `failure_events` при `Critical`.
|
||||
|
||||
## Подтвержденные решения
|
||||
1. Scope: `API + UI`.
|
||||
2. Endpoint: основной `POST /ingest/hardware`.
|
||||
3. Новые asset по умолчанию идут в служебный customer/project:
|
||||
- customer: `Без заказчика`
|
||||
- project: `Без проекта`
|
||||
4. Статусы:
|
||||
- `Critical` -> `failure_event` + `COMPONENT_FAILED`
|
||||
- `Warning` -> `COMPONENT_WARNING`
|
||||
5. Расширенные observation-поля храним в `observations.details JSON`.
|
||||
6. Ответ endpoint: как в guide (`status`, `bundle_id`, `asset_id`, `collected_at`, `duplicate`, `summary|message`).
|
||||
|
||||
## Публичные API / контракты
|
||||
1. Новый маршрут в `internal/api/ingest.go`:
|
||||
- `POST /ingest/hardware`
|
||||
2. Request JSON:
|
||||
- `collected_at` (required, RFC3339)
|
||||
- `target_host` (required)
|
||||
- `hardware` (required) с `board.serial_number` (required)
|
||||
- опционально: `filename`, `source_type`, `protocol`, секции `cpus/memory/storage/pcie_devices/power_supplies/firmware`
|
||||
3. Response success (201):
|
||||
- `status: "success"`
|
||||
- `bundle_id`, `asset_id`, `collected_at`, `duplicate: false`
|
||||
- `summary` с детализированными счетчиками
|
||||
4. Response duplicate (200):
|
||||
- те же поля + `duplicate: true`, `message`
|
||||
5. Validation error (400):
|
||||
- `status: "error"`, `error: "validation_failed"`, `details.field`, `details.message`
|
||||
|
||||
## Изменения по файлам (implementation design)
|
||||
1. `migrations/0007_hardware_ingest/up.sql` и `down.sql`
|
||||
- `ALTER TABLE observations ADD COLUMN details JSON NULL;`
|
||||
2. `internal/api/ingest.go`
|
||||
- новый handler `handleHardware`
|
||||
- строгая decode/validate
|
||||
- вызов `IngestHardware(...)`
|
||||
3. `internal/ingest/service.go`
|
||||
- `HardwareInput`, `HardwareResult`, summary counters
|
||||
- transactional pipeline
|
||||
- `ensureServiceProject()`:
|
||||
- customer `Без заказчика` (create if missing)
|
||||
- project `Без проекта` (create if missing)
|
||||
- `ensureAssetByBoardSerial()`:
|
||||
- lookup по `assets.vendor_serial = board.serial_number`
|
||||
- create если нет (`name=target_host`, `project_id=service_project_id`, `vendor/model` по board)
|
||||
- update vendor/model при валидных значениях
|
||||
4. `internal/ingest/parser_hardware.go`
|
||||
- parse + normalize + flatten snapshot
|
||||
- генерация serial:
|
||||
- CPU: `{board_serial}-CPU-{socket}`
|
||||
- PCIe: `{board_serial}-PCIE-{slot}` если пусто/N/A
|
||||
- фильтрация `present=false`, `status=Empty`
|
||||
5. `internal/ingest/lot_classifier.go`
|
||||
- LOT rules для CPU/DIMM/Storage/PSU/PCIe
|
||||
- `ensureLotByCode()`
|
||||
6. `internal/ingest/service.go` (status/failure/events)
|
||||
- `observations.details` хранит: `component_type`, `slot`, `status`, `present`, `telemetry`, raw attrs
|
||||
- `Critical`:
|
||||
- `COMPONENT_FAILED` в timeline
|
||||
- `failure_events` (`source=hardware_ingest`, deterministic `external_id`)
|
||||
- `Warning`:
|
||||
- `COMPONENT_WARNING` в timeline
|
||||
- system firmware (`hardware.firmware`):
|
||||
- сравнение с последним значением и `FIRMWARE_CHANGED` для asset
|
||||
7. UI:
|
||||
- `internal/api/ui_ingest.tmpl`
|
||||
- `internal/api/ui.go`
|
||||
- добавить секцию “Hardware Ingest” с примером payload на основе `docs/import-example-full.json`
|
||||
|
||||
## Идемпотентность
|
||||
1. Хеш считается от канонически marshaled hardware request.
|
||||
2. Если hash уже есть:
|
||||
- без side effects
|
||||
- `200 duplicate=true`
|
||||
3. Если hash новый:
|
||||
- полный pipeline
|
||||
- `201`
|
||||
|
||||
## Тесты и acceptance
|
||||
1. `TestIngestHardwareIdempotent`: 201 -> 200 duplicate, без дублей в `log_bundles/observations/installations`.
|
||||
2. Тест авто-создания служебного customer/project и auto-create asset.
|
||||
3. Тест замены компонента: `REMOVED` + `INSTALLED`.
|
||||
4. Тест firmware change: `FIRMWARE_CHANGED`.
|
||||
5. Тест статусов:
|
||||
- Warning -> `COMPONENT_WARNING`
|
||||
- Critical -> `COMPONENT_FAILED` + `failure_events`
|
||||
6. Validation tests:
|
||||
- missing required fields
|
||||
- invalid RFC3339
|
||||
- unknown field -> 400
|
||||
7. UI smoke: `/ui/ingest` содержит форму hardware ingest.
|
||||
|
||||
## Assumptions and defaults
|
||||
1. Служебные записи:
|
||||
- customer.name = `Без заказчика`
|
||||
- project.name = `Без проекта`
|
||||
2. При дублях по имени используется запись с минимальным `id`.
|
||||
3. `manufacturer/product_name == "NULL"` -> `NULL` в БД.
|
||||
4. Новые `event_type` (`COMPONENT_WARNING`, `COMPONENT_FAILED`) хранятся как строки без enum-миграции.
|
||||
5. `/ingest/logbundle` остается полностью совместимым.
|
||||
Reference in New Issue
Block a user