20260914 强制 TOML 配置并完善 TA 刷新与 daemon
This commit is contained in:
@@ -1,15 +1,12 @@
|
||||
use std::process::Command;
|
||||
|
||||
fn command() -> Command {
|
||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_panda-rpki"));
|
||||
cmd.env_remove("PANDA_RPKI_LOG_LEVEL")
|
||||
.env_remove("PANDA_RPKI_LOG_FORMAT");
|
||||
cmd
|
||||
Command::new(env!("CARGO_BIN_EXE_panda-rpki"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_is_successful_and_goes_to_stdout() {
|
||||
for args in [vec![], vec!["--help"], vec!["-h"]] {
|
||||
fn top_level_help_and_version_are_successful() {
|
||||
for args in [vec!["--help"], vec!["-h"], vec!["--version"], vec!["-V"]] {
|
||||
let result = command().args(args).output().unwrap();
|
||||
assert!(result.status.success());
|
||||
assert!(String::from_utf8_lossy(&result.stdout).contains("panda-rpki"));
|
||||
@@ -18,20 +15,26 @@ fn help_is_successful_and_goes_to_stdout() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_command_returns_two_without_stdout() {
|
||||
let result = command().arg("not-a-command").output().unwrap();
|
||||
assert_eq!(result.status.code(), Some(2));
|
||||
assert!(result.stdout.is_empty());
|
||||
assert!(String::from_utf8_lossy(&result.stderr).contains("unknown command"));
|
||||
fn subcommand_help_does_not_read_a_config() {
|
||||
for mode in ["validate", "daemon"] {
|
||||
let result = command()
|
||||
.args([mode, "--help", "--config", "missing.toml"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(result.status.success());
|
||||
assert!(String::from_utf8_lossy(&result.stdout).contains("--config FILE"));
|
||||
assert!(result.stderr.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_validation_arguments_do_not_create_outputs() {
|
||||
fn missing_config_and_removed_options_are_rejected_without_outputs() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
for args in [
|
||||
vec!["validate", "--out", "output"],
|
||||
vec!["validate", "--unknown", "value"],
|
||||
vec!["validate", "--out"],
|
||||
vec!["validate"],
|
||||
vec!["validate", "--tal", "input.tal"],
|
||||
vec!["daemon", "--state-root", "data"],
|
||||
vec!["validate", "--config", "missing.toml", "--unknown"],
|
||||
] {
|
||||
let result = command()
|
||||
.current_dir(directory.path())
|
||||
@@ -40,25 +43,51 @@ fn malformed_validation_arguments_do_not_create_outputs() {
|
||||
.unwrap();
|
||||
assert_eq!(result.status.code(), Some(2));
|
||||
assert!(result.stdout.is_empty());
|
||||
assert!(String::from_utf8_lossy(&result.stderr).contains("panda-rpki:"));
|
||||
assert_eq!(std::fs::read_dir(directory.path()).unwrap().count(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_daemon_state_root_fails_without_creating_state() {
|
||||
fn check_config_is_read_only_for_invalid_inputs() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
std::fs::write(
|
||||
directory.path().join("validate.toml"),
|
||||
r#"
|
||||
config-version = 1
|
||||
mode = "validate"
|
||||
out = "output"
|
||||
|
||||
[[anchors]]
|
||||
id = "missing"
|
||||
tal = "missing.tal"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = command()
|
||||
.current_dir(directory.path())
|
||||
.args([
|
||||
"daemon",
|
||||
"--",
|
||||
"--tal",
|
||||
"missing.tal",
|
||||
"--ta",
|
||||
"missing.cer",
|
||||
])
|
||||
.args(["validate", "--config", "validate.toml", "--check-config"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(result.status.code(), Some(2));
|
||||
assert_eq!(std::fs::read_dir(directory.path()).unwrap().count(), 0);
|
||||
assert!(!directory.path().join("output").exists());
|
||||
|
||||
let result = command()
|
||||
.current_dir(directory.path())
|
||||
.args(["validate", "--config", "validate.toml", "--print-config"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(result.status.code(), Some(2));
|
||||
assert!(!directory.path().join("output").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_mode_and_mutually_exclusive_inspection_flags_are_rejected() {
|
||||
let result = command()
|
||||
.args(["validate", "--check-config", "--print-config"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(result.status.code(), Some(2));
|
||||
assert!(String::from_utf8_lossy(&result.stderr).contains("mutually exclusive"));
|
||||
}
|
||||
|
||||
@@ -1,70 +1,15 @@
|
||||
use std::process::Command;
|
||||
|
||||
#[test]
|
||||
fn default_format_is_human_readable_text() {
|
||||
fn environment_log_overrides_are_ignored() {
|
||||
let result = Command::new(env!("CARGO_BIN_EXE_panda-rpki"))
|
||||
.env_remove("PANDA_RPKI_LOG_FORMAT")
|
||||
.env_remove("PANDA_RPKI_LOG_LEVEL")
|
||||
.args(["validate"])
|
||||
.env("PANDA_RPKI_LOG_LEVEL", "invalid")
|
||||
.env("PANDA_RPKI_LOG_FORMAT", "invalid")
|
||||
.args(["validate", "--log-level", "debug"])
|
||||
.output()
|
||||
.unwrap();
|
||||
let diagnostic = String::from_utf8(result.stderr).unwrap();
|
||||
assert_eq!(result.status.code(), Some(2));
|
||||
assert!(diagnostic.contains("ERROR Command failed"));
|
||||
assert!(!diagnostic.contains("command_failed"));
|
||||
let call_line = include_str!("../src/main.rs")
|
||||
.lines()
|
||||
.position(|line| line.contains("panda_rpki::logging::emit("))
|
||||
.unwrap()
|
||||
+ 1;
|
||||
assert!(diagnostic.ends_with(&format!("[src/main.rs:{call_line}]\n")));
|
||||
assert!(result.stdout.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_progress_interval_is_rejected_without_outputs() {
|
||||
for value in ["-1", "wrong", "18446744073709551616"] {
|
||||
let result = Command::new(env!("CARGO_BIN_EXE_panda-rpki"))
|
||||
.args(["validate", "--log-progress-interval-secs", value])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(result.status.code(), Some(2));
|
||||
assert!(result.stdout.is_empty());
|
||||
assert!(String::from_utf8_lossy(&result.stderr).contains("log-progress-interval-secs"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn progress_cli_overrides_invalid_environment() {
|
||||
let result = Command::new(env!("CARGO_BIN_EXE_panda-rpki"))
|
||||
.env("PANDA_RPKI_LOG_PROGRESS_INTERVAL_SECS", "invalid")
|
||||
.args([
|
||||
"validate",
|
||||
"--log-progress-interval-secs",
|
||||
"0",
|
||||
"--log-format",
|
||||
"json",
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
let record: serde_json::Value = serde_json::from_slice(&result.stderr).unwrap();
|
||||
let call_line = include_str!("../src/main.rs")
|
||||
.lines()
|
||||
.position(|line| line.contains("panda_rpki::logging::emit("))
|
||||
.unwrap()
|
||||
+ 1;
|
||||
assert_eq!(record["source"]["file"], "src/main.rs");
|
||||
assert_eq!(record["source"]["line"], call_line);
|
||||
assert!(
|
||||
record["fields"]["error"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("--tal")
|
||||
);
|
||||
assert!(
|
||||
!record["fields"]["error"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("progress")
|
||||
);
|
||||
let stderr = String::from_utf8_lossy(&result.stderr);
|
||||
assert!(stderr.contains("unsupported or repeated option: --log-level"));
|
||||
assert!(!stderr.contains("invalid log level"));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Real CLI regression: HTTPS snapshot/delta, restart, interval, lock and signals."""
|
||||
"""Real CLI regression for the forced-TOML validate and daemon contracts."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import signal
|
||||
import socket
|
||||
@@ -28,6 +27,62 @@ def state_is(root, state):
|
||||
return path.exists() and read(path)["state"] == state
|
||||
|
||||
|
||||
def validate_config(path, fixture, out, state, mode="auto", ccr=None, timeout=2,
|
||||
progress=10):
|
||||
ccr = ccr or (out / "result.ccr")
|
||||
path.write_text(
|
||||
f'''config-version = 1
|
||||
mode = "validate"
|
||||
out = {str(out)!r}
|
||||
rrdp-state-dir = {str(state)!r}
|
||||
rrdp-sync-mode = "{mode}"
|
||||
ccr-out = {str(ccr)!r}
|
||||
http-timeout-secs = {timeout}
|
||||
http-root-cert = [{str(fixture / "certs/rrdp-ca.pem")!r}]
|
||||
parallel-max-repo-sync-workers-global = 2
|
||||
parallel-phase2-object-workers = 2
|
||||
log-level = "info"
|
||||
log-format = "json"
|
||||
log-progress-interval-secs = {progress}
|
||||
|
||||
[[anchors]]
|
||||
id = "custom"
|
||||
tal = {str(fixture / "tal/custom.tal")!r}
|
||||
ta = {str(fixture / "ta/custom-ta.cer")!r}
|
||||
''',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def daemon_config(path, fixture, root, max_runs=None, interval=2, retain=3,
|
||||
timeout=0, grace=30, tal=None, auto=False, progress=10):
|
||||
tal = tal or (fixture / "tal/custom.tal")
|
||||
ta_line = "" if auto else f'ta = {str(fixture / "ta/custom-ta.cer")!r}\n'
|
||||
max_line = "" if max_runs is None else f"max-runs = {max_runs}\n"
|
||||
path.write_text(
|
||||
f'''config-version = 1
|
||||
mode = "daemon"
|
||||
state-root = {str(root)!r}
|
||||
interval-secs = {interval}
|
||||
{max_line}retain-runs = {retain}
|
||||
run-timeout-secs = {timeout}
|
||||
shutdown-grace-secs = {grace}
|
||||
http-timeout-secs = 2
|
||||
http-root-cert = [{str(fixture / "certs/rrdp-ca.pem")!r}]
|
||||
parallel-max-repo-sync-workers-global = 2
|
||||
parallel-phase2-object-workers = 2
|
||||
log-level = "info"
|
||||
log-format = "json"
|
||||
log-progress-interval-secs = {progress}
|
||||
|
||||
[[anchors]]
|
||||
id = "custom"
|
||||
tal = {str(tal)!r}
|
||||
{ta_line}''',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def run(binary):
|
||||
support = Path(__file__).resolve().parent
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
@@ -36,41 +91,44 @@ def run(binary):
|
||||
with socket.socket() as sock:
|
||||
sock.bind(("127.0.0.1", 0))
|
||||
port = sock.getsockname()[1]
|
||||
subprocess.run([sys.executable, str(support / "generate_repository.py"),
|
||||
"--rrdp-host", "127.0.0.1", "--rrdp-port", str(port),
|
||||
# Key generation is CPU-heavy under instrumented CI builds.
|
||||
"--output", str(fixture)], check=True, timeout=120)
|
||||
subprocess.run(
|
||||
[sys.executable, str(support / "generate_repository.py"),
|
||||
"--rrdp-host", "127.0.0.1", "--rrdp-port", str(port),
|
||||
"--output", str(fixture)], check=True, timeout=120)
|
||||
active = fixture / "active"
|
||||
active.symlink_to("cases/baseline-v1/http")
|
||||
server = subprocess.Popen([sys.executable, str(support / "serve_repository.py"),
|
||||
str(active), str(port), str(fixture / "certs/rrdp-server.pem"),
|
||||
str(fixture / "certs/rrdp-server.key")],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
|
||||
server = subprocess.Popen(
|
||||
[sys.executable, str(support / "serve_repository.py"), str(active),
|
||||
str(port), str(fixture / "certs/rrdp-server.pem"),
|
||||
str(fixture / "certs/rrdp-server.key")],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
|
||||
processes = []
|
||||
logs = []
|
||||
try:
|
||||
assert server.stdout.readline().strip() == "ready"
|
||||
child_args = ["--tal", str(fixture / "tal/custom.tal"), "--ta", str(fixture / "ta/custom-ta.cer"),
|
||||
"--http-root-cert", str(fixture / "certs/rrdp-ca.pem"),
|
||||
"--http-timeout-secs", "2", "--log-format", "json"]
|
||||
|
||||
def launch(root, options, inputs=None):
|
||||
def launch(root, *, max_runs=None, interval=2, retain=3, timeout=0,
|
||||
grace=30, tal=None, auto=False, progress=10):
|
||||
config = base / f"daemon-{len(logs)}.toml"
|
||||
daemon_config(config, fixture, root, max_runs=max_runs,
|
||||
interval=interval, retain=retain, timeout=timeout,
|
||||
grace=grace, tal=tal, auto=auto, progress=progress)
|
||||
log = (base / f"controller-{len(logs)}.log").open("w")
|
||||
logs.append(log)
|
||||
proc = subprocess.Popen([binary, "daemon", "--state-root", str(root), *options,
|
||||
"--", *(child_args if inputs is None else inputs)],
|
||||
stdout=log, stderr=log)
|
||||
proc = subprocess.Popen(
|
||||
[binary, "daemon", "--config", str(config)],
|
||||
stdout=log, stderr=log)
|
||||
processes.append(proc)
|
||||
return proc
|
||||
|
||||
root = base / "normal"
|
||||
proc = launch(root, ["--interval-secs", "2", "--max-runs", "2", "--retain-runs", "2"])
|
||||
proc = launch(root, max_runs=2, interval=2, retain=2)
|
||||
wait_for(lambda: state_is(root, "sleeping"), "snapshot completion")
|
||||
first = read(root / "runs/run_000001/run-summary.json")
|
||||
assert first["status"] == "success" and first["sync_mode"] == "snapshot", first
|
||||
assert first["summary"]["vrps"] == 1, first
|
||||
# Single controller even in the interval when RocksDB itself is closed.
|
||||
rival = launch(root, ["--max-runs", "1"])
|
||||
|
||||
rival = launch(root, max_runs=1)
|
||||
assert rival.wait(timeout=10) == 2
|
||||
active.unlink()
|
||||
active.symlink_to("cases/baseline-v2/http")
|
||||
@@ -80,101 +138,69 @@ def run(binary):
|
||||
assert second["summary"]["vrps"] == 2, second
|
||||
timing = read(root / "runs/run_000002/stage-timing.json")
|
||||
assert timing["analysis_counts"]["rrdp_delta_ops_applied_total"] > 0
|
||||
from datetime import datetime
|
||||
gap = (datetime.fromisoformat(second["started_at"].replace("Z", "+00:00")) -
|
||||
datetime.fromisoformat(first["finished_at"].replace("Z", "+00:00"))).total_seconds()
|
||||
assert gap >= 1.9, gap
|
||||
assert (root / "latest").is_symlink()
|
||||
for seq in (1, 2):
|
||||
out = root / f"runs/run_{seq:06}"
|
||||
assert not (out / "stderr.log").exists()
|
||||
assert not (out / "stdout.log").exists()
|
||||
assert (out / "process-output.txt").read_bytes() == b""
|
||||
records = [json.loads(line) for line in (out / "validator.log").read_text().splitlines()]
|
||||
assert (out / "validator-config.toml").is_file()
|
||||
assert (out / "result-complete.json").is_file()
|
||||
records = [json.loads(line) for line in
|
||||
(out / "validator.log").read_text().splitlines()]
|
||||
assert records[-1]["event"] == "validation_completed", records[-1]
|
||||
assert all(record["source"]["line"] > 0 and record["source"]["file"].endswith(".rs") for record in records)
|
||||
assert all(record["source"]["file"] not in ("src/logging.rs", "src/logging/progress.rs") for record in records)
|
||||
assert all(record["fields"]["run_seq"] == seq for record in records)
|
||||
assert len({record["fields"]["run_id"] for record in records}) == 1
|
||||
assert any(record["event"] == "artifact_write_completed" for record in records)
|
||||
assert len((out / "vrps.csv").read_text().splitlines()) - 1 == seq
|
||||
subprocess.run(["openssl", "asn1parse", "-inform", "DER", "-in",
|
||||
str(out / "result.ccr"), "-noout"], check=True)
|
||||
# Restart appends sequence and keeps protocol state; retention only drops completed runs.
|
||||
|
||||
(root / "runs/operator-notes").mkdir()
|
||||
(root / "runs/run_000003").mkdir() # Simulated interrupted run evidence.
|
||||
restart = launch(root, ["--interval-secs", "0", "--max-runs", "1", "--retain-runs", "1"])
|
||||
(root / "runs/run_000003").mkdir()
|
||||
restart = launch(root, max_runs=1, interval=0, retain=1)
|
||||
assert restart.wait(timeout=30) == 0
|
||||
fourth = read(root / "runs/run_000004/run-summary.json")
|
||||
assert fourth["sync_mode"] == "auto" and fourth["summary"]["vrps"] == 2
|
||||
assert not (root / "runs/run_000001").exists()
|
||||
assert (root / "runs/operator-notes").is_dir() and (root / "runs/run_000003").is_dir()
|
||||
assert (root / "runs/operator-notes").is_dir()
|
||||
assert (root / "runs/run_000003").is_dir()
|
||||
|
||||
sleeper = launch(root, ["--interval-secs", "600"])
|
||||
wait_for(lambda: state_is(root, "sleeping"), "daemon sleep")
|
||||
sleeper.send_signal(signal.SIGTERM)
|
||||
assert sleeper.wait(timeout=5) == 0
|
||||
assert state_is(root, "exited")
|
||||
|
||||
# A real validator failure is recorded, retried, and returned as nonzero at max-runs.
|
||||
# A malformed output destination is rejected during read-only config validation.
|
||||
bad_config = base / "bad.toml"
|
||||
validate_config(bad_config, fixture, base / "bad-output", base / "bad-state",
|
||||
ccr=base)
|
||||
output_failure = subprocess.run(
|
||||
[binary, "validate", *child_args, "--out", str(base / "bad-output"), "--ccr-out", str(base)],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
[binary, "validate", "--config", str(bad_config)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert output_failure.returncode == 2 and not output_failure.stdout
|
||||
failure_records = [json.loads(line) for line in output_failure.stderr.splitlines()]
|
||||
assert any(record["event"] == "artifact_write_failed" and record["fields"]["artifact_type"] == "ccr" for record in failure_records)
|
||||
assert any(record["event"] == "validation_stage_failed" for record in failure_records)
|
||||
assert not any(record["event"] == "validation_completed" for record in failure_records)
|
||||
assert "ccr-out must be a file" in output_failure.stderr
|
||||
|
||||
# A valid TAL with an unreachable automatic TA URI creates failed daemon runs.
|
||||
bad_tal = base / "unreachable.tal"
|
||||
tal_text = (fixture / "tal/custom.tal").read_text()
|
||||
bad_tal.write_text(
|
||||
tal_text
|
||||
.replace("rpki-local-rsync:873", "127.0.0.1:1")
|
||||
.replace(f"https://127.0.0.1:{port}", "https://127.0.0.1:1")
|
||||
)
|
||||
failed = base / "failed"
|
||||
bad_inputs = ["--tal", str(base / "missing.tal"), "--ta", str(base / "missing.cer")]
|
||||
failure = launch(failed, ["--max-runs", "2", "--interval-secs", "0"], bad_inputs)
|
||||
assert failure.wait(timeout=20) == 2
|
||||
failure = launch(failed, max_runs=2, interval=0, tal=bad_tal, auto=True)
|
||||
assert failure.wait(timeout=30) == 2
|
||||
for seq in (1, 2):
|
||||
assert read(failed / f"runs/run_{seq:06}/run-summary.json")["status"] == "failed"
|
||||
|
||||
# Suspend the HTTPS server so the actual validator cannot complete a request.
|
||||
# Whole-run timeout and SIGTERM are exercised while the RRDP server is stopped.
|
||||
server.send_signal(signal.SIGSTOP)
|
||||
slow_inputs = list(child_args)
|
||||
slow_inputs[slow_inputs.index("--http-timeout-secs") + 1] = "30"
|
||||
slow_inputs += ["--log-progress-interval-secs", "1"]
|
||||
progress_root = base / "progress"
|
||||
progress = launch(progress_root, ["--max-runs", "1", "--run-timeout-secs", "20"], slow_inputs)
|
||||
progress_log = progress_root / "runs/run_000001/validator.log"
|
||||
timeout_root = base / "timeout"
|
||||
timeout_proc = launch(timeout_root, max_runs=1, timeout=2, progress=0)
|
||||
assert timeout_proc.wait(timeout=15) == 2
|
||||
timeout_run = read(timeout_root / "runs/run_000001/run-summary.json")
|
||||
assert timeout_run["error"] == "timeout", timeout_run
|
||||
|
||||
def has_progress():
|
||||
if not progress_log.exists():
|
||||
return False
|
||||
lines = progress_log.read_text().splitlines()
|
||||
return sum('"event":"validation_progress"' in line for line in lines) >= 2
|
||||
|
||||
wait_for(has_progress, "periodic progress during blocked network", timeout=15)
|
||||
server.send_signal(signal.SIGCONT)
|
||||
assert progress.wait(timeout=30) == 0
|
||||
progress_records = [json.loads(line) for line in progress_log.read_text().splitlines()]
|
||||
assert progress_records[-1]["event"] == "validation_completed"
|
||||
assert any(record["event"] == "validation_progress" and record["fields"]["repository_attempts_inflight"] > 0 for record in progress_records)
|
||||
server.send_signal(signal.SIGSTOP)
|
||||
hung = base / "timeout"
|
||||
disabled_inputs = list(slow_inputs)
|
||||
disabled_inputs[-1] = "0"
|
||||
timeout = launch(hung, ["--max-runs", "1", "--run-timeout-secs", "2"], disabled_inputs)
|
||||
assert timeout.wait(timeout=10) == 2
|
||||
assert read(hung / "runs/run_000001/run-summary.json")["error"] == "timeout"
|
||||
assert '"event":"validation_progress"' not in (hung / "runs/run_000001/validator.log").read_text()
|
||||
interrupted = base / "interrupted"
|
||||
active_proc = launch(interrupted, ["--shutdown-grace-secs", "0"])
|
||||
active_proc = launch(interrupted, grace=0)
|
||||
wait_for(lambda: state_is(interrupted, "running"), "active validator")
|
||||
time.sleep(0.2)
|
||||
active_proc.send_signal(signal.SIGTERM)
|
||||
assert active_proc.wait(timeout=10) == 2
|
||||
assert active_proc.wait(timeout=15) == 2
|
||||
assert read(interrupted / "runs/run_000001/run-summary.json")["error"] == "interrupted"
|
||||
server.send_signal(signal.SIGCONT)
|
||||
recovered = launch(interrupted, ["--max-runs", "1", "--interval-secs", "0"])
|
||||
assert recovered.wait(timeout=30) == 0
|
||||
resumed = read(interrupted / "runs/run_000002/run-summary.json")
|
||||
assert resumed["status"] == "success" and resumed["summary"]["vrps"] == 2
|
||||
assert resumed["sync_mode"] in ("auto", "snapshot")
|
||||
print("Real daemon E2E passed: snapshot/delta/restart/interval/retention/lock/failure/timeout/SIGTERM")
|
||||
print("Real daemon E2E passed: TOML config/snapshot/delta/restart/retention/lock/timeout/SIGTERM")
|
||||
except BaseException:
|
||||
for log in logs:
|
||||
log.flush()
|
||||
|
||||
@@ -47,18 +47,32 @@ fi
|
||||
run_validator() {
|
||||
local out="$1"
|
||||
local mode="$2"
|
||||
local config="$work_root/validate-$mode.toml"
|
||||
cat >"$config" <<EOF
|
||||
config-version = 1
|
||||
mode = "validate"
|
||||
out = "/output"
|
||||
rrdp-state-dir = "/state"
|
||||
rrdp-sync-mode = "$mode"
|
||||
ccr-out = "/output/result.ccr"
|
||||
http-timeout-secs = 5
|
||||
http-root-cert = ["/fixture/certs/rrdp-ca.pem"]
|
||||
parallel-max-repo-sync-workers-global = 2
|
||||
parallel-phase2-object-workers = 2
|
||||
log-level = "info"
|
||||
log-format = "json"
|
||||
|
||||
[[anchors]]
|
||||
id = "custom"
|
||||
tal = "/fixture/tal/custom.tal"
|
||||
ta = "/fixture/ta/custom-ta.cer"
|
||||
EOF
|
||||
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
|
||||
-v "$config:/config/validate.toml:ro" \
|
||||
"$image" validate --config /config/validate.toml
|
||||
}
|
||||
|
||||
run_validator "$work_root/output-snapshot" snapshot
|
||||
@@ -95,14 +109,32 @@ echo "Synthetic Docker snapshot+delta E2E passed"
|
||||
mkdir "$work_root/daemon"
|
||||
rm "$fixture/active"
|
||||
ln -s "cases/baseline-v1/http" "$fixture/active"
|
||||
daemon_config="$work_root/daemon.toml"
|
||||
cat >"$daemon_config" <<EOF
|
||||
config-version = 1
|
||||
mode = "daemon"
|
||||
state-root = "/data"
|
||||
interval-secs = 2
|
||||
max-runs = 3
|
||||
retain-runs = 3
|
||||
http-timeout-secs = 5
|
||||
http-root-cert = ["/fixture/certs/rrdp-ca.pem"]
|
||||
parallel-max-repo-sync-workers-global = 2
|
||||
parallel-phase2-object-workers = 2
|
||||
log-level = "info"
|
||||
log-format = "json"
|
||||
|
||||
[[anchors]]
|
||||
id = "custom"
|
||||
tal = "/fixture/tal/custom.tal"
|
||||
ta = "/fixture/ta/custom-ta.cer"
|
||||
EOF
|
||||
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 \
|
||||
-v "$daemon_config:/config/daemon.toml:ro" \
|
||||
"$image" daemon --config /config/daemon.toml \
|
||||
>"$work_root/daemon-container.log" 2>&1 &
|
||||
daemon_job=$!
|
||||
python3 - "$work_root/daemon" "$fixture" <<'PY'
|
||||
|
||||
Reference in New Issue
Block a user