# Запрос на полный анализ autotest — для нового чата с агентом Дата: 2026-07-30 Версия: v1.1.48 --- ## 1. ЧТО ЭТО ЗА ПРОЕКТ **autotest** — веб-приложение для автоматизированного тестирования сервисов платформы Nubes (Cloud Director). Позволяет запускать операции (create/modify/delete/suspend/resume/redeploy) над инстансами сервисов через Nubes REST API, отслеживать статус, вести историю запусков. Приложение запущено на тестовом стенде: `atest.pythonk8s.dev.nubes.ru` **Стек:** - Backend: Flask 3.0 + gunicorn (multi-worker) - DB: PostgreSQL 17 (Zalando Operator), схемы: `runs`, `scenario_runs`, `scenario_definitions` (pending) - Frontend: ванильный JS (один файл `app.js`, 550 строк), Jinja2-шаблон - Nubes REST API: `https://lk-api-gateway-test.ngcloud.ru/api/v1/svc` - Аутентификация: JWT-токен (cookie или env), автоопределение стенда (dev/test) - Деплой: Nubes pythonk8s, кластер iot-naeel **Автор:** naeel (tazetdinovn@gmail.com, WZ03709) **Репозитории:** - `https://gitea.services.ngcloud.ru/forcloud/app-autotest` — код приложения - `https://gitea.services.ngcloud.ru/forcloud/autotest` — документация, YAML-описания сервисов --- ## 2. АРХИТЕКТУРА — ФАЙЛЫ И ИХ РОЛИ ### site/app.py — точка входа Flask-приложение, регистрирует blueprint'ы: - `main_bp` — главная страница, токен, инфраструктура - `api_test_bp` — основной API (run/params/status/log/history) - `api_scenario_bp` — API сценариев (run/status/CRUD — pending) - `api_bp` — **УДАЛЁН в v1.1.45** (legacy: /api/run, /api/status, /api/config) - VERSION — меняется при КАЖДОМ изменении ### site/api/http_client.py — HTTP-клиент - `HttpClient` — обёртка над requests.Session - GET: `raise_for_status()` → `.json()`, пустой ответ → `{}` - POST: `json=data`, извлекает Location-заголовок → `_location` в ответе - `raw_delete(url)` — DELETE без auth (для CMDB API) - `detect_endpoint(token)` — автоопределение стенда (dev→test) - `stand_name(endpoint)` — "dev"/"test" по URL ### site/api/auth.py — аутентификация - `get_token()` — из cookie или env - `get_client()` — HttpClient с автостендом - `get_client_id()` — ClientID из JWT - `get_stand()` — "dev"/"test" - `get_token_info()` — {email, company, client_id} ### site/routes/main.py — главная страница - `GET/POST /` — Jinja2-рендер: организация, инфраструктура, сервисы, форма токена - `GET /api/operations/` — cloud-first инстансы + tracked-fallback - `_resolve_instance_status()` — explainedStatus из облака или "creating" из трекера ### site/routes/api_test.py — ОСНОВНОЙ API (400 строк) Эндпоинты: - `GET /api/services` — список сервисов - `GET /api/instances/list` — все инстансы - `GET /api/params/[?instanceUid=xxx]` — параметры операции (текущие или шаблон) - `POST /api/test` — запуск операции (CREATE или non-CREATE) - `GET /api/test/status/` — поллинг - `GET /api/log` — логи - `GET /api/history` — история из БД Ключевые функции: - `_send_params_terraform()` — шаги 3-7 Terraform (cfsParams → resolveRefSvc → send → validate) - `_normalize_value()` — normalizeUniversalValueV6 (Terraform-equivalent) - `_resolve_ref_svc()` — автоподстановка UUID инстанса для refSvcId-параметров - `_finish_op()` — фоновый поллинг до dtFinish, сохранение в БД - `_redact_params()` — замена secret/password/token на *** - `_unique_display_name()` — проверка на дубликат + суффикс - `_find_uid(resp)` — извлечение UUID из вложенных dict - `_uid_from_location(loc)` — извлечение UUID из Location-заголовка ### site/routes/api_scenario.py — API СЦЕНАРИЕВ (90 строк) - `GET /api/scenarios` — список из config.yaml (должен быть переключён на БД) - `POST /api/scenario/run` — запуск (создаёт запись ДО потока, возвращает run_id + 202) - `GET /api/scenario/run/` — статус конкретного запуска - `GET /api/scenario/status` — последние 10 запусков (legacy, должен уйти) ### site/operations/scenario.py — ЯДРО СЦЕНАРИЕВ (290 строк) - `run_scenario()` — главный исполнитель: 1. service имя → svc_id (из config.yaml services) 2. operation имя → svcOperationId (GET /services/{id}) 3. param коды → numeric IDs (GET /instanceOperations/default/{opId}) 4. create: новый инстанс; остальные: переиспользовать instance_map 5. POST /instances → POST /instanceOperations → _send_params_terraform → run → poll 6. Сохранить в runs (RUNNING до API, финальный после) + scenario_runs - `_resolve_service()` — поиск svc_id по символическому имени - `_resolve_params()` — symbolic codes → numeric IDs - `_find_uid()`, `_uid_from_location()` — ДУБЛИКАТЫ из api_test.py (!) - `_save_scenario_run()` — UPDATE scenario_runs - `_create_scenario_run()` — INSERT scenario_runs ### site/operations/get_params.py — параметры с текущими значениями - `get_params_with_current_values()` — смержить state.params инстанса с шаблоном операции - `_normalize_value_list()` — CSV-строка/массив → list ### site/operations/get_services.py — сервисы - `get_services()` — GET /services → все сервисы - `get_service_detail()` — GET /services/{id} → детали + операции ### site/operations/get_instances.py — инстансы - `get_organization()` — инстанс с serviceId=19 - `get_instances()` — GET /instances?pageSize=500 → все инстансы ### site/operations/service_list.py — разрешённые сервисы - `load_service_ids(stand)` — чтение `config/services_{stand}.txt` ### site/operations/tracker.py — трекер инстансов (файловый) - JSON-файлы в /tmp/: `instances-{clientId}-{stand}.json` - fcntl.flock для multi-worker safety - Используется как КРАТКОСРОЧНЫЙ fallback (инстанс создан, но облако ещё не показывает) ### site/db/pool.py — connection pool - Lazy-init ThreadedConnectionPool (1-5) - `_ensure_schema()` → `init_db()` после успешного коннекта - ENV: DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD, DB_SSLMODE ### site/db/init_db.py — схема БД - Таблица `runs` — история операций (id, client_id, stand, svc_id, op_name, status, duration, params JSONB, stages JSONB, scenario_run_id, step_number) - Таблица `scenario_runs` — запуски сценариев (id, scenario_name, status, current_step, total_steps, duration, error_log) - Миграции: ALTER TABLE ADD COLUMN IF NOT EXISTS - **НЕТ** таблицы `scenario_definitions` — её ещё предстоит создать ### site/db/save_run.py — сохранение в БД - `save_run()` — INSERT в runs - Принимает опциональные `scenario_run_id` + `step_number` (добавлены в v1.1.48) ### site/runner.py — LEGACY (будет удалён) - Старый механизм запуска тестов из config.yaml - Используется только `load_config()` из него ### site/static/app.js — ФРОНТЕНД (550 строк) - Глобальное состояние: svcInstances, selectedInst, selectedOp, pollTimer, currentSvcId, busy, AUTOTEST_PREFIX - selectService(), toggleInstance(), startCreate(), runOp(), showParams(), executeOp() - Поллинг: setFinishedState(), showStages(), stopPoll(), refreshInstances() - История: toggleHistory(), loadHistory() - Сценарии: toggleScenario(), loadScenarios(), runScenario(), stopScenarioPoll() - Хелперы: _esc() (HTML-escape), validateJson() - Логи: toggleLog(), startLogPoll() ### site/static/style.css — стили ### site/templates/index.html — Jinja2-шаблон - Три колонки: инфраструктура | сервисы | инстансы+операции+параметры - Секции: история (сворачиваемая), сценарии (сворачиваемая) - window.APP: version, stand, hasUserToken, firstServiceId ### site/config.yaml — конфигурация - services: маппинг name → service_id - scenarios: один сценарий dummy_test (create → delete) - **Проблема:** сценарии в контейнере, требуют redeploy для изменения ### STANDS/dev/resources_yaml/ — YAML-описания сервисов (50+ файлов) - Полные описания: name, service_id, outputs, operations с параметрами (коды, типы, valueList, sub_params) - Используются ТОЛЬКО как документация, код их не читает ### DOCS/ — документация - ARCHITECTURE-FULL.md — полный обзор - sol-answers.md — 14 ответов Sol (MVP-план) - sol-scenario-editor.md — ответы Sol по редактору сценариев --- ## 3. ПОТОК ОПЕРАЦИИ CREATE (Terraform parity) 1. POST /instances → `instance_uid` из Location-заголовка `./UUID` 2. POST /instanceOperations → `op_uid` из Location-заголовка 3. GET /instanceOperations/{op_uid}?fields=cfsParams → шаблон параметров 4. _resolve_ref_svc → автоподстановка UUID для refSvcId-параметров 5. POST /instanceOperationCfsParams → отправить пользовательские параметры 6. POST /instanceOperationCfsParams → дослать неотправленные (с normalize) 7. GET /instanceOperations/{op_uid}/validate-cfs → валидация 8. POST /instanceOperations/{op_uid}/run → запуск 9. Поллинг GET /instanceOperations/{op_uid}?fields=dtFinish,... → dtFinish --- ## 4. ТЕКУЩЕЕ СОСТОЯНИЕ (v1.1.48) ### Работает: - ✅ Ручной запуск операций (create/modify/delete/suspend/resume/redeploy) - ✅ Terraform-совместимая отправка параметров (9 шагов) - ✅ Поллинг с этапами (⏳/✅/❌) - ✅ История в БД (runs) - ✅ Лог-панель (fcntl.flock, ротация) - ✅ HTML-escape (statusError, d.error, логи, currentSvcName) - ✅ Redact secrets перед сохранением - ✅ Валидация входов (UUID, int, dict) - ✅ refSvcId — верхнеуровневый + в dataDescriptor - ✅ Убран legacy api_bp ### Частично работает: - ⚠️ Сценарии — запуск есть, но источник = config.yaml (redeploy для правки) - ⚠️ save_run пишет scenario_run_id + step_number (v1.1.48), но не протестировано - ⚠️ run_id возвращается клиенту (v1.1.48), поллинг по конкретному запуску ### Не работает / pending: - ❌ Редактор сценариев в UI (нужна таблица scenario_definitions + CRUD API + UI) - ❌ Сценарии требуют redeploy для изменения (запечены в config.yaml) - ❌ Дубликаты _find_uid / _uid_from_location в scenario.py и api_test.py - ❌ scenario.py не протестирован на полный цикл create→delete - ❌ seed из config.yaml в БД при первом старте не реализован - ❌ Нет сервисных токенов (runner использует персональный токен) - ❌ Нет блокировки параллельных запусков сценариев --- ## 5. КЛЮЧЕВЫЕ НАХОДКИ ИЗ HAR-ТРАССИРОВКИ POST /instances: - Request: `{"serviceId":1,"displayName":"dummy-11255555","descr":""}` - Response: status 201, body `{}`, Location: `./CDEBB216-E5EB-4C09-8736-7E5F01A4EE12` - **UUID ТОЛЬКО в Location-заголовке**, тело ответа — пустой объект `{}` POST /instanceOperations: - Request: `{"instanceUid":"...","operation":"create"}` - Response: также Location-заголовок с opUid **Следствие:** `_find_uid(resp)` никогда не найдёт UUID в теле ответа (тело пустое). UUID всегда в Location. `_uid_from_location(resp.get("_location",""))` — ЕДИНСТВЕННЫЙ работающий метод извлечения. **Но:** `_find_uid` нужен для других ответов API (get instanceOperation), где UUID внутри `instanceOperation.instanceOperationUid`. --- ## 6. РЕКОМЕНДАЦИИ SOL (2026-07-30) ### По редактору сценариев (sol-scenario-editor.md): 1. **Структура steps:** JSONB, поле `resource` для связи create→modify→delete одного инстанса 2. **Параметры:** выпадающие списки из живого API (сервис→операции→коды), кеш на время сессии 3. **Seed:** marker `scenario_seed_v1=completed`, `INSERT ON CONFLICT DO NOTHING`, отдельный `scenario_seed.yaml` 4. **UI:** модальное окно, кнопки вверх/вниз, мягкое удаление (is_active=false) 5. **Валидация:** при сохранении + при запуске, невалидный не запускать 6. **Критические дыры:** - run_id до потока (✅ исправлено v1.1.48) - GET /api/scenario/run/ (✅ добавлен v1.1.48) - save_run пишет scenario_run_id/step_number (✅ исправлено v1.1.48) - TIMEOUT: op_data инициализировать (✅ исправлено v1.1.48) - RUNNING до API (✅ исправлено v1.1.48) - Блокировка параллельных запусков (pending) - version в definitions для optimistic locking (pending) - Сервисные токены (pending) --- ## 7. ЧТО НУЖНО ОТ АГЕНТА ### Проанализировать и ответить: 1. **Общая архитектура** — оценить, найти слабые места, предложить улучшения 2. **Дублирование кода** — `_find_uid` и `_uid_from_location` есть и в api_test.py и в scenario.py. Вынести в общий модуль? Куда? 3. **scenario_definitions** — спроектировать таблицу: - Поля: id, client_id, stand, name, steps JSONB, version, is_active, created_at, updated_at, updated_by - Индексы: UNIQUE(client_id, stand, lower(name)) - API CRUD: какие именно эндпоинты, какие проверки - Seed: как именно импортировать из scenario_seed.yaml при первом старте 4. **UI редактора сценариев:** - Модальное окно: структура HTML - JS-логика: загрузка списка, создание/редактирование/удаление - Выпадающие списки: сервисы из GET /api/services, операции из GET /api/operations/{svcId}, параметры из GET /api/params/{opId} - Валидация на фронте перед отправкой 5. **Runner** — нужно ли что-то менять в `run_scenario()`? Он сейчас берёт шаги из config.yaml, должен из БД. Также нужно сохранять snapshot шагов в scenario_runs. 6. **Безопасность:** - CRUD сценариев: проверка client_id+stand - Optimistic locking (version) - Блокировка параллельных запусков (HTTP 409) 7. **Что ещё критично упущено?** — любые дыры, которые не покрыты текущим планом 8. **Приоритетный порядок реализации:** - Что делать сначала, что потом - Что можно выпустить в v1.1.49, что отложить ### НЕ ДЕЛАТЬ: - Не писать код (только анализ и план) - Не трогать git - Не менять файлы --- ## 8. ФАЙЛЫ ДЛЯ АНАЛИЗА (читать в этом порядке) 1. `app-autotest/site/app.py` 2. `app-autotest/site/api/http_client.py` 3. `app-autotest/site/api/auth.py` 4. `app-autotest/site/routes/main.py` 5. `app-autotest/site/routes/api_test.py` (самый важный) 6. `app-autotest/site/routes/api_scenario.py` 7. `app-autotest/site/operations/scenario.py` 8. `app-autotest/site/operations/get_params.py` 9. `app-autotest/site/operations/get_services.py` 10. `app-autotest/site/operations/get_instances.py` 11. `app-autotest/site/operations/tracker.py` 12. `app-autotest/site/operations/service_list.py` 13. `app-autotest/site/db/pool.py` 14. `app-autotest/site/db/init_db.py` 15. `app-autotest/site/db/save_run.py` 16. `app-autotest/site/runner.py` 17. `app-autotest/site/config.yaml` 18. `app-autotest/site/static/app.js` 19. `app-autotest/site/templates/index.html` 20. `app-autotest/site/static/style.css` 21. `development/dummycreate.har` (HAR-трассировка CREATE) 22. `DOCS/ARCHITECTURE-FULL.md` 23. `DOCS/sol-answers.md` 24. `DOCS/sol-scenario-editor.md` 25. `app-autotest/site/config/services_test.txt` 26. Любой YAML из `STANDS/dev/resources_yaml/` (например `1_dummy.yaml`, `115_mariadb.yaml`)