168 lines
4.6 KiB
Python
168 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from pydantic import BaseModel
|
|
|
|
from app.core.config import ROOT_DIR
|
|
from app.core.security import get_current_user
|
|
from app.models.user import User
|
|
from app.services.system_control import (
|
|
build_task_id,
|
|
clear_active_task_id,
|
|
get_active_task_id,
|
|
get_allowed_command,
|
|
get_runner_script_path,
|
|
is_task_stale,
|
|
get_task_logs,
|
|
require_super_admin,
|
|
serialize_task,
|
|
set_active_task_id,
|
|
upsert_task_state,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class RestartTaskCreate(BaseModel):
|
|
action: str
|
|
|
|
|
|
class RestartTaskResponse(BaseModel):
|
|
task_id: str
|
|
action: str
|
|
status: str
|
|
stage: str
|
|
message: str
|
|
created_at: str
|
|
updated_at: str
|
|
requested_by: dict[str, object] | None = None
|
|
|
|
|
|
class RestartTaskLogsResponse(BaseModel):
|
|
task_id: str
|
|
lines: list[str]
|
|
|
|
|
|
def ensure_super_admin(current_user: User) -> None:
|
|
if not require_super_admin(current_user.role):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Only super_admin can restart services",
|
|
)
|
|
|
|
|
|
@router.post("/restart-tasks", response_model=RestartTaskResponse)
|
|
async def create_restart_task(
|
|
payload: RestartTaskCreate,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
ensure_super_admin(current_user)
|
|
|
|
command = get_allowed_command(payload.action)
|
|
if command is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Unsupported system action",
|
|
)
|
|
|
|
active_task_id = get_active_task_id()
|
|
if active_task_id:
|
|
active_task = serialize_task(active_task_id)
|
|
if active_task and is_task_stale(active_task):
|
|
upsert_task_state(
|
|
active_task_id,
|
|
status="failed",
|
|
stage="failed",
|
|
message="Previous restart task became stale and was released",
|
|
)
|
|
clear_active_task_id(active_task_id)
|
|
elif active_task and active_task.get("status") in {"queued", "running"}:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
detail="Another restart task is already in progress",
|
|
)
|
|
|
|
task_id = build_task_id()
|
|
requested_by = {"id": current_user.id, "username": current_user.username}
|
|
task_state = upsert_task_state(
|
|
task_id,
|
|
action=payload.action,
|
|
status="queued",
|
|
stage="accepted",
|
|
message="Restart task accepted",
|
|
requested_by=requested_by,
|
|
)
|
|
set_active_task_id(task_id)
|
|
|
|
env = os.environ.copy()
|
|
backend_path = str(ROOT_DIR / "backend")
|
|
existing_pythonpath = env.get("PYTHONPATH", "")
|
|
env["PYTHONPATH"] = (
|
|
f"{backend_path}{os.pathsep}{existing_pythonpath}"
|
|
if existing_pythonpath
|
|
else backend_path
|
|
)
|
|
|
|
try:
|
|
subprocess.Popen(
|
|
[
|
|
sys.executable,
|
|
str(get_runner_script_path()),
|
|
"--task-id",
|
|
task_id,
|
|
"--action",
|
|
payload.action,
|
|
],
|
|
cwd=str(ROOT_DIR),
|
|
env=env,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
start_new_session=True,
|
|
)
|
|
except OSError as exc:
|
|
task_state = upsert_task_state(
|
|
task_id,
|
|
action=payload.action,
|
|
status="failed",
|
|
stage="failed",
|
|
message=f"Unable to start restart runner: {exc}",
|
|
requested_by=requested_by,
|
|
)
|
|
clear_active_task_id(task_id)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=task_state["message"],
|
|
) from exc
|
|
|
|
return task_state
|
|
|
|
|
|
@router.get("/restart-tasks/{task_id}", response_model=RestartTaskResponse)
|
|
async def get_restart_task(
|
|
task_id: str,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
ensure_super_admin(current_user)
|
|
|
|
task = serialize_task(task_id)
|
|
if task is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Restart task not found")
|
|
return task
|
|
|
|
|
|
@router.get("/restart-tasks/{task_id}/logs", response_model=RestartTaskLogsResponse)
|
|
async def get_restart_task_logs(
|
|
task_id: str,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
ensure_super_admin(current_user)
|
|
|
|
task = serialize_task(task_id)
|
|
if task is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Restart task not found")
|
|
return {"task_id": task_id, "lines": get_task_logs(task_id)}
|