doc: README для DOCS/HISTORY, polygon-docs/, +.venv в gitignore

This commit is contained in:
2026-07-31 20:39:48 +04:00
parent 633537a775
commit 6978d13d0e
7 changed files with 850 additions and 0 deletions
+339
View File
@@ -0,0 +1,339 @@
# Задача: спроектировать мок-полигон 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: <краткий заголовок>
<развёрнутый вопрос с контекстом>
```