import pytest import subprocess import os import uuid import testinfra import time # Use the bin/infra wrapper for testing CLI_BIN = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "bin", "infra")) CONFIG_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "config.yaml")) SSH_KEY = os.path.expanduser("~/.ssh/id_ed25519_no_pass") ROUTER_PASS = "kpvoh58zhq2sq6ms" def run_infra(cmd, env=None): full_env = os.environ.copy() if env: full_env.update(env) full_env["ROUTER_PASS"] = ROUTER_PASS full_cmd = [CLI_BIN, "--config", CONFIG_PATH] + cmd return subprocess.run(full_cmd, capture_output=True, text=True, env=full_env) def get_testinfra_host(host_str): return testinfra.get_host( f"ssh://root@{host_str}", ssh_identity_file=SSH_KEY, ssh_extra_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" ) @pytest.fixture def agent_context(): """Provides a unique context for an agent service deployment""" uid = str(uuid.uuid4())[:8] return { "id": uid, "name": f"agent-svc-{uid}", "domain": f"agent-{uid}.loopaware.com", "mac": f"aa:bb:cc:ee:ff:{uid[:2]}", "port": 8080 } def test_e2e_agent_deployment_lifecycle(agent_context): """ Tests the complete flow: IPAM -> DB -> DNS -> Ingress -> Cloudflare -> Decommission """ ctx = agent_context print(f"\n[E2E] Starting deployment for {ctx['name']}") # 1. IPAM: Find next free IP res = run_infra(["ip", "next-free"]) assert res.returncode == 0 ip = res.stdout.strip() print(f"[E2E] Allocated IP: {ip}") # 2. Database: Provision PostgreSQL res = run_infra(["db", "provision", ctx['name']]) assert res.returncode == 0 assert ctx['name'].replace("-", "_") in res.stdout print("[E2E] Database provisioned") # 3. DNS: Register DHCP Host res = run_infra(["dns", "add-host", ctx['mac'], ip, ctx['name']]) assert res.returncode == 0 print("[E2E] DNS/DHCP host registered") # 4. Ingress: Configure HAProxy res = run_infra(["ingress", "add", ctx['domain'], ip, str(ctx['port'])]) assert res.returncode == 0 print(f"[E2E] Ingress configured for {ctx['domain']}") # 5. Cloudflare: Add to DDNS list res = run_infra(["cloudflare", "add-ddns", ctx['domain']]) assert res.returncode == 0 print("[E2E] Cloudflare DDNS registered") # --- VERIFICATION PHASE --- print("[E2E] Verifying infrastructure state...") # Verify HAProxy points to the CORRECT allocated IP pve = get_testinfra_host("10.32.2.1") # Wrap in single quotes to protect from local shell, double for remote be_name = f"be_{ctx['domain'].replace('.', '_')}" res = pve.run(f"pct exec 11220 -- grep -A 5 '{be_name}' /etc/haproxy/conf.d/99-dynamic.cfg") assert res.rc == 0 assert ip in res.stdout assert str(ctx['port']) in res.stdout # Verify Database exists db_host = get_testinfra_host("10.32.70.54") db_name = ctx['name'].lower().replace("-", "_") # Safer check: use psql -t -c to get raw list res = db_host.run(f"su - postgres -c \"psql -t -c '\\l' | grep -qw {db_name}\"") assert res.rc == 0 # --- CLEANUP / DECOMMISSION PHASE --- print("[E2E] Starting decommissioning...") res = run_infra(["decommission", "--domain", ctx['domain'], "--mac", ctx['mac']]) assert res.returncode == 0 # Final state check: should be gone in sites.json res = pve.run(f"pct exec 11220 -- grep '{ctx['domain']}' /etc/haproxy/dynamic/sites.json") assert res.rc != 0 print("[E2E] Flow completed successfully.")