feat: stabilize multi-region runtime sync and worker orchestration
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter, Body
|
||||
|
||||
from app.core.config import settings
|
||||
from app.schemas.common import ApiResponse
|
||||
@@ -18,7 +18,7 @@ from app.services.detect_job_service import (
|
||||
from app.services.detect_service import get_detect_status
|
||||
from app.services.detect_run_service import create_detect_run_snapshot, finalize_detect_run, mark_detect_run_stopping
|
||||
from app.services.ops_job_service import create_ops_job, list_managed_nodes
|
||||
from app.services.settings_service import get_settings_payload, resolve_thread_count
|
||||
from app.services.settings_service import get_settings_payload, resolve_process_count, resolve_thread_count
|
||||
from app.services.worker_control_service import send_worker_command, start_worker
|
||||
|
||||
router = APIRouter(tags=["detect"])
|
||||
@@ -66,7 +66,13 @@ def _build_detect_action_result(
|
||||
|
||||
def _build_settings_summary(settings_payload: dict) -> dict:
|
||||
thread_count_resolution = resolve_thread_count(settings_payload=settings_payload)
|
||||
process_count_resolution = resolve_process_count(settings_payload=settings_payload)
|
||||
return {
|
||||
"process_count": int(process_count_resolution["effective_process_count"]),
|
||||
"process_count_default": int(process_count_resolution["default_process_count"]),
|
||||
"process_count_source": str(process_count_resolution["source"]),
|
||||
"process_count_override": process_count_resolution["override_process_count"],
|
||||
"process_count_node_code": str(process_count_resolution["node_code"]),
|
||||
"thread_count": int(thread_count_resolution["effective_thread_count"]),
|
||||
"thread_count_default": int(thread_count_resolution["default_thread_count"]),
|
||||
"thread_count_source": str(thread_count_resolution["source"]),
|
||||
@@ -78,6 +84,38 @@ def _build_settings_summary(settings_payload: dict) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _normalize_target_node_codes(payload: dict | None) -> list[str]:
|
||||
if not isinstance(payload, dict):
|
||||
return []
|
||||
|
||||
normalized_targets: list[str] = []
|
||||
|
||||
def append_target(raw_value: object) -> None:
|
||||
normalized_value = str(raw_value or "").strip()
|
||||
if normalized_value and normalized_value not in normalized_targets:
|
||||
normalized_targets.append(normalized_value)
|
||||
|
||||
for key in ("target_node_codes", "node_codes"):
|
||||
raw_value = payload.get(key)
|
||||
if isinstance(raw_value, (list, tuple, set)):
|
||||
for item in raw_value:
|
||||
append_target(item)
|
||||
elif isinstance(raw_value, str) and raw_value.strip():
|
||||
for item in raw_value.split(","):
|
||||
append_target(item)
|
||||
if normalized_targets:
|
||||
return normalized_targets
|
||||
|
||||
for key in ("target_node_code", "node_code"):
|
||||
raw_value = payload.get(key)
|
||||
if raw_value not in (None, ""):
|
||||
append_target(raw_value)
|
||||
if normalized_targets:
|
||||
return normalized_targets
|
||||
|
||||
return normalized_targets
|
||||
|
||||
|
||||
def _mainland_detect_targets() -> dict[str, list[dict]]:
|
||||
controllers: list[dict] = []
|
||||
workers: list[dict] = []
|
||||
@@ -212,15 +250,18 @@ def _dispatch_remote_detect_start(*, job_summary: dict, cycle_token: str) -> dic
|
||||
}
|
||||
|
||||
|
||||
def _dispatch_remote_detect_stop(*, active_job: dict | None, cycle_token: str = "") -> dict:
|
||||
def _dispatch_remote_detect_stop(*, active_job: dict | None, cycle_token: str = "", payload: dict | None = None) -> dict:
|
||||
targets = _mainland_detect_targets()
|
||||
job_summary = active_job or {}
|
||||
queued: list[dict] = []
|
||||
target_node_codes = _normalize_target_node_codes(payload)
|
||||
|
||||
for node in [*targets["controllers"], *targets["workers"]]:
|
||||
node_code = str(node.get("node_code") or "").strip()
|
||||
if not node_code:
|
||||
continue
|
||||
if target_node_codes and node_code not in target_node_codes:
|
||||
continue
|
||||
queued.append(
|
||||
_queue_remote_detect_job(
|
||||
node_code=node_code,
|
||||
@@ -317,26 +358,63 @@ def start_detect(step_code: str | None = None) -> ApiResponse:
|
||||
|
||||
local_worker_expected = _local_worker_expected_on_this_node()
|
||||
if local_worker_expected:
|
||||
ok, message = start_worker()
|
||||
if not ok:
|
||||
result = _build_detect_action_result(
|
||||
action="start",
|
||||
ok=False,
|
||||
message=message,
|
||||
data={"job": job_summary},
|
||||
)
|
||||
prestart_snapshot = get_detect_status()
|
||||
worker_already_running = bool(prestart_snapshot.get("worker_online", False)) or int(
|
||||
prestart_snapshot.get("worker_process_count", 0) or 0
|
||||
) > 0
|
||||
if worker_already_running:
|
||||
ok = True
|
||||
message = "检测端已在运行,跳过重复启动,直接发送控制指令"
|
||||
append_detect_job_event(
|
||||
job_summary["job_id"],
|
||||
event_type="job_dispatch_failed",
|
||||
level="error",
|
||||
message=f"启动 Worker 失败: {message}",
|
||||
payload={"cycle_token": cycle_token},
|
||||
)
|
||||
return ApiResponse(
|
||||
code=1,
|
||||
event_type="job_dispatch_start_skipped",
|
||||
level="info",
|
||||
message=message,
|
||||
data=result,
|
||||
payload={
|
||||
"cycle_token": cycle_token,
|
||||
"worker_process_count": int(prestart_snapshot.get("worker_process_count", 0) or 0),
|
||||
},
|
||||
)
|
||||
else:
|
||||
ok, message = start_worker()
|
||||
if not ok:
|
||||
degraded_snapshot = get_detect_status()
|
||||
worker_already_running = bool(degraded_snapshot.get("worker_online", False)) or int(
|
||||
degraded_snapshot.get("worker_process_count", 0) or 0
|
||||
) > 0
|
||||
if not worker_already_running:
|
||||
result = _build_detect_action_result(
|
||||
action="start",
|
||||
ok=False,
|
||||
message=message,
|
||||
data={"job": job_summary},
|
||||
)
|
||||
append_detect_job_event(
|
||||
job_summary["job_id"],
|
||||
event_type="job_dispatch_failed",
|
||||
level="error",
|
||||
message=f"启动 Worker 失败: {message}",
|
||||
payload={"cycle_token": cycle_token},
|
||||
)
|
||||
return ApiResponse(
|
||||
code=1,
|
||||
message=message,
|
||||
data=result,
|
||||
)
|
||||
|
||||
degraded_message = f"{message};检测端已在运行,改为直接发送控制指令"
|
||||
append_detect_job_event(
|
||||
job_summary["job_id"],
|
||||
event_type="job_dispatch_start_degraded",
|
||||
level="warning",
|
||||
message=degraded_message,
|
||||
payload={
|
||||
"cycle_token": cycle_token,
|
||||
"worker_process_count": int(degraded_snapshot.get("worker_process_count", 0) or 0),
|
||||
},
|
||||
)
|
||||
ok = True
|
||||
message = degraded_message
|
||||
|
||||
command_ok, command_message = send_worker_command(
|
||||
"start_detection",
|
||||
@@ -372,18 +450,19 @@ def start_detect(step_code: str | None = None) -> ApiResponse:
|
||||
settings_summary = _build_settings_summary(settings_payload)
|
||||
if command_ok:
|
||||
remote_dispatch = _dispatch_remote_detect_start(job_summary=job_summary, cycle_token=cycle_token)
|
||||
create_detect_run_snapshot(
|
||||
message=f"{message};{command_message}",
|
||||
runtime={
|
||||
"mode": snapshot.get("worker_mode", ""),
|
||||
"running": snapshot.get("worker_online", False),
|
||||
"process_count": snapshot.get("worker_process_count", 0),
|
||||
"latest_start_time": snapshot.get("worker_latest_start_time", ""),
|
||||
"message": snapshot.get("worker_runtime_message", ""),
|
||||
},
|
||||
progress=snapshot.get("progress", {}),
|
||||
settings_summary=settings_summary,
|
||||
)
|
||||
if local_worker_expected:
|
||||
create_detect_run_snapshot(
|
||||
message=f"{message};{command_message}",
|
||||
runtime={
|
||||
"mode": snapshot.get("worker_mode", ""),
|
||||
"running": snapshot.get("worker_online", False),
|
||||
"process_count": snapshot.get("worker_process_count", 0),
|
||||
"latest_start_time": snapshot.get("worker_latest_start_time", ""),
|
||||
"message": snapshot.get("worker_runtime_message", ""),
|
||||
},
|
||||
progress=snapshot.get("progress", {}),
|
||||
settings_summary=settings_summary,
|
||||
)
|
||||
append_detect_job_event(
|
||||
job_summary["job_id"],
|
||||
event_type="job_dispatch_remote_queued",
|
||||
@@ -411,11 +490,16 @@ def start_detect(step_code: str | None = None) -> ApiResponse:
|
||||
|
||||
|
||||
@router.post("/detect/stop", response_model=ApiResponse)
|
||||
def stop_detect() -> ApiResponse:
|
||||
def stop_detect(payload: dict | None = Body(default=None)) -> ApiResponse:
|
||||
active_job = get_active_detect_job_summary(event_limit=10)
|
||||
ok, message = send_worker_command("stop_detection")
|
||||
normalized_payload = {
|
||||
key: value
|
||||
for key, value in dict(payload or {}).items()
|
||||
if value not in (None, "")
|
||||
}
|
||||
ok, message = send_worker_command("stop_detection", payload=normalized_payload)
|
||||
cycle_token = str((active_job or {}).get("current_cycle_token") or "").strip()
|
||||
remote_dispatch = _dispatch_remote_detect_stop(active_job=active_job, cycle_token=cycle_token)
|
||||
remote_dispatch = _dispatch_remote_detect_stop(active_job=active_job, cycle_token=cycle_token, payload=normalized_payload)
|
||||
if active_job:
|
||||
append_detect_job_event(
|
||||
active_job["job_id"],
|
||||
|
||||
Reference in New Issue
Block a user