9.9 KiB
Задача: Code Review polygon v0.2.0
Адресат: Claude Sonnet 4.6 (новый чат, с нуля) Дата: 2026-07-31 ⛔ ОТВЕТ — ТОЛЬКО В ЧАТ. Не редактировать файлы. Не создавать файлы.
1. Что такое polygon
Polygon — эмулятор REST API облачной платформы Nubes. Отдельный managed-сервис
(polygon.pythonk8s.dev.nubes.ru), притворяется реальным Nubes API для интеграционных
тестов приложения app-autotest.
- Flask 3.0 + gunicorn, деплой на Nubes pythonk8s
- Все данные в памяти (MockState), без БД
- Data-driven: конфиги сервисов генерируются из STANDS YAML через
from_stands.py - 37 сервисов (dummy, postgres, redis, kafka, flask, ...)
- 17 API-эндпоинтов, префикс
/api/v1/svc - 19 юнит-тестов (все PASS)
Репозиторий: https://gitea.services.ngcloud.ru/forcloud/polygon.git (ветка master)
Деплой: https://polygon.pythonk8s.dev.nubes.ru/
Версия: v0.2.0
2. Структура кода
polygon/
├── requirements.txt # Flask>=3.0, gunicorn>=21.2, PyYAML>=6.0
├── .gitignore
├── tests/
│ ├── test_converter.py # 10 юнит-тестов from_stands.py
│ └── test_state_machine.py # 9 юнит-тестов state_machine.py + mock_state
└── site/
├── app.py # Flask-приложение (17 эндпоинтов)
├── mock_state.py # MockState: instances, operations, op_params в памяти
├── state_machine.py # apply_effect() — мутация состояния по kind/action
├── config_loader.py # загрузка services/*.yaml → {svcId: def} + {opId: def}
├── from_stands.py # конвертер STANDS YAML → polygon config
└── services/ # 37 сгенерированных YAML-конфигов
3. Что делает каждый модуль
app.py — 17 эндпоинтов
Полный эмулятор Nubes API. Порядок маршрутов критичен: default/<int> ДО <uid>.
| # | Метод | Путь | Назначение |
|---|---|---|---|
| 1 | GET | /health |
"OK" (для Nubes healthcheck) |
| 2 | GET | / |
HTML с версией, счётчиками |
| 3 | GET | /api/v1/svc/services |
список сервисов |
| 4 | GET | /api/v1/svc/services/<id> |
операции сервиса |
| 5 | GET | /api/v1/svc/instances |
пагинация |
| 6 | GET | /api/v1/svc/instances/<uid> |
инстанс + state.params + state.out |
| 7 | POST | /api/v1/svc/instances |
создать shell → 201 + Location: ./{uid} |
| 8 | GET | /api/v1/svc/instanceOperations/default/<id> |
cfsParams с dataDescriptor, valueList |
| 9 | POST | /api/v1/svc/instanceOperations |
создать операцию → 201 + Location |
| 10 | GET | /api/v1/svc/instanceOperations/<uid> |
статус + cfsParams (?fields=...) |
| 11 | POST | /api/v1/svc/instanceOperationCfsParams |
paramId → value |
| 12 | GET | /api/v1/svc/instanceOperations/<uid>/validate-cfs |
пустое тело, 200 |
| 13 | POST | /api/v1/svc/instanceOperations/<uid>/run |
sleep → apply_effect → dtFinish |
| 14 | POST | /api/v1/svc/_mock/reset |
сброс |
| 15 | GET | /api/v1/svc/_mock/state |
отладка |
| 16 | GET | /api/v1/svc/_mock/services |
отладка |
| 17 | POST | /api/v1/svc/_mock/delay/<s> |
MOCK_OP_DELAY |
Критические точки:
POST /instancesиPOST /instanceOperationsобязаны отдаватьLocation: ./{uuid}— app-autotest достаёт UUID из заголовкаPOST /instanceOperationsприoperation=="create"— тело НЕ содержитsvcOperationId, polygon ищет самvalidate-cfs:return "", 200(НЕjsonify) — app-autotest ждёт пустое телоrun: синхронныйtime.sleep(MOCK_OP_DELAY)+apply_effect+dtFinish = nowMOCK_OP_DELAYиз env, по умолчанию 0.1с
mock_state.py — состояние в памяти
class MockState:
instances = {} # instanceUid → {instanceUid, serviceId, displayName, status, state: {params, out}, ...}
operations = {} # opUid → {instanceOperationUid, instanceUid, svcOperationId, operation, kind, action, dtStart, dtFinish, isSuccessful, ...}
op_params = {} # opUid → {paramId(int): paramValue(str)}
Методы: create_instance, get_instance, list_instances (пагинация), create_operation,
get_operation, set_param, get_params, reset.
UUID через uuid.uuid4(). Пагинация: pageSize ≤ 200, стоп по len(batch) < pageSize.
state_machine.py — apply_effect
Мутирует MockState после завершения операции:
| kind | action | Эффект |
|---|---|---|
| instance | create | статус running, stateParams из шаблона, stateOut из шаблона |
| instance | modify | мерж op_params в state.params через cfsParamsByOp |
| instance | delete | удалить инстанс |
| instance | suspend | статус suspended |
| instance | resume | статус running |
| instance | redeploy | статус running |
| instance | restart/recovery/... | no-op |
| subresource | create | state.out[plural][name] = {} |
| subresource | delete | del state.out[plural][name] |
_extract_subresource_name: ищет первое непустое строковое значение в op_params, fallback на subresource_name.
config_loader.py — загрузка конфигов
Читает все .yaml из services/. Возвращает:
services:{service_id: service_def}ops_index:{svcOperationId: service_def}— для поиска сервиса по ID операции
from_stands.py — конвертер
Конвертирует STANDS YAML (из Terraform-провайдера) → polygon-конфиг.
Запуск: python from_stands.py <STANDS_DIR> [SERVICES_DIR]
html.unescape()для всех строк (>→>,"→")- Маппинг полей:
id→svcOperationCfsParamId,code→svcOperationCfsParam,data_type→dataType,value_list→valueList,sub_params→dataDescriptor dataDescriptorгенерируется для всех типов сsub_params(map, map-fixed, array-map-fixed)state_out_template: авто-генерация из операций сkind=subresource, action=createstateParams: defaults из create-операции, JSON-генерация для map-fixedcfsParamsByOp: связка opId → список paramId
4. Что проверять (фокус code review)
Безопасность
- Все ли входные данные валидируются (JSON body, query params)?
- Есть ли возможность инъекции через
displayName,paramValue? _mock/*эндпоинты — не утечка ли это на проде?
Корректность API
- Совпадают ли форматы ответов с реальным Nubes API?
- Правильно ли обрабатываются краевые случаи: отсутствующий сервис, невалидный instanceUid, пустой body?
- Корректны ли статус-коды (201, 400, 404)?
Location-заголовки правильного формата?
Состояние и стейт-машина
- Нет ли гонок в MockState (хотя воркер один, но Flask debug mode reloads)?
- Все ли переходы стейт-машины корректны?
- Не теряются ли данные при modify/reset?
- Правильно ли работает пагинация при пустом/частичном списке?
Конвертер
- Все ли краевые случаи STANDS YAML обрабатываются (пустые поля, отсутствующие sub_params)?
- Правильно ли
html.unescapeприменяется ко всем строковым полям? - Не падает ли на нестандартных YAML (template, s3bucket)?
Код и архитектура
- Нет ли дублирования логики?
- Понятны ли имена функций/переменных?
- Нет ли мёртвого кода?
- Правильно ли обрабатываются ошибки (try/except где нужно)?
Nubes-совместимость
site/__init__.pyотсутствует?app.run(host="0.0.0.0", port=5000)на месте?from site.xxxнигде нет?/healthвозвращает"OK"?
5. Формат ответа
Сгруппируй находки по категориям:
- 🔴 Критические (сломает работу)
- 🟡 Средние (потенциальная проблема)
- 🔵 Минорные (стиль, имена)
Для каждой находки: файл, строка (примерная), проблема, предлагаемое исправление.
Если код в порядке — скажи что всё ок по каждой категории.
6. Что можно спрашивать у меня
Можешь задавать уточняющие вопросы. Например:
- «Какой формат у real Nubes API для эндпоинта X?»
- «Почему сделано так, а не иначе?»
- «Какие именно поля ждёт app-autotest в ответе Y?»
Формат вопросов:
### Вопрос N: <краткий заголовок>
<развёрнутый вопрос>