# polygon v0.5.18 Эмулятор REST API облачной платформы Nubes для интеграционных тестов app-autotest. Отдельный managed-сервис на Nubes pythonk8s: `polygon.pythonk8s.dev.nubes.ru`. Притворяется реальным Nubes API (префикс `/api/v1/svc`). ## Архитектура ``` polygon/ ├── requirements.txt # Flask>=3.0, gunicorn>=21.2, PyYAML>=6.0 ├── tests/ │ ├── test_converter.py # тесты from_stands.py │ ├── test_loader.py # тесты CWD-independent loader │ ├── test_state_machine.py # тесты state_machine.py │ └── test_route_regressions.py # локальные регрессионные тесты API └── site/ ├── app.py # Flask: только blueprint'ы + app.run() (57 строк) ├── routes/ # blueprint-файлы по доменам │ ├── root.py # /health, /, /swagger │ ├── services_routes.py# /api/v1/svc/services │ ├── instances_routes.py# /api/v1/svc/instances │ ├── operations_routes.py# /api/v1/svc/instanceOperations/* │ ├── run.py # /api/v1/svc/instanceOperations//run │ ├── mock_routes.py # /api/v1/svc/_mock/* │ └── openapi.py # /api/v1/svc/openapi.json ├── mock_state.py # MockState: instances, operations, op_params ├── state_machine.py # apply_effect() — мутация состояния ├── config/ │ └── loader.py # загрузка YAML + SERVICES/OPS_INDEX/DELAY/VERSION ├── utils/ │ ├── now.py # now() — UTC ISO с 'Z' │ └── pluralize.py # pluralize() — плюрализация ├── from_stands.py # конвертер STANDS YAML → polygon config ├── static/ │ └── style.css # тёмная тема ├── templates/ │ ├── index.html # главная и инструкции API │ └── swagger.html # Swagger UI и инструкция Authorize └── services/ # fallback YAML, также генерируются from_stands.py ``` ⛔ **Ровно 1 gunicorn-воркер.** Состояние в памяти, не shared. ## Запуск ```bash # Локально cd site && python app.py # порт 5000 # Тесты pytest tests/ -v # локальные тесты Polygon ``` ## Откуда берутся сервисы (YAML) Polygon эмулирует API Nubes. Чтобы знать какие сервисы, операции и параметры существуют — он читает YAML-конфиги. Но Nubes генерит YAML в **другом формате**, неудобном для полигона. Поэтому есть двухэтапный процесс: ``` ~/TF/tf_provider/generated/{dev,test,prod}/resources_yaml/*.yaml ← источник истины │ │ from_stands.py — конвертация формата, дедупликация, нормализация │ Запускается ВРУЧНУЮ при изменении терраформ-репы │ ▼ polygon/site/services/{dev,test,prod}/*.yaml ← скоммичены в git-репу полигона │ │ config/loader.py — загрузка ВСЕХ YAML в память при старте │ ▼ Память Flask-процесса (конфиги загружаются один раз при старте) ``` Если `POLYGON_STANDS` не задан, loader использует один стенд `default` из `site/services/`. Значения относительных путей в `POLYGON_STANDS` разрешаются относительно `site/`. ### Почему нельзя читать терраформ-формат напрямую? Терраформ-формат хранит параметры **внутри** операций, с дубликатами. Например параметр `resourceRealm` повторяется в create, modify и delete — три копии одного и того же. Полигону нужен плоский список **уникальных** параметров (`cfsParams`), плюс индекс «какой параметр к какой операции» (`cfsParamsByOp`). Плюс `stateParams` (дефолты для create), `stateOut` (subresource-ключи), раскодированные HTML-entities (`"` → `"`). Всё это делает `from_stands.py` **один раз**, а не на лету при каждом запросе. ### Когда запускать обновление? Когда терраформ-репа обновилась (добавили/удалили сервис, изменили параметры) — нужно перегенерить YAML и передеплоить полигон: ```bash cd site python from_stands.py ~/TF/tf_provider/generated/dev/resources_yaml services/dev python from_stands.py ~/TF/tf_provider/generated/test/resources_yaml services/test python from_stands.py ~/TF/tf_provider/generated/prod/resources_yaml services/prod cd .. git add services/ git commit -m "regenerate YAML from terraform (добавлен сервис X)" git push # → редеплоить polygon через Nubes UI ``` После успешной конвертации генератор удаляет из целевого каталога YAML, которых больше нет в источнике. Если конвертация завершилась ошибками, устаревшие файлы не удаляются. Если `SERVICES_DIR` не передан, генератор пишет в `site/services/` независимо от CWD. Явно переданные относительные `STANDS_DIR` и `SERVICES_DIR` остаются относительно CWD. ### Как проверить что всё загрузилось? ```bash curl https://polygon.pythonk8s.dev.nubes.ru/dev/api/v1/svc/services | python3 -m json.tool | head -20 ``` ## API | Метод | Путь | Назначение | |-------|------|------------| | GET | `/health` | Healthcheck | | GET | `/` | HTML с версией | | GET | `/swagger` | Swagger UI | | GET | `/api/v1/svc/openapi.json` | OpenAPI JSON | | GET | `/api/v1/svc/services` | Список сервисов | | GET | `/api/v1/svc/services/` | Операции сервиса | | GET | `/api/v1/svc/instances` | Пагинация | | GET | `/api/v1/svc/instances/` | Инстанс + state | | POST | `/api/v1/svc/instances` | Создать → 201 + Location | | GET | `/api/v1/svc/instanceOperations/default/` | Шаблон операции и cfsParams | | POST | `/api/v1/svc/instanceOperations` | Создать операцию → 201 + Location | | GET | `/api/v1/svc/instanceOperations/` | Статус + cfsParams | | POST | `/api/v1/svc/instanceOperationCfsParams` | param → value | | GET | `/api/v1/svc/instanceOperations//validate-cfs` | 200, пустое тело | | POST | `/api/v1/svc/instanceOperations//run` | Выполнить | | POST | `/api/v1/svc/_mock/reset` | Сброс | | GET | `/api/v1/svc/_mock/state` | Отладка: состояние | | GET | `/api/v1/svc/_mock/services` | Отладка: сервисы | | POST | `/api/v1/svc/_mock/delay/` | MOCK_OP_DELAY | | POST | `/api/v1/svc/_mock/fail-next` | Следующий run завершится ошибкой | Основные API-маршруты не требуют авторизации. Если задан `MOCK_AUTH_TOKEN`, запросы к `/_mock/*` должны передавать заголовок `X-Mock-Auth`. ### Где взять и куда вводить токен `MOCK_AUTH_TOKEN` задаёт администратор в переменных окружения запущенного сервиса Polygon. Приложение не генерирует и не показывает это значение. Если у вас нет доступа к настройкам сервиса, запросите токен у его администратора. В Swagger нажмите **Authorize**, в поле `X-Mock-Auth` → **Value** вставьте выданный токен и нажмите **Authorize** в диалоге. После этого Swagger автоматически добавит заголовок к запросам `/_mock/*`. В curl передавайте его явно: `-H "X-Mock-Auth: "`. Если `MOCK_AUTH_TOKEN` на сервере не задан, токен вводить не нужно. ## Переменные окружения | Переменная | По умолчанию | Описание | |------------|-------------|----------| | `POLYGON_STANDS` | `default:services` | Список стендов `name:path,name:path`; относительные пути считаются от `site/` | | `MOCK_OP_DELAY` | `0.1` | Задержка операции в секундах | | `MOCK_AUTH_TOKEN` | пусто | Опциональный токен для `/_mock/*`; при пустом значении локальные mock endpoints открыты | | `POLYGON_ENDPOINT` | `https://polygon.pythonk8s.dev.nubes.ru` | Базовый URL для серверов в OpenAPI | | `WEB_CONCURRENCY` | `1` | Использовать один gunicorn-воркер, поскольку состояние хранится в памяти | ## Деплой - Репозиторий: `https://gitea.services.ngcloud.ru/forcloud/polygon.git` - URL: `https://polygon.pythonk8s.dev.nubes.ru/` - Nubes pythonk8s managed service ## Связанные документы - [DEPLOYMENT_DIAGNOSIS.md](DEPLOYMENT_DIAGNOSIS.md) — разбор `201` при создании, NXDOMAIN и риска пути к YAML - [DOCS/polygon-plan.md](../DOCS/polygon-plan.md) — план реализации - [polygon-docs/sonnet-code-review-prompt.md](../polygon-docs/sonnet-code-review-prompt.md) — промпт code review - [polygon-docs/tf-provider-refs.md](../polygon-docs/tf-provider-refs.md) — ссылки на генератор YAML