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

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

  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)