Files
polygon/site/routes/instances_routes.py
T
naeel 58d54f2a95 v0.5.0: 3 стенда — изоляция состояния, WSGI middleware, OpenAPI server selector, POLYGON_STANDS env
- loader.py: STANDS dict, get_services/get_ops_index per stand, POLYGON_STANDS env
- mock_state.py: get_state(stand_id) — ленивая инициализация
- app.py: StandMiddleware — перезапись PATH_INFO до Flask-роутинга
- Все роуты: LocalProxy(state), LocalProxy(SERVICES/OPS_INDEX) через g.stand_id
- openapi.py: _build_servers() — по одному серверу на стенд
- services/stand{1,2,3}/ — 37 YAML на стенд из STANDS/dev и STANDS/test
- tests/test_api.py: +4 multi-stand теста (изоляция, fail-next, OpenAPI servers)
- Обратная совместимость: /api/v1/svc/... → стенд по умолчанию
2026-08-01 20:10:19 +04:00

94 lines
3.8 KiB
Python
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.
"""
routes/instances_routes.py — эндпоинты инстансов.
Blueprint "instances" с префиксом /api/v1/svc/instances.
GET /instances — список с пагинацией
GET /instances/<uid> — полные данные инстанса (state.params + state.out)
POST /instances — создать shell инстанса → 201 + Location: ./{uid}
КРИТИЧНО: POST /instances ОБЯЗАН возвращать заголовок Location.
HttpClient.post() в app-autotest достаёт instanceUid ИМЕННО из Location.
"""
from flask import Blueprint, g, jsonify, make_response, request
from werkzeug.local import LocalProxy
import mock_state
import config.loader as _cfg
state = LocalProxy(lambda: mock_state.get_state(g.stand_id))
SERVICES = LocalProxy(lambda: _cfg.get_services(g.stand_id))
bp = Blueprint("instances", __name__, url_prefix="/api/v1/svc/instances")
@bp.route("", methods=["GET"])
@bp.route("/", methods=["GET"])
def list_instances():
"""GET /api/v1/svc/instances — список инстансов с пагинацией.
Query-параметры:
pageSize — размер страницы (по умолчанию 200, максимум 200)
page — номер страницы (по умолчанию 1)
Возвращает {results, pageSize, page, total}.
Пагинация: стоп по len(batch) < pageSize (как в реальном API).
"""
page_size = request.args.get("pageSize", 200, type=int)
page = request.args.get("page", 1, type=int)
result = state.list_instances(page_size=page_size, page=page)
return jsonify(result)
@bp.route("/<uid>", methods=["GET"])
def get_instance(uid):
"""GET /api/v1/svc/instances/{uid} — полные данные инстанса.
Возвращает {instance: {instanceUid, serviceId, displayName, status,
explainedStatus, svc, dtCreate, state: {params: {...}, out: {...}}}}.
Если инстанс не найден — 404 с JSON-ошибкой.
"""
inst = state.get_instance(uid)
if not inst:
return jsonify({"error": "instance not found"}), 404
return jsonify({"instance": dict(inst)})
@bp.route("", methods=["POST"])
@bp.route("/", methods=["POST"])
def create_instance():
"""POST /api/v1/svc/instances — создать shell инстанса.
Ожидает JSON: {serviceId: int, displayName: str?}
⛔ КРИТИЧНО: возвращает 201 + заголовок Location: ./{instanceUid}.
HttpClient.post() в app-autotest парсит Location чтобы получить UUID:
- Берёт последний сегмент после /
- Проверяет len(сегмент) >= 32 (длина UUID без дефисов)
Также возвращает {"instanceUid": uid} в теле — двойная защита
на случай если заголовок не дойдёт.
"""
body = request.get_json(silent=True) or {}
service_id = body.get("serviceId")
display_name = body.get("displayName", "unnamed")
# Валидация: serviceId обязателен
if service_id is None:
return jsonify({"error": "serviceId is required"}), 400
# Проверка что сервис существует в конфиге
svc_def = SERVICES.get(service_id)
if not svc_def:
return jsonify({"error": f"service {service_id} not found"}), 404
# Создать shell инстанса (статус "creating")
uid = state.create_instance(service_id, display_name, svc_def)
# ⛔ Location ОБЯЗАТЕЛЕН — без него executor не получит instanceUid
resp = make_response(jsonify({"instanceUid": uid}), 201)
resp.headers["Location"] = f"./{uid}"
return resp