# Задача: спроектировать мок-полигон Nubes API > Адресат: Claude Sonnet 4.6 > Дата: 2026-07-31 > ⛔ ОТВЕТ — ТОЛЬКО В ЧАТ. Не редактировать файлы. Не создавать файлы. Только текст в чат. --- ## 1. Что такое polygon **Polygon** — отдельный managed-сервис на Nubes (`polygon.pythonk8s.dev.nubes.ru`), эмулирующий REST API облачной платформы Nubes. Нужен для интеграционных тестов приложения **app-autotest** — чтобы тесты гонялись не на реальном облаке, а на локальном/CI эмуляторе. **Ключевое:** polygon — это НЕ часть app-autotest. Это самостоятельный сервис со своим репозиторием (`https://gitea.services.ngcloud.ru/forcloud/polygon.git`), своим деплоем, своей версией (v0.1.0). ### Текущее состояние Сервис запущен на Nubes, код минимальный: ``` polygon/ ├── requirements.txt # Flask>=3.0, gunicorn>=21.2, PyYAML>=6.0 ├── README.md ├── .gitignore └── site/ └── app.py # 4 эндпоинта: /health, /, /api/v1/svc/instances, # /api/v1/svc/_mock/reset ``` `site/app.py` — стандартный Flask по инструкции Nubes: - `app = Flask(__name__, template_folder="templates", static_folder="static")` - `/health` → `"OK"` (healthcheck) - `/` → HTML-страница с версией - `app.run(debug=True, host="0.0.0.0", port=5000)` в `if __name__ == "__main__"` Gunicorn на проде: `gunicorn app:app --workers 2 --bind 0.0.0.0:8000`. ## 2. Исходные данные: STANDS YAML В `STANDS/test/resources_yaml/` лежат **37 YAML-файлов** — описания ВСЕХ сервисов Nubes в формате Terraform-провайдера. Это КАНОНИЧЕСКИЙ источник данных о сервисах: их параметры, операции, типы данных, дефолты, valueList'ы. ### Структура STANDS YAML (на примере dummy и postgres) ```yaml # 1_dummy.yaml (простой сервис, 339 строк) name: dummy service_id: 1 service_display_name: Болванка service_short_name: dummy lifecycle: suspend_on_destroy_default: true adopt_existing_on_create_default: false outputs: params: - code: state_params # map - code: state_out # map - code: vault_secrets # map, sensitive # ... ещё 5 outputs operations: - name: create id: 18 kind: instance action: create params: - id: 242 code: resourceRealm data_type: string required: true default: dummy value_list: [dummy] - id: 198 code: durationMs data_type: "integer >= 0" required: true default: "0" - id: 199 code: failAtStart data_type: boolean required: true default: "false" value_list: ["false", "true"] - id: 321 code: someMapParam data_type: map sub_params: - id: 322 code: subparam1 data_type: string - id: 323 code: secret data_type: string - name: modify id: 92 kind: instance action: modify params: [...] # те же параметры, is_modifiable: true - name: delete id: 71 ... ``` ```yaml # 90_postgres.yaml (самый сложный, 934 строки) operations: - name: create # id: 19, kind: instance - name: delete # id: 20 - name: modify # id: 40 - name: suspend # id: 41 - name: resume # id: 42 - name: restart # id: 43 ← лишняя? - name: recovery # id: 24 ← лишняя? - name: create_user # id: 44 ← subresource! - name: delete_user # id: 45 ← subresource! - name: create_database # id: 46 ← subresource! - name: delete_database # id: 47 ← subresource! # У create-операции — map-fixed с sub_params: - name: create params: - id: 788 code: clusterConfiguration data_type: map-fixed sub_params: - id: 106 code: replicas data_type: "integer > 0" default: "1" value_list: ["1", "3", "5", "7"] - id: 107 code: disk data_type: "integer > 0" default: "10" ``` ### Спектр сложности (37 сервисов) | Тип | Примеры | Особенности | |-----|---------|-------------| | Простые | dummy, s3, http, gitea, nodejs, flask | Плоские параметры, 6 операций | | Средние | redis, mongodb, rabbitmq, clickhouse, kafka | map-fixed (cluster/access/startup) | | Сложные | postgres, mariadb | + subresources (create_user, create_database) | | Инфраструктурные | vc_org, vc_vdc, vc_nsxt, vapp, vc_vm_v2/v3 | Другие kind'ы, refSvcId на другие сервисы | | Особые | s3bucket (refSvcId на s3), template, k8s_velero, llm_ai | Нестандартные параметры | Все 37 файлов лежат здесь: `STANDS/test/resources_yaml/` Полный список: `1_dummy.yaml`, `2_template.yaml`, `12_s3.yaml`, `13_s3bucket.yaml`, `19_vc_org.yaml`, `21_vc_vdc.yaml`, `22_vc_nsxt.yaml`, `25_vcexternalip.yaml`, `26_vapp.yaml`, `27_vc_vm_v2.yaml`, `28_vc_vm_v3.yaml`, `29_vc_vdc_group.yaml`, `50_nextcloud.yaml`, `81_superset.yaml`, `82_harbor.yaml`, `86_k8s_velero.yaml`, `89_flask.yaml`, `90_postgres.yaml`, `91_redis.yaml`, `92_mongodb.yaml`, `93_rabbitmq.yaml`, `94_lucee.yaml`, `95_nodejs.yaml`, `96_pgadmin.yaml`, `98_http.yaml`, `99_gitea.yaml`, `109_zones_v2.yaml`, `111_dnsrecord.yaml`, `115_mariadb.yaml`, `116_kafka.yaml`, `119_akhq.yaml`, `120_clickhouse.yaml`, `148_vc_mgmt_sthutrval_cluster.yaml`, `149_valo_tenant.yaml`, `150_k8s_sthutrval_cluster.yaml`, `151_k8s_openbao.yaml`, `163_llm_ai.yaml`. ## 3. Архитектурные решения (уже приняты) Предыдущий архитектор (Опус) принял 10 решений. Их нужно УЧЕСТЬ, но можно ОСПОРИТЬ если найдёшь лучший подход. | # | Решение | Обоснование | |---|---------|-------------| | 1 | Отдельный процесс :5000 | `http_client` делает реальные HTTP-запросы — blueprint не проверит | | 2 | Data-driven: сервисы из YAML | Не хардкодить 37 сервисов в Python | | 3 | Мин. поля + `default_for(dataType)` | 20+ параметров вручную — ад | | 4 | Ленивый dtFinish | Без потоков, `MOCK_OP_DELAY` (по умолчанию 0.1с) | | 5 | Единая стейт-машина | create→running→suspended→deleted | | 6 | Реальный мерж params при modify | Иначе тест modify→проверить state.params бессмысленен | | 7 | Статический stateOut из YAML | Для MVP, генерация из параметров — потом | | 8 | `/_mock/reset` для тестов | Без сброса тесты влияют друг на друга | | 9 | Тесты через `app_client` | Проверяет реальную связку app-autotest ↔ эмулятор | | 10 | refSvcId игнорируем в MVP | validate-cfs всегда OK, ссылки не проверяются | ## 4. Критические точки интеграции (из кода app-autotest) Это НЕ предположения — это факты из реального кода, который будет ходить в polygon: ### 4.1 Location-заголовки обязательны `HttpClient.post()` достаёт UUID из заголовка `Location` (последний сегмент, `len >= 32`). Polygon ОБЯЗАН отдавать: - `POST /api/v1/svc/instances` → `201` + `Location: ./{instanceUid}` - `POST /api/v1/svc/instanceOperations` → `201` + `Location: ./{instanceOperationUid}` Без Location executor не получит UUID → ошибка. ### 4.2 Поллинг `poll_until_done()` делает `GET /instanceOperations/{uid}?fields=...`, ждёт `dtFinish`, спит 5 секунд между попытками. При `MOCK_OP_DELAY=0.1` dtFinish появится на первом же GET — вторая итерация со сном не случится. ### 4.3 Short-circuit localhost в auth.py `detect_endpoint()` в app-autotest хардкодит dev/test стенды и игнорирует `NUBES_API_ENDPOINT`. Нужно добавить проверку: если endpoint начинается с `http://localhost` или `http://127.0.0.1` → пропустить detect, сразу использовать. Это будет сделано в app-autotest, не в polygon. ### 4.4 validate-cfs = пустое тело `send_params_terraform()` считает успехом пустой/не-JSON ответ. Polygon отдаёт `200` с пустым телом. ### 4.5 state.params по коду, cfsParams по числовому id `get_params_with_current_values()` мержит `state.params[код]` с шаблоном из `GET /instanceOperations/default/{opId}`. YAML должен связывать числовой `svcOperationCfsParamId` ↔ код параметра. ### 4.6 Nubes-совместимость Polygon деплоится как managed-сервис Nubes (pythonk8s). Следовательно: - `site/app.py` — точка входа (платформа ждёт `python site/app.py`) - `app.run(host="0.0.0.0", port=5000)` — обязательно - ⛔ `site/__init__.py` — НЕЛЬЗЯ (конфликтует со stdlib `site.py`) - ⛔ `from site.xxx import ...` — НЕЛЬЗЯ (импорт без префикса) - `/health` → `"OK"` — healthcheck для Nubes ## 5. Что нужно от тебя ### 5.1 Архитектура проекта Полная архитектура polygon с обоснованием каждого решения. Включая: - **Структура файлов** в `site/` — какие модули, за что отвечают - **Схема данных в памяти** — MockState: instances, operations, связи - **Стейт-машина инстанса** — все статусы, переходы - **Поток запроса** — что происходит от POST /instances до GET /instanceOperations/{uid} - **Схема API** — полный список эндпоинтов с форматами запросов/ответов ### 5.2 Универсальный конвертер STANDS → polygon Главный вопрос: **можно ли написать ОДИН конвертер для всех 37 сервисов без сервис-специфичного кода?** Если да — спроектировать `from_stands.py`: - Алгоритм конвертации одного YAML - Маппинг ВСЕХ полей STANDS → polygon (таблица) - Обработка краевых случаев (map-fixed, sub_params, subresources) - Генерация stateParams из create-операции (defaults) - Генерация stateOut (из операций create_user/create_database и т.п.) - Выходной формат: отдельные файлы в `services/` или один dict? Если нет — объяснить ГДЕ проходит граница и что придётся делать вручную. ### 5.3 Подробный план реализации Пошаговый план с разбивкой на этапы. Для каждого этапа: - Какие файлы создать/изменить - Что именно реализовать (функции, классы, эндпоинты) - Критерий готовности этапа - Ожидаемые сложности ## 6. Конкретные вопросы Ответь на каждый: ### Q1. from_stands.py Как обрабатывать `map-fixed` параметры (clusterConfiguration, startupConfiguration)? В STANDS: `params[].sub_params[]`. В реальном API: `dataDescriptor`. Должен ли конвертер генерировать `dataDescriptor` из `sub_params`? Или моку достаточно знать структуру чтобы принимать такие параметры при create? ### Q2. subresources У postgres/mariadb есть `create_user`, `delete_user`, `create_database`, `delete_database`. Можно ли вывести **общее правило**: «если операция имеет kind ≠ instance — это subresource»? Или «если действие create/delete а операция не create/delete инстанса — subresource»? Как универсально эмулировать subresource-операции? Сейчас идея: `apply_effect` смотрит на `action` и `kind` — если `kind != "instance"`, значит меняем `state.out`, не `state.params`. ### Q3. stateOut В реальном API после create PostgreSQL: `state.out = {users: {pgadmin: {}}, databases: {mydb: {}}}`. Может ли конвертер автоматически вывести структуру stateOut из наличия subresource-операций? Например: есть `create_user` → добавить `users: {}` в stateOut. ### Q4. Лишние операции У postgres есть restart, recovery. У некоторых сервисов есть специфичные операции. Включать их в мок? Если да — как эмулировать? Если нет — что отвечать на запрос `GET /instanceOperations/default/{id}` для несуществующей операции? ### Q5. valueList В STANDS YAML `value_list` есть у многих параметров. Нужно ли моку хранить их и отдавать в `GET /instanceOperations/default/{id}` (cfsParams)? Или мок может отдавать пустой `valueList` для всех? ### Q6. Config loader vs генерация Два подхода: - **A)** `from_stands.py` генерит YAML в `services/`, `config_loader.py` их читает при старте - **B)** `from_stands.py` сразу возвращает dict, без промежуточных YAML Какой лучше? Плюсы A: можно редактировать сгенерированные YAML руками для сложных сервисов. Плюсы B: проще, меньше файлов. ### Q7. mock-специфичные эндпоинты `/_mock/reset` — сброс состояния. Нужны ли ещё? - `/_mock/state` — посмотреть текущее состояние (для отладки)? - `/_mock/delay/{seconds}` — изменить MOCK_OP_DELAY на лету? - `/_mock/services` — список загруженных сервисов? ### Q8. Идентификаторы UUID для instanceUid и instanceOperationUid — генерить через `uuid.uuid4()`? Или детерминированно (например, `uuid5(namespace, service_id + counter)`)? ### Q9. Пагинация `GET /instances?pageSize=N&page=P` — как правильно эмулировать? Стоп по `len(batch) < pageSize`? Что если pageSize > 200? ### Q10. Тестирование 5 сценариев из плана Опуса — достаточны? Какие ещё сценарии добавить? Как организовать тесты чтобы они не зависели друг от друга? ## 7. Что можно спрашивать у меня Ты можешь задавать мне уточняющие вопросы в текстовом виде. Я отвечу. Что можно спрашивать: - Уточнения по STANDS YAML (структура конкретных сервисов, поля которые не видел) - Детали работы app-autotest (какие запросы шлёт, какие форматы ждёт) - Приоритеты (что важнее сделать сначала) - Ограничения Nubes-платформы Что НЕ надо спрашивать: - «Какие технологии использовать?» — только Flask, Python, PyYAML - «Может использовать БД?» — нет, всё в памяти - «Может Kubernetes API?» — нет, это чистый HTTP-эмулятор Формат вопросов: ``` ### Вопрос 1: <краткий заголовок> <развёрнутый вопрос с контекстом> ```