panda-rpki-oss/tests/synthetic_docker_e2e.sh
yuyr 591bed90a8
Some checks failed
ci / rust (push) Has been cancelled
ci / docker (push) Has been cancelled
ci / audit (push) Has been cancelled
Harden first-release validation and dependency hygiene
2026-09-10 09:53:45 +08:00

145 lines
5.6 KiB
Bash
Executable File

#!/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"
docker run --rm --network host --user "$(id -u):$(id -g)" \
-v "$fixture:/fixture:ro" \
-v "$work_root/state:/state" \
-v "$out:/output" \
"$image" validate \
--tal /fixture/tal/custom.tal --ta /fixture/ta/custom-ta.cer \
--http-root-cert /fixture/certs/rrdp-ca.pem \
--http-timeout-secs 5 \
--parallel-max-repo-sync-workers-global 2 \
--parallel-phase2-object-workers 2 \
--rrdp-state-dir /state --rrdp-sync-mode "$mode" \
--ccr-out /output/result.ccr --out /output --log-level info --log-format json
}
run_validator "$work_root/output-snapshot" snapshot
rm "$fixture/active"
ln -s "cases/baseline-v2/http" "$fixture/active"
run_validator "$work_root/output-delta" delta
for output in "$work_root/output-snapshot" "$work_root/output-delta"; do
test -s "$output/summary.json"
test -s "$output/vrps.csv"
test -s "$output/result.ccr"
openssl asn1parse -inform DER -in "$output/result.ccr" -noout
done
test -s "$work_root/state/repository-db/CURRENT"
python3 - "$work_root/output-snapshot" "$work_root/output-delta" <<'PY'
import json
import pathlib
import sys
snapshot, delta = (pathlib.Path(item) for item in sys.argv[1:])
first = json.loads((snapshot / "summary.json").read_text(encoding="utf-8"))
second = json.loads((delta / "summary.json").read_text(encoding="utf-8"))
assert first["vrps"] == 1, first
assert second["vrps"] == 2, second
assert len((snapshot / "vaps.csv").read_text(encoding="utf-8").splitlines()) == 2
assert len((delta / "vaps.csv").read_text(encoding="utf-8").splitlines()) == 2
timing = json.loads((delta / "stage-timing.json").read_text(encoding="utf-8"))
assert timing["analysis_counts"]["rrdp_delta_ops_applied_total"] > 0, timing
PY
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_container="panda-rpki-daemon-$(basename "$work_root")"
docker run --rm --name "$daemon_container" --network host --read-only --tmpfs /tmp \
--user "$(id -u):$(id -g)" \
-v "$fixture:/fixture:ro" -v "$work_root/daemon:/data" \
"$image" daemon \
--state-root /data --max-runs 3 --interval-secs 2 --retain-runs 3 -- \
--tal /fixture/tal/custom.tal --ta /fixture/ta/custom-ta.cer \
--http-root-cert /fixture/certs/rrdp-ca.pem --log-format json \
>"$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