Files
autotest/DOCS/architecture-final.md
T

230 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Архитектура 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.