Files
app-autotest/site/routes/api_test.py
T

236 lines
10 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.
from flask import Blueprint, current_app, jsonify, request
from api.http_client import HttpClient
from operations.get_services import get_services, get_service_detail
from operations.get_instances import get_instances
from operations.tracker import add as tracker_add, list_all as tracker_list
from operations.tracker import remove as tracker_remove
bp = Blueprint("api_test", __name__)
def _client():
token = request.cookies.get("token") or current_app.config["NUBES_API_TOKEN"]
return HttpClient(current_app.config["NUBES_API_ENDPOINT"], token)
@bp.route("/api/services")
def api_services():
try:
raw = get_services(_client())
svc_list = [{"svcId": s["svcId"], "svc": s["svc"], "svcExtendedName": s.get("svcExtendedName", "")} for s in raw]
svc_list.sort(key=lambda s: s["svcId"])
return jsonify(svc_list)
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/api/instances/list")
def api_instances_list():
try:
raw = get_instances(_client())
inst = [{"instanceUid": i["instanceUid"], "displayName": i["displayName"], "serviceId": i["serviceId"], "svc": i["svc"], "explainedStatus": i.get("explainedStatus", "?")} for i in raw]
return jsonify(inst)
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/api/operations/<int:svc_id>")
def api_operations(svc_id):
try:
detail = get_service_detail(_client(), svc_id)
ops = detail.get("operations", [])
# только отслеживаемые инстансы этого сервиса
tracked = tracker_list()
tracked_uids = {t["instanceUid"] for t in tracked if t["svcId"] == svc_id}
instances = get_instances(_client())
svc_instances = [i for i in instances
if i.get("instanceUid") in tracked_uids
and i.get("explainedStatus") not in ("deleted", "not created")]
return jsonify({
"svc": detail.get("svc", ""),
"operations": [{"svcOperationId": o["svcOperationId"], "operation": o["operation"]} for o in ops],
"instances": svc_instances,
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/api/params/<int:op_id>")
def api_params(op_id):
try:
data = _client().get(f"/instanceOperations/default/{op_id}")
params = data["svcOperation"]["cfsParams"]
result = []
for p in params:
result.append({
"svcOperationCfsParamId": p["svcOperationCfsParamId"],
"name": p.get("svcOperationCfsParam", ""),
"dataType": p.get("dataType", ""),
"isRequired": p.get("isRequired", False),
"defaultValue": p.get("defaultValue"),
"valueList": p.get("valueList"),
"dataDescriptor": {k: {"dataType": v.get("dataType",""), "valueList": v.get("valueList",""), "isRequired": v.get("isRequired", False)} for k, v in p.get("dataDescriptor", {}).items()} if p.get("dataDescriptor") else None,
})
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/api/test", methods=["POST"])
def api_test():
"""Запустить операцию — возвращает opUid сразу, выполнение в фоне."""
import threading, time
data = request.get_json()
svc_id = data["serviceId"]
op_name = data["operation"]
svc_op_id = data["svcOperationId"]
params = data.get("params", {})
instance_uid = data.get("instanceUid")
display_name = data.get("displayName", f"autotest-{svc_id}")
client = _client()
try:
if op_name == "create":
payload = {"serviceId": svc_id, "displayName": display_name, "descr": ""}
resp = client.post("/instances", payload)
instance_uid = resp.get("instanceUid") or _find_uid(resp) or _uid_from_location(resp.get("_location", ""))
if not instance_uid:
return jsonify({"status": "FAIL", "error": "Не удалось получить instanceUid"}), 500
op_payload = {"instanceUid": instance_uid, "operation": "create"}
op_resp = client.post("/instanceOperations", op_payload)
op_uid = _find_uid(op_resp) or _uid_from_location(op_resp.get("_location", ""))
if not op_uid:
return jsonify({"status": "FAIL", "error": "Не удалось получить opUid"}), 500
for pid, pval in params.items():
client.post("/instanceOperationCfsParams",
{"instanceOperationUid": op_uid, "svcOperationCfsParamId": int(pid), "paramValue": str(pval)})
client.post(f"/instanceOperations/{op_uid}/run")
# Записать в трекер СРАЗУ, до фонового потока (поток может умереть под gunicorn)
try:
tracker_add(instance_uid, svc_id, display_name)
except Exception:
pass
# фоном ждать завершения
threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, True), daemon=True).start()
return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid})
else:
if not instance_uid:
return jsonify({"status": "FAIL", "error": "Нет instanceUid"}), 400
if op_name == "redeploy":
op_payload = {"instanceUid": instance_uid, "svcOperationId": svc_op_id, "operation": op_name}
op_resp = client.post("/instanceOperations", op_payload)
op_uid = _find_uid(op_resp) or _uid_from_location(op_resp.get("_location", ""))
if not op_uid:
return jsonify({"status": "FAIL", "error": "Не удалось получить opUid"}), 500
client.post(f"/instanceOperations/{op_uid}/run")
else:
op_payload = {"instanceUid": instance_uid, "svcOperationId": svc_op_id, "operation": op_name}
op_resp = client.post("/instanceOperations", op_payload)
op_uid = _find_uid(op_resp) or _uid_from_location(op_resp.get("_location", ""))
if not op_uid:
return jsonify({"status": "FAIL", "error": "Не удалось получить opUid"}), 500
for pid, pval in params.items():
client.post("/instanceOperationCfsParams",
{"instanceOperationUid": op_uid, "svcOperationCfsParamId": int(pid), "paramValue": str(pval)})
client.post(f"/instanceOperations/{op_uid}/run")
# фоном ждать завершения
is_delete = (op_name == "delete")
threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, False, is_delete), daemon=True).start()
return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid})
except Exception as e:
import traceback
return jsonify({"status": "FAIL", "error": str(e) + " | " + traceback.format_exc()[-200:]})
# Результаты фоновых операций: opUid → {status, error, stages, duration}
_op_results = {}
def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, is_create, is_delete=False):
"""Фоном ждать dtFinish и сохранить результат."""
import time
t0 = time.time()
deadline = t0 + 300
while time.time() < deadline:
try:
data = client.get(f"/instanceOperations/{op_uid}?fields=dtFinish,isSuccessful,errorLog,isInProgress,duration,stages")
except Exception:
time.sleep(5)
continue
op = data.get("instanceOperation", {})
dt_finish = op.get("dtFinish")
# Обновляем stages по мере появления
_op_results[op_uid] = {
"status": "RUNNING",
"stages": op.get("stages", []),
"duration": round(time.time() - t0, 1),
}
if dt_finish and str(dt_finish).strip():
is_ok = op.get("isSuccessful")
err = op.get("errorLog") or ""
print(f"[DEBUG] _finish_op op_uid={op_uid} isSuccessful={is_ok!r}", flush=True)
# tracker_add для create уже вызван синхронно в api_test()
if is_ok and is_delete:
tracker_remove(instance_uid)
_op_results[op_uid] = {
"status": "OK" if is_ok else "FAIL",
"error": str(err) if err else "",
"stages": op.get("stages", []),
"duration": round(time.time() - t0, 1),
}
return
time.sleep(5)
_op_results[op_uid] = {"status": "TIMEOUT", "duration": round(time.time() - t0, 1)}
@bp.route("/api/test/status/<op_uid>")
def api_test_status(op_uid):
"""Получить текущий статус операции (поллинг с UI)."""
# сначала проверяем фоновый трекер
if op_uid in _op_results:
return jsonify(_op_results[op_uid])
# иначе спрашиваем API напрямую
try:
data = _client().get(f"/instanceOperations/{op_uid}?fields=dtFinish,isSuccessful,errorLog,isInProgress,duration,stages")
op = data.get("instanceOperation", {})
dt_finish = op.get("dtFinish")
done = bool(dt_finish and str(dt_finish).strip())
return jsonify({
"status": "OK" if (done and op.get("isSuccessful")) else ("FAIL" if done else "RUNNING"),
"done": done,
"isSuccessful": op.get("isSuccessful"),
"isInProgress": op.get("isInProgress"),
"duration": op.get("duration"),
"stages": op.get("stages", []),
"errorLog": op.get("errorLog"),
})
except Exception as e:
return jsonify({"error": str(e)}), 500
def _find_uid(resp):
if isinstance(resp, dict):
for v in resp.values():
if isinstance(v, str) and len(v) == 36 and v.count("-") == 4:
return v
return None
def _uid_from_location(loc):
if loc:
parts = loc.rstrip("/").split("/")
if len(parts[-1]) == 36:
return parts[-1]
return None