feat: stabilize multi-region runtime sync and worker orchestration

This commit is contained in:
root
2026-04-27 15:48:12 +08:00
parent 7cbde2aa78
commit 215a364891
137 changed files with 31931 additions and 1943 deletions

View File

@@ -7,6 +7,153 @@ from app.api.routes import detect as detect_route
class DetectApiRoutesTestCase(unittest.TestCase):
@patch("app.api.routes.detect.create_detect_run_snapshot")
@patch("app.api.routes.detect._dispatch_remote_detect_start")
@patch("app.api.routes.detect.get_settings_payload")
@patch("app.api.routes.detect.get_detect_status")
@patch("app.api.routes.detect.send_worker_command")
@patch("app.api.routes.detect.start_worker")
@patch("app.api.routes.detect.append_detect_job_event")
@patch("app.api.routes.detect.create_detect_job_if_needed")
def test_start_detect_skips_redundant_systemctl_when_worker_already_online(
self,
mock_create_job,
mock_append_event,
mock_start_worker,
mock_send_worker_command,
mock_get_detect_status,
mock_get_settings_payload,
mock_dispatch_remote,
mock_create_snapshot,
) -> None:
mock_create_job.return_value = {
"job_id": 30,
"job_code": "sync-overseas-30",
"status": "running",
"items_pending": 100,
"items_claimed": 0,
"items_running": 0,
"task_mode": "domain_pipeline",
"step_code": "",
}
mock_get_detect_status.return_value = {
"worker_mode": "linux-systemd",
"worker_online": True,
"worker_process_count": 60,
"worker_latest_start_time": "2026-04-24 23:30:00",
"worker_runtime_message": "running",
"progress": {},
}
mock_send_worker_command.return_value = (True, "已发送 Worker 控制指令: start_detection")
mock_get_settings_payload.return_value = {
"runtime": {"thread_count": 1000},
"proxy_config": {"proxy_enable": True, "allow_direct": True, "proxy_urls": ["a"]},
}
mock_dispatch_remote.return_value = {
"queued_jobs": [],
"queued_total": 0,
"failed_total": 0,
"target_summary": {"controller_nodes": [], "worker_nodes": []},
}
with patch.object(detect_route.settings, "node_region", "mainland"), patch.object(
detect_route.settings, "node_role", "control"
):
response = detect_route.start_detect()
self.assertEqual(0, response.code)
self.assertIn("检测端已在运行,跳过重复启动", response.message)
mock_start_worker.assert_not_called()
mock_send_worker_command.assert_called_once()
event_types = [call.kwargs.get("event_type") for call in mock_append_event.call_args_list]
self.assertIn("job_dispatch_start_skipped", event_types)
self.assertIn("job_dispatch_sent", event_types)
mock_create_snapshot.assert_called_once()
@patch("app.api.routes.detect.create_detect_run_snapshot")
@patch("app.api.routes.detect._dispatch_remote_detect_start")
@patch("app.api.routes.detect.get_settings_payload")
@patch("app.api.routes.detect.get_detect_status")
@patch("app.api.routes.detect.send_worker_command")
@patch("app.api.routes.detect.start_worker")
@patch("app.api.routes.detect.append_detect_job_event")
@patch("app.api.routes.detect.create_detect_job_if_needed")
def test_start_detect_falls_back_to_direct_command_when_worker_already_running(
self,
mock_create_job,
mock_append_event,
mock_start_worker,
mock_send_worker_command,
mock_get_detect_status,
mock_get_settings_payload,
mock_dispatch_remote,
mock_create_snapshot,
) -> None:
mock_create_job.return_value = {
"job_id": 31,
"job_code": "sync-overseas-31",
"status": "running",
"items_pending": 100,
"items_claimed": 0,
"items_running": 0,
"task_mode": "domain_pipeline",
"step_code": "",
}
mock_start_worker.return_value = (
False,
"domaincheck-worker 控制失败:当前运行用户没有免密 systemctl 权限,请为 API 进程授予对应 sudo/systemd 权限",
)
mock_send_worker_command.return_value = (True, "已发送 Worker 控制指令: start_detection")
mock_get_detect_status.side_effect = [
{
"worker_mode": "linux-systemd",
"worker_online": False,
"worker_process_count": 0,
"worker_latest_start_time": "",
"worker_runtime_message": "starting",
"progress": {},
},
{
"worker_mode": "linux-systemd",
"worker_online": True,
"worker_process_count": 60,
"worker_latest_start_time": "2026-04-24 23:30:00",
"worker_runtime_message": "running",
"progress": {},
},
{
"worker_mode": "linux-systemd",
"worker_online": True,
"worker_process_count": 60,
"worker_latest_start_time": "2026-04-24 23:30:00",
"worker_runtime_message": "running",
"progress": {},
},
]
mock_get_settings_payload.return_value = {
"runtime": {"thread_count": 1000},
"proxy_config": {"proxy_enable": True, "allow_direct": True, "proxy_urls": ["a"]},
}
mock_dispatch_remote.return_value = {
"queued_jobs": [],
"queued_total": 0,
"failed_total": 0,
"target_summary": {"controller_nodes": [], "worker_nodes": []},
}
with patch.object(detect_route.settings, "node_region", "mainland"), patch.object(
detect_route.settings, "node_role", "control"
):
response = detect_route.start_detect()
self.assertEqual(0, response.code)
self.assertIn("检测端已在运行,改为直接发送控制指令", response.message)
mock_send_worker_command.assert_called_once()
event_types = [call.kwargs.get("event_type") for call in mock_append_event.call_args_list]
self.assertIn("job_dispatch_start_degraded", event_types)
self.assertIn("job_dispatch_sent", event_types)
mock_create_snapshot.assert_called_once()
@patch("app.api.routes.detect.create_detect_run_snapshot")
@patch("app.api.routes.detect._dispatch_remote_detect_start")
@patch("app.api.routes.detect.get_settings_payload")
@@ -65,11 +212,94 @@ class DetectApiRoutesTestCase(unittest.TestCase):
mock_start_worker.assert_not_called()
mock_send_worker_command.assert_not_called()
mock_dispatch_remote.assert_called_once()
mock_create_snapshot.assert_called_once()
mock_create_snapshot.assert_not_called()
event_types = [call.kwargs.get("event_type") for call in mock_append_event.call_args_list]
self.assertIn("job_dispatch_requested", event_types)
self.assertIn("job_dispatch_skipped_local", event_types)
@patch("app.api.routes.detect.finalize_detect_run")
@patch("app.api.routes.detect.mark_detect_run_stopping")
@patch("app.api.routes.detect.get_settings_payload")
@patch("app.api.routes.detect.get_detect_status")
@patch("app.api.routes.detect._dispatch_remote_detect_stop")
@patch("app.api.routes.detect.send_worker_command")
@patch("app.api.routes.detect.get_active_detect_job_summary")
def test_stop_detect_forwards_target_payload(
self,
mock_get_active_job,
mock_send_worker_command,
mock_dispatch_remote_stop,
mock_get_detect_status,
mock_get_settings_payload,
mock_mark_detect_run_stopping,
mock_finalize_detect_run,
) -> None:
mock_get_active_job.return_value = {
"job_id": 29,
"job_code": "sync-overseas-29",
"status": "running",
"current_cycle_token": "cycle-29",
}
mock_send_worker_command.return_value = (True, "已发送 Worker 控制指令")
mock_dispatch_remote_stop.return_value = {
"queued_jobs": [{"node_code": "mainland-controller-01"}],
"queued_total": 1,
"failed_total": 0,
"target_summary": {"controller_nodes": ["mainland-controller-01"], "worker_nodes": []},
}
mock_get_detect_status.return_value = {
"worker_mode": "linux-systemd",
"worker_online": True,
"worker_process_count": 1,
"worker_latest_start_time": "",
"worker_runtime_message": "running",
"progress": {},
}
mock_get_settings_payload.return_value = {
"thread_count": 1000,
"node_thread_counts": {},
"process_count": 80,
"node_process_counts": {},
"proxy_config": {"proxy_enable": True, "allow_direct": False, "proxy_urls": ["a"]},
}
response = detect_route.stop_detect(payload={"target_node_codes": ["mainland-controller-01"]})
self.assertEqual(0, response.code)
mock_send_worker_command.assert_called_once_with(
"stop_detection",
payload={"target_node_codes": ["mainland-controller-01"]},
)
mock_dispatch_remote_stop.assert_called_once_with(
active_job=mock_get_active_job.return_value,
cycle_token="cycle-29",
payload={"target_node_codes": ["mainland-controller-01"]},
)
mock_mark_detect_run_stopping.assert_called_once()
mock_finalize_detect_run.assert_not_called()
@patch("app.api.routes.detect.create_ops_job")
@patch("app.api.routes.detect.list_managed_nodes")
def test_dispatch_remote_detect_stop_filters_target_nodes(self, mock_list_managed_nodes, mock_create_ops_job) -> None:
mock_list_managed_nodes.return_value = [
{"node_code": "mainland-controller-01", "region": "mainland", "role": "control", "last_seen_at": "2026-04-23T14:00:00", "is_enabled": True},
{"node_code": "mainland-worker-01", "region": "mainland", "role": "worker", "last_seen_at": "2026-04-23T14:00:00", "is_enabled": True},
]
mock_create_ops_job.return_value = (True, "queued", {})
result = detect_route._dispatch_remote_detect_stop(
active_job={"job_id": 29, "job_code": "sync-overseas-29"},
cycle_token="cycle-29",
payload={"target_node_codes": ["mainland-worker-01"]},
)
self.assertEqual(1, result["queued_total"])
self.assertEqual(["mainland-controller-01"], result["target_summary"]["controller_nodes"])
self.assertEqual(["mainland-worker-01"], result["target_summary"]["worker_nodes"])
mock_create_ops_job.assert_called_once()
create_payload = mock_create_ops_job.call_args.args[0]
self.assertEqual("mainland-worker-01", create_payload["target_node_code"])
if __name__ == "__main__":
unittest.main()