# ⚠️ LEGACY — НЕАКТУАЛЬНО. Исторический документ. # Архитектура app-autotest — финальный документ ## 1. Цель проекта Автотесты операций сервисов облачной платформы Nubes. Веб-интерфейс с запуском тестов прямо на платформе. ## 2. Текущее состояние - Flask-приложение задепоено на Nubes (pythonk8s) в тестовом стенде - Репозиторий: `https://gitea.services.ngcloud.ru/forcloud/app-autotest.git` - Сервис: `https://a-test.pythonk8s.dev.nubes.ru/` (instanceUid: `01a2d5b2-...`, кластер `iot-naeel`) - Сейчас умеет: вход по токену, показ организации, список инстансов, таблица сервисов с операциями ## 3. Ключевые ограничения платформы - **Структура:** `site/app.py` — точка входа. Платформа запускает `python site/app.py` - **`site/` — НЕ пакет.** Без `__init__.py`. Конфликт с stdlib `site.py` - **Импорты:** без префикса `site.`. `from api.client import ...` работает (Python добавляет директорию скрипта в sys.path[0]) - **`app.run(host="0.0.0.0", port=5000)`** — обязательно, иначе контейнер падает - **`User-Agent: Mozilla/5.0`** — обязательно для API (DDoS-Guard) - **Нет persistent volume.** Данные теряются при рестарте/редеплое - **Gunicorn:** запускается платформой. Вероятно 1 worker. ## 4. Принятые архитектурные решения | Решение | Обоснование | |---------|-------------| | **Нет БД** на MVP | Нет PV. Конфиг в YAML-файле, результаты в памяти | | **Config в git** | `site/config.yaml`. Менять → коммит → редеплой | | **Pipeline последовательный** | Безопаснее. Один сервис за другим, операции по порядку | | **threading.Thread** | Достаточно для 1-2 пользователей. 1 gunicorn worker | | **Поллинг статуса** | GET /instances каждые 10s, таймаут 5 мин на операцию | | **Результаты в памяти** | dict в модуле. Видны пока под живёт | | **API для данных сервисов** | GET /services, /instanceOperations/default/{id} — не нужны YAML-файлы | | **JSON textarea для параметров** | Пользователи — разработчики, понимают JSON | ## 5. Структура файлов ``` app-autotest/ ├── requirements.txt # Flask>=3.0, gunicorn>=21.2, requests>=2.31, PyYAML ├── site/ │ ├── app.py # Точка входа: Flask(__name__), blueprint, app.run() │ ├── config.yaml # Конфигурация тестов (см. раздел 7) │ ├── api/ │ │ └── http_client.py # HttpClient: get(), post(), Bearer, User-Agent │ ├── operations/ │ │ ├── get_services.py # get_services(), get_service_detail() │ │ ├── get_instances.py # get_instances(), get_organization() │ │ └── get_params.py # get_operation_params(svcOperationId) │ ├── runner.py # Pipeline: читает config → выполняет → результаты в memory │ ├── routes/ │ │ ├── main.py # GET/POST / — главная страница + токен │ │ └── api.py # /api/run (запуск), /api/status (статус прогона) │ ├── static/ │ │ ├── favicon.svg │ │ ├── logo.svg │ │ └── style.css │ └── templates/ │ └── index.html # Токен, организация, инстансы, сервисы, кнопка «Запустить», таблица результатов ``` ## 6. Pipeline (runner.py) ### Алгоритм ``` 1. Читаем site/config.yaml 2. test_org_uid берём из config или определяем через API (get_organization) 3. Для каждого сервиса в config.services: a. Если service.enabled = false → пропускаем b. instance_uid = None c. Для каждой операции в service.operations (в порядке: create, modify, suspend, resume, delete): - Если operation.enabled = false → статус SKIP - Определяем svcOperationId через API: get_service_detail(service_id) → ищем operation по имени → svcOperationId - Если create: POST /instances {serviceId, displayName, cfsParams} → сохраняем instance_uid из ответа → поллинг: GET /instances, ищем UID, ждём explainedStatus = running - Иначе: POST /instanceOperations {instanceUid, svcOperationId, cfsParams} → поллинг: ждём operationIsInProgress = false - Результат: OK / FAIL / TIMEOUT → пишем в memory - Если create упал → прерываем этот сервис, переходим к следующему 4. Все результаты — в глобальный dict last_run ``` ### Поллинг - Интервал: 10 секунд - Таймаут: 5 минут (300 секунд) - Проверка: GET /instances → ищем instanceUid → explainedStatus - Если таймаут → статус TIMEOUT ## 7. Формат config.yaml ```yaml test_org_uid: "87a34374-7dc6-488c-afde-1246bf754f6d" # или auto — найти через API services: - service_id: 90 name: PostgreSQL display_name: "autotest-pg" # для create enabled: true operations: - name: create enabled: true params: # cfsParams для POST /instances (без displayName — оно отдельно) - name: modify enabled: false params: {} - name: delete enabled: true params: {} - service_id: 19 name: "Организация в Cloud Director" display_name: "" enabled: false # нельзя тестировать на проде operations: - name: create enabled: false params: {} ``` `test_org_uid: "auto"` — найти организацию через API автоматически. ## 8. Данные в памяти (состояние прогона) ```python # runner.py — глобальное состояние last_run = { "run_id": "2026-07-23-140000", "status": "running", # running | done | error "started_at": "2026-07-23T14:00:00", "finished_at": None, "results": [ { "service_id": 90, "service_name": "PostgreSQL", "operation": "create", "status": "OK", # OK | FAIL | TIMEOUT | SKIP | RUNNING "instance_uid": "xxx-xxx", "error": None, "duration_sec": 45.2, }, ... ] } ``` ## 9. UI (index.html) Одна страница, три секции: ``` ┌──────────────────────────────────────────────────────┐ │ [token input] [OK] [Вых] │ ├──────────────────┬───────────────────────────────────┤ │ ОРГАНИЗАЦИЯ │ СЕРВИСЫ (свёрнуты) │ │ WZ03709-saas │ ┌─────────────────────────────┐ │ │ uid: 87a... │ │ # │ Сервис │ Опер │ │ │ status: running │ │ 90 │ PostgreSQL │ 5 │ │ │ │ │ 91 │ Redis │ 3 │ │ │ ИНСТАНСЫ (40) │ │ ... │ │ ┌──────────────┐ │ └─────────────────────────────┘ │ │ │ name │ svc │ │ │ │ │ ... │ ... │ │ [ЗАПУСТИТЬ ТЕСТЫ] │ │ └──────────────┘ │ │ │ │ РЕЗУЛЬТАТЫ │ │ │ ┌─────────────────────────────┐ │ │ │ │ Сервис │ Операция│Статус │ │ │ │ │ PostgreSQL│ create │ OK │ │ │ │ │ Redis │ create │ FAIL │ │ │ │ └─────────────────────────────┘ │ └──────────────────┴───────────────────────────────────┘ ``` Кнопка «Запустить тесты» → POST /api/run → запускает runner.py в threading.Thread → JS поллинг GET /api/status каждые 3 секунды → обновляет таблицу результатов. ## 10. API-роуты | Метод | Путь | Назначение | |-------|------|------------| | GET/POST | `/` | Главная страница + вход по токену | | GET | `/api/operations/` | Операции сервиса (уже есть) | | POST | `/api/run` | Запустить прогон тестов | | GET | `/api/status` | Текущий статус прогона (JSON last_run) | ## 11. Что НЕ делаем в MVP - ❌ База данных (SQLite/PostgreSQL) - ❌ UI конфигурации (правим config.yaml в git) - ❌ История прогонов (только текущий) - ❌ Мобильная версия - ❌ Авторизация пользователей - ❌ Параллельный запуск тестов - ❌ Drag-and-drop, динамические формы параметров - ❌ WebSocket ## 12. Фазы разработки ### Фаза 0 — MVP (сейчас) - `config.yaml` в репо - `runner.py` с pipeline - Кнопка «Запустить» + таблица результатов - Всё в памяти, без БД ### Фаза 1 (после успешных тестов) - UI редактирования config.yaml через браузер (пока без авто-коммита — пользователь копирует и коммитит сам) ### Фаза 2 (если нужна история) - Подключить Nubes PostgreSQL - Таблица test_runs + test_results - Страница истории ## 13. Переменные окружения (jsonEnv) ```json { "NUBES_API_TOKEN": "...", "NUBES_API_ENDPOINT": "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc" } ``` Всё. `test_org_uid` — в `config.yaml` или auto-detect.