Final architecture doc for Sonnet review

This commit is contained in:
2026-07-23 12:33:00 +04:00
parent c8ef5ead56
commit 5054c3aade
+229
View File
@@ -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/<svc_id>` | Операции сервиса (уже есть) |
| 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.