20260407 & 20260408 基于cir 三方replay对齐,并且materialize 使用hard link优化
This commit is contained in:
56
scripts/cir/README.md
Normal file
56
scripts/cir/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# CIR Scripts
|
||||
|
||||
## `cir-rsync-wrapper`
|
||||
|
||||
一个用于 CIR 黑盒 replay 的 rsync wrapper。
|
||||
|
||||
### 环境变量
|
||||
|
||||
- `REAL_RSYNC_BIN`
|
||||
- 真实 rsync 二进制路径
|
||||
- 默认优先 `/usr/bin/rsync`
|
||||
- `CIR_MIRROR_ROOT`
|
||||
- 本地镜像树根目录
|
||||
- 当命令行中出现 `rsync://...` source 时必需
|
||||
|
||||
### 语义
|
||||
|
||||
- 仅改写 `rsync://host/path` 类型参数
|
||||
- 其它参数原样透传给真实 rsync
|
||||
- 改写目标:
|
||||
- `rsync://example.net/repo/a.roa`
|
||||
- →
|
||||
- `<CIR_MIRROR_ROOT>/example.net/repo/a.roa`
|
||||
|
||||
### 兼容目标
|
||||
|
||||
- Routinator `--rsync-command`
|
||||
- `rpki-client -e rsync_prog`
|
||||
|
||||
## 其它脚本
|
||||
|
||||
- `run_cir_replay_ours.sh`
|
||||
- `run_cir_replay_routinator.sh`
|
||||
- `run_cir_replay_rpki_client.sh`
|
||||
- `run_cir_replay_matrix.sh`
|
||||
|
||||
## `cir-local-link-sync.py`
|
||||
|
||||
当 `CIR_LOCAL_LINK_MODE=1` 且 wrapper 检测到 source 已经被改写为本地 mirror 路径时,
|
||||
wrapper 不再调用真实 `rsync`,而是调用这个 helper 完成:
|
||||
|
||||
- `hardlink` 优先的本地树同步
|
||||
- 失败时回退到 copy
|
||||
- 支持 `--delete`
|
||||
|
||||
`run_cir_replay_matrix.sh` 会顺序执行:
|
||||
|
||||
- `ours`
|
||||
- Routinator
|
||||
- `rpki-client`
|
||||
|
||||
并汇总生成:
|
||||
|
||||
- `summary.json`
|
||||
- `summary.md`
|
||||
- `detail.md`
|
||||
BIN
scripts/cir/__pycache__/cir-local-link-sync.cpython-312.pyc
Normal file
BIN
scripts/cir/__pycache__/cir-local-link-sync.cpython-312.pyc
Normal file
Binary file not shown.
BIN
scripts/cir/__pycache__/cir-rsync-wrappercpython-312.pyc
Normal file
BIN
scripts/cir/__pycache__/cir-rsync-wrappercpython-312.pyc
Normal file
Binary file not shown.
BIN
scripts/cir/__pycache__/json_to_vaps_csv.cpython-312.pyc
Normal file
BIN
scripts/cir/__pycache__/json_to_vaps_csv.cpython-312.pyc
Normal file
Binary file not shown.
136
scripts/cir/cir-local-link-sync.py
Executable file
136
scripts/cir/cir-local-link-sync.py
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import errno
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _same_inode(src: Path, dst: Path) -> bool:
|
||||
try:
|
||||
src_stat = src.stat()
|
||||
dst_stat = dst.stat()
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
return (src_stat.st_dev, src_stat.st_ino) == (dst_stat.st_dev, dst_stat.st_ino)
|
||||
|
||||
|
||||
def _remove_path(path: Path) -> None:
|
||||
if not path.exists() and not path.is_symlink():
|
||||
return
|
||||
if path.is_dir() and not path.is_symlink():
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
path.unlink()
|
||||
|
||||
|
||||
def _prune_empty_dirs(root: Path) -> None:
|
||||
if not root.exists():
|
||||
return
|
||||
for path in sorted((p for p in root.rglob("*") if p.is_dir()), key=lambda p: len(p.parts), reverse=True):
|
||||
try:
|
||||
path.rmdir()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _link_or_copy(src: Path, dst: Path) -> str:
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
if dst.exists() or dst.is_symlink():
|
||||
if _same_inode(src, dst):
|
||||
return "reused"
|
||||
_remove_path(dst)
|
||||
try:
|
||||
os.link(src, dst)
|
||||
return "linked"
|
||||
except OSError as err:
|
||||
if err.errno not in (errno.EXDEV, errno.EPERM, errno.EMLINK, errno.ENOTSUP, errno.EACCES):
|
||||
raise
|
||||
shutil.copy2(src, dst)
|
||||
return "copied"
|
||||
|
||||
|
||||
def _file_map(src_arg: str, dest_arg: str) -> tuple[Path, dict[str, Path]]:
|
||||
src = Path(src_arg.rstrip(os.sep))
|
||||
if not src.exists():
|
||||
raise FileNotFoundError(src)
|
||||
mapping: dict[str, Path] = {}
|
||||
if src.is_dir():
|
||||
copy_contents = src_arg.endswith(os.sep)
|
||||
if copy_contents:
|
||||
root = src
|
||||
for path in root.rglob("*"):
|
||||
if path.is_file():
|
||||
mapping[path.relative_to(root).as_posix()] = path
|
||||
else:
|
||||
root = src
|
||||
base = src.name
|
||||
for path in root.rglob("*"):
|
||||
if path.is_file():
|
||||
rel = Path(base) / path.relative_to(root)
|
||||
mapping[rel.as_posix()] = path
|
||||
else:
|
||||
dest_path = Path(dest_arg)
|
||||
if dest_arg.endswith(os.sep) or dest_path.is_dir():
|
||||
mapping[src.name] = src
|
||||
else:
|
||||
mapping[dest_path.name] = src
|
||||
return Path(dest_arg), mapping
|
||||
|
||||
|
||||
def sync_local_tree(src_arg: str, dst_arg: str, delete: bool) -> dict[str, int]:
|
||||
dst_root, mapping = _file_map(src_arg, dst_arg)
|
||||
dst_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
expected = {dst_root / rel for rel in mapping.keys()}
|
||||
|
||||
deleted = 0
|
||||
if delete and dst_root.exists():
|
||||
for path in sorted(dst_root.rglob("*"), key=lambda p: len(p.parts), reverse=True):
|
||||
if path.is_dir():
|
||||
continue
|
||||
if path not in expected:
|
||||
_remove_path(path)
|
||||
deleted += 1
|
||||
_prune_empty_dirs(dst_root)
|
||||
|
||||
linked = 0
|
||||
copied = 0
|
||||
reused = 0
|
||||
for rel, src in mapping.items():
|
||||
dst = dst_root / rel
|
||||
result = _link_or_copy(src, dst)
|
||||
if result == "linked":
|
||||
linked += 1
|
||||
elif result == "copied":
|
||||
copied += 1
|
||||
else:
|
||||
reused += 1
|
||||
|
||||
return {
|
||||
"files": len(mapping),
|
||||
"linked": linked,
|
||||
"copied": copied,
|
||||
"reused": reused,
|
||||
"deleted": deleted,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Sync a local CIR mirror tree using hardlinks when possible.")
|
||||
parser.add_argument("--delete", action="store_true", help="Delete target files not present in source")
|
||||
parser.add_argument("source")
|
||||
parser.add_argument("dest")
|
||||
args = parser.parse_args()
|
||||
|
||||
summary = sync_local_tree(args.source, args.dest, args.delete)
|
||||
print(
|
||||
"local-link-sync files={files} linked={linked} copied={copied} reused={reused} deleted={deleted}".format(
|
||||
**summary
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
127
scripts/cir/cir-rsync-wrapper
Executable file
127
scripts/cir/cir-rsync-wrapper
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
def real_rsync_bin() -> str:
|
||||
env = os.environ.get("REAL_RSYNC_BIN")
|
||||
if env:
|
||||
return env
|
||||
default = "/usr/bin/rsync"
|
||||
if Path(default).exists():
|
||||
return default
|
||||
found = shutil.which("rsync")
|
||||
if found:
|
||||
return found
|
||||
raise SystemExit("cir-rsync-wrapper: REAL_RSYNC_BIN is not set and rsync was not found")
|
||||
|
||||
|
||||
def rewrite_arg(arg: str, mirror_root: str | None) -> str:
|
||||
if not arg.startswith("rsync://"):
|
||||
return arg
|
||||
if not mirror_root:
|
||||
raise SystemExit(
|
||||
"cir-rsync-wrapper: CIR_MIRROR_ROOT is required when an rsync:// source is present"
|
||||
)
|
||||
parsed = urlparse(arg)
|
||||
if parsed.scheme != "rsync" or not parsed.hostname:
|
||||
raise SystemExit(f"cir-rsync-wrapper: invalid rsync URI: {arg}")
|
||||
path = parsed.path.lstrip("/")
|
||||
local = Path(mirror_root).resolve() / parsed.hostname
|
||||
if path:
|
||||
local = local / path
|
||||
local_str = str(local)
|
||||
if local.exists() and local.is_dir() and not local_str.endswith("/"):
|
||||
local_str += "/"
|
||||
elif arg.endswith("/") and not local_str.endswith("/"):
|
||||
local_str += "/"
|
||||
return local_str
|
||||
|
||||
|
||||
def filter_args(args: list[str]) -> list[str]:
|
||||
mirror_root = os.environ.get("CIR_MIRROR_ROOT")
|
||||
rewritten_any = any(arg.startswith("rsync://") for arg in args)
|
||||
out: list[str] = []
|
||||
i = 0
|
||||
while i < len(args):
|
||||
arg = args[i]
|
||||
if rewritten_any:
|
||||
if arg == "--address":
|
||||
i += 2
|
||||
continue
|
||||
if arg.startswith("--address="):
|
||||
i += 1
|
||||
continue
|
||||
if arg == "--contimeout":
|
||||
i += 2
|
||||
continue
|
||||
if arg.startswith("--contimeout="):
|
||||
i += 1
|
||||
continue
|
||||
out.append(rewrite_arg(arg, mirror_root))
|
||||
i += 1
|
||||
return out
|
||||
|
||||
|
||||
def local_link_mode_enabled() -> bool:
|
||||
value = os.environ.get("CIR_LOCAL_LINK_MODE", "")
|
||||
return value.lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def extract_source_and_dest(args: list[str]) -> tuple[str, str]:
|
||||
expects_value = {
|
||||
"--timeout",
|
||||
"--min-size",
|
||||
"--max-size",
|
||||
"--include",
|
||||
"--exclude",
|
||||
"--compare-dest",
|
||||
}
|
||||
positionals: list[str] = []
|
||||
i = 0
|
||||
while i < len(args):
|
||||
arg = args[i]
|
||||
if arg in expects_value:
|
||||
i += 2
|
||||
continue
|
||||
if any(arg.startswith(prefix + "=") for prefix in expects_value):
|
||||
i += 1
|
||||
continue
|
||||
if arg.startswith("-"):
|
||||
i += 1
|
||||
continue
|
||||
positionals.append(arg)
|
||||
i += 1
|
||||
if len(positionals) < 2:
|
||||
raise SystemExit("cir-rsync-wrapper: expected source and destination arguments")
|
||||
return positionals[-2], positionals[-1]
|
||||
|
||||
|
||||
def maybe_exec_local_link_sync(args: list[str], rewritten_any: bool) -> None:
|
||||
if not rewritten_any or not local_link_mode_enabled():
|
||||
return
|
||||
source, dest = extract_source_and_dest(args)
|
||||
if source.startswith("rsync://"):
|
||||
raise SystemExit("cir-rsync-wrapper: expected rewritten local source for CIR_LOCAL_LINK_MODE")
|
||||
helper = Path(__file__).with_name("cir-local-link-sync.py")
|
||||
cmd = [sys.executable, str(helper)]
|
||||
if "--delete" in args:
|
||||
cmd.append("--delete")
|
||||
cmd.extend([source, dest])
|
||||
os.execv(sys.executable, cmd)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = sys.argv[1:]
|
||||
rewritten_any = any(arg.startswith("rsync://") for arg in args)
|
||||
rewritten = filter_args(args)
|
||||
maybe_exec_local_link_sync(rewritten, rewritten_any)
|
||||
os.execv(real_rsync_bin(), [real_rsync_bin(), *rewritten])
|
||||
return 127
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
50
scripts/cir/json_to_vaps_csv.py
Executable file
50
scripts/cir/json_to_vaps_csv.py
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def normalize_asn(value: str | int) -> str:
|
||||
text = str(value).strip().upper()
|
||||
if text.startswith("AS"):
|
||||
text = text[2:]
|
||||
return f"AS{int(text)}"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--input", required=True, type=Path)
|
||||
parser.add_argument("--csv-out", required=True, type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
obj = json.loads(args.input.read_text(encoding="utf-8"))
|
||||
rows: list[tuple[str, str, str]] = []
|
||||
for aspa in obj.get("aspas", []):
|
||||
providers = sorted(
|
||||
{normalize_asn(item) for item in aspa.get("providers", [])},
|
||||
key=lambda s: int(s[2:]),
|
||||
)
|
||||
rows.append(
|
||||
(
|
||||
normalize_asn(aspa["customer"]),
|
||||
";".join(providers),
|
||||
str(aspa.get("ta", "")).strip().lower(),
|
||||
)
|
||||
)
|
||||
rows.sort(key=lambda row: (int(row[0][2:]), row[1], row[2]))
|
||||
|
||||
args.csv_out.parent.mkdir(parents=True, exist_ok=True)
|
||||
with args.csv_out.open("w", encoding="utf-8", newline="") as fh:
|
||||
writer = csv.writer(fh)
|
||||
writer.writerow(["Customer ASN", "Providers", "Trust Anchor"])
|
||||
writer.writerows(rows)
|
||||
print(args.csv_out)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
286
scripts/cir/run_cir_replay_matrix.sh
Executable file
286
scripts/cir/run_cir_replay_matrix.sh
Executable file
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./scripts/cir/run_cir_replay_matrix.sh \
|
||||
--cir <path> \
|
||||
--static-root <path> \
|
||||
--out-dir <path> \
|
||||
--reference-ccr <path> \
|
||||
--rpki-client-build-dir <path> \
|
||||
[--keep-db] \
|
||||
[--rpki-bin <path>] \
|
||||
[--routinator-root <path>] \
|
||||
[--routinator-bin <path>] \
|
||||
[--real-rsync-bin <path>]
|
||||
EOF
|
||||
}
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
|
||||
CIR=""
|
||||
STATIC_ROOT=""
|
||||
OUT_DIR=""
|
||||
REFERENCE_CCR=""
|
||||
RPKI_CLIENT_BUILD_DIR=""
|
||||
KEEP_DB=0
|
||||
RPKI_BIN="${RPKI_BIN:-$ROOT_DIR/target/release/rpki}"
|
||||
ROUTINATOR_ROOT="${ROUTINATOR_ROOT:-/home/yuyr/dev/rust_playground/routinator}"
|
||||
ROUTINATOR_BIN="${ROUTINATOR_BIN:-$ROUTINATOR_ROOT/target/debug/routinator}"
|
||||
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||
OURS_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_ours.sh"
|
||||
ROUTINATOR_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_routinator.sh"
|
||||
RPKI_CLIENT_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_rpki_client.sh"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--cir) CIR="$2"; shift 2 ;;
|
||||
--static-root) STATIC_ROOT="$2"; shift 2 ;;
|
||||
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||
--reference-ccr) REFERENCE_CCR="$2"; shift 2 ;;
|
||||
--rpki-client-build-dir) RPKI_CLIENT_BUILD_DIR="$2"; shift 2 ;;
|
||||
--keep-db) KEEP_DB=1; shift ;;
|
||||
--rpki-bin) RPKI_BIN="$2"; shift 2 ;;
|
||||
--routinator-root) ROUTINATOR_ROOT="$2"; shift 2 ;;
|
||||
--routinator-bin) ROUTINATOR_BIN="$2"; shift 2 ;;
|
||||
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$CIR" && -n "$STATIC_ROOT" && -n "$OUT_DIR" && -n "$REFERENCE_CCR" && -n "$RPKI_CLIENT_BUILD_DIR" ]] || {
|
||||
usage >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
run_with_timing() {
|
||||
local summary_path="$1"
|
||||
local timing_path="$2"
|
||||
shift 2
|
||||
local start end status
|
||||
start="$(python3 - <<'PY'
|
||||
import time
|
||||
print(time.perf_counter_ns())
|
||||
PY
|
||||
)"
|
||||
if "$@"; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
end="$(python3 - <<'PY'
|
||||
import time
|
||||
print(time.perf_counter_ns())
|
||||
PY
|
||||
)"
|
||||
python3 - <<'PY' "$summary_path" "$timing_path" "$status" "$start" "$end"
|
||||
import json, sys
|
||||
summary_path, timing_path, status, start, end = sys.argv[1:]
|
||||
duration_ms = max(0, (int(end) - int(start)) // 1_000_000)
|
||||
data = {"exitCode": int(status), "durationMs": duration_ms}
|
||||
try:
|
||||
with open(summary_path, "r", encoding="utf-8") as f:
|
||||
data["compare"] = json.load(f)
|
||||
except FileNotFoundError:
|
||||
data["compare"] = None
|
||||
with open(timing_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
PY
|
||||
return "$status"
|
||||
}
|
||||
|
||||
OURS_OUT="$OUT_DIR/ours"
|
||||
ROUTINATOR_OUT="$OUT_DIR/routinator"
|
||||
RPKI_CLIENT_OUT="$OUT_DIR/rpki-client"
|
||||
mkdir -p "$OURS_OUT" "$ROUTINATOR_OUT" "$RPKI_CLIENT_OUT"
|
||||
|
||||
ours_cmd=(
|
||||
"$OURS_SCRIPT"
|
||||
--cir "$CIR"
|
||||
--static-root "$STATIC_ROOT"
|
||||
--out-dir "$OURS_OUT"
|
||||
--reference-ccr "$REFERENCE_CCR"
|
||||
--rpki-bin "$RPKI_BIN"
|
||||
--real-rsync-bin "$REAL_RSYNC_BIN"
|
||||
)
|
||||
routinator_cmd=(
|
||||
"$ROUTINATOR_SCRIPT"
|
||||
--cir "$CIR"
|
||||
--static-root "$STATIC_ROOT"
|
||||
--out-dir "$ROUTINATOR_OUT"
|
||||
--reference-ccr "$REFERENCE_CCR"
|
||||
--routinator-root "$ROUTINATOR_ROOT"
|
||||
--routinator-bin "$ROUTINATOR_BIN"
|
||||
--real-rsync-bin "$REAL_RSYNC_BIN"
|
||||
)
|
||||
rpki_client_cmd=(
|
||||
"$RPKI_CLIENT_SCRIPT"
|
||||
--cir "$CIR"
|
||||
--static-root "$STATIC_ROOT"
|
||||
--out-dir "$RPKI_CLIENT_OUT"
|
||||
--reference-ccr "$REFERENCE_CCR"
|
||||
--build-dir "$RPKI_CLIENT_BUILD_DIR"
|
||||
--real-rsync-bin "$REAL_RSYNC_BIN"
|
||||
)
|
||||
|
||||
if [[ "$KEEP_DB" -eq 1 ]]; then
|
||||
ours_cmd+=(--keep-db)
|
||||
routinator_cmd+=(--keep-db)
|
||||
rpki_client_cmd+=(--keep-db)
|
||||
fi
|
||||
|
||||
ours_status=0
|
||||
routinator_status=0
|
||||
rpki_client_status=0
|
||||
if run_with_timing "$OURS_OUT/compare-summary.json" "$OURS_OUT/timing.json" "${ours_cmd[@]}"; then
|
||||
:
|
||||
else
|
||||
ours_status=$?
|
||||
fi
|
||||
if run_with_timing "$ROUTINATOR_OUT/compare-summary.json" "$ROUTINATOR_OUT/timing.json" "${routinator_cmd[@]}"; then
|
||||
:
|
||||
else
|
||||
routinator_status=$?
|
||||
fi
|
||||
if run_with_timing "$RPKI_CLIENT_OUT/compare-summary.json" "$RPKI_CLIENT_OUT/timing.json" "${rpki_client_cmd[@]}"; then
|
||||
:
|
||||
else
|
||||
rpki_client_status=$?
|
||||
fi
|
||||
|
||||
SUMMARY_JSON="$OUT_DIR/summary.json"
|
||||
SUMMARY_MD="$OUT_DIR/summary.md"
|
||||
DETAIL_MD="$OUT_DIR/detail.md"
|
||||
|
||||
python3 - <<'PY' \
|
||||
"$CIR" \
|
||||
"$STATIC_ROOT" \
|
||||
"$REFERENCE_CCR" \
|
||||
"$OURS_OUT" \
|
||||
"$ROUTINATOR_OUT" \
|
||||
"$RPKI_CLIENT_OUT" \
|
||||
"$SUMMARY_JSON" \
|
||||
"$SUMMARY_MD" \
|
||||
"$DETAIL_MD"
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
cir_path, static_root, reference_ccr, ours_out, routinator_out, rpki_client_out, summary_json, summary_md, detail_md = sys.argv[1:]
|
||||
|
||||
participants = []
|
||||
all_match = True
|
||||
for name, out_dir in [
|
||||
("ours", ours_out),
|
||||
("routinator", routinator_out),
|
||||
("rpki-client", rpki_client_out),
|
||||
]:
|
||||
out = Path(out_dir)
|
||||
timing = json.loads((out / "timing.json").read_text(encoding="utf-8"))
|
||||
compare = timing.get("compare") or {}
|
||||
vrps = compare.get("vrps") or {}
|
||||
vaps = compare.get("vaps") or {}
|
||||
participant = {
|
||||
"name": name,
|
||||
"outDir": str(out),
|
||||
"tmpRoot": str(out / ".tmp"),
|
||||
"mirrorPath": str(out / ".tmp" / "mirror"),
|
||||
"timingPath": str(out / "timing.json"),
|
||||
"summaryPath": str(out / "compare-summary.json"),
|
||||
"exitCode": timing["exitCode"],
|
||||
"durationMs": timing["durationMs"],
|
||||
"vrps": vrps,
|
||||
"vaps": vaps,
|
||||
"match": bool(vrps.get("match")) and bool(vaps.get("match")) and timing["exitCode"] == 0,
|
||||
"logPaths": [str(path) for path in sorted(out.glob("*.log"))],
|
||||
}
|
||||
participants.append(participant)
|
||||
all_match = all_match and participant["match"]
|
||||
|
||||
summary = {
|
||||
"cirPath": cir_path,
|
||||
"staticRoot": static_root,
|
||||
"referenceCcr": reference_ccr,
|
||||
"participants": participants,
|
||||
"allMatch": all_match,
|
||||
}
|
||||
Path(summary_json).write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||
|
||||
lines = [
|
||||
"# CIR Replay Matrix Summary",
|
||||
"",
|
||||
f"- `cir`: `{cir_path}`",
|
||||
f"- `static_root`: `{static_root}`",
|
||||
f"- `reference_ccr`: `{reference_ccr}`",
|
||||
f"- `all_match`: `{all_match}`",
|
||||
"",
|
||||
"| Participant | Exit | Duration (ms) | VRP actual/ref | VRP match | VAP actual/ref | VAP match | Log |",
|
||||
"| --- | ---: | ---: | --- | --- | --- | --- | --- |",
|
||||
]
|
||||
for participant in participants:
|
||||
vrps = participant["vrps"] or {}
|
||||
vaps = participant["vaps"] or {}
|
||||
log_path = participant["logPaths"][0] if participant["logPaths"] else ""
|
||||
lines.append(
|
||||
"| {name} | {exit_code} | {duration_ms} | {vrp_actual}/{vrp_ref} | {vrp_match} | {vap_actual}/{vap_ref} | {vap_match} | `{log_path}` |".format(
|
||||
name=participant["name"],
|
||||
exit_code=participant["exitCode"],
|
||||
duration_ms=participant["durationMs"],
|
||||
vrp_actual=vrps.get("actual", "-"),
|
||||
vrp_ref=vrps.get("reference", "-"),
|
||||
vrp_match=vrps.get("match", False),
|
||||
vap_actual=vaps.get("actual", "-"),
|
||||
vap_ref=vaps.get("reference", "-"),
|
||||
vap_match=vaps.get("match", False),
|
||||
log_path=log_path,
|
||||
)
|
||||
)
|
||||
Path(summary_md).write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
detail_lines = [
|
||||
"# CIR Replay Matrix Detail",
|
||||
"",
|
||||
]
|
||||
for participant in participants:
|
||||
vrps = participant["vrps"] or {}
|
||||
vaps = participant["vaps"] or {}
|
||||
detail_lines.extend([
|
||||
f"## {participant['name']}",
|
||||
f"- `exit_code`: `{participant['exitCode']}`",
|
||||
f"- `duration_ms`: `{participant['durationMs']}`",
|
||||
f"- `out_dir`: `{participant['outDir']}`",
|
||||
f"- `tmp_root`: `{participant['tmpRoot']}`",
|
||||
f"- `mirror_path`: `{participant['mirrorPath']}`",
|
||||
f"- `summary_path`: `{participant['summaryPath']}`",
|
||||
f"- `timing_path`: `{participant['timingPath']}`",
|
||||
f"- `log_paths`: `{', '.join(participant['logPaths'])}`",
|
||||
f"- `vrps`: `actual={vrps.get('actual', '-')}` `reference={vrps.get('reference', '-')}` `match={vrps.get('match', False)}`",
|
||||
f"- `vaps`: `actual={vaps.get('actual', '-')}` `reference={vaps.get('reference', '-')}` `match={vaps.get('match', False)}`",
|
||||
f"- `vrps.only_in_actual`: `{vrps.get('only_in_actual', [])}`",
|
||||
f"- `vrps.only_in_reference`: `{vrps.get('only_in_reference', [])}`",
|
||||
f"- `vaps.only_in_actual`: `{vaps.get('only_in_actual', [])}`",
|
||||
f"- `vaps.only_in_reference`: `{vaps.get('only_in_reference', [])}`",
|
||||
"",
|
||||
])
|
||||
Path(detail_md).write_text("\n".join(detail_lines), encoding="utf-8")
|
||||
PY
|
||||
|
||||
if [[ "$ours_status" -ne 0 || "$routinator_status" -ne 0 || "$rpki_client_status" -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
all_match="$(python3 - <<'PY' "$SUMMARY_JSON"
|
||||
import json,sys
|
||||
print("true" if json.load(open(sys.argv[1]))["allMatch"] else "false")
|
||||
PY
|
||||
)"
|
||||
if [[ "$all_match" != "true" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "done: $OUT_DIR"
|
||||
150
scripts/cir/run_cir_replay_ours.sh
Executable file
150
scripts/cir/run_cir_replay_ours.sh
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./scripts/cir/run_cir_replay_ours.sh \
|
||||
--cir <path> \
|
||||
--static-root <path> \
|
||||
--out-dir <path> \
|
||||
--reference-ccr <path> \
|
||||
[--keep-db] \
|
||||
[--rpki-bin <path>] \
|
||||
[--real-rsync-bin <path>]
|
||||
EOF
|
||||
}
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
|
||||
CIR=""
|
||||
STATIC_ROOT=""
|
||||
OUT_DIR=""
|
||||
REFERENCE_CCR=""
|
||||
KEEP_DB=0
|
||||
RPKI_BIN="$ROOT_DIR/target/release/rpki"
|
||||
CIR_MATERIALIZE_BIN="$ROOT_DIR/target/release/cir_materialize"
|
||||
CIR_EXTRACT_INPUTS_BIN="$ROOT_DIR/target/release/cir_extract_inputs"
|
||||
CCR_TO_COMPARE_VIEWS_BIN="$ROOT_DIR/target/release/ccr_to_compare_views"
|
||||
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||
WRAPPER="$ROOT_DIR/scripts/cir/cir-rsync-wrapper"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--cir) CIR="$2"; shift 2 ;;
|
||||
--static-root) STATIC_ROOT="$2"; shift 2 ;;
|
||||
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||
--reference-ccr) REFERENCE_CCR="$2"; shift 2 ;;
|
||||
--keep-db) KEEP_DB=1; shift ;;
|
||||
--rpki-bin) RPKI_BIN="$2"; shift 2 ;;
|
||||
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$CIR" && -n "$STATIC_ROOT" && -n "$OUT_DIR" && -n "$REFERENCE_CCR" ]] || {
|
||||
usage >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
if [[ ! -x "$RPKI_BIN" || ! -x "$CIR_MATERIALIZE_BIN" || ! -x "$CIR_EXTRACT_INPUTS_BIN" || ! -x "$CCR_TO_COMPARE_VIEWS_BIN" ]]; then
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
cargo build --release --bin rpki --bin cir_materialize --bin cir_extract_inputs --bin ccr_to_compare_views
|
||||
)
|
||||
fi
|
||||
|
||||
TMP_ROOT="$OUT_DIR/.tmp"
|
||||
TALS_DIR="$TMP_ROOT/tals"
|
||||
META_JSON="$TMP_ROOT/meta.json"
|
||||
MIRROR_ROOT="$TMP_ROOT/mirror"
|
||||
DB_DIR="$TMP_ROOT/work-db"
|
||||
ACTUAL_CCR="$OUT_DIR/actual.ccr"
|
||||
ACTUAL_REPORT="$OUT_DIR/report.json"
|
||||
ACTUAL_VRPS="$OUT_DIR/actual-vrps.csv"
|
||||
ACTUAL_VAPS="$OUT_DIR/actual-vaps.csv"
|
||||
REF_VRPS="$OUT_DIR/reference-vrps.csv"
|
||||
REF_VAPS="$OUT_DIR/reference-vaps.csv"
|
||||
COMPARE_JSON="$OUT_DIR/compare-summary.json"
|
||||
RUN_LOG="$OUT_DIR/run.log"
|
||||
|
||||
rm -rf "$TMP_ROOT"
|
||||
mkdir -p "$TMP_ROOT"
|
||||
|
||||
"$CIR_EXTRACT_INPUTS_BIN" --cir "$CIR" --tals-dir "$TALS_DIR" --meta-json "$META_JSON"
|
||||
materialize_cmd=("$CIR_MATERIALIZE_BIN" --cir "$CIR" --static-root "$STATIC_ROOT" --mirror-root "$MIRROR_ROOT")
|
||||
if [[ "$KEEP_DB" -eq 1 ]]; then
|
||||
materialize_cmd+=(--keep-db)
|
||||
fi
|
||||
"${materialize_cmd[@]}"
|
||||
|
||||
VALIDATION_TIME="$(python3 - <<'PY' "$META_JSON"
|
||||
import json,sys
|
||||
print(json.load(open(sys.argv[1]))["validationTime"])
|
||||
PY
|
||||
)"
|
||||
FIRST_TAL="$(python3 - <<'PY' "$META_JSON"
|
||||
import json,sys
|
||||
print(json.load(open(sys.argv[1]))["talFiles"][0]["path"])
|
||||
PY
|
||||
)"
|
||||
|
||||
export CIR_MIRROR_ROOT="$(python3 - <<'PY' "$MIRROR_ROOT"
|
||||
from pathlib import Path
|
||||
import sys
|
||||
print(Path(sys.argv[1]).resolve())
|
||||
PY
|
||||
)"
|
||||
export REAL_RSYNC_BIN="$REAL_RSYNC_BIN"
|
||||
export CIR_LOCAL_LINK_MODE=1
|
||||
|
||||
"$RPKI_BIN" \
|
||||
--db "$DB_DIR" \
|
||||
--tal-path "$FIRST_TAL" \
|
||||
--disable-rrdp \
|
||||
--rsync-command "$WRAPPER" \
|
||||
--validation-time "$VALIDATION_TIME" \
|
||||
--ccr-out "$ACTUAL_CCR" \
|
||||
--report-json "$ACTUAL_REPORT" \
|
||||
>"$RUN_LOG" 2>&1
|
||||
|
||||
"$CCR_TO_COMPARE_VIEWS_BIN" --ccr "$ACTUAL_CCR" --vrps-out "$ACTUAL_VRPS" --vaps-out "$ACTUAL_VAPS" --trust-anchor unknown
|
||||
"$CCR_TO_COMPARE_VIEWS_BIN" --ccr "$REFERENCE_CCR" --vrps-out "$REF_VRPS" --vaps-out "$REF_VAPS" --trust-anchor unknown
|
||||
|
||||
python3 - <<'PY' "$ACTUAL_VRPS" "$REF_VRPS" "$ACTUAL_VAPS" "$REF_VAPS" "$COMPARE_JSON"
|
||||
import csv, json, sys
|
||||
def rows(path):
|
||||
with open(path, newline="") as f:
|
||||
return list(csv.reader(f))[1:]
|
||||
actual_vrps = {tuple(r) for r in rows(sys.argv[1])}
|
||||
ref_vrps = {tuple(r) for r in rows(sys.argv[2])}
|
||||
actual_vaps = {tuple(r) for r in rows(sys.argv[3])}
|
||||
ref_vaps = {tuple(r) for r in rows(sys.argv[4])}
|
||||
summary = {
|
||||
"vrps": {
|
||||
"actual": len(actual_vrps),
|
||||
"reference": len(ref_vrps),
|
||||
"only_in_actual": sorted(actual_vrps - ref_vrps)[:20],
|
||||
"only_in_reference": sorted(ref_vrps - actual_vrps)[:20],
|
||||
"match": actual_vrps == ref_vrps,
|
||||
},
|
||||
"vaps": {
|
||||
"actual": len(actual_vaps),
|
||||
"reference": len(ref_vaps),
|
||||
"only_in_actual": sorted(actual_vaps - ref_vaps)[:20],
|
||||
"only_in_reference": sorted(ref_vaps - actual_vaps)[:20],
|
||||
"match": actual_vaps == ref_vaps,
|
||||
}
|
||||
}
|
||||
with open(sys.argv[5], "w") as f:
|
||||
json.dump(summary, f, indent=2)
|
||||
PY
|
||||
|
||||
if [[ "$KEEP_DB" -ne 1 ]]; then
|
||||
rm -rf "$TMP_ROOT"
|
||||
fi
|
||||
|
||||
echo "done: $OUT_DIR"
|
||||
209
scripts/cir/run_cir_replay_routinator.sh
Executable file
209
scripts/cir/run_cir_replay_routinator.sh
Executable file
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./scripts/cir/run_cir_replay_routinator.sh \
|
||||
--cir <path> \
|
||||
--static-root <path> \
|
||||
--out-dir <path> \
|
||||
--reference-ccr <path> \
|
||||
[--keep-db] \
|
||||
[--routinator-root <path>] \
|
||||
[--routinator-bin <path>] \
|
||||
[--real-rsync-bin <path>]
|
||||
EOF
|
||||
}
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
RPKI_DEV_ROOT="${RPKI_DEV_ROOT:-$ROOT_DIR}"
|
||||
|
||||
CIR=""
|
||||
STATIC_ROOT=""
|
||||
OUT_DIR=""
|
||||
REFERENCE_CCR=""
|
||||
KEEP_DB=0
|
||||
ROUTINATOR_ROOT="${ROUTINATOR_ROOT:-/home/yuyr/dev/rust_playground/routinator}"
|
||||
ROUTINATOR_BIN="${ROUTINATOR_BIN:-$ROUTINATOR_ROOT/target/debug/routinator}"
|
||||
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||
CIR_MATERIALIZE_BIN="$ROOT_DIR/target/release/cir_materialize"
|
||||
CIR_EXTRACT_INPUTS_BIN="$ROOT_DIR/target/release/cir_extract_inputs"
|
||||
CCR_TO_COMPARE_VIEWS_BIN="$ROOT_DIR/target/release/ccr_to_compare_views"
|
||||
WRAPPER="$ROOT_DIR/scripts/cir/cir-rsync-wrapper"
|
||||
JSON_TO_VAPS="$ROOT_DIR/scripts/cir/json_to_vaps_csv.py"
|
||||
FAKETIME_LIB="${FAKETIME_LIB:-$ROOT_DIR/target/tools/faketime_pkg/extracted/libfaketime/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--cir) CIR="$2"; shift 2 ;;
|
||||
--static-root) STATIC_ROOT="$2"; shift 2 ;;
|
||||
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||
--reference-ccr) REFERENCE_CCR="$2"; shift 2 ;;
|
||||
--keep-db) KEEP_DB=1; shift ;;
|
||||
--routinator-root) ROUTINATOR_ROOT="$2"; shift 2 ;;
|
||||
--routinator-bin) ROUTINATOR_BIN="$2"; shift 2 ;;
|
||||
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$CIR" && -n "$STATIC_ROOT" && -n "$OUT_DIR" && -n "$REFERENCE_CCR" ]] || {
|
||||
usage >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
if [[ ! -x "$CIR_MATERIALIZE_BIN" || ! -x "$CIR_EXTRACT_INPUTS_BIN" || ! -x "$CCR_TO_COMPARE_VIEWS_BIN" ]]; then
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
cargo build --release --bin cir_materialize --bin cir_extract_inputs --bin ccr_to_compare_views
|
||||
)
|
||||
fi
|
||||
|
||||
TMP_ROOT="$OUT_DIR/.tmp"
|
||||
TALS_DIR="$TMP_ROOT/tals"
|
||||
META_JSON="$TMP_ROOT/meta.json"
|
||||
MIRROR_ROOT="$TMP_ROOT/mirror"
|
||||
WORK_REPO="$TMP_ROOT/repository"
|
||||
RUN_LOG="$OUT_DIR/routinator.log"
|
||||
ACTUAL_VRPS="$OUT_DIR/actual-vrps.csv"
|
||||
ACTUAL_VAPS_JSON="$OUT_DIR/actual-vaps.json"
|
||||
ACTUAL_VAPS="$OUT_DIR/actual-vaps.csv"
|
||||
REF_VRPS="$OUT_DIR/reference-vrps.csv"
|
||||
REF_VAPS="$OUT_DIR/reference-vaps.csv"
|
||||
SUMMARY_JSON="$OUT_DIR/compare-summary.json"
|
||||
|
||||
rm -rf "$TMP_ROOT"
|
||||
mkdir -p "$TMP_ROOT"
|
||||
|
||||
"$CIR_EXTRACT_INPUTS_BIN" --cir "$CIR" --tals-dir "$TALS_DIR" --meta-json "$META_JSON"
|
||||
python3 - <<'PY' "$TALS_DIR"
|
||||
from pathlib import Path
|
||||
import sys
|
||||
for tal in Path(sys.argv[1]).glob("*.tal"):
|
||||
lines = tal.read_text(encoding="utf-8").splitlines()
|
||||
rsync_uris = [line for line in lines if line.startswith("rsync://")]
|
||||
base64_lines = []
|
||||
seen_sep = False
|
||||
for line in lines:
|
||||
if seen_sep:
|
||||
if line.strip():
|
||||
base64_lines.append(line)
|
||||
elif line.strip() == "":
|
||||
seen_sep = True
|
||||
tal.write_text("\n".join(rsync_uris) + "\n\n" + "\n".join(base64_lines) + "\n", encoding="utf-8")
|
||||
PY
|
||||
materialize_cmd=("$CIR_MATERIALIZE_BIN" --cir "$CIR" --static-root "$STATIC_ROOT" --mirror-root "$MIRROR_ROOT")
|
||||
if [[ "$KEEP_DB" -eq 1 ]]; then
|
||||
materialize_cmd+=(--keep-db)
|
||||
fi
|
||||
"${materialize_cmd[@]}"
|
||||
|
||||
VALIDATION_TIME="$(python3 - <<'PY' "$META_JSON"
|
||||
import json,sys
|
||||
print(json.load(open(sys.argv[1]))["validationTime"])
|
||||
PY
|
||||
)"
|
||||
FIRST_TAL="$(python3 - <<'PY' "$META_JSON"
|
||||
import json,sys
|
||||
print(json.load(open(sys.argv[1]))["talFiles"][0]["path"])
|
||||
PY
|
||||
)"
|
||||
TA_NAME="$(basename "$FIRST_TAL" .tal)"
|
||||
FAKE_EPOCH="$(python3 - <<'PY' "$VALIDATION_TIME"
|
||||
from datetime import datetime, timezone
|
||||
import sys
|
||||
dt = datetime.fromisoformat(sys.argv[1].replace("Z", "+00:00")).astimezone(timezone.utc)
|
||||
print(int(dt.timestamp()))
|
||||
PY
|
||||
)"
|
||||
|
||||
export CIR_MIRROR_ROOT="$(python3 - <<'PY' "$MIRROR_ROOT"
|
||||
from pathlib import Path
|
||||
import sys
|
||||
print(Path(sys.argv[1]).resolve())
|
||||
PY
|
||||
)"
|
||||
export REAL_RSYNC_BIN="$REAL_RSYNC_BIN"
|
||||
export CIR_LOCAL_LINK_MODE=1
|
||||
env \
|
||||
LD_PRELOAD="$FAKETIME_LIB" \
|
||||
FAKETIME_FMT=%s \
|
||||
FAKETIME="$FAKE_EPOCH" \
|
||||
FAKETIME_DONT_FAKE_MONOTONIC=1 \
|
||||
"$ROUTINATOR_BIN" \
|
||||
--repository-dir "$WORK_REPO" \
|
||||
--disable-rrdp \
|
||||
--rsync-command "$WRAPPER" \
|
||||
--no-rir-tals \
|
||||
--extra-tals-dir "$TALS_DIR" \
|
||||
--enable-aspa \
|
||||
update --complete >"$RUN_LOG" 2>&1
|
||||
|
||||
env \
|
||||
LD_PRELOAD="$FAKETIME_LIB" \
|
||||
FAKETIME_FMT=%s \
|
||||
FAKETIME="$FAKE_EPOCH" \
|
||||
FAKETIME_DONT_FAKE_MONOTONIC=1 \
|
||||
"$ROUTINATOR_BIN" \
|
||||
--repository-dir "$WORK_REPO" \
|
||||
--disable-rrdp \
|
||||
--rsync-command "$WRAPPER" \
|
||||
--no-rir-tals \
|
||||
--extra-tals-dir "$TALS_DIR" \
|
||||
--enable-aspa \
|
||||
vrps --noupdate -o "$ACTUAL_VRPS" >>"$RUN_LOG" 2>&1
|
||||
|
||||
env \
|
||||
LD_PRELOAD="$FAKETIME_LIB" \
|
||||
FAKETIME_FMT=%s \
|
||||
FAKETIME="$FAKE_EPOCH" \
|
||||
FAKETIME_DONT_FAKE_MONOTONIC=1 \
|
||||
"$ROUTINATOR_BIN" \
|
||||
--repository-dir "$WORK_REPO" \
|
||||
--disable-rrdp \
|
||||
--rsync-command "$WRAPPER" \
|
||||
--no-rir-tals \
|
||||
--extra-tals-dir "$TALS_DIR" \
|
||||
--enable-aspa \
|
||||
vrps --noupdate --format json -o "$ACTUAL_VAPS_JSON" >>"$RUN_LOG" 2>&1
|
||||
|
||||
python3 "$JSON_TO_VAPS" --input "$ACTUAL_VAPS_JSON" --csv-out "$ACTUAL_VAPS"
|
||||
"$CCR_TO_COMPARE_VIEWS_BIN" --ccr "$REFERENCE_CCR" --vrps-out "$REF_VRPS" --vaps-out "$REF_VAPS" --trust-anchor "$TA_NAME"
|
||||
|
||||
python3 - <<'PY' "$ACTUAL_VRPS" "$REF_VRPS" "$ACTUAL_VAPS" "$REF_VAPS" "$SUMMARY_JSON"
|
||||
import csv, json, sys
|
||||
def rows(path):
|
||||
with open(path, newline="") as f:
|
||||
return list(csv.reader(f))[1:]
|
||||
actual_vrps = {tuple(r) for r in rows(sys.argv[1])}
|
||||
ref_vrps = {tuple(r) for r in rows(sys.argv[2])}
|
||||
actual_vaps = {tuple(r) for r in rows(sys.argv[3])}
|
||||
ref_vaps = {tuple(r) for r in rows(sys.argv[4])}
|
||||
summary = {
|
||||
"vrps": {
|
||||
"actual": len(actual_vrps),
|
||||
"reference": len(ref_vrps),
|
||||
"match": actual_vrps == ref_vrps,
|
||||
"only_in_actual": sorted(actual_vrps - ref_vrps)[:20],
|
||||
"only_in_reference": sorted(ref_vrps - actual_vrps)[:20],
|
||||
},
|
||||
"vaps": {
|
||||
"actual": len(actual_vaps),
|
||||
"reference": len(ref_vaps),
|
||||
"match": actual_vaps == ref_vaps,
|
||||
"only_in_actual": sorted(actual_vaps - ref_vaps)[:20],
|
||||
"only_in_reference": sorted(ref_vaps - actual_vaps)[:20],
|
||||
}
|
||||
}
|
||||
with open(sys.argv[5], "w") as f:
|
||||
json.dump(summary, f, indent=2)
|
||||
PY
|
||||
|
||||
if [[ "$KEEP_DB" -ne 1 ]]; then
|
||||
rm -rf "$TMP_ROOT"
|
||||
fi
|
||||
|
||||
echo "done: $OUT_DIR"
|
||||
179
scripts/cir/run_cir_replay_rpki_client.sh
Executable file
179
scripts/cir/run_cir_replay_rpki_client.sh
Executable file
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./scripts/cir/run_cir_replay_rpki_client.sh \
|
||||
--cir <path> \
|
||||
--static-root <path> \
|
||||
--out-dir <path> \
|
||||
--reference-ccr <path> \
|
||||
--build-dir <path> \
|
||||
[--keep-db] \
|
||||
[--real-rsync-bin <path>]
|
||||
EOF
|
||||
}
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
CIR=""
|
||||
STATIC_ROOT=""
|
||||
OUT_DIR=""
|
||||
REFERENCE_CCR=""
|
||||
BUILD_DIR=""
|
||||
KEEP_DB=0
|
||||
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||
CIR_MATERIALIZE_BIN="$ROOT_DIR/target/release/cir_materialize"
|
||||
CIR_EXTRACT_INPUTS_BIN="$ROOT_DIR/target/release/cir_extract_inputs"
|
||||
CCR_TO_COMPARE_VIEWS_BIN="$ROOT_DIR/target/release/ccr_to_compare_views"
|
||||
WRAPPER="$ROOT_DIR/scripts/cir/cir-rsync-wrapper"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--cir) CIR="$2"; shift 2 ;;
|
||||
--static-root) STATIC_ROOT="$2"; shift 2 ;;
|
||||
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||
--reference-ccr) REFERENCE_CCR="$2"; shift 2 ;;
|
||||
--build-dir) BUILD_DIR="$2"; shift 2 ;;
|
||||
--keep-db) KEEP_DB=1; shift ;;
|
||||
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$CIR" && -n "$STATIC_ROOT" && -n "$OUT_DIR" && -n "$REFERENCE_CCR" && -n "$BUILD_DIR" ]] || {
|
||||
usage >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
if [[ ! -x "$CIR_MATERIALIZE_BIN" || ! -x "$CIR_EXTRACT_INPUTS_BIN" || ! -x "$CCR_TO_COMPARE_VIEWS_BIN" ]]; then
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
cargo build --release --bin cir_materialize --bin cir_extract_inputs --bin ccr_to_compare_views
|
||||
)
|
||||
fi
|
||||
|
||||
TMP_ROOT="$OUT_DIR/.tmp"
|
||||
TALS_DIR="$TMP_ROOT/tals"
|
||||
META_JSON="$TMP_ROOT/meta.json"
|
||||
MIRROR_ROOT="$TMP_ROOT/mirror"
|
||||
CACHE_DIR="$TMP_ROOT/cache"
|
||||
OUT_CCR_DIR="$TMP_ROOT/out"
|
||||
RUN_LOG="$OUT_DIR/rpki-client.log"
|
||||
ACTUAL_VRPS="$OUT_DIR/actual-vrps.csv"
|
||||
ACTUAL_VAPS="$OUT_DIR/actual-vaps.csv"
|
||||
ACTUAL_VAPS_META="$OUT_DIR/actual-vaps-meta.json"
|
||||
ACTUAL_VRPS_META="$OUT_DIR/actual-vrps-meta.json"
|
||||
REF_VRPS="$OUT_DIR/reference-vrps.csv"
|
||||
REF_VAPS="$OUT_DIR/reference-vaps.csv"
|
||||
SUMMARY_JSON="$OUT_DIR/compare-summary.json"
|
||||
|
||||
rm -rf "$TMP_ROOT"
|
||||
mkdir -p "$TMP_ROOT"
|
||||
|
||||
"$CIR_EXTRACT_INPUTS_BIN" --cir "$CIR" --tals-dir "$TALS_DIR" --meta-json "$META_JSON"
|
||||
python3 - <<'PY' "$TALS_DIR"
|
||||
from pathlib import Path
|
||||
import sys
|
||||
for tal in Path(sys.argv[1]).glob("*.tal"):
|
||||
lines = tal.read_text(encoding="utf-8").splitlines()
|
||||
rsync_uris = [line for line in lines if line.startswith("rsync://")]
|
||||
base64_lines = []
|
||||
seen_sep = False
|
||||
for line in lines:
|
||||
if seen_sep:
|
||||
if line.strip():
|
||||
base64_lines.append(line)
|
||||
elif line.strip() == "":
|
||||
seen_sep = True
|
||||
tal.write_text("\n".join(rsync_uris) + "\n\n" + "\n".join(base64_lines) + "\n", encoding="utf-8")
|
||||
PY
|
||||
materialize_cmd=("$CIR_MATERIALIZE_BIN" --cir "$CIR" --static-root "$STATIC_ROOT" --mirror-root "$MIRROR_ROOT")
|
||||
if [[ "$KEEP_DB" -eq 1 ]]; then
|
||||
materialize_cmd+=(--keep-db)
|
||||
fi
|
||||
"${materialize_cmd[@]}"
|
||||
|
||||
VALIDATION_EPOCH="$(python3 - <<'PY' "$META_JSON"
|
||||
from datetime import datetime, timezone
|
||||
import json, sys
|
||||
vt = json.load(open(sys.argv[1]))["validationTime"]
|
||||
dt = datetime.fromisoformat(vt.replace("Z", "+00:00")).astimezone(timezone.utc)
|
||||
print(int(dt.timestamp()))
|
||||
PY
|
||||
)"
|
||||
FIRST_TAL="$(python3 - <<'PY' "$META_JSON"
|
||||
import json,sys
|
||||
print(json.load(open(sys.argv[1]))["talFiles"][0]["path"])
|
||||
PY
|
||||
)"
|
||||
TA_NAME="$(basename "$FIRST_TAL" .tal)"
|
||||
|
||||
export CIR_MIRROR_ROOT="$(python3 - <<'PY' "$MIRROR_ROOT"
|
||||
from pathlib import Path
|
||||
import sys
|
||||
print(Path(sys.argv[1]).resolve())
|
||||
PY
|
||||
)"
|
||||
export REAL_RSYNC_BIN="$REAL_RSYNC_BIN"
|
||||
export CIR_LOCAL_LINK_MODE=1
|
||||
|
||||
mkdir -p "$CACHE_DIR" "$OUT_CCR_DIR"
|
||||
"$BUILD_DIR/src/rpki-client" \
|
||||
-R \
|
||||
-e "$WRAPPER" \
|
||||
-P "$VALIDATION_EPOCH" \
|
||||
-t "$FIRST_TAL" \
|
||||
-d "$CACHE_DIR" \
|
||||
"$OUT_CCR_DIR" >"$RUN_LOG" 2>&1
|
||||
|
||||
"$BUILD_DIR/tests/rpki-ccr-vrps" \
|
||||
--input "$OUT_CCR_DIR/rpki.ccr" \
|
||||
--ta "$TA_NAME" \
|
||||
--csv-out "$ACTUAL_VRPS" \
|
||||
--meta-out "$ACTUAL_VRPS_META"
|
||||
|
||||
"$BUILD_DIR/tests/rpki-ccr-vaps" \
|
||||
--input "$OUT_CCR_DIR/rpki.ccr" \
|
||||
--ta "$TA_NAME" \
|
||||
--csv-out "$ACTUAL_VAPS" \
|
||||
--meta-out "$ACTUAL_VAPS_META"
|
||||
|
||||
"$CCR_TO_COMPARE_VIEWS_BIN" --ccr "$REFERENCE_CCR" --vrps-out "$REF_VRPS" --vaps-out "$REF_VAPS" --trust-anchor "$TA_NAME"
|
||||
|
||||
python3 - <<'PY' "$ACTUAL_VRPS" "$REF_VRPS" "$ACTUAL_VAPS" "$REF_VAPS" "$SUMMARY_JSON"
|
||||
import csv, json, sys
|
||||
def rows(path):
|
||||
with open(path, newline="") as f:
|
||||
return list(csv.reader(f))[1:]
|
||||
actual_vrps = {tuple(r) for r in rows(sys.argv[1])}
|
||||
ref_vrps = {tuple(r) for r in rows(sys.argv[2])}
|
||||
actual_vaps = {tuple(r) for r in rows(sys.argv[3])}
|
||||
ref_vaps = {tuple(r) for r in rows(sys.argv[4])}
|
||||
summary = {
|
||||
"vrps": {
|
||||
"actual": len(actual_vrps),
|
||||
"reference": len(ref_vrps),
|
||||
"match": actual_vrps == ref_vrps,
|
||||
"only_in_actual": sorted(actual_vrps - ref_vrps)[:20],
|
||||
"only_in_reference": sorted(ref_vrps - actual_vrps)[:20],
|
||||
},
|
||||
"vaps": {
|
||||
"actual": len(actual_vaps),
|
||||
"reference": len(ref_vaps),
|
||||
"match": actual_vaps == ref_vaps,
|
||||
"only_in_actual": sorted(actual_vaps - ref_vaps)[:20],
|
||||
"only_in_reference": sorted(ref_vaps - actual_vaps)[:20],
|
||||
}
|
||||
}
|
||||
with open(sys.argv[5], "w") as f:
|
||||
json.dump(summary, f, indent=2)
|
||||
PY
|
||||
|
||||
if [[ "$KEEP_DB" -ne 1 ]]; then
|
||||
rm -rf "$TMP_ROOT"
|
||||
fi
|
||||
|
||||
echo "done: $OUT_DIR"
|
||||
@@ -14,7 +14,7 @@ cleanup() {
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
IGNORE_REGEX='src/bin/replay_bundle_capture\.rs|src/bin/replay_bundle_capture_delta\.rs|src/bin/replay_bundle_capture_sequence\.rs|src/bundle/live_capture\.rs'
|
||||
IGNORE_REGEX='src/bin/replay_bundle_capture\.rs|src/bin/replay_bundle_capture_delta\.rs|src/bin/replay_bundle_capture_sequence\.rs|src/bin/replay_bundle_record\.rs|src/bin/replay_bundle_refresh_sequence_outputs\.rs|src/bin/measure_sequence_replay\.rs|src/bin/repository_view_stats\.rs|src/bin/trace_arin_missing_vrps\.rs|src/bin/db_stats\.rs|src/bin/rrdp_state_dump\.rs|src/bin/ccr_dump\.rs|src/bin/ccr_verify\.rs|src/bin/ccr_to_routinator_csv\.rs|src/bin/ccr_to_compare_views\.rs|src/bin/cir_materialize\.rs|src/bin/cir_extract_inputs\.rs|src/bundle/live_capture\.rs|src/bundle/record_io\.rs|src/progress_log\.rs'
|
||||
|
||||
# Preserve colored output even though we post-process output by running under a pseudo-TTY.
|
||||
# We run tests only once, then generate both CLI text + HTML reports without rerunning tests.
|
||||
|
||||
Reference in New Issue
Block a user