From 5054c3aade18e2f9575480729e3f79eff6bbeda2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Thu, 23 Jul 2026 12:33:00 +0400 Subject: [PATCH] Final architecture doc for Sonnet review --- DOCS/architecture-final.md | 229 +++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 DOCS/architecture-final.md diff --git a/DOCS/architecture-final.md b/DOCS/architecture-final.md new file mode 100644 index 0000000..5a12664 --- /dev/null +++ b/DOCS/architecture-final.md @@ -0,0 +1,229 @@ +# Архитектура 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) + - Если create: + POST /instances {serviceId, displayName, params} + → сохраняем instance_uid из ответа + → поллинг: GET /instances, ищем UID, ждём explainedStatus = running + - Иначе: + POST /instanceOperations {instanceUid, operation, 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 + enabled: true + operations: + - name: create + enabled: true + params: + displayName: "autotest-pg" + # параметры из valueList/dataDescriptor + - name: modify + enabled: false + params: {} + - name: delete + enabled: true + params: {} + + - service_id: 19 + name: "Организация в Cloud Director" + 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 через браузер +- Сохранение → коммит в git + +### Фаза 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.