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

17 KiB
Raw Blame History

Задача: спроектировать мок-полигон 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)

# 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
    ...
# 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/instances201 + Location: ./{instanceUid}
  • POST /api/v1/svc/instanceOperations201 + 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: <краткий заголовок>
<развёрнутый вопрос с контекстом>