#!/usr/bin/env bash # Self-contained Docker deployment regression. Requires Linux Docker because # the validator uses the host network to reach the ephemeral HTTPS RRDP server. set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" work_root="$(mktemp -d)" server_pid="" daemon_container="" cleanup() { [[ -n "$daemon_container" ]] && docker stop --time 5 "$daemon_container" >/dev/null 2>&1 || true [[ -n "$server_pid" ]] && kill "$server_pid" 2>/dev/null || true [[ -n "$server_pid" ]] && wait "$server_pid" 2>/dev/null || true rm -rf "$work_root" } trap cleanup EXIT port="$(python3 - <<'PY' import socket with socket.socket() as sock: sock.bind(("127.0.0.1", 0)) print(sock.getsockname()[1]) PY )" fixture="$work_root/fixture" python3 "$repo_root/tests/support/generate_repository.py" \ --rrdp-host 127.0.0.1 --rrdp-port "$port" --output "$fixture" ln -s "cases/baseline-v1/http" "$fixture/active" mkdir -p "$work_root/output-snapshot" "$work_root/output-delta" "$work_root/state" PYTHONUNBUFFERED=1 python3 "$repo_root/tests/support/serve_repository.py" \ "$fixture/active" "$port" "$fixture/certs/rrdp-server.pem" "$fixture/certs/rrdp-server.key" \ >"$work_root/server.log" 2>&1 & server_pid="$!" for _ in $(seq 1 50); do rg -qx 'ready' "$work_root/server.log" && break sleep 0.1 done rg -qx 'ready' "$work_root/server.log" image="${PANDA_RPKI_TEST_IMAGE:-panda-rpki:synthetic-e2e}" if [[ -z "${PANDA_RPKI_TEST_IMAGE:-}" ]]; then docker build -f "$repo_root/docker/Dockerfile" -t "$image" "$repo_root" else docker image inspect "$image" >/dev/null fi run_validator() { local out="$1" local mode="$2" local enabled="${3:-true}" local policy="${4:-}" local config="$work_root/validate-$mode.toml" cat >"$config" < 0, timing PY # Policy changes must affect payloads even when RRDP is a noop. printf 'deny 0.0.0.0/0\ndeny ::/0\ndeny 0 - 4294967295\n' >"$fixture/tal/custom.constraints" mkdir -p "$work_root/output-denied" "$work_root/output-custom" "$work_root/output-disabled" run_validator "$work_root/output-denied" delta true printf 'allow 0.0.0.0/0\nallow ::/0\nallow 0 - 4294967295\n' >"$fixture/tal/allow.constraints" run_validator "$work_root/output-custom" delta true 'constraints = "/fixture/tal/allow.constraints"' printf 'malformed policy\n' >"$fixture/tal/custom.constraints" run_validator "$work_root/output-disabled" delta false 'constraints = "/fixture/tal/missing.constraints"' python3 - "$work_root" <<'PYTEST' import json, pathlib, sys root = pathlib.Path(sys.argv[1]) for name, expected, enabled in [("denied", 0, True), ("custom", 2, True), ("disabled", 2, False)]: out = root / f"output-{name}" summary = json.loads((out / "summary.json").read_text()) assert summary["vrps"] == expected, summary assert summary["taConstraintsEnabled"] == enabled, summary assert len(summary["taConstraintsApplied"]) == int(enabled), summary assert len((out / "vaps.csv").read_text().splitlines()) - 1 == (0 if name == "denied" else 1) assert (out / "result-complete.json").exists() print("Constraints deny/custom/disabled payload checks passed with reused RRDP state") PYTEST rm "$fixture/tal/custom.constraints" echo "Synthetic Docker snapshot+delta E2E passed" # Exercise the actual public daemon in the image with a fresh protocol root. mkdir "$work_root/daemon" rm "$fixture/active" ln -s "cases/baseline-v1/http" "$fixture/active" daemon_config="$work_root/daemon.toml" cat >"$daemon_config" <"$work_root/daemon-container.log" 2>&1 & daemon_job=$! python3 - "$work_root/daemon" "$fixture" <<'PY' import json import pathlib import sys import time root, fixture = map(pathlib.Path, sys.argv[1:]) for _ in range(400): status = root / "daemon-status.json" if status.exists() and json.loads(status.read_text())["state"] == "sleeping": (fixture / "active").unlink() (fixture / "active").symlink_to("cases/baseline-v2/http") break time.sleep(0.05) else: raise AssertionError("Docker daemon did not finish snapshot") PY wait "$daemon_job" daemon_container="" python3 - "$work_root/daemon" <<'PY' import json import pathlib import subprocess import sys root = pathlib.Path(sys.argv[1]) assert json.loads((root / "daemon-status.json").read_text())["runs_completed"] == 3 for seq, mode, vrps in [(1, "snapshot", 1), (2, "delta", 2), (3, "delta", 2)]: out = root / f"runs/run_{seq:06}" run = json.loads((out / "run-summary.json").read_text()) assert run["status"] == "success" and run["sync_mode"] == mode, run assert run["summary"]["vrps"] == vrps, run assert run["process_metrics"]["maxRssKb"] > 0, run assert len((out / "vrps.csv").read_text().splitlines()) - 1 == vrps subprocess.run(["openssl", "asn1parse", "-inform", "DER", "-in", str(out / "result.ccr"), "-noout"], check=True) timing = json.loads((root / "runs/run_000002/stage-timing.json").read_text()) assert timing["analysis_counts"]["rrdp_delta_ops_applied_total"] > 0 print("Synthetic Docker daemon 3-run E2E passed") PY