229 lines
9.5 KiB
Python
229 lines
9.5 KiB
Python
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():
|
||
"""Запустить одну операцию — логика 1:1 с Terraform."""
|
||
import 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()
|
||
t0 = time.time()
|
||
|
||
try:
|
||
if op_name == "create":
|
||
# 1. POST /instances → instanceUid
|
||
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
|
||
|
||
# 2. POST /instanceOperations → opUid
|
||
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
|
||
|
||
# 3. POST /instanceOperationCfsParams ×N
|
||
for pid, pval in params.items():
|
||
client.post("/instanceOperationCfsParams",
|
||
{"instanceOperationUid": op_uid, "svcOperationCfsParamId": int(pid), "paramValue": str(pval)})
|
||
|
||
# 4. POST /run
|
||
client.post(f"/instanceOperations/{op_uid}/run")
|
||
|
||
# 5. waitForOperationFinish (dtFinish-based)
|
||
result = _wait_op(client, op_uid, t0)
|
||
if result["status"] == "OK":
|
||
tracker_add(instance_uid, svc_id, display_name)
|
||
result["instanceUid"] = instance_uid
|
||
return jsonify(result)
|
||
|
||
else:
|
||
# modify / suspend / delete / resume / redeploy
|
||
if not instance_uid:
|
||
return jsonify({"status": "FAIL", "error": "Нет instanceUid"}), 400
|
||
|
||
# redeploy: без параметров, сразу /run
|
||
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")
|
||
result = _wait_op(client, op_uid, t0)
|
||
result["instanceUid"] = instance_uid
|
||
return jsonify(result)
|
||
|
||
# modify/suspend/delete/resume: с параметрами
|
||
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")
|
||
|
||
result = _wait_op(client, op_uid, t0)
|
||
result["instanceUid"] = instance_uid
|
||
if result["status"] == "OK" and op_name == "delete":
|
||
tracker_remove(instance_uid)
|
||
return jsonify(result)
|
||
|
||
except Exception as e:
|
||
import traceback
|
||
return jsonify({
|
||
"status": "FAIL",
|
||
"error": str(e) + " | " + traceback.format_exc()[-200:],
|
||
"instanceUid": instance_uid,
|
||
"duration": round(time.time() - t0, 1),
|
||
})
|
||
|
||
|
||
def _wait_op(client, op_uid, t0):
|
||
"""Поллинг операции до dtFinish — 1:1 с waitForOperationFinish."""
|
||
import time
|
||
deadline = time.time() + 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")
|
||
if dt_finish and str(dt_finish).strip():
|
||
is_ok = op.get("isSuccessful")
|
||
if not is_ok:
|
||
err = op.get("errorLog") or "неизвестная ошибка"
|
||
return {"status": "FAIL", "error": str(err), "duration": round(time.time() - t0, 1), "stages": op.get("stages", [])}
|
||
return {"status": "OK", "duration": round(time.time() - t0, 1), "stages": op.get("stages", [])}
|
||
time.sleep(5)
|
||
return {"status": "TIMEOUT", "duration": round(time.time() - t0, 1)}
|
||
|
||
|
||
@bp.route("/api/test/status/<op_uid>")
|
||
def api_test_status(op_uid):
|
||
"""Получить текущий статус операции (для поллинга с UI)."""
|
||
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({
|
||
"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
|