route(signals, fleet) -> RouteDecision via a deterministic cascade: explicit host > capability-pin (uses hosts_with_capability) > sticky (subject's session/task already runs on a host, via sessions+assignments) > default-local. Pure + auditable (reason+candidates surfaced); the LLM classify step and cross-host execution are separate layers. 13 tests. Part of task 13764f2f. (manual commit via ALLOW_COMMIT — autocommit LLM still down on claire)
143 lines
5.2 KiB
Python
143 lines
5.2 KiB
Python
"""Routing cascade: explicit > capability > sticky > default-local."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from uuid import UUID
|
|
|
|
from claire.config import ClaireConfig, HostEntry
|
|
from claire.routing import route
|
|
from claire.web import service
|
|
|
|
|
|
@pytest.fixture
|
|
def cfg() -> ClaireConfig:
|
|
return ClaireConfig(
|
|
machine_id="m",
|
|
this_host="plum",
|
|
known_hosts=[
|
|
HostEntry(name="plum", aliases=["local"]),
|
|
HostEntry(name="apricot", capabilities=["cores:64", "gpu"]),
|
|
HostEntry(name="black", capabilities=["media", "transmission"]),
|
|
],
|
|
)
|
|
|
|
|
|
def _add_session(conn, uuid: str, host: str) -> None:
|
|
conn.execute(
|
|
"INSERT INTO sessions (uuid, host, updated_hlc) VALUES (?, ?, ?)",
|
|
(uuid, host, "1"),
|
|
)
|
|
|
|
|
|
def _task_with_worker(conn, gen, *, project: str, host: str, session_uuid: str):
|
|
"""Create a project+task and an active assignment to a session on `host`.
|
|
Returns the task id (str). Uses service so FK constraints are satisfied."""
|
|
service.create_project(conn, gen, name=project)
|
|
task = service.add_task(conn, gen, project=project, title="t")
|
|
_add_session(conn, session_uuid, host)
|
|
service.create_assignment(conn, gen, task_id=task.id, session_uuid=UUID(session_uuid))
|
|
return str(task.id)
|
|
|
|
|
|
# 1. explicit -----------------------------------------------------------------
|
|
|
|
def test_explicit_host_wins(conn, cfg) -> None:
|
|
d = route(conn, cfg, receiving_host="plum", explicit_host="apricot")
|
|
assert (d.host, d.reason) == ("apricot", "explicit")
|
|
|
|
|
|
def test_explicit_alias_resolves(conn, cfg) -> None:
|
|
# "local" → plum even when received on plum
|
|
d = route(conn, cfg, receiving_host="plum", explicit_host="local")
|
|
assert (d.host, d.reason) == ("plum", "explicit")
|
|
|
|
|
|
def test_explicit_unknown_host_falls_back_local_not_silent(conn, cfg) -> None:
|
|
d = route(conn, cfg, receiving_host="plum", explicit_host="mars")
|
|
assert d.host == "plum"
|
|
assert d.reason == "unknown-host"
|
|
|
|
|
|
# 2. capability ---------------------------------------------------------------
|
|
|
|
def test_capability_single(conn, cfg) -> None:
|
|
d = route(conn, cfg, receiving_host="plum", capability_needs=["media"])
|
|
assert (d.host, d.reason) == ("black", "capability")
|
|
|
|
|
|
def test_capability_key_prefix(conn, cfg) -> None:
|
|
# asking "cores" matches "cores:64"
|
|
d = route(conn, cfg, receiving_host="plum", capability_needs=["cores"])
|
|
assert d.host == "apricot"
|
|
|
|
|
|
def test_capability_intersection_of_needs(conn, cfg) -> None:
|
|
# gpu AND cores → only apricot has both; media-only black excluded
|
|
d = route(conn, cfg, receiving_host="plum", capability_needs=["gpu", "cores"])
|
|
assert d.host == "apricot"
|
|
|
|
|
|
def test_capability_no_match_falls_through_to_default(conn, cfg) -> None:
|
|
d = route(conn, cfg, receiving_host="plum", capability_needs=["fpga"])
|
|
assert (d.host, d.reason) == ("plum", "default-local")
|
|
|
|
|
|
def test_capability_tiebreak_least_loaded(conn) -> None:
|
|
cfg = ClaireConfig(
|
|
machine_id="m", this_host="plum",
|
|
known_hosts=[
|
|
HostEntry(name="a", capabilities=["media"]),
|
|
HostEntry(name="b", capabilities=["media"]),
|
|
],
|
|
)
|
|
d = route(None, cfg, receiving_host="plum", capability_needs=["media"],
|
|
host_load={"a": 5, "b": 1})
|
|
assert d.host == "b"
|
|
assert set(d.candidates) == {"a", "b"}
|
|
|
|
|
|
# 3. sticky -------------------------------------------------------------------
|
|
|
|
def test_sticky_by_session(conn, cfg) -> None:
|
|
_add_session(conn, "11111111-1111-1111-1111-111111111111", "apricot")
|
|
d = route(conn, cfg, receiving_host="plum",
|
|
session_uuid="11111111-1111-1111-1111-111111111111")
|
|
assert (d.host, d.reason) == ("apricot", "sticky")
|
|
|
|
|
|
def test_sticky_by_task_via_active_assignment(conn, gen, cfg) -> None:
|
|
task_id = _task_with_worker(
|
|
conn, gen, project="p", host="black",
|
|
session_uuid="22222222-2222-2222-2222-222222222222",
|
|
)
|
|
d = route(conn, cfg, receiving_host="plum", task_id=task_id)
|
|
assert (d.host, d.reason) == ("black", "sticky")
|
|
|
|
|
|
def test_session_reference_beats_task(conn, gen, cfg) -> None:
|
|
_add_session(conn, "33333333-3333-3333-3333-333333333333", "apricot")
|
|
task_id = _task_with_worker(
|
|
conn, gen, project="p", host="black",
|
|
session_uuid="44444444-4444-4444-4444-444444444444",
|
|
)
|
|
d = route(conn, cfg, receiving_host="plum",
|
|
session_uuid="33333333-3333-3333-3333-333333333333", task_id=task_id)
|
|
assert d.host == "apricot" # session wins
|
|
|
|
|
|
# 4. default ------------------------------------------------------------------
|
|
|
|
def test_default_local_when_no_signal(conn, cfg) -> None:
|
|
d = route(conn, cfg, receiving_host="apricot")
|
|
assert (d.host, d.reason) == ("apricot", "default-local")
|
|
|
|
|
|
def test_precedence_explicit_over_everything(conn, cfg) -> None:
|
|
# a sticky session on black, capability=media (black), but explicit apricot wins
|
|
_add_session(conn, "55555555-5555-5555-5555-555555555555", "black")
|
|
d = route(conn, cfg, receiving_host="plum", explicit_host="apricot",
|
|
capability_needs=["media"],
|
|
session_uuid="55555555-5555-5555-5555-555555555555")
|
|
assert (d.host, d.reason) == ("apricot", "explicit")
|