Files
autotest/polygon-docs/sonnet-polygon-prompt.md
T

340 lines
17 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.
# Задача: спроектировать мок-полигон 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: <краткий заголовок>
<развёрнутый вопрос с контекстом>
```