ADK-gateway/backend/tests/test_scheduler.py
2026-08-05 16:33:27 +08:00

174 lines
7.5 KiB
Python
Raw Permalink 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.

"""调度服务单元测试。"""
import pytest
from app.constants import AgentStatus, TaskStatus
from app.models.schemas import AgentInfo, TaskInfo
from app.repository.agent_repo import AgentRepo
from app.repository.task_repo import TaskRepo
from app.services.scheduler import Scheduler
class _StubRelay:
"""模拟 relay不发起真实网络请求直接返回推送成功。"""
def __init__(self, ok: bool = True):
self.ok = ok
async def dispatch_command(self, agent_id: str, request_id: str, payload: dict) -> bool:
return self.ok
def _sched(redis, task_repo, agent_repo):
return Scheduler(redis, task_repo, agent_repo, relay=_StubRelay())
@pytest.mark.asyncio
async def test_dispatch_binds_min_load_agent(redis):
task_repo = TaskRepo(redis)
agent_repo = AgentRepo(redis)
# 两个 compile Agentb2 负载更低
await agent_repo.upsert(AgentInfo(agent_id="b1", endpoint="e1", agent_tags=["compile"], max_concurrent=2, current_load=1))
await agent_repo.upsert(AgentInfo(agent_id="b2", endpoint="e2", agent_tags=["compile"], max_concurrent=2, current_load=0))
await task_repo.create(TaskInfo(request_id="t1", task_type="compile", task_tags=["compile"]))
sched = _sched(redis, task_repo, agent_repo)
ok = await sched.dispatch("t1")
assert ok is True
task = await task_repo.get("t1")
assert task.agent_id == "b2"
assert task.status == TaskStatus.RUNNING
assert "t1" in await task_repo.running_tasks()
@pytest.mark.asyncio
async def test_pick_candidate_lowest_priority(redis):
agent_repo = AgentRepo(redis)
# 三个 compile Agentb1 负载最低但优先级高(最不被优先)b2 负载高但优先级低(最优先)
await agent_repo.upsert(AgentInfo(agent_id="b1", endpoint="e1", agent_tags=["compile"], max_concurrent=2, current_load=0, priority=5))
await agent_repo.upsert(AgentInfo(agent_id="b2", endpoint="e2", agent_tags=["compile"], max_concurrent=2, current_load=1, priority=1))
await agent_repo.upsert(AgentInfo(agent_id="b3", endpoint="e3", agent_tags=["compile"], max_concurrent=2, current_load=0, priority=3))
sched = Scheduler(redis)
picked = await sched.pick_candidate(["compile"])
assert picked is not None
assert picked.agent_id == "b2" # 优先级最小优先分配
@pytest.mark.asyncio
async def test_pick_candidate_same_priority_min_load(redis):
agent_repo = AgentRepo(redis)
# 同优先级(默认3)时,负载更低者优先
await agent_repo.upsert(AgentInfo(agent_id="b1", endpoint="e1", agent_tags=["compile"], max_concurrent=2, current_load=1))
await agent_repo.upsert(AgentInfo(agent_id="b2", endpoint="e2", agent_tags=["compile"], max_concurrent=2, current_load=0))
sched = Scheduler(redis)
picked = await sched.pick_candidate(["compile"])
assert picked is not None
assert picked.agent_id == "b2"
@pytest.mark.asyncio
async def test_dispatch_no_agent_stays_pending(redis):
task_repo = TaskRepo(redis)
agent_repo = AgentRepo(redis)
await task_repo.create(TaskInfo(request_id="t1", task_type="compile", task_tags=["compile"]))
sched = Scheduler(redis, task_repo, agent_repo)
ok = await sched.dispatch("t1")
assert ok is False
task = await task_repo.get("t1")
assert task.status == TaskStatus.PENDING
@pytest.mark.asyncio
async def test_dispatch_skips_full_agent(redis):
task_repo = TaskRepo(redis)
agent_repo = AgentRepo(redis)
# 唯一 agent 已满载
await agent_repo.upsert(AgentInfo(agent_id="b1", endpoint="e1", agent_tags=["compile"], max_concurrent=1, current_load=1))
await task_repo.create(TaskInfo(request_id="t1", task_type="compile", task_tags=["compile"]))
sched = Scheduler(redis, task_repo, agent_repo)
assert await sched.dispatch("t1") is False
@pytest.mark.asyncio
async def test_dispatch_pending(redis):
task_repo = TaskRepo(redis)
agent_repo = AgentRepo(redis)
await agent_repo.upsert(AgentInfo(agent_id="b1", endpoint="e1", agent_tags=["compile"], max_concurrent=2))
await task_repo.create(TaskInfo(request_id="t1", task_type="compile", task_tags=["compile"]))
await task_repo.create(TaskInfo(request_id="t2", task_type="compile", task_tags=["compile"]))
sched = _sched(redis, task_repo, agent_repo)
n = await sched.dispatch_pending(10)
assert n == 2
@pytest.mark.asyncio
async def test_dispatch_excludes_offline_unavailable_stopping(redis):
"""调度排除 offline心跳失联、unavailable手动不可用与 stopping 的 agent。"""
task_repo = TaskRepo(redis)
agent_repo = AgentRepo(redis)
# b1 手动不可用b2 正在停止b3 离线b4 就绪
await agent_repo.upsert(AgentInfo(agent_id="b1", endpoint="e1", agent_tags=["compile"], max_concurrent=2))
await agent_repo.upsert(AgentInfo(agent_id="b2", endpoint="e2", agent_tags=["compile"], max_concurrent=2))
await agent_repo.upsert(AgentInfo(agent_id="b3", endpoint="e3", agent_tags=["compile"], max_concurrent=2))
await agent_repo.upsert(AgentInfo(agent_id="b4", endpoint="e4", agent_tags=["compile"], max_concurrent=2))
await agent_repo.set_unavailable("b1")
await agent_repo.update_status("b2", AgentStatus.STOPPING)
await agent_repo.update_status("b3", AgentStatus.OFFLINE)
await task_repo.create(TaskInfo(request_id="t1", task_type="compile", task_tags=["compile"]))
sched = _sched(redis, task_repo, agent_repo)
ok = await sched.dispatch("t1")
assert ok is True
task = await task_repo.get("t1")
assert task.agent_id == "b4"
@pytest.mark.asyncio
async def test_dispatch_processing_agent_with_capacity(redis):
"""processing 的 agent 若还有并发容量,仍可接收新任务。"""
task_repo = TaskRepo(redis)
agent_repo = AgentRepo(redis)
await agent_repo.upsert(AgentInfo(agent_id="b1", endpoint="e1", agent_tags=["compile"], max_concurrent=2))
await agent_repo.update_status("b1", AgentStatus.PROCESSING)
await task_repo.create(TaskInfo(request_id="t1", task_type="compile", task_tags=["compile"]))
sched = _sched(redis, task_repo, agent_repo)
assert await sched.dispatch("t1") is True
assert (await task_repo.get("t1")).agent_id == "b1"
@pytest.mark.asyncio
async def test_dispatch_marks_agent_processing(redis):
"""调度下发成功后agent 状态流转为 processing。"""
task_repo = TaskRepo(redis)
agent_repo = AgentRepo(redis)
await agent_repo.upsert(AgentInfo(agent_id="b1", endpoint="e1", agent_tags=["compile"], max_concurrent=2))
await task_repo.create(TaskInfo(request_id="t1", task_type="compile", task_tags=["compile"]))
sched = _sched(redis, task_repo, agent_repo)
assert await sched.dispatch("t1") is True
assert (await agent_repo.get("b1")).status == AgentStatus.PROCESSING
@pytest.mark.asyncio
async def test_dispatch_push_fail_reverts_agent_to_ready(redis):
"""推送失败时任务回退 pendingagent 释放负载并回到 ready。"""
task_repo = TaskRepo(redis)
agent_repo = AgentRepo(redis)
await agent_repo.upsert(AgentInfo(agent_id="b1", endpoint="e1", agent_tags=["compile"], max_concurrent=2))
await task_repo.create(TaskInfo(request_id="t1", task_type="compile", task_tags=["compile"]))
sched = Scheduler(redis, task_repo, agent_repo, relay=_StubRelay(ok=False))
assert await sched.dispatch("t1") is False
task = await task_repo.get("t1")
assert task.status == TaskStatus.PENDING
assert not task.agent_id
a = await agent_repo.get("b1")
assert a.status == AgentStatus.READY
assert a.current_load == 0