18 KiB
Запрос на полный анализ 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 или envget_client()— HttpClient с автостендомget_client_id()— ClientID из JWTget_stand()— "dev"/"test"get_token_info()— {email, company, client_id}
site/routes/main.py — главная страница
GET/POST /— Jinja2-рендер: организация, инфраструктура, сервисы, форма токенаGET /api/operations/<svc_id>— 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/<op_id>[?instanceUid=xxx]— параметры операции (текущие или шаблон)POST /api/test— запуск операции (CREATE или non-CREATE)GET /api/test/status/<op_uid>— поллинг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/<int:run_id>— статус конкретного запускаGET /api/scenario/status— последние 10 запусков (legacy, должен уйти)
site/operations/scenario.py — ЯДРО СЦЕНАРИЕВ (290 строк)
run_scenario()— главный исполнитель:- service имя → svc_id (из config.yaml services)
- operation имя → svcOperationId (GET /services/{id})
- param коды → numeric IDs (GET /instanceOperations/default/{opId})
- create: новый инстанс; остальные: переиспользовать instance_map
- POST /instances → POST /instanceOperations → _send_params_terraform → run → poll
- Сохранить в 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=19get_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)
- POST /instances →
instance_uidиз Location-заголовка./UUID - POST /instanceOperations →
op_uidиз Location-заголовка - GET /instanceOperations/{op_uid}?fields=cfsParams → шаблон параметров
- _resolve_ref_svc → автоподстановка UUID для refSvcId-параметров
- POST /instanceOperationCfsParams → отправить пользовательские параметры
- POST /instanceOperationCfsParams → дослать неотправленные (с normalize)
- GET /instanceOperations/{op_uid}/validate-cfs → валидация
- POST /instanceOperations/{op_uid}/run → запуск
- Поллинг 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):
- Структура steps: JSONB, поле
resourceдля связи create→modify→delete одного инстанса - Параметры: выпадающие списки из живого API (сервис→операции→коды), кеш на время сессии
- Seed: marker
scenario_seed_v1=completed,INSERT ON CONFLICT DO NOTHING, отдельныйscenario_seed.yaml - UI: модальное окно, кнопки вверх/вниз, мягкое удаление (is_active=false)
- Валидация: при сохранении + при запуске, невалидный не запускать
- Критические дыры:
- 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. ЧТО НУЖНО ОТ АГЕНТА
Проанализировать и ответить:
-
Общая архитектура — оценить, найти слабые места, предложить улучшения
-
Дублирование кода —
_find_uidи_uid_from_locationесть и в api_test.py и в scenario.py. Вынести в общий модуль? Куда? -
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 при первом старте
-
UI редактора сценариев:
- Модальное окно: структура HTML
- JS-логика: загрузка списка, создание/редактирование/удаление
- Выпадающие списки: сервисы из GET /api/services, операции из GET /api/operations/{svcId}, параметры из GET /api/params/{opId}
- Валидация на фронте перед отправкой
-
Runner — нужно ли что-то менять в
run_scenario()? Он сейчас берёт шаги из config.yaml, должен из БД. Также нужно сохранять snapshot шагов в scenario_runs. -
Безопасность:
- CRUD сценариев: проверка client_id+stand
- Optimistic locking (version)
- Блокировка параллельных запусков (HTTP 409)
-
Что ещё критично упущено? — любые дыры, которые не покрыты текущим планом
-
Приоритетный порядок реализации:
- Что делать сначала, что потом
- Что можно выпустить в v1.1.49, что отложить
НЕ ДЕЛАТЬ:
- Не писать код (только анализ и план)
- Не трогать git
- Не менять файлы
8. ФАЙЛЫ ДЛЯ АНАЛИЗА (читать в этом порядке)
app-autotest/site/app.pyapp-autotest/site/api/http_client.pyapp-autotest/site/api/auth.pyapp-autotest/site/routes/main.pyapp-autotest/site/routes/api_test.py(самый важный)app-autotest/site/routes/api_scenario.pyapp-autotest/site/operations/scenario.pyapp-autotest/site/operations/get_params.pyapp-autotest/site/operations/get_services.pyapp-autotest/site/operations/get_instances.pyapp-autotest/site/operations/tracker.pyapp-autotest/site/operations/service_list.pyapp-autotest/site/db/pool.pyapp-autotest/site/db/init_db.pyapp-autotest/site/db/save_run.pyapp-autotest/site/runner.pyapp-autotest/site/config.yamlapp-autotest/site/static/app.jsapp-autotest/site/templates/index.htmlapp-autotest/site/static/style.cssdevelopment/dummycreate.har(HAR-трассировка CREATE)DOCS/ARCHITECTURE-FULL.mdDOCS/sol-answers.mdDOCS/sol-scenario-editor.mdapp-autotest/site/config/services_test.txt- Любой YAML из
STANDS/dev/resources_yaml/(например1_dummy.yaml,115_mariadb.yaml)