fix(api): route auto repair docker restart through mcp
Some checks failed
CD Pipeline / tests (push) Successful in 1m21s
Code Review / ai-code-review (push) Successful in 13s
run-migration / migrate (push) Successful in 9s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-01 14:35:06 +08:00
parent ea8e2b1106
commit 2faa167ed2
5 changed files with 503 additions and 6 deletions

View File

@@ -459,6 +459,44 @@ class TestAutoRepairService:
assert route is None
def test_legacy_ssh_docker_restart_routes_to_write_mcp_gateway(self, service):
"""Safe legacy Docker restart steps use the governed write MCP tool."""
incident = create_test_incident(
severity=Severity.P2,
alert_category="infrastructure",
alert_name="DockerContainerUnhealthy",
)
incident.signals[0].labels.update({
"host": "110",
"container_name": "minio",
})
route = service._route_legacy_ssh_write_command_to_mcp(
incident,
'ssh {host} \'docker inspect {container} --format="{{.State.Health.Status}}" && docker restart {container}\'',
)
assert route is not None
assert route.tool_name == "ssh_docker_restart"
assert route.required_scope == "write"
assert route.params == {
"host": "192.168.0.110",
"container_name": "minio",
"trust_score": 0.85,
}
def test_legacy_ssh_complex_restart_stays_blocked(self, service):
"""Command substitution/fallback shell must not enter auto write MCP."""
incident = create_test_incident(severity=Severity.P2)
incident.signals[0].labels.update({"host": "110", "container_name": "node-exporter"})
route = service._route_legacy_ssh_write_command_to_mcp(
incident,
'ssh {host} \'docker restart $(docker ps -a --filter name=exporter --format "{{.Names}}" | head -1) || systemctl restart node_exporter\'',
)
assert route is None
@pytest.mark.asyncio
async def test_execute_legacy_ssh_diagnostic_uses_mcp_gateway(
self,
@@ -508,6 +546,71 @@ class TestAutoRepairService:
assert calls[0]["params"]["container_name"] == "momo-scheduler"
assert calls[0]["params"]["_mcp_audit"]["flywheel_node"] == "execute"
@pytest.mark.asyncio
async def test_execute_legacy_ssh_docker_restart_uses_write_mcp_gateway(
self,
service,
monkeypatch,
):
incident = create_test_incident(
severity=Severity.P2,
alert_category="infrastructure",
alert_name="DockerContainerUnhealthy",
)
incident.signals[0].labels.update({
"host": "110",
"container_name": "minio",
})
step = RepairStep(
step_number=1,
action_type=ActionType.SSH_COMMAND,
command='ssh {host} \'docker inspect {container} --format="{{.State.Health.Status}}" && docker restart {container}\'',
risk_level=RiskLevel.MEDIUM,
requires_approval=False,
)
calls = []
redis_sets = []
class FakeRedis:
async def set(self, key, value, ex=None):
redis_sets.append({"key": key, "value": value, "ex": ex})
return True
class FakeGateway:
def __init__(self, db: object) -> None:
self.db = db
async def call(self, ctx, params):
calls.append({"ctx": ctx, "params": params, "db": self.db})
return MCPToolResult(
success=True,
execution_id="gw-write-ok",
output={"stdout": "minio"},
)
monkeypatch.setattr("src.core.redis_client.get_redis", lambda: FakeRedis())
monkeypatch.setattr("src.db.base.get_db_context", lambda _project_id: _DbContext())
monkeypatch.setattr("src.plugins.mcp.gateway.McpGateway", FakeGateway)
result = await service._execute_step(incident, step)
assert result.startswith("SUCCESS: mcp:ssh_docker_restart")
assert calls
assert redis_sets
assert calls[0]["ctx"].agent_id == "auto_repair_executor"
assert calls[0]["ctx"].tool_name == "ssh_docker_restart"
assert calls[0]["ctx"].required_scope == "write"
assert calls[0]["ctx"].is_shadow is False
assert calls[0]["ctx"].run_id is not None
assert calls[0]["params"]["host"] == "192.168.0.110"
assert calls[0]["params"]["container_name"] == "minio"
assert calls[0]["params"]["trust_score"] == 0.85
assert calls[0]["params"]["_mcp_audit"]["flywheel_node"] == "execute"
assert redis_sets[0]["key"].startswith(
"mcp_approval:awoooi:auto_repair_executor:ssh_docker_restart:"
)
assert redis_sets[0]["value"] == "approved:auto_repair_policy"
@pytest.mark.asyncio
async def test_evaluate_low_risk_allowed(self, service, mock_playbook_service):
"""Test that LOW risk actions are allowed"""