{ pkgs, serverPkg, clientPkg }: { name = "lightscale-lab-standalone"; nodes = { node1 = { ... }: { networking.hostName = "node1"; networking.usePredictableInterfaceNames = false; virtualisation.vlans = [ 1 ]; networking.interfaces.eth1.useDHCP = false; networking.interfaces.eth1.ipv4.addresses = [ { address = "10.0.0.1"; prefixLength = 24; } ]; networking.firewall.enable = false; boot.kernelModules = [ "wireguard" ]; environment.systemPackages = [ serverPkg clientPkg pkgs.wireguard-tools pkgs.iproute2 pkgs.iputils pkgs.netcat-openbsd pkgs.curl ]; }; node2 = { ... }: { networking.hostName = "node2"; networking.usePredictableInterfaceNames = false; virtualisation.vlans = [ 1 ]; networking.interfaces.eth1.useDHCP = false; networking.interfaces.eth1.ipv4.addresses = [ { address = "10.0.0.2"; prefixLength = 24; } ]; networking.firewall.enable = false; boot.kernelModules = [ "wireguard" ]; environment.systemPackages = [ clientPkg pkgs.wireguard-tools pkgs.iproute2 pkgs.iputils pkgs.curl ]; }; }; testScript = '' start_all() node1.wait_for_unit("multi-user.target") node2.wait_for_unit("multi-user.target") node1.wait_until_succeeds("ip -4 addr show dev eth1 | grep -q '10.0.0.1/24'") node2.wait_until_succeeds("ip -4 addr show dev eth1 | grep -q '10.0.0.2/24'") # Start control plane as a plain process (no systemd-run usage). node1.succeed("touch /tmp/lightscale-server.log") node1.succeed( "sh -c 'LIGHTSCALE_ADMIN_TOKEN=test-admin RUST_LOG=info " "lightscale-server --listen 10.0.0.1:8080 --state /tmp/lightscale-state.json " ">/tmp/lightscale-server.log 2>&1 & echo $! >/tmp/lightscale-server.pid'" ) node1.wait_until_succeeds("sh -c 'kill -0 $(cat /tmp/lightscale-server.pid)'", timeout=30) node1.wait_for_open_port(8080, addr="10.0.0.1", timeout=120) import json net = json.loads(node1.succeed( "curl -sSf -X POST http://10.0.0.1:8080/v1/networks " "-H 'authorization: Bearer test-admin' " "-H 'content-type: application/json' " "-d '{\"name\":\"standalone\",\"bootstrap_token_ttl_seconds\":600," "\"bootstrap_token_uses\":10,\"bootstrap_token_tags\":[\"standalone\"]}'" )) token = net["bootstrap_token"]["token"] def enroll(node, name, endpoint): node.succeed( "lightscale-client --profile std --config /tmp/ls-config.json " "init http://10.0.0.1:8080" ) node.succeed( f"lightscale-client --profile std --config /tmp/ls-config.json " f"--state-dir /tmp/ls-state register --node-name {name} -- {token}" ) node.succeed( f"lightscale-client --profile std --config /tmp/ls-config.json " f"--state-dir /tmp/ls-state heartbeat --endpoint {endpoint}" ) enroll(node1, "node1", "10.0.0.1:51820") enroll(node2, "node2", "10.0.0.2:51820") def start_daemon(node, endpoint): node.succeed("touch /tmp/lightscale-daemon.log") node.succeed( "sh -c 'nohup lightscale-client --profile std --config /tmp/ls-config.json " "--state-dir /tmp/ls-state daemon --profiles std " "--agent-arg=--listen-port --agent-arg=51820 " "--agent-arg=--heartbeat-interval --agent-arg=5 " "--agent-arg=--longpoll-timeout --agent-arg=5 " "--agent-arg=--endpoint --agent-arg=" + endpoint + " " ">/tmp/lightscale-daemon.log 2>&1 & echo $! >/tmp/lightscale-daemon.pid'" ) node.wait_until_succeeds("sh -c 'kill -0 $(cat /tmp/lightscale-daemon.pid)'", timeout=30) node.wait_until_succeeds("ip link show ls-std", timeout=120) def stop_daemon(node): node.execute("sh -c 'kill -TERM $(cat /tmp/lightscale-daemon.pid) || true'") node.wait_until_succeeds( "sh -c 'pid=$(cat /tmp/lightscale-daemon.pid); " "if kill -0 \"$pid\" 2>/dev/null; then exit 1; else exit 0; fi'", timeout=120, ) start_daemon(node1, "10.0.0.1:51820") start_daemon(node2, "10.0.0.2:51820") data1 = json.loads(node1.succeed("cat /tmp/ls-state/state.json")) data2 = json.loads(node2.succeed("cat /tmp/ls-state/state.json")) ip1 = data1["ipv4"] ip2 = data2["ipv4"] node1.wait_until_succeeds(f"ping -c 3 {ip2}", timeout=180) node2.wait_until_succeeds(f"ping -c 3 {ip1}", timeout=180) # Restart plain daemons and verify connectivity recovers. stop_daemon(node1) stop_daemon(node2) node1.fail("systemctl is-active lightscale-agent.service") node2.fail("systemctl is-active lightscale-agent.service") start_daemon(node1, "10.0.0.1:51820") start_daemon(node2, "10.0.0.2:51820") node1.wait_until_succeeds(f"ping -c 3 {ip2}", timeout=240) node2.wait_until_succeeds(f"ping -c 3 {ip1}", timeout=240) stop_daemon(node1) stop_daemon(node2) node1.execute("sh -c 'kill -TERM $(cat /tmp/lightscale-server.pid) || true'") ''; }