- 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/... → стенд по умолчанию
82 lines
3.5 KiB
Python
82 lines
3.5 KiB
Python
"""
|
||
routes/run.py — запуск операции.
|
||
|
||
Blueprint "run" с префиксом /api/v1/svc.
|
||
POST /instanceOperations/<uid>/run — выполнить операцию.
|
||
|
||
Вынесен в отдельный файл потому что логика run критична и специфична:
|
||
синхронный sleep, вызов state_machine.apply_effect, простановка dtFinish.
|
||
"""
|
||
|
||
import time
|
||
|
||
from flask import Blueprint, g, jsonify
|
||
from werkzeug.local import LocalProxy
|
||
|
||
import mock_state
|
||
import state_machine
|
||
import config.loader as _cfg
|
||
from utils.now import now as _now
|
||
|
||
state = LocalProxy(lambda: mock_state.get_state(g.stand_id))
|
||
SERVICES = LocalProxy(lambda: _cfg.get_services(g.stand_id))
|
||
|
||
bp = Blueprint("run", __name__, url_prefix="/api/v1/svc")
|
||
|
||
|
||
@bp.route("/instanceOperations/<uid>/run", methods=["POST"])
|
||
def run_operation(uid):
|
||
"""POST /instanceOperations/{uid}/run — выполнить операцию.
|
||
|
||
Алгоритм (синхронный, без потоков):
|
||
1. Проверить что операция существует (→ 404 если нет)
|
||
2. Записать dtStart = now()
|
||
3. sleep(DELAY) — эмуляция времени выполнения
|
||
4. Вызвать state_machine.apply_effect() — мутировать состояние инстанса
|
||
5. Записать dtFinish = now(), isSuccessful = True
|
||
|
||
После этого GET /instanceOperations/{uid} вернёт dtFinish,
|
||
и poll_until_done() в app-autotest завершит поллинг.
|
||
|
||
Возвращает {"ok": True} — формат как в реальном Nubes API.
|
||
Повторный вызов → 409 (operation already completed).
|
||
"""
|
||
op = state.get_operation(uid)
|
||
if not op:
|
||
return jsonify({"error": "operation not found"}), 404
|
||
|
||
# Защита от повторного запуска: если dtFinish уже проставлен —
|
||
# операция уже выполнена, повторный run может испортить данные.
|
||
if op.get("dtFinish") is not None:
|
||
return jsonify({"error": "operation already completed"}), 409
|
||
|
||
# Фиксируем время старта
|
||
op["dtStart"] = _now()
|
||
|
||
# Эмулируем задержку выполнения (по умолчанию 0.1с)
|
||
# ⛔ _cfg.DELAY — модульная переменная, может меняться через /_mock/delay
|
||
if _cfg.DELAY > 0:
|
||
time.sleep(_cfg.DELAY)
|
||
|
||
# Проверка /_mock/fail-next — симуляция падения операции
|
||
st = state
|
||
if st.fail_next:
|
||
st.fail_next = False # one-shot: сбросить после использования
|
||
op["dtFinish"] = _now()
|
||
op["isSuccessful"] = False
|
||
op["errorLog"] = "mock failure (requested by /_mock/fail-next)"
|
||
op["stages"] = [
|
||
{"stage": "mock_error", "status": "failed", "error": "mock failure"}
|
||
]
|
||
return jsonify({"ok": False, "error": op["errorLog"]})
|
||
|
||
# Применить эффект операции к состоянию инстанса
|
||
# (create → running+params, delete → удалить, modify → мерж params, ...)
|
||
state_machine.apply_effect(uid, st, _cfg.SERVICES)
|
||
|
||
# Фиксируем время завершения — операция выполнена успешно
|
||
op["dtFinish"] = _now()
|
||
op["isSuccessful"] = True
|
||
|
||
return jsonify({"ok": True})
|