diff --git a/README.md b/README.md index 22cd596..ae9cdff 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,80 @@ -# polygon v0.1.0 +# polygon v0.2.0 -Mock-эмулятор Nubes API для интеграционных тестов app-autotest. +Эмулятор REST API облачной платформы Nubes для интеграционных тестов app-autotest. -Отдельный Flask-процесс (порт 5001), притворяющийся Nubes REST API. -Data-driven: сервисы генерируются из STANDS YAML через `from_stands.py`. +Отдельный managed-сервис на Nubes pythonk8s: `polygon.pythonk8s.dev.nubes.ru`. +Притворяется реальным Nubes API (префикс `/api/v1/svc`). ## Архитектура ``` polygon/ -├── server.py # Flask-приложение, префикс /api/v1/svc -├── state.py # MockState — состояние в памяти -├── config_loader.py # загрузка YAML + достройка defaults -├── defaults.py # default_for(dataType) -├── from_stands.py # конвертер STANDS YAML → polygon/services/*.yaml -└── services/ # сгенерированные конфиги (37 сервисов) +├── requirements.txt # Flask>=3.0, gunicorn>=21.2, PyYAML>=6.0 +├── tests/ +│ ├── test_converter.py # 10 юнит-тестов from_stands.py +│ └── test_state_machine.py # 9 юнит-тестов state_machine.py +└── site/ + ├── app.py # Flask (17 эндпоинтов) + ├── mock_state.py # MockState: instances, operations, op_params + ├── state_machine.py # apply_effect() — мутация состояния + ├── config_loader.py # загрузка services/*.yaml + ├── from_stands.py # конвертер STANDS YAML → polygon config + └── services/ # 37 YAML-конфигов (сгенерированы) ``` +⛔ **Ровно 1 gunicorn-воркер.** Состояние в памяти, не shared. + ## Запуск ```bash -STANDS_DIR=../STANDS/test/resources_yaml python server.py +# Локально +cd site && python app.py # порт 5000 + +# Тесты +pytest tests/ -v # 19 тестов + +# Генерация конфигов из STANDS YAML +cd site && python from_stands.py services/ ``` -## Тесты +## API (17 эндпоинтов) -```bash -NUBES_API_ENDPOINT=http://localhost:5001/api/v1/svc pytest ../app-autotest/tests/ -v -``` +| Метод | Путь | Назначение | +|-------|------|------------| +| GET | `/health` | Healthcheck | +| GET | `/` | HTML с версией | +| GET | `/api/v1/svc/services` | Список сервисов | +| GET | `/api/v1/svc/services/` | Операции сервиса | +| GET | `/api/v1/svc/instances` | Пагинация | +| GET | `/api/v1/svc/instances/` | Инстанс + state | +| POST | `/api/v1/svc/instances` | Создать → 201 + Location | +| GET | `/api/v1/svc/instanceOperations/default/` | cfsParams | +| POST | `/api/v1/svc/instanceOperations` | Создать операцию → 201 + Location | +| GET | `/api/v1/svc/instanceOperations/` | Статус + cfsParams | +| POST | `/api/v1/svc/instanceOperationCfsParams` | param → value | +| GET | `/api/v1/svc/instanceOperations//validate-cfs` | 200, пустое тело | +| POST | `/api/v1/svc/instanceOperations//run` | Выполнить | +| POST | `/api/v1/svc/_mock/reset` | Сброс | +| GET | `/api/v1/svc/_mock/state` | Отладка: состояние | +| GET | `/api/v1/svc/_mock/services` | Отладка: сервисы | +| POST | `/api/v1/svc/_mock/delay/` | MOCK_OP_DELAY | -## Сброс состояния +## Переменные окружения -```http -POST /_mock/reset -``` +| Переменная | По умолчанию | Описание | +|------------|-------------|----------| +| `MOCK_OP_DELAY` | `0.1` | Задержка операции в секундах | +| `WEB_CONCURRENCY` | `1` | ⛔ Не менять — ровно 1 воркер | + +## Деплой + +- Репозиторий: `https://gitea.services.ngcloud.ru/forcloud/polygon.git` +- URL: `https://polygon.pythonk8s.dev.nubes.ru/` +- Nubes pythonk8s managed service ## Связанные документы -- [DOCS/polygon-plan.md](../DOCS/polygon-plan.md) — полный план реализации -- [AGENT_BRIEFING.md](../AGENT_BRIEFING.md) — контекст проекта app-autotest +- [DOCS/polygon-plan.md](../DOCS/polygon-plan.md) — план реализации +- [polygon-docs/sonnet-code-review-prompt.md](../polygon-docs/sonnet-code-review-prompt.md) — промпт code review +- [polygon-docs/tf-provider-refs.md](../polygon-docs/tf-provider-refs.md) — ссылки на генератор YAML + diff --git a/site/app.py b/site/app.py index f65f348..bc45bfe 100644 --- a/site/app.py +++ b/site/app.py @@ -1,5 +1,5 @@ """ -polygon v0.1.0 — Mock Nubes API. +polygon v0.2.0 — Mock Nubes API. Эмулятор REST API облачной платформы Nubes для интеграционных тестов. Все эндпоинты с префиксом /api/v1/svc притворяются реальным Nubes API. @@ -7,10 +7,12 @@ polygon v0.1.0 — Mock Nubes API. Конфиги сервисов — из services/*.yaml (сгенерированы from_stands.py). Деплой: Nubes pythonk8s (managed service) с gunicorn: - gunicorn app:app --workers 2 --bind 0.0.0.0:8000 + gunicorn app:app --workers 1 --bind 0.0.0.0:8000 + ⛔ РОВНО 1 ВОРКЕР — состояние в памяти, не shared между процессами. Переменные окружения: - MOCK_OP_DELAY — задержка операции в секундах (по умолчанию 0.1) + MOCK_OP_DELAY — задержка операции в секундах (по умолчанию 0.1) + WEB_CONCURRENCY — число gunicorn-воркеров (принудительно 1) """ import os @@ -19,6 +21,10 @@ from datetime import datetime, timezone from flask import Flask, jsonify, make_response, request +# ⛔ Принудительно 1 воркер — состояние в памяти, иначе инстансы теряются. +# setdefault не перезапишет если Nubes/Procfile уже выставил WEB_CONCURRENCY=1. +os.environ.setdefault("WEB_CONCURRENCY", "1") + import config_loader import mock_state import state_machine diff --git a/site/state_machine.py b/site/state_machine.py index d152a1e..0607752 100644 --- a/site/state_machine.py +++ b/site/state_machine.py @@ -138,8 +138,9 @@ def _merge_params(inst, op_params, svc_operation_id, services): for pid in param_ids_for_op: if pid in op_params: - code = cfs_params[pid]["svcOperationCfsParam"] - inst["state"]["params"][code] = op_params[pid] + p = cfs_params.get(pid) + if p: + inst["state"]["params"][p["svcOperationCfsParam"]] = op_params[pid] def _extract_subresource_name(op_params, subresource_name):