Files
autotest/DOCS/ARCHITECTURE.md

191 lines
9.3 KiB
Markdown
Raw Permalink 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 — текущее состояние
v1.0.93, 28.07.2026
---
## 1. Обзор
Flask-приложение (один HTML-файл, без SPA-фреймворка) для ручного тестирования операций
сервисов облачной платформы Nubes. Работает через REST API Nubes, автоопределяет стенд
(dev/test) по JWT-токену.
**Деплой**: Nubes pythonk8s (gunicorn, несколько воркеров).
**Кластер**: `iot-naeel` (K8s resourceRealm, DEV-стенд)
**URL**: `https://atest.pythonk8s.dev.nubes.ru`
**Репозиторий**: `https://gitea.services.ngcloud.ru/forcloud/app-autotest.git`
### Ключевые принципы
1. **Источник правды — Nubes API**. Все данные (инстансы, параметры, статусы) получаются
только из API. Никаких локальных копий или кеша (кроме краткосрочного трекера).
2. **Разделение CREATE и non-CREATE**. Это два принципиально разных потока.
3. **Multi-worker safety**. Все общие ресурсы (/tmp файлы) защищены `fcntl.flock`.
4. **Мелкие функции**. Каждое действие — отдельная функция.
---
## 2. Структура файлов
```
app-autotest/
├── site/ ← НЕ пакет (без __init__.py)
│ ├── app.py ← Flask(__name__), VERSION, blueprints, /health
│ ├── runner.py ← [LEGACY] Раннер тестов
│ ├── config.yaml ← [LEGACY] Конфиг раннера
│ ├── api/
│ │ └── http_client.py ← HttpClient, автостенд
│ ├── operations/
│ │ ├── get_services.py ← get_services(), get_service_detail()
│ │ ├── get_instances.py ← get_instances() (пагинация), get_organization()
│ │ ├── get_params.py ← get_params_with_current_values() — слияние state.params + шаблон
│ │ └── tracker.py ← /tmp/instances-{clientId}-{stand}.json + flock
│ ├── routes/
│ │ ├── main.py ← GET/POST /, /api/operations/<svc_id>
│ │ ├── api.py ← [LEGACY] /api/run, /api/status, /api/config
│ │ └── api_test.py ← /api/test, /api/params, /api/log
│ ├── templates/
│ │ └── index.html ← Весь UI (Jinja2 + ванильный JS)
│ └── static/
│ ├── style.css
│ ├── logo.svg
│ └── favicon.svg
└── secrets/
├── dev.token
└── test.token
```
---
## 3. Бэкенд: HTTP-клиент и автостенд
### `http_client.py`
- `HttpClient(endpoint, token)` — обёртка над `requests.Session`:
- Заголовки: `Authorization: Bearer`, `User-Agent: Mozilla/5.0` (DDoS-Guard)
- `get(path)``raise_for_status()``.json()`
- `post(path, data)` → проверка `r.ok`, парсинг JSON, извлечение `Location`
- `detect_endpoint(token)` — пробует dev→test стенды, возвращает рабочий URL
- `stand_name(endpoint)` — "dev"/"test" по URL
- `create_client(token, fallback)` — HttpClient + endpoint
### `get_instances.py`
- `get_instances(client)``GET /instances` с пагинацией (pageSize=200). Остановка по `len(batch) < pageSize`.
- `get_organization(client)` — первый инстанс с `serviceId == 19`
### `get_services.py`
- `get_services(client)``GET /services` → список
- `get_service_detail(client, svc_id)``GET /services/{id}` → детали + операции
### `get_params.py` — КЛЮЧЕВОЙ модуль
`get_params_with_current_values(client, op_id, instance_uid)`:
1. `GET /instances/{uid}``state.params` (текущие значения: `{"whereFail":"1",...}`)
2. `GET /instanceOperations/default/{opId}` → шаблон (коды, типы, valueList, dataDescriptor)
3. Слияние: `defaultValue = state.params["код"] ?? template.defaultValue`
4. Приведение типов: `bool``"true"/"false"`, `dict``json.dumps()`
### `tracker.py`
- Файл: `/tmp/instances-{clientId}-{stand}.json` (изолирован по пользователю и стенду)
- Блокировка: `fcntl.flock(LOCK_EX | LOCK_NB)` + retry 2s
- `add(client_id, stand, uid, svc_id, name)`, `remove(...)`, `list_all(...)`
- Нужен ТОЛЬКО как fallback — когда инстанс только создан и ещё не в `/instances`
---
## 4. Бэкенд: Роуты
### `main.py`
- **`GET/POST /`** — главная: организация, инфраструктура (serviceId 2,12,19,21,22,25,26,29,110,150), сервисы
- `action=save` → сохранить токен в cookie
- `action=clear` → удалить cookie
- **`GET /api/operations/<svc_id>`** — операции + autotest-инстансы:
- Cloud-first: фильтр по префиксу `autotest-`
- Tracker-fallback: статус "creating" для ещё невидимых
### `api_test.py` — основной модуль операций
- **`GET /api/params/<op_id>[?instanceUid=xxx]`**:
- Без `instanceUid` → шаблонные `defaultValue`
- С `instanceUid``get_params_with_current_values()`
- **`POST /api/test`** — запуск операции:
- **CREATE**: `POST /instances``instanceUid``POST /instanceOperations``opUid``tracker_add` → params → `/run`
- **non-CREATE**: `POST /instanceOperations` → params → `/run` (redeploy: сразу `/run`)
- `displayName` = `_get_instance_display_name()` (из API, не UUID!)
- Фоновый поток: `_finish_op()` — поллинг до `dtFinish` (300s таймаут)
- **`GET /api/test/status/<op_uid>`** — поллинг: in-memory `_op_results` → прямой API
- **`GET /api/log`** — последние 200 строк из `/tmp/app-autotest.log`
### Логирование
`_log(msg)` в `api_test.py`:
- stdout (gunicorn) + `/tmp/app-autotest.log` (flock, общий для воркеров)
- Ротация при 512 КБ
- UI: скрытая панель, кнопка `log` (правый нижний угол)
---
## 5. Фронтенд
### Структура
Один HTML-файл, три колонки:
- Левая (280px): инфраструктура
- Средняя (240px): сервисы
- Правая (flex): инстансы + параметры + кнопка + этапы
### Глобальное состояние (JS)
```
svcInstances — кеш инстансов (из /api/operations)
selectedInst — UID выбранного инстанса
selectedOp — {opId, opName, svcId}
pollTimer — таймер поллинга
SVC_ID = 1 — фиксированный сервис (Болванка)
```
### Потоки операций
**CREATE**: `startCreate()``showParams(18,'create')` → форма → `executeOp(pp)` → POST /api/test → поллинг → refreshInstances
**MODIFY**: `toggleInstance()` → кнопки → `runOp('modify',opId)``showParams(opId,'modify')` → GET /api/params?instanceUid= → форма с ТЕКУЩИМИ значениями → валидация map → `executeOp(pp)` → POST /api/test → поллинг
**Без параметров** (suspend/delete/resume/redeploy): `runOp()` → confirm → `executeOp({})` → POST /api/test → поллинг
### Валидация JSON
- `_esc(s)` — HTML-экранирование (`"``&quot;`, `&``&amp;`, `<``&lt;`)
- `validateJson(el, quiet)``JSON.parse()` на `onblur` (красная рамка + текст)
- Batch-проверка перед отправкой — ошибка → запрос не уходит
---
## 6. Nubes API — используемые endpoint'ы
| Метод | Путь | Назначение |
|-------|------|-----------|
| GET | `/instances?pageSize=200&page=N` | Все инстансы (пагинация) |
| GET | `/instances/{uid}` | Детали + state.params |
| GET | `/services` | Список сервисов |
| GET | `/services/{id}` | Детали + операции |
| GET | `/instanceOperations/default/{opId}` | Шаблон параметров |
| POST | `/instances` | Создать инстанс |
| POST | `/instanceOperations` | Создать операцию |
| POST | `/instanceOperationCfsParams` | Установить параметр |
| POST | `/instanceOperations/{opUid}/run` | Запустить операцию |
| GET | `/instanceOperations/{opUid}?fields=...` | Поллинг статуса |
---
## 7. Ограничения платформы
- `site/` — НЕ пакет (без `__init__.py`), импорты без префикса `site.`
- `app.run(host="0.0.0.0", port=5000)`
- Gunicorn multi-worker → общие данные через файлы + flock
- `/tmp/` теряется при редеплое
- User-Agent: `Mozilla/5.0` обязателен (DDoS-Guard)