Spaces:
Sleeping
Sleeping
Update Deployment_UI_BE.py
Browse files- Deployment_UI_BE.py +74 -96
Deployment_UI_BE.py
CHANGED
|
@@ -152,7 +152,6 @@ def _normalize_blob_url(u: str | None) -> str | None:
|
|
| 152 |
u = str(u).strip()
|
| 153 |
if u.startswith(("http://", "https://")):
|
| 154 |
return u
|
| 155 |
-
# Treat '/x' or 'x' as local to this app (same origin as FE)
|
| 156 |
if u.startswith("/"):
|
| 157 |
return f"{_LOCAL_BASE}{u}"
|
| 158 |
return f"{_LOCAL_BASE}/{u}"
|
|
@@ -170,37 +169,35 @@ def _fetch_url(u: str):
|
|
| 170 |
def _fetch_blob_from_page():
|
| 171 |
return _fetch_url(f"{_LOCAL_BASE}/modelblob.json")
|
| 172 |
|
| 173 |
-
|
| 174 |
-
def
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
""
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
_ingest_blob(parsed, model_id_hint=model_id, container_image_hint=container_image)
|
| 203 |
-
return RedirectResponse("/Deployment_UI", 303)
|
| 204 |
# ---------------------------------------------------------------------
|
| 205 |
# Create instance
|
| 206 |
# ---------------------------------------------------------------------
|
|
@@ -348,8 +345,8 @@ async def api_create_instance(req: Request):
|
|
| 348 |
_INST["ip"] = _INST.get("ip") or ""
|
| 349 |
_INST["port"] = _INST.get("port") or ""
|
| 350 |
return JSONResponse(content, r.status_code)
|
| 351 |
-
|
| 352 |
-
# Poll / read instance status + explicit readiness fields
|
| 353 |
# ---------------------------------------------------------------------
|
| 354 |
@router.get("/api/compute/pods/{pod_id}")
|
| 355 |
def api_get_instance(pod_id: str = None):
|
|
@@ -380,7 +377,6 @@ def api_get_instance(pod_id: str = None):
|
|
| 380 |
|
| 381 |
if ip and isinstance(pm, dict) and pm:
|
| 382 |
# choose mapped public port for the declared internal port; else first mapping
|
| 383 |
-
chosen = None
|
| 384 |
if isinstance(declared, int) and str(declared) in pm:
|
| 385 |
chosen = str(pm[str(declared)])
|
| 386 |
else:
|
|
@@ -398,45 +394,34 @@ def api_get_instance(pod_id: str = None):
|
|
| 398 |
_log_status(f"PORT_MAPPING declared={declared} chosen={chosen} all={pm}")
|
| 399 |
_log_status(f"RESOLVED_ENDPOINT base={base}")
|
| 400 |
|
| 401 |
-
# health
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
elif not _INST.get("predictRoute"):
|
| 411 |
-
_INST["predictRoute"] = "/predict"
|
| 412 |
-
_log_status("PREDICT_ROUTE_SET /predict (Vertex default)")
|
| 413 |
-
|
| 414 |
-
# If still unset for some reason, try lightweight discovery once
|
| 415 |
-
if not _INST.get("predictRoute"):
|
| 416 |
-
route = _discover_route(base)
|
| 417 |
-
if route:
|
| 418 |
-
_INST["predictRoute"] = route
|
| 419 |
-
_log_status(f"PREDICT_ROUTE_SET {route} (discovered)")
|
| 420 |
-
else:
|
| 421 |
-
_log_status("PREDICT_ROUTE_SET none")
|
| 422 |
-
|
| 423 |
-
# Final prompt URL (prefer IP; else proxy host)
|
| 424 |
-
pr = _INST.get("predictRoute") or "/predict"
|
| 425 |
-
if _INST.get("ip") and _INST.get("port"):
|
| 426 |
-
prompt_url = f"http://{_INST['ip']}:{_INST['port']}{pr}"
|
| 427 |
else:
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
_log_status(f"PROMPT_ENDPOINT {prompt_url}")
|
| 432 |
|
| 433 |
-
# Always include cached readiness data for the UI
|
| 434 |
merged = {**last, "cachedState": {
|
| 435 |
"podId": _INST.get("podId"),
|
| 436 |
"status": _INST.get("status"),
|
| 437 |
"ip": _INST.get("ip"),
|
| 438 |
"port": _INST.get("port"),
|
| 439 |
"predictRoute": _INST.get("predictRoute"),
|
|
|
|
| 440 |
}}
|
| 441 |
return JSONResponse(merged)
|
| 442 |
|
|
@@ -492,7 +477,7 @@ async def api_end_all():
|
|
| 492 |
return JSONResponse(status_code=500, content={"error": f"RunPod delete failed: {e}"})
|
| 493 |
|
| 494 |
# ---------------------------------------------------------------------
|
| 495 |
-
# Wait instance
|
| 496 |
# ---------------------------------------------------------------------
|
| 497 |
@router.get("/api/compute/wait_instance")
|
| 498 |
def api_wait_instance(pod_id: str = None):
|
|
@@ -519,43 +504,35 @@ def api_wait_instance(pod_id: str = None):
|
|
| 519 |
pass
|
| 520 |
|
| 521 |
if ip and pm:
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
except Exception:
|
| 526 |
-
pass
|
| 527 |
-
if not port and "8080" in pm:
|
| 528 |
port = str(pm["8080"])
|
| 529 |
-
elif
|
| 530 |
port = str(pm[next(iter(pm.keys()))])
|
| 531 |
|
| 532 |
if ip and port:
|
| 533 |
base = f"http://{ip}:{port}"
|
| 534 |
_log_status(f"RESOLVED_IP {base}")
|
| 535 |
-
code, ms, snippet = _probe("GET", f"{base}/health")
|
| 536 |
-
_log_status(f"HEALTH code={code} ms={ms} body_snippet={snippet}")
|
| 537 |
-
|
| 538 |
-
# --- Vertex-mirrored route + final prompt URL log ---
|
| 539 |
-
pr_explicit = (c.get("predictRoute") or "").strip() if isinstance(c, dict) else ""
|
| 540 |
-
if pr_explicit:
|
| 541 |
-
_INST["predictRoute"] = pr_explicit
|
| 542 |
-
_log_status(f"PREDICT_ROUTE_SET {pr_explicit} (from blob)")
|
| 543 |
-
elif not _INST.get("predictRoute"):
|
| 544 |
-
_INST["predictRoute"] = "/predict"
|
| 545 |
-
_log_status("PREDICT_ROUTE_SET /predict (Vertex default)")
|
| 546 |
-
|
| 547 |
-
if not _INST.get("predictRoute"):
|
| 548 |
-
route = _discover_route(base)
|
| 549 |
-
if route:
|
| 550 |
-
_INST["predictRoute"] = route
|
| 551 |
-
_log_status(f"PREDICT_ROUTE_SET {route} (discovered)")
|
| 552 |
-
else:
|
| 553 |
-
_log_status("PREDICT_ROUTE_SET none")
|
| 554 |
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
|
|
|
|
| 559 |
try:
|
| 560 |
cspec = _get_container_spec()
|
| 561 |
internal, _ = _get_port_and_proto(cspec)
|
|
@@ -567,7 +544,7 @@ def api_wait_instance(pod_id: str = None):
|
|
| 567 |
except Exception:
|
| 568 |
pass
|
| 569 |
|
| 570 |
-
_INST.update({"ip": ip or "", "port": port or ""
|
| 571 |
_save_state()
|
| 572 |
|
| 573 |
merged = {
|
|
@@ -579,6 +556,7 @@ def api_wait_instance(pod_id: str = None):
|
|
| 579 |
"port": _INST.get("port"),
|
| 580 |
"base": _INST.get("base"),
|
| 581 |
"predictRoute": _INST.get("predictRoute"),
|
|
|
|
| 582 |
},
|
| 583 |
}
|
| 584 |
return JSONResponse(merged)
|
|
|
|
| 152 |
u = str(u).strip()
|
| 153 |
if u.startswith(("http://", "https://")):
|
| 154 |
return u
|
|
|
|
| 155 |
if u.startswith("/"):
|
| 156 |
return f"{_LOCAL_BASE}{u}"
|
| 157 |
return f"{_LOCAL_BASE}/{u}"
|
|
|
|
| 169 |
def _fetch_blob_from_page():
|
| 170 |
return _fetch_url(f"{_LOCAL_BASE}/modelblob.json")
|
| 171 |
|
| 172 |
+
# --- UPDATED: healthRoute + predictRoute caching ---
|
| 173 |
+
def _ingest_blob(parsed: dict, model_id_hint: str = "", container_image_hint: str = ""):
|
| 174 |
+
if not isinstance(parsed, dict):
|
| 175 |
+
raise HTTPException(400, "Invalid blob (expected JSON object).")
|
| 176 |
+
|
| 177 |
+
_INST.update({
|
| 178 |
+
"blob": parsed,
|
| 179 |
+
"model_id": model_id_hint or "",
|
| 180 |
+
"container_image_hint": container_image_hint or "",
|
| 181 |
+
})
|
| 182 |
+
|
| 183 |
+
c = (((parsed.get("supportedActions") or {}).get("deploy") or {}).get("containerSpec")
|
| 184 |
+
or parsed.get("container") or {}) or {}
|
| 185 |
+
|
| 186 |
+
for k in ("predictRoute", "healthRoute", "readinessRoute", "livenessRoute"):
|
| 187 |
+
v = c.get(k)
|
| 188 |
+
if isinstance(v, str) and v.strip():
|
| 189 |
+
_INST[k] = v.strip()
|
| 190 |
+
|
| 191 |
+
image_uri = (c.get("imageUri") or "").strip().lower()
|
| 192 |
+
pr_hint, hr_hint = _infer_routes_from_image(image_uri)
|
| 193 |
+
|
| 194 |
+
# mirror Vertex defaults
|
| 195 |
+
if not _INST.get("predictRoute"):
|
| 196 |
+
_INST["predictRoute"] = pr_hint or "/predict"
|
| 197 |
+
if not _INST.get("healthRoute"):
|
| 198 |
+
_INST["healthRoute"] = hr_hint or "/health"
|
| 199 |
+
|
| 200 |
+
return True
|
|
|
|
|
|
|
| 201 |
# ---------------------------------------------------------------------
|
| 202 |
# Create instance
|
| 203 |
# ---------------------------------------------------------------------
|
|
|
|
| 345 |
_INST["ip"] = _INST.get("ip") or ""
|
| 346 |
_INST["port"] = _INST.get("port") or ""
|
| 347 |
return JSONResponse(content, r.status_code)
|
| 348 |
+
# ---------------------------------------------------------------------
|
| 349 |
+
# Poll / read instance status + explicit readiness fields (updated)
|
| 350 |
# ---------------------------------------------------------------------
|
| 351 |
@router.get("/api/compute/pods/{pod_id}")
|
| 352 |
def api_get_instance(pod_id: str = None):
|
|
|
|
| 377 |
|
| 378 |
if ip and isinstance(pm, dict) and pm:
|
| 379 |
# choose mapped public port for the declared internal port; else first mapping
|
|
|
|
| 380 |
if isinstance(declared, int) and str(declared) in pm:
|
| 381 |
chosen = str(pm[str(declared)])
|
| 382 |
else:
|
|
|
|
| 394 |
_log_status(f"PORT_MAPPING declared={declared} chosen={chosen} all={pm}")
|
| 395 |
_log_status(f"RESOLVED_ENDPOINT base={base}")
|
| 396 |
|
| 397 |
+
# --- health-first readiness (mirror Vertex), fallback to predict existence ---
|
| 398 |
+
hr = (_INST.get("healthRoute") or "/health").strip()
|
| 399 |
+
pr = (_INST.get("predictRoute") or "/predict").strip()
|
| 400 |
+
|
| 401 |
+
code_h, ms_h, snippet_h = _probe("GET", f"{base}{hr}")
|
| 402 |
+
_log_status(f"HEALTH_PROBE path={hr} code={code_h} ms={ms_h} body_snippet={snippet_h[:120]}")
|
| 403 |
+
|
| 404 |
+
if code_h in (200, 204):
|
| 405 |
+
_INST["status"] = "READY"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
else:
|
| 407 |
+
code_p, ms_p, _ = _probe("HEAD", f"{base}{pr}")
|
| 408 |
+
_log_status(f"PREDICT_PROBE path={pr} code={code_p} ms={ms_p}")
|
| 409 |
+
if code_p in (200, 204, 400, 405):
|
| 410 |
+
_INST["status"] = "READY"
|
| 411 |
+
|
| 412 |
+
# final prompt URL log (unchanged)
|
| 413 |
+
if _INST.get("predictRoute"):
|
| 414 |
+
proute = _INST["predictRoute"]
|
| 415 |
+
prompt_url = f"http://{_INST['ip']}:{_INST['port']}{proute}"
|
| 416 |
_log_status(f"PROMPT_ENDPOINT {prompt_url}")
|
| 417 |
|
|
|
|
| 418 |
merged = {**last, "cachedState": {
|
| 419 |
"podId": _INST.get("podId"),
|
| 420 |
"status": _INST.get("status"),
|
| 421 |
"ip": _INST.get("ip"),
|
| 422 |
"port": _INST.get("port"),
|
| 423 |
"predictRoute": _INST.get("predictRoute"),
|
| 424 |
+
"healthRoute": _INST.get("healthRoute"),
|
| 425 |
}}
|
| 426 |
return JSONResponse(merged)
|
| 427 |
|
|
|
|
| 477 |
return JSONResponse(status_code=500, content={"error": f"RunPod delete failed: {e}"})
|
| 478 |
|
| 479 |
# ---------------------------------------------------------------------
|
| 480 |
+
# Wait instance (updated: same health-first + predict fallback)
|
| 481 |
# ---------------------------------------------------------------------
|
| 482 |
@router.get("/api/compute/wait_instance")
|
| 483 |
def api_wait_instance(pod_id: str = None):
|
|
|
|
| 504 |
pass
|
| 505 |
|
| 506 |
if ip and pm:
|
| 507 |
+
if isinstance(declared, int) and str(declared) in pm:
|
| 508 |
+
port = str(pm[str(declared)])
|
| 509 |
+
elif "8080" in pm:
|
|
|
|
|
|
|
|
|
|
| 510 |
port = str(pm["8080"])
|
| 511 |
+
elif pm:
|
| 512 |
port = str(pm[next(iter(pm.keys()))])
|
| 513 |
|
| 514 |
if ip and port:
|
| 515 |
base = f"http://{ip}:{port}"
|
| 516 |
_log_status(f"RESOLVED_IP {base}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
|
| 518 |
+
hr = (_INST.get("healthRoute") or "/health").strip()
|
| 519 |
+
pr = (_INST.get("predictRoute") or "/predict").strip()
|
| 520 |
+
|
| 521 |
+
code_h, ms_h, snippet_h = _probe("GET", f"{base}{hr}")
|
| 522 |
+
_log_status(f"HEALTH_PROBE path={hr} code={code_h} ms={ms_h} body_snippet={snippet_h[:120]}")
|
| 523 |
+
|
| 524 |
+
if code_h in (200, 204):
|
| 525 |
+
_INST["status"] = "READY"
|
| 526 |
+
else:
|
| 527 |
+
code_p, ms_p, _ = _probe("HEAD", f"{base}{pr}")
|
| 528 |
+
_log_status(f"PREDICT_PROBE path={pr} code={code_p} ms={ms_p}")
|
| 529 |
+
if code_p in (200, 204, 400, 405):
|
| 530 |
+
_INST["status"] = "READY"
|
| 531 |
+
|
| 532 |
+
if _INST.get("predictRoute"):
|
| 533 |
+
_log_status(f"PROMPT_ENDPOINT {base}{_INST['predictRoute']}")
|
| 534 |
|
| 535 |
+
# proxy base (unchanged)
|
| 536 |
try:
|
| 537 |
cspec = _get_container_spec()
|
| 538 |
internal, _ = _get_port_and_proto(cspec)
|
|
|
|
| 544 |
except Exception:
|
| 545 |
pass
|
| 546 |
|
| 547 |
+
_INST.update({"ip": ip or "", "port": port or ""})
|
| 548 |
_save_state()
|
| 549 |
|
| 550 |
merged = {
|
|
|
|
| 556 |
"port": _INST.get("port"),
|
| 557 |
"base": _INST.get("base"),
|
| 558 |
"predictRoute": _INST.get("predictRoute"),
|
| 559 |
+
"healthRoute": _INST.get("healthRoute"),
|
| 560 |
},
|
| 561 |
}
|
| 562 |
return JSONResponse(merged)
|