Files
autotest/DOCS/opus-architecture-prompt.md

214 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Prompt for Opus — Architecture: Unified Scenario System
**Ты можешь задавать уточняющие вопросы.** Если чего-то не хватает для принятия решения — спроси. Я (DeepSeek V4 Pro, ассистент Naael) отвечу.
## ПОРЯДОК ЧТЕНИЯ (обязательно прочитай в этом порядке)
**Шаг 1 — понять что такое Nubes и как работает API:**
`/home/naeel/nubes/autotest/DOCS/terraform-operations-full-logic.md`
**Шаг 2 — увидеть дублирование своими глазами (самое важное):**
Смотри раздел «КЛЮЧЕВОЙ КОД» ниже — там оба CREATE-флоу (ручной и сценарный) бок о бок.
**Шаг 3 — понять текущую архитектуру кода:**
- `/home/naeel/nubes/autotest/app-autotest/site/routes/api_test.py` (строки 175-270 — ручной CREATE)
- `/home/naeel/nubes/autotest/app-autotest/site/operations/scenario.py` (строки 85-210 — сценарный CREATE)
- `/home/naeel/nubes/autotest/app-autotest/site/operations/terraform.py` (общая send_params_terraform)
- `/home/naeel/nubes/autotest/app-autotest/site/routes/api_scenario_defs.py` (CRUD определений)
- `/home/naeel/nubes/autotest/app-autotest/site/db/scenario_defs.py` (SQL-функции)
- `/home/naeel/nubes/autotest/app-autotest/site/db/init_db.py` (схема БД — найди scenario_definitions)
**Шаг 4 — понять фронтенд:**
- `/home/naeel/nubes/autotest/app-autotest/site/templates/index.html` (вся страница, CSS, Jinja2)
- `/home/naeel/nubes/autotest/app-autotest/site/static/js/operations.js` (showParams, executeOp — как запускается ручная операция)
- `/home/naeel/nubes/autotest/app-autotest/site/static/js/scenario-form.js` (renderEditor, saveScenario — текущий редактор)
**НЕ читай — это легаси/устарело:**
- `DOCS/ARCHITECTURE.legacy.md`, `DOCS/ARCHITECTURE.md`
- `DOCS/sonnet-*.md` — переписки с другим AI
- `DOCS/gpt56-*`, `DOCS/questions-to-sol.md`, `DOCS/sol-*`
- `HISTORY/` — история сессий
- `development/` — HAR-файлы
---
## Что это за приложение
**Nubes** — облачная платформа (IaaS/PaaS). ~30 типов сервисов: VM, K8s, PostgreSQL, Redis, S3, Kafka, ClickHouse и др. Каждый сервис имеет операции: create, modify, delete, suspend, resume, redeploy.
**Autotest** — Flask-приложение, которое делает то же самое что личный кабинет Nubes, но через REST API и автоматически. Два режима:
1. **Ручной** — пользователь выбирает сервис → видит список autotest-инстансов → кликает → выбирает операцию (modify/delete/suspend/...) → заполняет параметры → запускает. Или жмёт «+ Создать» для CREATE.
2. **Сценарии** — пользователь создаёт последовательность шагов (create → modify → delete), сохраняет в БД, запускает одним кликом. Каждый шаг = атомарная операция над инстансом.
### Как работает API Nubes (CREATE)
```
1. POST /instances body={serviceId, displayName, descr} → 201, Location: ./UUID
2. POST /instanceOperations body={instanceUid, operation:"create"} → opUid
3. GET /instanceOperations/{opUid}?fields=cfsParams → параметры с defaults
4. POST /instanceOperationCfsParams (×N — все параметры)
5. GET /instanceOperations/{opUid}/validate-cfs → валидация
6. POST /instanceOperations/{opUid}/run → запуск
7. Поллинг GET /instanceOperations/{opUid}?fields=dtFinish,isSuccessful,... до dtFinish
```
MODIFY/DELETE/SUSPEND/RESUME: шаг 1 пропускается, шаг 2 с `{instanceUid, svcOperationId, operation}` (с svcOperationId!).
### БД (PostgreSQL, одна на все gunicorn-воркеры)
```sql
scenario_definitions (id, client_id, stand, name, steps JSONB, version, is_active, ...)
scenario_runs (id, scenario_name, status, current_step, total_steps, error_log, app_version, ...)
runs (id, svc_id, op_name, instance_uid, status, duration_sec, params JSONB, stages JSONB, ...)
```
---
## КЛЮЧЕВОЙ КОД — ДУБЛИРОВАНИЕ CREATE-ФЛОУ
### Ручной режим (api_test.py, функция api_test)
```python
if op_name == "create":
display_name = _unique_display_name(client, display_name)
descr = f"created by autotest v{current_app.config.get('VERSION', '')}"
payload = {"serviceId": svc_id, "displayName": display_name, "descr": descr}
resp = client.post("/instances", payload)
instance_uid = resp.get("instanceUid") or _find_uid(resp) or _uid_from_location(resp.get("_location", ""))
if not instance_uid:
return jsonify({"status": "FAIL", "error": "Не удалось получить instanceUid"}), 500
# ----------------------------------------------------------
op_payload = {"instanceUid": instance_uid, "operation": "create"}
op_resp = client.post("/instanceOperations", op_payload)
op_uid = _find_uid(op_resp) or _uid_from_location(op_resp.get("_location", ""))
if not op_uid:
return jsonify({"status": "FAIL", "error": "Не удалось получить opUid"}), 500
# ----------------------------------------------------------
send_params_terraform(client, op_uid, params) # ← ОБЩАЯ ФУНКЦИЯ
client.post(f"/instanceOperations/{op_uid}/run")
# ----------------------------------------------------------
threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, ...), daemon=True).start()
return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid, ...})
```
### Сценарий (scenario.py, функция run_scenario)
```python
if op_name == "create":
display_name = f"{AUTOTEST_PREFIX}{scenario_name}-{uuid.uuid4().hex[:6]}"
descr = f"scenario {scenario_name} step {step_num}"
payload = {"serviceId": svc_id, "displayName": display_name, "descr": descr}
resp = client.post("/instances", payload)
instance_uid = resp.get("instanceUid") or _find_uid(resp) or _uid_from_location(resp.get("_location", ""))
if not instance_uid:
raise RuntimeError(f"CREATE: no instanceUid in response: ...")
instance_map[svc_id] = instance_uid
# ----------------------------------------------------------
op_payload = {"instanceUid": instance_uid, "operation": op_name} # без svcOperationId
op_resp = client.post("/instanceOperations", op_payload)
op_uid = op_resp.get("instanceOperationUid") or _find_uid(op_resp) or _uid_from_location(...)
if not op_uid:
raise RuntimeError(f"No opUid in response: ...")
# ----------------------------------------------------------
from operations.terraform import send_params_terraform
send_params_terraform(client, op_uid, resolved_params) # ← ТА ЖЕ ФУНКЦИЯ
client.post(f"/instanceOperations/{op_uid}/run")
# ----------------------------------------------------------
# ДАЛЕЕ: синхронный while-поллинг (1800s таймаут), save_run(), _save_scenario_run()
```
**Эти два блока делают ОДНО И ТО ЖЕ.** Различаются только:
- displayName (autotest-xxx vs autotest-scenario-xxx)
- Поллинг (async thread vs sync while)
- Сохранение (runs через _finish_op vs runs + scenario_runs)
---
## КЛЮЧЕВОЙ КОД — ОГРАНИЧЕНИЕ instance_map
```python
# scenario.py, строка 91
instance_map = {} # service_id → instanceUid
# Шаг CREATE:
instance_map[svc_id] = instance_uid
# Шаг НЕ-CREATE:
instance_uid = instance_map.get(svc_id)
if not instance_uid:
raise RuntimeError(f"No instance for service_id {svc_id} — need CREATE first")
```
**Проблема:** привязано к `service_id`. Нельзя:
- Два инстанса одного сервиса в сценарии (второй CREATE перезапишет первый)
- Сослаться на инстанс из другого сценария
- Использовать существующий инстанс по UUID
---
## Что нужно спроектировать
### 1. Единый executor (operations/executor.py)
```python
def execute_operation(client, service_id, operation, instance_uid_or_none, params, display_name=None) -> dict:
"""
Единая точка входа для ручного и сценарного запуска.
Возвращает {"instance_uid": ..., "op_uid": ..., "display_name": ...}
"""
```
`api_test.py` и `scenario.py` вызывают эту функцию. Поллинг и save_run — снаружи (у каждого свой).
### 2. Гибкие ссылки на инстансы
Новый формат шага в `scenario_definitions.steps`:
```json
[
{"service_id": 1, "operation": "create", "params": {...}, "output": "d1"},
{"service_id": 1, "operation": "modify", "params": {...}, "instance_ref": "d1"},
{"service_id": 90, "operation": "create", "params": {...}, "output": "pg"},
{"service_id": 1, "operation": "delete", "params": {}, "instance_uid": "UUID-явно"}
]
```
Резолвинг на бэкенде: `output` → сохраняем в словарь `{name: instance_uid}`. `instance_ref` → берём из словаря. `instance_uid` → используем как есть.
### 3. UI редактора сценариев
**Текущее:** inline-форма в `scenario-body`, сервис = numeric input, операция = text input (БАГ), параметры = key:value строки.
**Нужно:** полноценный редактор с:
- Выпадающий список сервисов (`GET /api/services`)
- Выпадающий список операций (`GET /api/operations/{svcId}`)
- Параметры с автоподгрузкой из `/api/params/{svcOpId}`: name, type, default, valueList, dataDescriptor
- Поле `output` для create-шагов (имя для ссылок)
- Дропдаун `instance_ref` для не-create шагов (output-имена предыдущих шагов)
- `[↑][↓]` для перестановки шагов
**Вопросы:**
1. Модальное окно или раскрытие внутри `scenario-body`? Аргументируй.
2. Как показывать параметры: таблица (name|type|default|value) или упрощённо (key=value)?
3. pre-fill параметров при смене операции — авто или по кнопке?
4. Куда скроллится страница при открытии редактора?
### 4. Документация для чтения (кроме кода)
- `/home/naeel/nubes/autotest/DOCS/terraform-operations-full-logic.md` ← обязательно
- `/home/naeel/nubes/autotest/DOCS/ARCHITECTURE-FULL.md` ← общая архитектура
- `/home/naeel/nubes/autotest/DOCS/architecture-final.md` ← финальная версия
---
## Вопросы
1. **Unified executor:** сигнатура, возврат, обработка ошибок на каждом шаге CREATE-флоу
2. **Формат шагов:** как парсить `output`/`instance_ref`/`instance_uid`, валидация, резолвинг на бэкенде
3. **Схема БД:** нужны ли изменения в `scenario_definitions.steps`? Новая колонка для output-блоков?
4. **UI редактора:** модал vs inline, компоновка блоков, автоподгрузка параметров, скролл
5. **Миграция:** что делать с существующим dummy_test при смене формата шагов
6. **Порядок:** в какой последовательности реализовывать