Files
autotest/DOCS/architecture-final.md
T

11 KiB
Raw Blame History

Архитектура 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

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. Данные в памяти (состояние прогона)

# 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 через браузер (пока без авто-коммита — пользователь копирует и коммитит сам)

Фаза 2 (если нужна история)

  • Подключить Nubes PostgreSQL
  • Таблица test_runs + test_results
  • Страница истории

13. Переменные окружения (jsonEnv)

{
  "NUBES_API_TOKEN": "...",
  "NUBES_API_ENDPOINT": "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
}

Всё. test_org_uid — в config.yaml или auto-detect.