151 lines
6.6 KiB
Python
151 lines
6.6 KiB
Python
"""
|
||
routes/mock_routes.py — служебные эндпоинты /_mock/*.
|
||
|
||
Blueprint "mock" с префиксом /api/v1/svc/_mock.
|
||
POST /reset — полный сброс состояния (для тестов)
|
||
GET /state — отладочный дамп instances + operations
|
||
GET /services — отладочный список загруженных сервисов
|
||
POST /delay/<s> — изменить MOCK_OP_DELAY на лету (не перезапуская сервер)
|
||
"""
|
||
|
||
import os
|
||
|
||
from flask import Blueprint, jsonify, request, abort
|
||
|
||
import mock_state
|
||
import config.loader as _cfg # для чтения/записи модульных переменных DELAY, SERVICES
|
||
|
||
bp = Blueprint("mock", __name__, url_prefix="/api/v1/svc/_mock")
|
||
|
||
# Токен для защиты _mock/* эндпоинтов (из env, опционально).
|
||
# Дублирует глобальную проверку в app.py, но оставлен для обратной совместимости
|
||
# и ясности — _mock/* исторически имели отдельную защиту.
|
||
_MOCK_TOKEN = os.getenv("MOCK_AUTH_TOKEN", "")
|
||
|
||
|
||
@bp.before_request
|
||
def _check_mock_auth():
|
||
"""Проверка X-Mock-Auth заголовка если MOCK_AUTH_TOKEN задан.
|
||
|
||
Вызывается Flask перед КАЖДЫМ обработчиком в этом blueprint'е.
|
||
Если токен не задан в env — пропускаем всех (dev-режим).
|
||
Если задан — требуем совпадения с заголовком.
|
||
|
||
Примечание: app.before_request уже проверяет Authorization: Bearer;
|
||
этот хук — дополнительная защита для _mock/* эндпоинтов."""
|
||
if _MOCK_TOKEN and request.headers.get("X-Mock-Auth") != _MOCK_TOKEN:
|
||
abort(403)
|
||
|
||
|
||
@bp.route("/reset", methods=["POST"])
|
||
def mock_reset():
|
||
"""POST /api/v1/svc/_mock/reset — полный сброс состояния.
|
||
|
||
Удаляет ВСЕ инстансы, операции и параметры из MockState.
|
||
Используется в тестах (autouse fixture) для изоляции тестов друг от друга.
|
||
"""
|
||
mock_state.state.reset()
|
||
return jsonify({"reset": "ok"})
|
||
|
||
|
||
@bp.route("/state", methods=["GET"])
|
||
def mock_state_view():
|
||
"""GET /api/v1/svc/_mock/state — отладочный дамп состояния.
|
||
|
||
Возвращает {instances: {uid: {instanceUid, displayName, status, serviceId}},
|
||
operations: {uid: {instanceOperationUid, instanceUid, operation,
|
||
dtFinish, isSuccessful}}}.
|
||
|
||
Полезно для отладки тестов: «какие инстансы сейчас живы?», «завершилась ли
|
||
операция?». НЕ для production-использования.
|
||
"""
|
||
st = mock_state.state
|
||
return jsonify({
|
||
"instances": {
|
||
uid: {
|
||
"instanceUid": inst["instanceUid"],
|
||
"displayName": inst["displayName"],
|
||
"status": inst["status"],
|
||
"serviceId": inst["serviceId"],
|
||
}
|
||
for uid, inst in st.instances.items()
|
||
},
|
||
"operations": {
|
||
uid: {
|
||
"instanceOperationUid": op["instanceOperationUid"],
|
||
"instanceUid": op["instanceUid"],
|
||
"operation": op["operation"],
|
||
"dtFinish": op["dtFinish"],
|
||
"isSuccessful": op["isSuccessful"],
|
||
}
|
||
for uid, op in st.operations.items()
|
||
},
|
||
})
|
||
|
||
|
||
@bp.route("/services", methods=["GET"])
|
||
def mock_services():
|
||
"""GET /api/v1/svc/_mock/services — отладочный список сервисов.
|
||
|
||
Возвращает {count: N, services: {svcId: {name, displayName, operations, params}}}.
|
||
|
||
Полезно чтобы быстро проверить что все 37+ сервисов загрузились,
|
||
не дёргая реальный /api/v1/svc/services.
|
||
"""
|
||
result = {}
|
||
for svc_id, svc_def in _cfg.SERVICES.items():
|
||
result[str(svc_id)] = {
|
||
"name": svc_def.get("name", ""),
|
||
"displayName": svc_def.get("service_display_name", ""),
|
||
"operations": len(svc_def.get("operations", [])),
|
||
"params": len(svc_def.get("cfsParams", [])),
|
||
}
|
||
return jsonify({"count": len(result), "services": result})
|
||
|
||
|
||
# Максимальная задержка — защита от блокировки /health (1 воркер).
|
||
# При DELAY > healthcheck timeout платформа рестартит под.
|
||
_MAX_DELAY = 5.0
|
||
|
||
|
||
@bp.route("/delay/<string:seconds>", methods=["POST"])
|
||
def mock_set_delay(seconds):
|
||
"""POST /api/v1/svc/_mock/delay/{s} — изменить задержку операции.
|
||
|
||
Меняет config.loader.DELAY «на лету», без перезапуска сервера.
|
||
Полезно в тестах: установить delay=0 для мгновенных операций,
|
||
или delay=5 чтобы проверить таймауты поллинга.
|
||
|
||
⛔ Максимум {max_delay}с — защита от блокировки /health (1 воркер).
|
||
⛔ Минимум 0с — отрицательные значения запрещены.
|
||
|
||
Принимает строку и конвертирует в float — Flask <float:> не матчит
|
||
целые числа без десятичной точки (0, 2).
|
||
""".format(max_delay=_MAX_DELAY)
|
||
try:
|
||
val = float(seconds)
|
||
except (ValueError, TypeError):
|
||
return jsonify({"error": f"invalid delay: {seconds}"}), 400
|
||
if val < 0:
|
||
return jsonify({"error": "delay must be >= 0"}), 400
|
||
if val > _MAX_DELAY:
|
||
return jsonify({"error": f"delay must be <= {_MAX_DELAY}s (Nubes healthcheck protection)"}), 400
|
||
_cfg.DELAY = val
|
||
return jsonify({"delay": _cfg.DELAY})
|
||
|
||
|
||
@bp.route("/fail-next", methods=["POST"])
|
||
def mock_fail_next():
|
||
"""POST /api/v1/svc/_mock/fail-next — следующая операция упадёт.
|
||
|
||
Устанавливает флаг в MockState. При следующем POST /run:
|
||
- isSuccessful = False
|
||
- errorLog = "mock failure (requested by /_mock/fail-next)"
|
||
- эффект НЕ применяется (состояние инстанса не меняется)
|
||
|
||
После использования флаг сбрасывается (one-shot).
|
||
Позволяет тестировать негативные сценарии.
|
||
"""
|
||
mock_state.state.fail_next = True
|
||
return jsonify({"fail_next": True})
|