Files
autotest/DOCS/opus-plan-2026-07-31.md
T

139 lines
8.0 KiB
Markdown

# Opus Implementation Plan — Унификация сценариев autotest (2026-07-31)
> Основано на DOCS/opus-questions-2026-07-31.md (19 Q+A) и ревью пользователя.
## Цель
Устранить дублирование CREATE-флоу (ручной api_test.py vs сценарный scenario.py),
ввести единый execute_operation, гибкие ссылки на инстансы (output/instance_ref/instance_uid),
модальный редактор сценариев с богатым рендером параметров.
## Порядок реализации (E19): бэкенд → формат → миграция вызовов → UI
---
## Фаза 1 — Общие модули (фундамент)
### 1. api/utils.py (NEW)
- `find_uid(resp)` — поиск UUID по ключам: instanceOperationUid → instanceUid → uid.
- `uid_from_location(loc)` — UUID из Location-заголовка.
- Заменяет 2 дубля (в api_test.py и scenario.py — сейчас разные реализации).
### 2. operations/poll.py (NEW)
- `poll_until_done(client, op_uid, timeout=1800)` → dict {status, is_successful,
error_log, stages, duration, svc}.
- Критерий завершения: dtFinish != "" (НЕ isInProgress).
- Общий цикл для async (_finish_op) и sync (run_scenario). Убирает хардкод 1800s в 3 местах.
### 3. operations/executor.py (NEW)
- `execute_operation(client, service_id, operation, instance_uid, params,
svc_op_id=None, display_name=None)` → dict {ok, error, failed_step,
instance_uid, op_uid, display_name}.
- Делает всё ДО /run включительно: POST /instances (только create) →
POST /instanceOperations → send_params_terraform → POST /run. НЕ поллит.
- Ветвится по operation=="create" ВНУТРИ:
- create: POST /instances → op_payload {instanceUid, operation}; svc_op_id игнорируется.
- non-create: op_payload {instanceUid, svcOperationId, operation}.
- failed_step ∈ {instances, instanceOperations, params, run}.
- tracker_add вызывается ВНУТРИ executor для всех create (E3) — сразу после
получения instanceUid, до params/run (защита от сирот).
- Переиспользует существующую send_params_terraform (operations/terraform.py) как есть.
---
## Фаза 2 — Формат шагов и резолвинг (depends Фаза 1)
### 4. routes/api_scenario_defs.py — _validate_steps
Новые опциональные ключи шага: output, instance_ref, instance_uid.
Валидация:
- output уникален в пределах сценария.
- instance_ref ссылается на output из ПРЕДЫДУЩИХ шагов.
- для не-create шага обязателен instance_ref | instance_uid | (fallback старый формат).
- Старый формат (только service_id) продолжает проходить валидацию.
### 5. operations/scenario.py — резолвинг инстанса
Приоритет: instance_uid > instance_ref > instance_map[service_id] (fallback).
После create: bindings[output] = instance_uid — в память И в
scenario_runs.instance_bindings (колонка JSONB уже есть, DEFAULT '{}').
Резолвинг во время выполнения — ТОЛЬКО из памяти (БД для наблюдаемости).
---
## Фаза 3 — Миграция вызывающих (depends Фаза 1-2)
### 6. routes/api_test.py
- CMDB delete ОСТАЁТСЯ как предпроверка ПЕРЕД executor: при cmdb_ok → early return
(opUid="cmdb-...", без /run, без поллинга). Иначе → execute_operation.
- Остальные операции: заменить inline флоу на execute_operation.
- _finish_op использует poll_until_done (save_run, _op_results, tracker_remove(delete)
остаются здесь).
- Импорт find_uid/uid_from_location из api/utils.py; удалить локальные копии.
### 7. operations/scenario.py
- Заменить inline флоу на execute_operation.
- sync while-поллинг → poll_until_done.
- Удалить локальные _find_uid/_uid_from_location.
### 8. db/init_db.py — startup cleanup (E16)
В init_db() добавить:
UPDATE scenario_runs SET status='TIMEOUT', error_log='worker restart'
WHERE status='RUNNING' AND created_at < NOW() - INTERVAL '1 hour';
(init_db вызывается из pool._ensure_schema() раз на воркер, идемпотентно).
---
## Фаза 4 — UI редактора (depends Фаза 2)
### 9. static/js/params-render.js (NEW)
Вынести из operations.js:
- renderParamRow(p, allInst) — уже чистая, без глобалов.
- renderMapFixedRow(p, dfl) — чистая.
- collectParams(containerSelector='#params-form') — параметризовать контейнер
(единственная правка сигнатуры; сейчас хардкодит #params-form).
Глобалы AUTOTEST_PREFIX/currentSvcId/makeCreateDisplayName остаются в operations.js.
_esc (utils.js) и validateJson — общие глобалы.
### 10. static/js/operations.js
Использовать общий params-render.js (удалить дубли рендера).
### 11. static/js/scenario-form.js — модальный редактор
- Модал на весь экран (не inline scenario-body — параметры map-fixed слишком тесны).
- Дропдаун сервисов (GET /api/services).
- Дропдаун операций (GET /api/operations/{svcId}).
- Авто pre-fill параметров при смене операции (GET /api/params/{svcOpId}),
показать ВСЕ параметры с defaults + кнопка «Сбросить на defaults».
- Поле output для create-шагов.
- Дропдаун instance_ref для не-create (output'ы предыдущих шагов).
- Кнопки [↑][↓] перестановки шагов.
- В БД — символические имена параметров ({"durationMs":"5000"}), резолв в numeric ID в runtime.
### 12. templates/index.html
Разметка модала + подключить params-render.js.
### 13. app.py — bump VERSION.
---
## Решения (зафиксировано)
- Схема БД без изменений (C9), только новые ключи в steps JSONB.
- Обе версии формата параллельно, без миграции данных (B8), fallback на service_id.
- _op_results в памяти — вне scope (E18).
- lock_check (один RUNNING сценарий) сохраняется (E17).
- instance_bindings (JSONB) — используется (C10).
## Verification
1. py_compile для .py, node -c для .js.
2. Ручной CREATE через UI → инстанс создан, в трекере, запись в runs.
3. Ручной DELETE → CMDB-путь работает (early return).
4. Сценарий старый формат (seed dummy_test) → работает без изменений.
5. Сценарий новый формат: create output:d1 → modify instance_ref:d1 →
delete instance_ref:d1 → все OK, instance_bindings заполнен.
6. Два create одного сервиса с разными output → два разных инстанса.
7. Валидация: instance_ref на несуществующий output → ошибка при сохранении.
8. Startup-cleanup: зависший RUNNING >1ч → TIMEOUT после рестарта.
## Файлы
NEW: operations/executor.py, operations/poll.py, api/utils.py, static/js/params-render.js
MOD: routes/api_test.py, operations/scenario.py, routes/api_scenario_defs.py,
db/init_db.py, static/js/scenario-form.js, static/js/operations.js,
templates/index.html, app.py (VERSION)