8.0 KiB
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
- py_compile для .py, node -c для .js.
- Ручной CREATE через UI → инстанс создан, в трекере, запись в runs.
- Ручной DELETE → CMDB-путь работает (early return).
- Сценарий старый формат (seed dummy_test) → работает без изменений.
- Сценарий новый формат: create output:d1 → modify instance_ref:d1 → delete instance_ref:d1 → все OK, instance_bindings заполнен.
- Два create одного сервиса с разными output → два разных инстанса.
- Валидация: instance_ref на несуществующий output → ошибка при сохранении.
- 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)