1187 lines
51 KiB
Python
1187 lines
51 KiB
Python
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from app.services.ops_service import get_ops_inspection_overview, get_ops_overview
|
|
|
|
|
|
class OpsServiceOverviewTests(unittest.TestCase):
|
|
@patch("app.services.ops_service.list_managed_nodes_with_agent_state")
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
def test_inspection_overview_fetches_managed_nodes_when_omitted(
|
|
self,
|
|
mock_list_ops_jobs,
|
|
mock_list_managed_nodes_with_agent_state,
|
|
) -> None:
|
|
mock_list_managed_nodes_with_agent_state.return_value = {
|
|
"nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"is_managed": True,
|
|
"is_enabled": True,
|
|
"is_agent_online": True,
|
|
"cluster_status": "online",
|
|
"cluster_is_effective_worker": True,
|
|
"cluster_detect_participating": True,
|
|
}
|
|
]
|
|
}
|
|
mock_list_ops_jobs.return_value = [
|
|
{
|
|
"id": 21,
|
|
"job_code": "ops-health-21",
|
|
"action": "health.snapshot",
|
|
"status": "success",
|
|
"target_node_code": "mainland-worker-01",
|
|
"created_at": "2026-04-17 10:00:00",
|
|
"updated_at": "2026-04-17 10:01:00",
|
|
}
|
|
]
|
|
|
|
overview = get_ops_inspection_overview(fetch_limit=20)
|
|
|
|
self.assertEqual(1, overview["eligible_nodes_total"])
|
|
self.assertEqual(1, overview["rows_total"])
|
|
self.assertEqual("attention", overview["status"])
|
|
self.assertEqual("待处理", overview["status_label"])
|
|
self.assertEqual("mainland", overview["rows"][0]["region"])
|
|
self.assertEqual("worker", overview["rows"][0]["role"])
|
|
self.assertEqual("online", overview["rows"][0]["cluster_status"])
|
|
self.assertIn("latest_health_snapshot", overview["rows"][0])
|
|
self.assertIn("latest_worker_logs", overview["rows"][0])
|
|
self.assertIn("latest_diagnostics", overview["rows"][0])
|
|
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
def test_inspection_overview_prioritizes_attention_and_supports_filters(self, mock_list_ops_jobs) -> None:
|
|
mock_list_ops_jobs.return_value = [
|
|
{
|
|
"id": 11,
|
|
"job_code": "ops-health-1",
|
|
"action": "health.snapshot",
|
|
"status": "success",
|
|
"target_node_code": "mainland-worker-01",
|
|
"created_at": "2026-04-17 10:00:00",
|
|
"updated_at": "2026-04-17 10:01:00",
|
|
},
|
|
{
|
|
"id": 12,
|
|
"job_code": "ops-logs-1",
|
|
"action": "logs.collect",
|
|
"status": "failed",
|
|
"target_node_code": "mainland-worker-01",
|
|
"created_at": "2026-04-17 10:02:00",
|
|
"updated_at": "2026-04-17 10:03:00",
|
|
"error_message": "journalctl permission denied",
|
|
"payload": {
|
|
"service_name": "domaincheck-worker",
|
|
"lines": 120,
|
|
},
|
|
},
|
|
]
|
|
|
|
managed_nodes = [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"is_managed": True,
|
|
"is_enabled": True,
|
|
"is_agent_online": True,
|
|
"cluster_status": "online",
|
|
"cluster_is_effective_worker": True,
|
|
"cluster_detect_participating": True,
|
|
},
|
|
{
|
|
"node_code": "mainland-controller-01",
|
|
"region": "mainland",
|
|
"role": "control",
|
|
"is_managed": True,
|
|
"is_enabled": True,
|
|
"is_agent_online": True,
|
|
"cluster_status": "online",
|
|
"cluster_is_effective_worker": True,
|
|
"cluster_detect_participating": False,
|
|
},
|
|
]
|
|
|
|
overview = get_ops_inspection_overview(managed_nodes=managed_nodes, fetch_limit=20)
|
|
|
|
self.assertEqual(2, overview["rows_total"])
|
|
self.assertEqual(1, overview["summary"]["attention"])
|
|
self.assertEqual(1, overview["summary"]["missing"])
|
|
self.assertEqual("attention", overview["status"])
|
|
self.assertEqual("待处理", overview["status_label"])
|
|
self.assertEqual(1, overview["counts"]["attention_nodes"])
|
|
self.assertEqual(1, overview["counts"]["missing_nodes"])
|
|
self.assertEqual("mainland-worker-01", overview["priority_queue"][0]["node_code"])
|
|
self.assertEqual("attention", overview["priority_queue"][0]["overall_status"])
|
|
self.assertEqual("terminal_failure", overview["priority_queue"][0]["problem_kind"])
|
|
self.assertEqual("failed", overview["rows"][0]["latest_worker_logs"]["status"])
|
|
self.assertEqual("成功", overview["rows"][0]["latest_health_snapshot"]["status_label"])
|
|
|
|
filtered = get_ops_inspection_overview(
|
|
managed_nodes=managed_nodes,
|
|
fetch_limit=20,
|
|
only_problem=True,
|
|
only_participating=True,
|
|
)
|
|
self.assertEqual(1, filtered["filtered_rows_total"])
|
|
self.assertEqual("mainland-worker-01", filtered["rows"][0]["node_code"])
|
|
self.assertEqual("attention", filtered["rows"][0]["overall_status"])
|
|
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
def test_inspection_overview_includes_visible_handover_gap_nodes(self, mock_list_ops_jobs) -> None:
|
|
mock_list_ops_jobs.return_value = []
|
|
|
|
managed_nodes = [
|
|
{
|
|
"node_code": "mainland-controller-01",
|
|
"region": "mainland",
|
|
"role": "control",
|
|
"is_managed": True,
|
|
"is_enabled": True,
|
|
"is_agent_online": True,
|
|
"cluster_status": "online",
|
|
"cluster_is_effective_worker": True,
|
|
"cluster_detect_participating": False,
|
|
},
|
|
{
|
|
"node_code": "mainland-worker-02",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"is_managed": False,
|
|
"is_enabled": False,
|
|
"is_agent_online": False,
|
|
"cluster_status": "online",
|
|
"cluster_is_effective_worker": True,
|
|
"cluster_detect_participating": False,
|
|
},
|
|
]
|
|
|
|
overview = get_ops_inspection_overview(managed_nodes=managed_nodes, fetch_limit=20)
|
|
row_map = {item["node_code"]: item for item in overview["rows"]}
|
|
|
|
self.assertEqual(2, overview["visible_nodes_total"])
|
|
self.assertEqual(1, overview["eligible_nodes_total"])
|
|
self.assertEqual(1, overview["handover_gap_nodes_total"])
|
|
self.assertEqual("attention", row_map["mainland-worker-02"]["overall_status"])
|
|
self.assertEqual("handover_gap", row_map["mainland-worker-02"]["problem_kind"])
|
|
self.assertEqual("handover_first_gap", row_map["mainland-worker-02"]["recommended_action_code"])
|
|
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
def test_inspection_overview_marks_participating_nodes_without_inspection_as_attention(self, mock_list_ops_jobs) -> None:
|
|
mock_list_ops_jobs.return_value = []
|
|
|
|
managed_nodes = [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"is_managed": True,
|
|
"is_enabled": True,
|
|
"is_agent_online": True,
|
|
"cluster_status": "busy",
|
|
"cluster_is_effective_worker": True,
|
|
"cluster_detect_participating": True,
|
|
}
|
|
]
|
|
|
|
overview = get_ops_inspection_overview(
|
|
managed_nodes=managed_nodes,
|
|
fetch_limit=20,
|
|
only_participating=True,
|
|
only_problem=True,
|
|
)
|
|
|
|
self.assertEqual(1, overview["filtered_rows_total"])
|
|
self.assertEqual("attention", overview["rows"][0]["overall_status"])
|
|
self.assertEqual("participating_no_inspection", overview["rows"][0]["problem_kind"])
|
|
self.assertEqual("open_worker_logs_participating", overview["rows"][0]["recommended_action_code"])
|
|
self.assertEqual("attention", overview["status"])
|
|
|
|
@patch("app.services.ops_service.build_release_execution_mode_recommendation")
|
|
@patch("app.services.ops_service.get_release_launchpad")
|
|
@patch("app.services.ops_service.get_latest_release")
|
|
@patch("app.services.ops_service.get_release_summary")
|
|
@patch("app.services.ops_service.list_release_rollouts")
|
|
@patch("app.services.ops_service.get_recent_ops_playbook_runs")
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
@patch("app.services.ops_service.list_managed_nodes_with_agent_state")
|
|
@patch("app.services.ops_service.get_ops_job_summary")
|
|
@patch("app.services.ops_service.get_sync_summary")
|
|
@patch("app.services.ops_service.get_runtime_status")
|
|
def test_ops_overview_uses_top_driver_recommendation_as_summary(
|
|
self,
|
|
mock_get_runtime_status,
|
|
mock_get_sync_summary,
|
|
mock_get_ops_job_summary,
|
|
mock_list_managed_nodes_with_agent_state,
|
|
mock_list_ops_jobs,
|
|
mock_get_recent_ops_playbook_runs,
|
|
mock_list_release_rollouts,
|
|
mock_get_release_summary,
|
|
mock_get_latest_release,
|
|
mock_get_release_launchpad,
|
|
mock_build_release_execution_mode_recommendation,
|
|
) -> None:
|
|
mock_get_runtime_status.return_value = {
|
|
"readiness": {
|
|
"status": "attention",
|
|
"summary": "存在接管缺口",
|
|
},
|
|
"detect": {
|
|
"log_sync": {
|
|
"enabled": False,
|
|
"mode": "key",
|
|
"line_count": 0,
|
|
"source_node_count": 0,
|
|
"source_nodes": [],
|
|
"last_at": "",
|
|
"last_line": "",
|
|
"preview_lines": [],
|
|
},
|
|
"participation_summary": {},
|
|
"participating_nodes": [],
|
|
"non_participating_nodes": [],
|
|
"active_job": {},
|
|
"phase_label": "",
|
|
"phase_detail": "",
|
|
"recent_event": "",
|
|
"recent_warning": "",
|
|
},
|
|
"cluster": {
|
|
"summary": {
|
|
"online_worker_nodes": 1,
|
|
"online_control_nodes": 1,
|
|
"dedicated_online_worker_nodes": 1,
|
|
"busy_nodes": [],
|
|
},
|
|
"nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"status": "online",
|
|
"current_load": 0,
|
|
"hostname": "worker-01",
|
|
"last_heartbeat_at": "2026-04-17 10:00:00",
|
|
"is_effective_worker": True,
|
|
"detect_participating": False,
|
|
}
|
|
],
|
|
},
|
|
}
|
|
mock_get_sync_summary.return_value = {}
|
|
mock_get_ops_job_summary.return_value = {"total": 0, "queued": 0, "running": 0, "awaiting_approval": 0}
|
|
mock_list_managed_nodes_with_agent_state.return_value = {
|
|
"nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"is_managed": False,
|
|
"is_enabled": False,
|
|
"is_agent_online": False,
|
|
"cluster_status": "online",
|
|
"cluster_is_effective_worker": True,
|
|
"cluster_detect_participating": False,
|
|
}
|
|
],
|
|
"summary": {
|
|
"total": 1,
|
|
"managed_total": 0,
|
|
"managed_enabled": 0,
|
|
"online": 0,
|
|
"stale": 0,
|
|
"pending_bootstrap": 1,
|
|
"runtime_only": 1,
|
|
"unmanaged": 1,
|
|
"token_issue": 0,
|
|
"status_counts": {},
|
|
},
|
|
}
|
|
mock_list_ops_jobs.return_value = []
|
|
mock_get_recent_ops_playbook_runs.return_value = {"runs": []}
|
|
mock_list_release_rollouts.return_value = []
|
|
mock_get_release_summary.return_value = {}
|
|
mock_get_latest_release.return_value = {}
|
|
mock_get_release_launchpad.return_value = {"launchpad_status": {"status": "attention"}}
|
|
mock_build_release_execution_mode_recommendation.return_value = {
|
|
"recommended_mode": "remote-agent",
|
|
"recommended_mode_label": "remote-agent",
|
|
"reason": "",
|
|
"options": [],
|
|
"recommended_gate": {
|
|
"status": "release_not_ready",
|
|
"summary": "",
|
|
"target_nodes": [],
|
|
"target_node_codes": [],
|
|
"blocking_reasons": [],
|
|
"warning_reasons": [],
|
|
"execution_mode": "remote-agent",
|
|
"execution_mode_label": "remote-agent",
|
|
"operational_readiness": {"rows": [], "summary": {}},
|
|
},
|
|
"gates": {},
|
|
}
|
|
|
|
overview = get_ops_overview()
|
|
|
|
mock_list_managed_nodes_with_agent_state.assert_any_call(
|
|
participation_payload=mock_get_runtime_status.return_value["detect"]
|
|
)
|
|
self.assertEqual("driver_recommendations", overview["recommendation"]["source"])
|
|
self.assertEqual("handover-gap", overview["recommendation"]["key"])
|
|
self.assertEqual("先补接管缺口", overview["recommendation"]["priority"])
|
|
self.assertIn("有效执行节点", overview["recommendation"]["summary"])
|
|
self.assertEqual("纳管首台节点", overview["recommendation"]["primary_label"])
|
|
self.assertEqual("handover_first_gap", overview["recommendation"]["primary_action_code"])
|
|
self.assertEqual("attention", overview["release_hub"]["launchpad"]["launchpad_status"]["status"])
|
|
|
|
@patch("app.services.ops_service.build_release_execution_mode_recommendation")
|
|
@patch("app.services.ops_service.get_release_launchpad")
|
|
@patch("app.services.ops_service.get_latest_release")
|
|
@patch("app.services.ops_service.get_release_summary")
|
|
@patch("app.services.ops_service.list_release_rollouts")
|
|
@patch("app.services.ops_service.get_recent_ops_playbook_runs")
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
@patch("app.services.ops_service.list_managed_nodes_with_agent_state")
|
|
@patch("app.services.ops_service.get_ops_job_summary")
|
|
@patch("app.services.ops_service.get_sync_summary")
|
|
@patch("app.services.ops_service.get_runtime_status")
|
|
def test_ops_overview_prefers_launchpad_bootstrap_recovery_for_handover_gap(
|
|
self,
|
|
mock_get_runtime_status,
|
|
mock_get_sync_summary,
|
|
mock_get_ops_job_summary,
|
|
mock_list_managed_nodes_with_agent_state,
|
|
mock_list_ops_jobs,
|
|
mock_get_recent_ops_playbook_runs,
|
|
mock_list_release_rollouts,
|
|
mock_get_release_summary,
|
|
mock_get_latest_release,
|
|
mock_get_release_launchpad,
|
|
mock_build_release_execution_mode_recommendation,
|
|
) -> None:
|
|
mock_get_runtime_status.return_value = {
|
|
"readiness": {"status": "attention", "summary": "当前还需补执行面接管"},
|
|
"worker": {"running": True},
|
|
"sync_agent": {"running": True},
|
|
"detect": {"participation_summary": {"summary": ""}},
|
|
}
|
|
mock_get_sync_summary.return_value = {}
|
|
mock_get_ops_job_summary.return_value = {}
|
|
mock_list_managed_nodes_with_agent_state.return_value = {
|
|
"nodes": [
|
|
{
|
|
"node_code": "mainland-worker-02",
|
|
"role": "worker",
|
|
"is_managed": False,
|
|
"is_enabled": False,
|
|
"is_agent_online": False,
|
|
"cluster_status": "online",
|
|
"cluster_is_effective_worker": True,
|
|
"is_current_participant": False,
|
|
}
|
|
],
|
|
"summary": {
|
|
"total": 1,
|
|
"managed_total": 0,
|
|
"managed_enabled": 0,
|
|
"online": 1,
|
|
"stale": 0,
|
|
"pending_bootstrap": 1,
|
|
"runtime_only": 0,
|
|
"unmanaged": 1,
|
|
"token_issue": 0,
|
|
"status_counts": {},
|
|
},
|
|
}
|
|
mock_list_ops_jobs.return_value = []
|
|
mock_get_recent_ops_playbook_runs.return_value = {"runs": []}
|
|
mock_list_release_rollouts.return_value = []
|
|
mock_get_release_summary.return_value = {}
|
|
mock_get_latest_release.return_value = {}
|
|
mock_get_release_launchpad.return_value = {
|
|
"launchpad_status": {
|
|
"status": "attention",
|
|
"status_label": "待补执行面",
|
|
"summary": "当前节点仍处于接入阶段,建议先签发 onboarding.bootstrap。",
|
|
"recommended_action_code": "bootstrap_run",
|
|
},
|
|
"worker_rollout_preview": {
|
|
"policy_preview": {
|
|
"release_gate": {
|
|
"operational_readiness": {
|
|
"rows": [
|
|
{
|
|
"node_code": "mainland-worker-02",
|
|
"execution_ready": False,
|
|
"recovery_action": "bootstrap_run",
|
|
"recovery_summary": "当前节点仍处于接入阶段,建议先签发 onboarding.bootstrap。",
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
mock_build_release_execution_mode_recommendation.return_value = {
|
|
"recommended_mode": "remote-agent",
|
|
"recommended_mode_label": "remote-agent",
|
|
"reason": "",
|
|
"options": [],
|
|
"recommended_gate": {
|
|
"status": "release_not_ready",
|
|
"summary": "",
|
|
"target_nodes": [],
|
|
"target_node_codes": [],
|
|
"blocking_reasons": [],
|
|
"warning_reasons": [],
|
|
"execution_mode": "remote-agent",
|
|
"execution_mode_label": "remote-agent",
|
|
"operational_readiness": {"rows": [], "summary": {}},
|
|
},
|
|
"gates": {},
|
|
}
|
|
|
|
overview = get_ops_overview()
|
|
|
|
self.assertEqual("handover-gap-bootstrap_run", overview["recommendation"]["key"])
|
|
self.assertEqual("bootstrap_run", overview["recommendation"]["primary_action_code"])
|
|
self.assertEqual("跑接入收口", overview["recommendation"]["primary_label"])
|
|
self.assertEqual(["mainland-worker-02"], overview["recommendation"]["primary_node_codes"])
|
|
|
|
@patch("app.services.ops_service.build_release_execution_mode_recommendation")
|
|
@patch("app.services.ops_service.get_release_launchpad")
|
|
@patch("app.services.ops_service.get_latest_release")
|
|
@patch("app.services.ops_service.get_release_summary")
|
|
@patch("app.services.ops_service.list_release_rollouts")
|
|
@patch("app.services.ops_service.get_recent_ops_playbook_runs")
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
@patch("app.services.ops_service.list_managed_nodes_with_agent_state")
|
|
@patch("app.services.ops_service.get_ops_job_summary")
|
|
@patch("app.services.ops_service.get_sync_summary")
|
|
@patch("app.services.ops_service.get_runtime_status")
|
|
def test_ops_overview_exposes_remote_log_preview_lines(
|
|
self,
|
|
mock_get_runtime_status,
|
|
mock_get_sync_summary,
|
|
mock_get_ops_job_summary,
|
|
mock_list_managed_nodes_with_agent_state,
|
|
mock_list_ops_jobs,
|
|
mock_get_recent_ops_playbook_runs,
|
|
mock_list_release_rollouts,
|
|
mock_get_release_summary,
|
|
mock_get_latest_release,
|
|
mock_get_release_launchpad,
|
|
mock_build_release_execution_mode_recommendation,
|
|
) -> None:
|
|
mock_get_runtime_status.return_value = {
|
|
"readiness": {"status": "ready", "summary": "ok"},
|
|
"detect": {
|
|
"log_sync": {
|
|
"enabled": True,
|
|
"mode": "key",
|
|
"line_count": 2,
|
|
"source_node_count": 1,
|
|
"source_nodes": ["mainland-worker-01"],
|
|
"last_at": "2026-04-17 10:01:00",
|
|
"last_line": "[2026-04-17 10:01:00] [mainland-worker-01] 域名检测完成: alpha.com",
|
|
"preview_lines": [
|
|
"[2026-04-17 10:00:00] [mainland-worker-01] 开始检测域名: alpha.com",
|
|
"[2026-04-17 10:01:00] [mainland-worker-01] 域名检测完成: alpha.com",
|
|
],
|
|
},
|
|
"participation_summary": {
|
|
"participating_nodes": 1,
|
|
"dispatch_active_nodes": 1,
|
|
"recent_only_nodes": 0,
|
|
"non_participating_nodes": 0,
|
|
"standby_nodes": 0,
|
|
"load_syncing_nodes": 0,
|
|
"effective_online_nodes": 1,
|
|
"dedicated_worker_nodes": 1,
|
|
"controller_worker_nodes": 0,
|
|
"dispatch_active_node_codes": ["mainland-worker-01"],
|
|
"recent_only_node_codes": [],
|
|
"non_participating_node_codes": [],
|
|
"standby_node_codes": [],
|
|
"load_syncing_node_codes": [],
|
|
"summary": "有效执行节点 1 台;正在执行/领任务 1 台;近窗刚有吞吐 0 台;在线但未参与 0 台",
|
|
},
|
|
"participating_nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"status": "busy",
|
|
"current_load": 1,
|
|
"participation_state": "running",
|
|
"participation_label": "执行中",
|
|
"participation_reason": "当前正在执行 1 项检测任务。",
|
|
"is_dispatch_active": True,
|
|
}
|
|
],
|
|
"non_participating_nodes": [],
|
|
"active_job": {"job_code": "detect-001", "status": "running"},
|
|
"phase_label": "运行中",
|
|
"phase_detail": "测试中",
|
|
"recent_event": "",
|
|
"recent_warning": "",
|
|
},
|
|
"cluster": {
|
|
"summary": {
|
|
"online_worker_nodes": 1,
|
|
"online_control_nodes": 1,
|
|
"dedicated_online_worker_nodes": 1,
|
|
"busy_nodes": ["mainland-worker-01"],
|
|
},
|
|
"nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"status": "busy",
|
|
"current_load": 1,
|
|
"hostname": "worker-01",
|
|
"last_heartbeat_at": "2026-04-17 10:01:00",
|
|
"is_effective_worker": True,
|
|
"detect_participating": True,
|
|
}
|
|
],
|
|
},
|
|
}
|
|
mock_get_sync_summary.return_value = {}
|
|
mock_get_ops_job_summary.return_value = {"total": 0, "queued": 0, "running": 0, "awaiting_approval": 0}
|
|
mock_list_managed_nodes_with_agent_state.return_value = {
|
|
"nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"is_managed": True,
|
|
"is_enabled": True,
|
|
"is_agent_online": True,
|
|
"cluster_status": "busy",
|
|
"cluster_is_effective_worker": True,
|
|
"cluster_detect_participating": True,
|
|
}
|
|
],
|
|
"summary": {
|
|
"total": 1,
|
|
"managed_total": 1,
|
|
"managed_enabled": 1,
|
|
"online": 1,
|
|
"stale": 0,
|
|
"pending_bootstrap": 0,
|
|
"runtime_only": 0,
|
|
"unmanaged": 0,
|
|
"token_issue": 0,
|
|
"status_counts": {},
|
|
"participating": 1,
|
|
"dispatch_active": 1,
|
|
"recent_only": 0,
|
|
"standby": 0,
|
|
"load_syncing": 0,
|
|
"non_participating": 0,
|
|
},
|
|
}
|
|
mock_list_ops_jobs.return_value = []
|
|
mock_get_recent_ops_playbook_runs.return_value = {"runs": []}
|
|
mock_list_release_rollouts.return_value = []
|
|
mock_get_release_summary.return_value = {}
|
|
mock_get_latest_release.return_value = {}
|
|
mock_get_release_launchpad.return_value = {"launchpad_status": {"status": "ready"}}
|
|
mock_build_release_execution_mode_recommendation.return_value = {
|
|
"recommended_mode": "remote-agent",
|
|
"recommended_mode_label": "remote-agent",
|
|
"reason": "",
|
|
"options": [],
|
|
"recommended_gate": {
|
|
"status": "release_not_ready",
|
|
"summary": "",
|
|
"target_nodes": [],
|
|
"target_node_codes": [],
|
|
"blocking_reasons": [],
|
|
"warning_reasons": [],
|
|
"execution_mode": "remote-agent",
|
|
"execution_mode_label": "remote-agent",
|
|
"operational_readiness": {"rows": [], "summary": {}},
|
|
},
|
|
"gates": {},
|
|
}
|
|
|
|
overview = get_ops_overview()
|
|
execution_scene = overview["execution_scene"]
|
|
log_sync = overview["execution_scene"]["log_sync"]
|
|
|
|
self.assertEqual("有效执行节点 1 台;正在执行/领任务 1 台;近窗刚有吞吐 0 台;在线但未参与 0 台", execution_scene["summary"])
|
|
self.assertEqual(1, execution_scene["counts"]["dispatch_active"])
|
|
self.assertEqual(0, execution_scene["counts"]["recent_only"])
|
|
self.assertEqual(0, execution_scene["counts"]["standby"])
|
|
self.assertEqual(0, execution_scene["counts"]["load_syncing"])
|
|
self.assertEqual(1, len(execution_scene["dispatch_active_nodes"]))
|
|
self.assertEqual("dispatch_active", execution_scene["dispatch_active_nodes"][0]["participation_bucket"])
|
|
self.assertEqual([], execution_scene["recent_only_nodes"])
|
|
self.assertEqual([], execution_scene["load_syncing_nodes"])
|
|
self.assertTrue(log_sync["enabled"])
|
|
self.assertEqual("key", log_sync["mode"])
|
|
self.assertEqual(2, log_sync["preview_line_count"])
|
|
self.assertEqual(
|
|
[
|
|
"[2026-04-17 10:00:00] [mainland-worker-01] 开始检测域名: alpha.com",
|
|
"[2026-04-17 10:01:00] [mainland-worker-01] 域名检测完成: alpha.com",
|
|
],
|
|
log_sync["preview_lines"],
|
|
)
|
|
self.assertEqual(1, log_sync["covered_participating_node_count"])
|
|
self.assertEqual([], log_sync["missing_participating_nodes"])
|
|
self.assertEqual(1, len(log_sync["source_node_summaries"]))
|
|
self.assertEqual("mainland-worker-01", log_sync["source_node_summaries"][0]["node_code"])
|
|
self.assertEqual(2, log_sync["source_node_summaries"][0]["line_count"])
|
|
self.assertEqual(2, log_sync["source_node_summaries"][0]["key_line_count"])
|
|
self.assertEqual([], log_sync["missing_participating_node_summaries"])
|
|
self.assertEqual("ready", overview["release_hub"]["launchpad"]["launchpad_status"]["status"])
|
|
|
|
@patch("app.services.ops_service.build_release_execution_mode_recommendation")
|
|
@patch("app.services.ops_service.get_release_launchpad")
|
|
@patch("app.services.ops_service.get_latest_release")
|
|
@patch("app.services.ops_service.get_release_summary")
|
|
@patch("app.services.ops_service.list_release_rollouts")
|
|
@patch("app.services.ops_service.get_recent_ops_playbook_runs")
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
@patch("app.services.ops_service.list_managed_nodes_with_agent_state")
|
|
@patch("app.services.ops_service.get_ops_job_summary")
|
|
@patch("app.services.ops_service.get_sync_summary")
|
|
@patch("app.services.ops_service.get_runtime_status")
|
|
def test_ops_overview_marks_missing_remote_log_reason_code(
|
|
self,
|
|
mock_get_runtime_status,
|
|
mock_get_sync_summary,
|
|
mock_get_ops_job_summary,
|
|
mock_list_managed_nodes_with_agent_state,
|
|
mock_list_ops_jobs,
|
|
mock_get_recent_ops_playbook_runs,
|
|
mock_list_release_rollouts,
|
|
mock_get_release_summary,
|
|
mock_get_latest_release,
|
|
mock_get_release_launchpad,
|
|
mock_build_release_execution_mode_recommendation,
|
|
) -> None:
|
|
mock_get_runtime_status.return_value = {
|
|
"readiness": {"status": "ready", "summary": "ok"},
|
|
"detect": {
|
|
"log_sync": {
|
|
"enabled": True,
|
|
"mode": "key",
|
|
"line_count": 0,
|
|
"source_node_count": 0,
|
|
"source_nodes": [],
|
|
"last_at": "",
|
|
"last_line": "",
|
|
"preview_lines": [],
|
|
},
|
|
"participation_summary": {
|
|
"participating_nodes": 1,
|
|
"dispatch_active_nodes": 1,
|
|
"recent_only_nodes": 0,
|
|
"non_participating_nodes": 0,
|
|
"standby_nodes": 0,
|
|
"load_syncing_nodes": 0,
|
|
"effective_online_nodes": 1,
|
|
"dedicated_worker_nodes": 1,
|
|
"controller_worker_nodes": 0,
|
|
"dispatch_active_node_codes": ["mainland-worker-01"],
|
|
"recent_only_node_codes": [],
|
|
"non_participating_node_codes": [],
|
|
"standby_node_codes": [],
|
|
"load_syncing_node_codes": [],
|
|
"summary": "有效执行节点 1 台;正在执行/领任务 1 台;近窗刚有吞吐 0 台;在线但未参与 0 台",
|
|
},
|
|
"participating_nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"status": "busy",
|
|
"current_load": 2,
|
|
"participation_state": "claimed",
|
|
"participation_label": "已领待跑",
|
|
"participation_reason": "已领取 2 项任务,等待线程继续执行。",
|
|
"is_dispatch_active": True,
|
|
}
|
|
],
|
|
"non_participating_nodes": [],
|
|
"active_job": {"job_code": "detect-001", "status": "running"},
|
|
"phase_label": "运行中",
|
|
"phase_detail": "等待样本",
|
|
"recent_event": "",
|
|
"recent_warning": "",
|
|
},
|
|
"cluster": {"summary": {}, "nodes": []},
|
|
}
|
|
mock_get_sync_summary.return_value = {}
|
|
mock_get_ops_job_summary.return_value = {"total": 0, "queued": 0, "running": 0, "awaiting_approval": 0}
|
|
mock_list_managed_nodes_with_agent_state.return_value = {"nodes": [], "summary": {}}
|
|
mock_list_ops_jobs.return_value = []
|
|
mock_get_recent_ops_playbook_runs.return_value = {"runs": []}
|
|
mock_list_release_rollouts.return_value = []
|
|
mock_get_release_summary.return_value = {}
|
|
mock_get_latest_release.return_value = {}
|
|
mock_get_release_launchpad.return_value = {"launchpad_status": {"status": "ready"}}
|
|
mock_build_release_execution_mode_recommendation.return_value = {
|
|
"recommended_mode": "remote-agent",
|
|
"recommended_mode_label": "remote-agent",
|
|
"reason": "",
|
|
"options": [],
|
|
"recommended_gate": {
|
|
"status": "release_not_ready",
|
|
"summary": "",
|
|
"target_nodes": [],
|
|
"target_node_codes": [],
|
|
"blocking_reasons": [],
|
|
"warning_reasons": [],
|
|
"execution_mode": "remote-agent",
|
|
"execution_mode_label": "remote-agent",
|
|
"operational_readiness": {"rows": [], "summary": {}},
|
|
},
|
|
"gates": {},
|
|
}
|
|
|
|
overview = get_ops_overview()
|
|
missing_rows = overview["execution_scene"]["log_sync"]["missing_participating_node_summaries"]
|
|
|
|
self.assertEqual(1, len(missing_rows))
|
|
self.assertEqual("waiting_sample", missing_rows[0]["missing_reason_code"])
|
|
self.assertEqual("dispatch_active", overview["execution_scene"]["participating_nodes"][0]["participation_bucket"])
|
|
|
|
@patch("app.services.ops_service.get_ops_activity_stream")
|
|
@patch("app.services.ops_service.build_release_execution_mode_recommendation")
|
|
@patch("app.services.ops_service.get_release_launchpad")
|
|
@patch("app.services.ops_service.get_latest_release")
|
|
@patch("app.services.ops_service.get_release_summary")
|
|
@patch("app.services.ops_service.list_release_rollouts")
|
|
@patch("app.services.ops_service.get_recent_ops_playbook_runs")
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
@patch("app.services.ops_service.list_managed_nodes_with_agent_state")
|
|
@patch("app.services.ops_service.get_ops_job_summary")
|
|
@patch("app.services.ops_service.get_sync_summary")
|
|
@patch("app.services.ops_service.get_runtime_status")
|
|
def test_ops_overview_prioritizes_node_agent_dead_letter_queue(
|
|
self,
|
|
mock_get_runtime_status,
|
|
mock_get_sync_summary,
|
|
mock_get_ops_job_summary,
|
|
mock_list_managed_nodes_with_agent_state,
|
|
mock_list_ops_jobs,
|
|
mock_get_recent_ops_playbook_runs,
|
|
mock_list_release_rollouts,
|
|
mock_get_release_summary,
|
|
mock_get_latest_release,
|
|
mock_get_release_launchpad,
|
|
mock_build_release_execution_mode_recommendation,
|
|
mock_get_ops_activity_stream,
|
|
) -> None:
|
|
mock_get_runtime_status.return_value = {
|
|
"readiness": {"status": "ready", "summary": "ok"},
|
|
"detect": {
|
|
"log_sync": {
|
|
"enabled": False,
|
|
"mode": "key",
|
|
"line_count": 0,
|
|
"source_node_count": 0,
|
|
"source_nodes": [],
|
|
"last_at": "",
|
|
"last_line": "",
|
|
"preview_lines": [],
|
|
},
|
|
"participation_summary": {},
|
|
"participating_nodes": [],
|
|
"non_participating_nodes": [],
|
|
"active_job": {},
|
|
"phase_label": "",
|
|
"phase_detail": "",
|
|
"recent_event": "",
|
|
"recent_warning": "",
|
|
},
|
|
"cluster": {
|
|
"summary": {
|
|
"online_worker_nodes": 1,
|
|
"online_control_nodes": 1,
|
|
"dedicated_online_worker_nodes": 1,
|
|
"busy_nodes": [],
|
|
},
|
|
"nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"status": "online",
|
|
"current_load": 0,
|
|
"hostname": "worker-01",
|
|
"last_heartbeat_at": "2026-04-18 05:10:00",
|
|
"is_effective_worker": True,
|
|
"detect_participating": False,
|
|
}
|
|
],
|
|
},
|
|
}
|
|
mock_get_sync_summary.return_value = {}
|
|
mock_get_ops_job_summary.return_value = {"total": 0, "queued": 0, "running": 0, "awaiting_approval": 0}
|
|
mock_list_managed_nodes_with_agent_state.return_value = {
|
|
"nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"is_managed": True,
|
|
"is_enabled": True,
|
|
"is_agent_online": True,
|
|
"cluster_status": "online",
|
|
"cluster_is_effective_worker": True,
|
|
"cluster_detect_participating": False,
|
|
"delivery_queue_state": "dead_letter",
|
|
"delivery_queue_label": "死信 2",
|
|
"delivery_queue_reason": "当前存在 2 条死信记录,建议优先查看节点日志或诊断编排。",
|
|
"delivery_queue_pending_count": 1,
|
|
"delivery_queue_dead_letter_count": 2,
|
|
}
|
|
],
|
|
"summary": {
|
|
"total": 1,
|
|
"managed_total": 1,
|
|
"managed_enabled": 1,
|
|
"online": 1,
|
|
"stale": 0,
|
|
"pending_bootstrap": 0,
|
|
"runtime_only": 0,
|
|
"unmanaged": 0,
|
|
"token_issue": 0,
|
|
"queue_retrying_nodes": 0,
|
|
"queue_dead_letter_nodes": 1,
|
|
"queue_pending_records": 1,
|
|
"queue_dead_letter_records": 2,
|
|
"status_counts": {},
|
|
},
|
|
}
|
|
mock_list_ops_jobs.return_value = []
|
|
mock_get_recent_ops_playbook_runs.return_value = {"runs": []}
|
|
mock_list_release_rollouts.return_value = []
|
|
mock_get_release_summary.return_value = {}
|
|
mock_get_latest_release.return_value = {}
|
|
mock_get_release_launchpad.return_value = {"launchpad_status": {}}
|
|
mock_build_release_execution_mode_recommendation.return_value = {
|
|
"recommended_mode": "remote-agent",
|
|
"recommended_mode_label": "remote-agent",
|
|
"reason": "",
|
|
"options": [],
|
|
"recommended_gate": {
|
|
"status": "ready",
|
|
"summary": "",
|
|
"target_nodes": [],
|
|
"target_node_codes": [],
|
|
"blocking_reasons": [],
|
|
"warning_reasons": [],
|
|
"execution_mode": "remote-agent",
|
|
"execution_mode_label": "remote-agent",
|
|
"operational_readiness": {"rows": [], "summary": {}},
|
|
"release": {"id": 0},
|
|
},
|
|
"gates": {},
|
|
}
|
|
mock_get_ops_activity_stream.return_value = {"items": []}
|
|
|
|
overview = get_ops_overview()
|
|
|
|
self.assertEqual("node-agent-dead-letter", overview["recommendation"]["key"])
|
|
self.assertEqual("先处理 Agent 死信", overview["recommendation"]["priority"])
|
|
self.assertEqual("replay_delivery_queue", overview["recommendation"]["primary_action_code"])
|
|
self.assertEqual(
|
|
"delivery.queue.replay",
|
|
overview["recommendation"]["primary_action_payload"]["template_key"],
|
|
)
|
|
self.assertEqual(1, overview["managed_nodes"]["queue_dead_letter_nodes"])
|
|
self.assertEqual(2, overview["managed_nodes"]["queue_dead_letter_records"])
|
|
|
|
@patch("app.services.ops_service.get_ops_activity_stream")
|
|
@patch("app.services.ops_service.build_release_execution_mode_recommendation")
|
|
@patch("app.services.ops_service.get_release_launchpad")
|
|
@patch("app.services.ops_service.get_latest_release")
|
|
@patch("app.services.ops_service.get_release_summary")
|
|
@patch("app.services.ops_service.list_release_rollouts")
|
|
@patch("app.services.ops_service.get_recent_ops_playbook_runs")
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
@patch("app.services.ops_service.list_managed_nodes_with_agent_state")
|
|
@patch("app.services.ops_service.get_ops_job_summary")
|
|
@patch("app.services.ops_service.get_sync_summary")
|
|
@patch("app.services.ops_service.get_runtime_status")
|
|
def test_ops_overview_prioritizes_start_delivery_gap_from_activity_stream(
|
|
self,
|
|
mock_get_runtime_status,
|
|
mock_get_sync_summary,
|
|
mock_get_ops_job_summary,
|
|
mock_list_managed_nodes_with_agent_state,
|
|
mock_list_ops_jobs,
|
|
mock_get_recent_ops_playbook_runs,
|
|
mock_list_release_rollouts,
|
|
mock_get_release_summary,
|
|
mock_get_latest_release,
|
|
mock_get_release_launchpad,
|
|
mock_build_release_execution_mode_recommendation,
|
|
mock_get_ops_activity_stream,
|
|
) -> None:
|
|
mock_get_runtime_status.return_value = {
|
|
"readiness": {"status": "ready", "summary": "ok"},
|
|
"detect": {
|
|
"log_sync": {
|
|
"enabled": True,
|
|
"mode": "key",
|
|
"line_count": 12,
|
|
"source_node_count": 1,
|
|
"source_nodes": ["mainland-worker-01"],
|
|
"last_at": "2026-04-18 11:00:00",
|
|
"last_line": "ok",
|
|
"preview_lines": ["ok"],
|
|
},
|
|
"participation_summary": {},
|
|
"participating_nodes": [],
|
|
"non_participating_nodes": [],
|
|
"active_job": {},
|
|
"phase_label": "",
|
|
"phase_detail": "",
|
|
"recent_event": "",
|
|
"recent_warning": "",
|
|
},
|
|
"cluster": {"summary": {}, "nodes": []},
|
|
}
|
|
mock_get_sync_summary.return_value = {}
|
|
mock_get_ops_job_summary.return_value = {"total": 0, "queued": 0, "running": 0, "awaiting_approval": 0}
|
|
mock_list_managed_nodes_with_agent_state.return_value = {"nodes": [], "summary": {}}
|
|
mock_list_ops_jobs.return_value = []
|
|
mock_get_recent_ops_playbook_runs.return_value = {"runs": []}
|
|
mock_list_release_rollouts.return_value = []
|
|
mock_get_release_summary.return_value = {}
|
|
mock_get_latest_release.return_value = {}
|
|
mock_get_release_launchpad.return_value = {"launchpad_status": {}}
|
|
mock_build_release_execution_mode_recommendation.return_value = {
|
|
"recommended_mode": "remote-agent",
|
|
"recommended_mode_label": "remote-agent",
|
|
"reason": "",
|
|
"options": [],
|
|
"recommended_gate": {
|
|
"status": "ready",
|
|
"summary": "",
|
|
"target_nodes": [],
|
|
"target_node_codes": [],
|
|
"blocking_reasons": [],
|
|
"warning_reasons": [],
|
|
"execution_mode": "remote-agent",
|
|
"execution_mode_label": "remote-agent",
|
|
"operational_readiness": {"rows": [], "summary": {}},
|
|
"release": {"id": 0},
|
|
},
|
|
"gates": {},
|
|
}
|
|
mock_get_ops_activity_stream.return_value = {
|
|
"items": [
|
|
{
|
|
"kind": "ops_job",
|
|
"activity_key": "ops-job:81",
|
|
"status": "attention",
|
|
"job_status": "running",
|
|
"summary": "节点已接单执行,但开始回执未成功送达控制面。",
|
|
"job_code": "ops-agent-81",
|
|
"start_delivery_state": "failed_local",
|
|
"source_focus_ref": {
|
|
"kind": "ops_job_event",
|
|
"job_id": 81,
|
|
"job_code": "ops-agent-81",
|
|
"event_key": "job-event:81",
|
|
"target_node_code": "mainland-worker-01",
|
|
},
|
|
"target_node_codes": ["mainland-worker-01"],
|
|
"ui_intent": {"kind": "job_events", "job_id": 81},
|
|
}
|
|
]
|
|
}
|
|
|
|
overview = get_ops_overview()
|
|
|
|
self.assertEqual("activity-start-delivery-gap", overview["recommendation"]["key"])
|
|
self.assertEqual("先检查开始回执异常", overview["recommendation"]["priority"])
|
|
self.assertEqual("focus_latest_job_events", overview["recommendation"]["primary_action_code"])
|
|
self.assertEqual("ops_job_event", overview["recommendation"]["primary_action_payload"]["focus_ref"]["kind"])
|
|
|
|
@patch("app.services.ops_service.build_release_execution_mode_recommendation")
|
|
@patch("app.services.ops_service.get_release_launchpad")
|
|
@patch("app.services.ops_service.get_latest_release")
|
|
@patch("app.services.ops_service.get_release_summary")
|
|
@patch("app.services.ops_service.list_release_rollouts")
|
|
@patch("app.services.ops_service.get_recent_ops_playbook_runs")
|
|
@patch("app.services.ops_service.list_ops_jobs")
|
|
@patch("app.services.ops_service.list_managed_nodes_with_agent_state")
|
|
@patch("app.services.ops_service.get_ops_job_summary")
|
|
@patch("app.services.ops_service.get_sync_summary")
|
|
@patch("app.services.ops_service.get_runtime_status")
|
|
def test_ops_overview_uses_release_launchpad_driver_card_when_execution_plane_is_clean(
|
|
self,
|
|
mock_get_runtime_status,
|
|
mock_get_sync_summary,
|
|
mock_get_ops_job_summary,
|
|
mock_list_managed_nodes_with_agent_state,
|
|
mock_list_ops_jobs,
|
|
mock_get_recent_ops_playbook_runs,
|
|
mock_list_release_rollouts,
|
|
mock_get_release_summary,
|
|
mock_get_latest_release,
|
|
mock_get_release_launchpad,
|
|
mock_build_release_execution_mode_recommendation,
|
|
) -> None:
|
|
mock_get_runtime_status.return_value = {
|
|
"readiness": {"status": "ready", "summary": "ok"},
|
|
"detect": {
|
|
"log_sync": {
|
|
"enabled": True,
|
|
"mode": "key",
|
|
"line_count": 0,
|
|
"source_node_count": 0,
|
|
"source_nodes": [],
|
|
"last_at": "",
|
|
"last_line": "",
|
|
"preview_lines": [],
|
|
},
|
|
"participation_summary": {},
|
|
"participating_nodes": [],
|
|
"non_participating_nodes": [],
|
|
"active_job": {},
|
|
"phase_label": "",
|
|
"phase_detail": "",
|
|
"recent_event": "",
|
|
"recent_warning": "",
|
|
},
|
|
"cluster": {
|
|
"summary": {
|
|
"online_worker_nodes": 1,
|
|
"online_control_nodes": 1,
|
|
"dedicated_online_worker_nodes": 1,
|
|
"busy_nodes": [],
|
|
},
|
|
"nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"status": "online",
|
|
"current_load": 0,
|
|
"hostname": "worker-01",
|
|
"last_heartbeat_at": "2026-04-18 10:00:00",
|
|
"is_effective_worker": True,
|
|
"detect_participating": False,
|
|
}
|
|
],
|
|
},
|
|
}
|
|
mock_get_sync_summary.return_value = {}
|
|
mock_get_ops_job_summary.return_value = {"total": 0, "queued": 0, "running": 0, "awaiting_approval": 0}
|
|
mock_list_managed_nodes_with_agent_state.return_value = {
|
|
"nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"is_managed": True,
|
|
"is_enabled": True,
|
|
"is_agent_online": True,
|
|
"cluster_status": "online",
|
|
"cluster_is_effective_worker": True,
|
|
"cluster_detect_participating": False,
|
|
}
|
|
],
|
|
"summary": {
|
|
"total": 1,
|
|
"managed_total": 1,
|
|
"managed_enabled": 1,
|
|
"online": 1,
|
|
"stale": 0,
|
|
"pending_bootstrap": 0,
|
|
"runtime_only": 0,
|
|
"unmanaged": 0,
|
|
"token_issue": 0,
|
|
"status_counts": {},
|
|
},
|
|
}
|
|
mock_list_ops_jobs.return_value = []
|
|
mock_get_recent_ops_playbook_runs.return_value = {"runs": []}
|
|
mock_list_release_rollouts.return_value = []
|
|
mock_get_release_summary.return_value = {}
|
|
mock_get_latest_release.return_value = {}
|
|
mock_get_release_launchpad.return_value = {
|
|
"channel": "stable",
|
|
"control_plane_base_url": "http://control.example:8100",
|
|
"latest_package": {
|
|
"available": True,
|
|
"package_name": "domaincheck_release_demo.tar.gz",
|
|
},
|
|
"latest_release": {
|
|
"id": 17,
|
|
"release_version": "domaincheck_release_demo",
|
|
"channel": "stable",
|
|
},
|
|
"worker_rollout_preview": {
|
|
"message": "智能 Rollout 预检通过",
|
|
"smart_rollout": {
|
|
"available": True,
|
|
"summary": "已按 Worker 模式预选 1 台节点。",
|
|
"target_node_codes": ["mainland-worker-01"],
|
|
},
|
|
},
|
|
"control_rollout_preview": {
|
|
"message": "",
|
|
"smart_rollout": {
|
|
"available": False,
|
|
"target_node_codes": [],
|
|
},
|
|
},
|
|
"launchpad_status": {
|
|
"status": "ready",
|
|
"status_label": "可发 Worker 灰度",
|
|
"summary": "最新发布包、Release 记录与 Worker 智能 Rollout 预检均已收口。",
|
|
"recommended_action_code": "publish_latest_worker",
|
|
},
|
|
}
|
|
mock_build_release_execution_mode_recommendation.return_value = {
|
|
"recommended_mode": "ssh",
|
|
"recommended_mode_label": "SSH",
|
|
"reason": "当前目标节点通过 SSH 路径的门禁更完整,建议先走 SSH 发布。",
|
|
"options": [],
|
|
"recommended_gate": {
|
|
"status": "ready",
|
|
"summary": "",
|
|
"target_nodes": [],
|
|
"target_node_codes": [],
|
|
"blocking_reasons": [],
|
|
"warning_reasons": [],
|
|
"execution_mode": "ssh",
|
|
"execution_mode_label": "SSH",
|
|
"operational_readiness": {"rows": [], "summary": {}},
|
|
},
|
|
"gates": {},
|
|
}
|
|
|
|
overview = get_ops_overview()
|
|
|
|
self.assertEqual("driver_recommendations", overview["recommendation"]["source"])
|
|
self.assertEqual("release-launchpad-ready", overview["recommendation"]["key"])
|
|
self.assertEqual("当前可以发 Worker 灰度", overview["recommendation"]["priority"])
|
|
self.assertEqual("publish_latest_worker", overview["recommendation"]["primary_action_code"])
|
|
self.assertEqual(["mainland-worker-01"], overview["recommendation"]["primary_node_codes"])
|
|
self.assertEqual("ready", overview["release_hub"]["launchpad"]["launchpad_status"]["status"])
|
|
self.assertEqual("ssh", overview["release_hub"]["default_rollout_gate"]["execution_mode"])
|
|
self.assertEqual("SSH", overview["release_hub"]["default_rollout_execution"]["recommended_mode_label"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|