"""Unit tests for the swarm-aware container view builder (pure, no DB/ORM).""" from types import SimpleNamespace from plugins.docker.swarm_view import build_swarm_services NODE_HOSTNAME = {"nA": "docker01", "nB": "docker02", "nC": "docker03"} HOST_NAME = {"h1": "docker01", "h2": "docker02"} # agents run on docker01/02 only def _c(host_id, name, *, service=None, node=None, status="running", cpu=1.0): return SimpleNamespace(host_id=host_id, name=name, service_name=service, node_id=node, status=status, cpu_pct=cpu, mem_pct=2.0, health=None, restart_count=0, container_id=name) def _svc(name, running, desired, placement, mode="replicated"): import json return SimpleNamespace(service_name=name, running=running, desired=desired, mode=mode, placement_json=json.dumps(placement)) def test_real_replicas_plus_agentless_ghost(): containers = [ _c("h1", "web.1.aaa", service="web", node="nA"), _c("h2", "web.2.bbb", service="web", node="nB"), _c("h1", "db", node=None), # standalone, non-swarm ] services = [_svc("web", running=3, desired=3, placement=[{"node_id": "nA", "running": 1}, {"node_id": "nB", "running": 1}, {"node_id": "nC", "running": 1}])] # nC has no agent view = build_swarm_services(containers, services, NODE_HOSTNAME, HOST_NAME) assert len(view["services"]) == 1 svc = view["services"][0] assert svc["name"] == "web" and svc["state"] == "healthy" reals = [r for r in svc["replicas"] if not r["ghost"]] ghosts = [r for r in svc["replicas"] if r["ghost"]] assert sorted(r["host"] for r in reals) == ["docker01", "docker02"] assert len(ghosts) == 1 and ghosts[0]["host"] == "docker03" and ghosts[0]["count"] == 1 # The non-swarm container is grouped by host, untouched. assert len(view["standalone"]) == 1 g = view["standalone"][0] assert g["host"] == "docker01" and [c.name for c in g["containers"]] == ["db"] def test_placement_fully_covered_has_no_ghosts(): containers = [_c("h1", "api.1.x", service="api", node="nA"), _c("h2", "api.2.y", service="api", node="nB")] services = [_svc("api", 2, 2, [{"node_id": "nA", "running": 1}, {"node_id": "nB", "running": 1}])] view = build_swarm_services(containers, services, NODE_HOSTNAME, HOST_NAME) assert all(not r["ghost"] for r in view["services"][0]["replicas"]) def test_multiple_tasks_same_node_partially_agentless(): # Placement says 2 running on nA, but we only have 1 local row → 1 ghost on nA. containers = [_c("h1", "cache.1.x", service="cache", node="nA")] services = [_svc("cache", 2, 2, [{"node_id": "nA", "running": 2}])] view = build_swarm_services(containers, services, NODE_HOSTNAME, HOST_NAME) ghosts = [r for r in view["services"][0]["replicas"] if r["ghost"]] assert len(ghosts) == 1 and ghosts[0]["count"] == 1 and ghosts[0]["host"] == "docker01" def test_service_without_a_service_row_is_synthesised(): # Container labeled with a service, but no manager reported the service row. containers = [_c("h1", "orphan.1.x", service="orphan", node="nA")] view = build_swarm_services(containers, [], NODE_HOSTNAME, HOST_NAME) svc = view["services"][0] assert svc["name"] == "orphan" and svc["running"] == 1 and svc["desired"] == 1 assert all(not r["ghost"] for r in svc["replicas"]) def test_down_service_state(): services = [_svc("idle", 0, 2, [])] view = build_swarm_services([], services, NODE_HOSTNAME, HOST_NAME) assert view["services"][0]["state"] == "down"