GenieHive/tests/test_control_registry.py

153 lines
5.6 KiB
Python

from pathlib import Path
from geniehive_control.main import create_app
from geniehive_control.models import HostHeartbeat, HostRegistration, RegisteredService, RoleProfile
from geniehive_control.registry import Registry
def test_registry_persists_registration_and_heartbeat(tmp_path: Path) -> None:
db_path = tmp_path / "geniehive.sqlite3"
registry = Registry(db_path)
host = registry.register_host(
HostRegistration(
host_id="atlas-01",
display_name="Atlas GPU Box",
address="192.168.1.101",
labels={"site": "home-lab"},
capabilities={"cuda": True},
resources={"cpu_threads": 24},
services=[
RegisteredService(
service_id="atlas-01/chat/qwen3-8b",
host_id="atlas-01",
kind="chat",
protocol="openai",
endpoint="http://192.168.1.101:18091",
runtime={"engine": "llama.cpp", "launcher": "managed"},
assets=[{"asset_id": "qwen3-8b-q4km", "loaded": True}],
state={"health": "healthy", "load_state": "loaded", "accept_requests": True},
observed={"p50_latency_ms": 900, "tokens_per_sec": 40},
)
],
)
)
assert host is not None
assert host["host_id"] == "atlas-01"
updated = registry.heartbeat_host(
HostHeartbeat(
host_id="atlas-01",
status={"state": "online"},
metrics={"gpu_utilization_pct": 77},
)
)
assert updated is not None
assert updated["metrics"]["gpu_utilization_pct"] == 77
hosts = registry.list_hosts()
services = registry.list_services()
health = registry.cluster_health(stale_after_s=30)
assert len(hosts) == 1
assert len(services) == 1
assert services[0]["service_id"] == "atlas-01/chat/qwen3-8b"
assert services[0]["state"]["health"] == "healthy"
assert health["host_count"] == 1
assert health["healthy_service_count"] == 1
def test_registry_persists_roles_and_resolves_direct_and_role_routes(tmp_path: Path) -> None:
db_path = tmp_path / "geniehive.sqlite3"
registry = Registry(db_path)
registry.register_host(
HostRegistration(
host_id="atlas-01",
address="192.168.1.101",
services=[
RegisteredService(
service_id="atlas-01/chat/qwen3-8b",
host_id="atlas-01",
kind="chat",
endpoint="http://192.168.1.101:18091",
assets=[{"asset_id": "qwen3-8b-q4km", "loaded": True}],
state={"health": "healthy", "load_state": "loaded", "accept_requests": True},
observed={"p50_latency_ms": 900},
),
RegisteredService(
service_id="atlas-01/embeddings/bge-small",
host_id="atlas-01",
kind="embeddings",
endpoint="http://192.168.1.101:18092",
assets=[{"asset_id": "bge-small-en", "loaded": True}],
state={"health": "healthy", "load_state": "loaded", "accept_requests": True},
observed={"p50_latency_ms": 120},
),
],
)
)
registry.upsert_roles(
[
RoleProfile(
role_id="mentor",
display_name="Mentor",
operation="chat",
modality="text",
routing_policy={"preferred_families": ["qwen3"]},
),
RoleProfile(
role_id="embedder",
display_name="Embedder",
operation="embeddings",
modality="text",
routing_policy={"require_loaded": True},
),
]
)
roles = registry.list_roles()
assert len(roles) == 2
assert roles[0]["role_id"] == "embedder"
direct = registry.resolve_route("qwen3-8b-q4km")
assert direct is not None
assert direct["match_type"] == "direct"
assert direct["service"]["service_id"] == "atlas-01/chat/qwen3-8b"
by_role = registry.resolve_route("mentor")
assert by_role is not None
assert by_role["match_type"] == "role"
assert by_role["role"]["role_id"] == "mentor"
assert by_role["service"]["service_id"] == "atlas-01/chat/qwen3-8b"
embed_role = registry.resolve_route("embedder")
assert embed_role is not None
assert embed_role["service"]["service_id"] == "atlas-01/embeddings/bge-small"
models = registry.list_client_models()
ids = {item["id"] for item in models}
assert "atlas-01/chat/qwen3-8b" in ids
assert "qwen3-8b-q4km" in ids
assert "mentor" in ids
mentor = next(item for item in models if item["id"] == "mentor")
assert mentor["geniehive"]["route_type"] == "role"
assert mentor["geniehive"]["offload_hint"]["suitability"] == "good_for_low_complexity"
asset = next(item for item in models if item["id"] == "qwen3-8b-q4km")
assert asset["geniehive"]["route_type"] == "asset"
assert asset["geniehive"]["offload_hint"]["recommended_for"] == "lower-complexity offload"
def test_control_app_exposes_expected_routes() -> None:
app = create_app()
paths = {route.path for route in app.routes}
assert "/health" in paths
assert "/v1/models" in paths
assert "/v1/nodes/register" in paths
assert "/v1/nodes/heartbeat" in paths
assert "/v1/cluster/hosts" in paths
assert "/v1/cluster/services" in paths
assert "/v1/cluster/roles" in paths
assert "/v1/cluster/health" in paths
assert "/v1/cluster/routes/resolve" in paths