Files
getDomain/domain-api/app/services/runtime_control_service.py
Your Name 1921318c25 debug
2026-04-17 14:07:50 +08:00

178 lines
7.8 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 __future__ import annotations
import os
import subprocess
from pathlib import Path
from app.core.config import settings
from app.services.debug_event_service import push_debug_event
from app.services.sync_push_service import pull_detect_task_batch_now, push_runtime_projection_now
from app.services.runtime_settings_service import get_runtime_settings
from app.services.worker_control_service import _run_systemctl, start_worker, stop_worker
def _workspace_root() -> Path:
return Path(settings.domain_root).parent
def _run_shell(command: list[str], timeout: int = 30) -> subprocess.CompletedProcess[str]:
return subprocess.run(command, capture_output=True, text=True, timeout=timeout)
def restart_api() -> tuple[bool, str]:
runtime = get_runtime_settings()
api_service_name = runtime.get("api_service_name", settings.api_service_name)
worker_mode = runtime.get("worker_mode", settings.worker_mode)
if worker_mode == "linux-systemd":
# When the API restarts itself under systemd, wait-free restart avoids
# blocking the HTTP request until uvicorn is torn down.
result = _run_systemctl(["restart", api_service_name], timeout=5, no_block=True)
if result.returncode != 0:
return False, (result.stderr or result.stdout or "重启 Linux API 失败").strip()
return True, f"Linux API 重启命令已发送: {api_service_name}"
if os.name != "nt":
return False, "当前仅实现 Windows 本地 API 重启Linux 请将 worker_mode 设为 linux-systemd。"
workspace = _workspace_root()
stop_script = workspace / "stop_domain_api.ps1"
start_script = workspace / "start_domain_api.ps1"
if not stop_script.exists() or not start_script.exists():
return False, "未找到 API 启停脚本"
command = (
"Start-Process powershell "
"-ArgumentList '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', "
f"\"Start-Sleep -Seconds 2; & '{stop_script}'; Start-Sleep -Seconds 1; & '{start_script}'\""
)
result = _run_shell(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command], timeout=20)
if result.returncode != 0:
return False, (result.stderr or result.stdout or "重启 API 失败").strip()
return True, "API 重启命令已发送"
def _run_systemd_action(service_name: str, action: str, *, no_block: bool = False) -> tuple[bool, str]:
systemctl_command = []
if no_block:
systemctl_command.append("--no-block")
systemctl_command.extend([action, service_name])
result = _run_systemctl(systemctl_command, timeout=10 if no_block else 30)
if result.returncode != 0:
return False, (result.stderr or result.stdout or f"{action} {service_name} 失败").strip()
return True, f"{service_name} {action} 命令已发送"
def _emit_runtime_action_event(action: str, *, stage: str, ok: bool, message: str, data: dict | None = None) -> None:
try:
push_debug_event(
service="runtime-control",
event_type=f"runtime_action_{stage}",
level="info" if ok else "warning",
message=message,
payload={
"action": action,
"ok": ok,
**(data or {}),
},
)
except Exception:
pass
def start_sync_agent() -> tuple[bool, str]:
runtime = get_runtime_settings()
worker_mode = runtime.get("worker_mode", settings.worker_mode)
service_name = runtime.get("sync_agent_service_name", settings.sync_agent_service_name)
if worker_mode != "linux-systemd":
return False, "sync-agent 仅在 Linux systemd 多机部署中使用。"
return _run_systemd_action(service_name, "start")
def stop_sync_agent() -> tuple[bool, str]:
runtime = get_runtime_settings()
worker_mode = runtime.get("worker_mode", settings.worker_mode)
service_name = runtime.get("sync_agent_service_name", settings.sync_agent_service_name)
if worker_mode != "linux-systemd":
return False, "sync-agent 仅在 Linux systemd 多机部署中使用。"
return _run_systemd_action(service_name, "stop")
def runtime_action(action: str) -> tuple[bool, str, dict]:
normalized_action = str(action or "").strip().lower().replace("-", "_")
if normalized_action != "push_debug_probe":
_emit_runtime_action_event(
normalized_action,
stage="requested",
ok=True,
message=f"收到运行时动作请求: {normalized_action}",
)
if normalized_action == "start_worker":
ok, message = start_worker()
result = {"action": normalized_action, "poll_after_seconds": 2, "refresh_runtime": True}
_emit_runtime_action_event(normalized_action, stage="finished", ok=ok, message=message, data=result)
return ok, message, result
if normalized_action == "stop_worker":
ok, message = stop_worker()
result = {"action": normalized_action, "poll_after_seconds": 2, "refresh_runtime": True}
_emit_runtime_action_event(normalized_action, stage="finished", ok=ok, message=message, data=result)
return ok, message, result
if normalized_action == "restart_api":
ok, message = restart_api()
result = {"action": normalized_action, "poll_after_seconds": 4, "refresh_runtime": True}
_emit_runtime_action_event(normalized_action, stage="finished", ok=ok, message=message, data=result)
return ok, message, result
if normalized_action == "start_sync_agent":
ok, message = start_sync_agent()
result = {"action": normalized_action, "poll_after_seconds": 2, "refresh_runtime": True}
_emit_runtime_action_event(normalized_action, stage="finished", ok=ok, message=message, data=result)
return ok, message, result
if normalized_action == "stop_sync_agent":
ok, message = stop_sync_agent()
result = {"action": normalized_action, "poll_after_seconds": 2, "refresh_runtime": True}
_emit_runtime_action_event(normalized_action, stage="finished", ok=ok, message=message, data=result)
return ok, message, result
if normalized_action == "push_sync":
ok, message, data = push_runtime_projection_now()
result = {
"action": normalized_action,
"poll_after_seconds": 2,
"refresh_runtime": True,
**(data or {}),
}
_emit_runtime_action_event(normalized_action, stage="finished", ok=ok, message=message, data=result)
return ok, message, result
if normalized_action == "pull_tasks":
ok, message, data = pull_detect_task_batch_now()
result = {
"action": normalized_action,
"poll_after_seconds": 2,
"refresh_runtime": True,
**(data or {}),
}
_emit_runtime_action_event(normalized_action, stage="finished", ok=ok, message=message, data=result)
return ok, message, result
if normalized_action == "push_debug_probe":
ok, message, data = push_debug_event(
service="domain-api",
event_type="debug_probe",
level="info",
message="manual debug probe",
payload={"node_code": settings.node_code, "node_region": settings.node_region},
)
result = {
"action": normalized_action,
"poll_after_seconds": 1,
"refresh_runtime": False,
**(data or {}),
}
_emit_runtime_action_event(normalized_action, stage="finished", ok=ok, message=message, data=result)
return ok, message, result
result = {
"action": normalized_action,
"poll_after_seconds": 0,
"refresh_runtime": False,
}
_emit_runtime_action_event(normalized_action, stage="finished", ok=False, message=f"不支持的运行时动作: {action}", data=result)
return False, f"不支持的运行时动作: {action}", result