20260316迭代 增加delta replay以及multi-rir
replay 对比,五个RIR 输出vrp与routinator一致
This commit is contained in:
@@ -171,6 +171,7 @@ fn apnic_tree_full_stats_serial() {
|
||||
timing: None,
|
||||
download_log: None,
|
||||
replay_archive_index: None,
|
||||
replay_delta_index: None,
|
||||
rrdp_dedup: true,
|
||||
rrdp_repo_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
|
||||
rsync_dedup: true,
|
||||
|
||||
73
tests/test_cli_payload_delta_replay_smoke.rs
Normal file
73
tests/test_cli_payload_delta_replay_smoke.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use std::process::Command;
|
||||
|
||||
#[test]
|
||||
fn cli_payload_delta_replay_rejects_wrong_base_locks() {
|
||||
let bin = env!("CARGO_BIN_EXE_rpki");
|
||||
let db_dir = tempfile::tempdir().expect("db tempdir");
|
||||
let out_dir = tempfile::tempdir().expect("out tempdir");
|
||||
let report_path = out_dir.path().join("report.json");
|
||||
let wrong_base_locks = out_dir.path().join("wrong-base-locks.json");
|
||||
std::fs::write(&wrong_base_locks, b"wrong-base-locks").expect("write wrong base locks");
|
||||
|
||||
let tal_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/fixtures/tal/apnic-rfc7730-https.tal");
|
||||
let ta_path =
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/ta/apnic-ta.cer");
|
||||
let demo_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("target/live/apnic_delta_demo/20260315-170223-autoplay");
|
||||
let base_archive = demo_root.join("base-payload-archive");
|
||||
let delta_archive = demo_root.join("payload-delta-archive");
|
||||
let delta_locks = demo_root.join("locks-delta.json");
|
||||
|
||||
assert!(
|
||||
base_archive.is_dir(),
|
||||
"base archive missing: {}",
|
||||
base_archive.display()
|
||||
);
|
||||
assert!(
|
||||
delta_archive.is_dir(),
|
||||
"delta archive missing: {}",
|
||||
delta_archive.display()
|
||||
);
|
||||
assert!(
|
||||
delta_locks.is_file(),
|
||||
"delta locks missing: {}",
|
||||
delta_locks.display()
|
||||
);
|
||||
|
||||
let out = Command::new(bin)
|
||||
.args([
|
||||
"--db",
|
||||
db_dir.path().to_string_lossy().as_ref(),
|
||||
"--tal-path",
|
||||
tal_path.to_string_lossy().as_ref(),
|
||||
"--ta-path",
|
||||
ta_path.to_string_lossy().as_ref(),
|
||||
"--payload-base-archive",
|
||||
base_archive.to_string_lossy().as_ref(),
|
||||
"--payload-base-locks",
|
||||
wrong_base_locks.to_string_lossy().as_ref(),
|
||||
"--payload-delta-archive",
|
||||
delta_archive.to_string_lossy().as_ref(),
|
||||
"--payload-delta-locks",
|
||||
delta_locks.to_string_lossy().as_ref(),
|
||||
"--validation-time",
|
||||
"2026-03-15T10:00:00Z",
|
||||
"--max-depth",
|
||||
"0",
|
||||
"--max-instances",
|
||||
"1",
|
||||
"--report-json",
|
||||
report_path.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run delta replay cli");
|
||||
|
||||
assert_eq!(out.status.code(), Some(2), "status={}", out.status);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(
|
||||
stderr.contains("base locks sha256 mismatch")
|
||||
|| stderr.contains("payload replay setup failed"),
|
||||
"stderr={stderr}"
|
||||
);
|
||||
}
|
||||
101
tests/test_multi_rir_case_info.rs
Normal file
101
tests/test_multi_rir_case_info.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use std::process::Command;
|
||||
|
||||
fn multi_rir_bundle_root() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../rpki/target/live/20260316-112341-multi-final3")
|
||||
}
|
||||
|
||||
fn helper_script() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("scripts/payload_replay/multi_rir_case_info.py")
|
||||
}
|
||||
|
||||
fn wrapper_script() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("scripts/payload_replay/run_multi_rir_replay_case.sh")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_rir_case_info_resolves_all_five_rirs_and_timings() {
|
||||
let bundle_root = multi_rir_bundle_root();
|
||||
assert!(bundle_root.is_dir(), "bundle root missing: {}", bundle_root.display());
|
||||
|
||||
let expected = [
|
||||
("afrinic", "afrinic", "afrinic.tal", "afrinic-ta.cer"),
|
||||
("apnic", "apnic", "apnic-rfc7730-https.tal", "apnic-ta.cer"),
|
||||
("arin", "arin", "arin.tal", "arin-ta.cer"),
|
||||
("lacnic", "lacnic", "lacnic.tal", "lacnic-ta.cer"),
|
||||
("ripe", "ripe", "ripe-ncc.tal", "ripe-ncc-ta.cer"),
|
||||
];
|
||||
|
||||
for (rir, trust_anchor, tal_suffix, ta_suffix) in expected {
|
||||
let out = Command::new("python3")
|
||||
.arg(helper_script())
|
||||
.args([
|
||||
"--bundle-root",
|
||||
bundle_root.to_string_lossy().as_ref(),
|
||||
"--rir",
|
||||
rir,
|
||||
])
|
||||
.output()
|
||||
.expect("run helper script");
|
||||
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"helper failed for {rir}: status={}\nstdout={}\nstderr={}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
|
||||
let json: serde_json::Value =
|
||||
serde_json::from_slice(&out.stdout).expect("parse helper json");
|
||||
assert_eq!(json["rir"].as_str(), Some(rir));
|
||||
assert_eq!(json["trust_anchor"].as_str(), Some(trust_anchor));
|
||||
assert!(json["base_archive"].as_str().unwrap_or("").ends_with("base-payload-archive"));
|
||||
assert!(json["delta_archive"].as_str().unwrap_or("").ends_with("payload-delta-archive"));
|
||||
assert!(json["base_locks"].as_str().unwrap_or("").ends_with("base-locks.json"));
|
||||
assert!(json["delta_locks"].as_str().unwrap_or("").ends_with("locks-delta.json"));
|
||||
assert!(json["tal_path"].as_str().unwrap_or("").ends_with(tal_suffix));
|
||||
assert!(json["ta_path"].as_str().unwrap_or("").ends_with(ta_suffix));
|
||||
assert!(json["validation_times"]["snapshot"].as_str().unwrap_or("").contains("T"));
|
||||
assert!(json["validation_times"]["delta"].as_str().unwrap_or("").contains("T"));
|
||||
assert!(json["routinator_timings"]["base_replay_seconds"]
|
||||
.as_f64()
|
||||
.unwrap_or(0.0)
|
||||
> 0.0);
|
||||
assert!(json["routinator_timings"]["delta_replay_seconds"]
|
||||
.as_f64()
|
||||
.unwrap_or(0.0)
|
||||
> 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_rir_wrapper_describe_mode_works_for_ripe() {
|
||||
let bundle_root = multi_rir_bundle_root();
|
||||
assert!(bundle_root.is_dir(), "bundle root missing: {}", bundle_root.display());
|
||||
|
||||
let out = Command::new(wrapper_script())
|
||||
.env("BUNDLE_ROOT", &bundle_root)
|
||||
.args(["ripe", "describe"])
|
||||
.output()
|
||||
.expect("run wrapper script");
|
||||
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"wrapper failed: status={}\nstdout={}\nstderr={}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
|
||||
let json: serde_json::Value =
|
||||
serde_json::from_slice(&out.stdout).expect("parse wrapper describe json");
|
||||
assert_eq!(json["rir"].as_str(), Some("ripe"));
|
||||
assert_eq!(json["trust_anchor"].as_str(), Some("ripe"));
|
||||
assert!(json["verification_json"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.ends_with("verification.json"));
|
||||
}
|
||||
@@ -122,3 +122,165 @@ fn compare_with_routinator_record_reports_diff_counts() {
|
||||
let only_record_text = std::fs::read_to_string(&only_record).expect("read only record csv");
|
||||
assert!(only_record_text.contains("AS64498,203.0.113.0/24,24,apnic"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_multi_rir_case_report_combines_compare_and_timing() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let snapshot_meta = dir.path().join("snapshot_meta.json");
|
||||
let delta_meta = dir.path().join("delta_meta.json");
|
||||
let snapshot_compare = dir.path().join("snapshot_compare.md");
|
||||
let delta_compare = dir.path().join("delta_compare.md");
|
||||
let out_md = dir.path().join("case_report.md");
|
||||
let out_json = dir.path().join("case_report.json");
|
||||
|
||||
std::fs::write(
|
||||
&snapshot_meta,
|
||||
r#"{
|
||||
"durations_secs": {"rpki_run": 12},
|
||||
"counts": {"vrps": 10, "aspas": 1}
|
||||
}"#,
|
||||
)
|
||||
.expect("write snapshot meta");
|
||||
std::fs::write(
|
||||
&delta_meta,
|
||||
r#"{
|
||||
"durations_secs": {"rpki_run": 8},
|
||||
"counts": {"vrps": 11, "aspas": 1}
|
||||
}"#,
|
||||
)
|
||||
.expect("write delta meta");
|
||||
std::fs::write(
|
||||
&snapshot_compare,
|
||||
"# compare\n\n| metric | value |\n|---|---:|\n| ours_total | 10 |\n| record_total | 10 |\n| intersection | 10 |\n| only_in_ours | 0 |\n| only_in_record | 0 |\n",
|
||||
)
|
||||
.expect("write snapshot compare");
|
||||
std::fs::write(
|
||||
&delta_compare,
|
||||
"# compare\n\n| metric | value |\n|---|---:|\n| ours_total | 11 |\n| record_total | 11 |\n| intersection | 11 |\n| only_in_ours | 0 |\n| only_in_record | 0 |\n",
|
||||
)
|
||||
.expect("write delta compare");
|
||||
|
||||
let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("scripts/payload_replay/write_multi_rir_case_report.py");
|
||||
let out = Command::new("python3")
|
||||
.arg(&script)
|
||||
.args([
|
||||
"--rir",
|
||||
"afrinic",
|
||||
"--snapshot-meta",
|
||||
snapshot_meta.to_string_lossy().as_ref(),
|
||||
"--snapshot-compare",
|
||||
snapshot_compare.to_string_lossy().as_ref(),
|
||||
"--delta-meta",
|
||||
delta_meta.to_string_lossy().as_ref(),
|
||||
"--delta-compare",
|
||||
delta_compare.to_string_lossy().as_ref(),
|
||||
"--routinator-base-seconds",
|
||||
"6.0",
|
||||
"--routinator-delta-seconds",
|
||||
"4.0",
|
||||
"--out-md",
|
||||
out_md.to_string_lossy().as_ref(),
|
||||
"--out-json",
|
||||
out_json.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run case report script");
|
||||
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"script failed: status={}\nstdout={}\nstderr={}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
|
||||
let report: serde_json::Value =
|
||||
serde_json::from_slice(&std::fs::read(&out_json).expect("read report json"))
|
||||
.expect("parse report json");
|
||||
assert_eq!(report["rir"].as_str(), Some("afrinic"));
|
||||
assert_eq!(report["snapshot"]["match"].as_bool(), Some(true));
|
||||
assert_eq!(report["delta"]["match"].as_bool(), Some(true));
|
||||
assert_eq!(report["snapshot"]["ratio"].as_f64(), Some(2.0));
|
||||
assert_eq!(report["delta"]["ratio"].as_f64(), Some(2.0));
|
||||
|
||||
let md = std::fs::read_to_string(&out_md).expect("read markdown");
|
||||
assert!(md.contains("AFRINIC Replay Report"), "{md}");
|
||||
assert!(md.contains("| snapshot | true | 12.000 | 6.000 | 2.000 | 0 | 0 |"), "{md}");
|
||||
assert!(md.contains("| delta | true | 8.000 | 4.000 | 2.000 | 0 | 0 |"), "{md}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_multi_rir_summary_aggregates_case_reports() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let case_root = dir.path().join("cases");
|
||||
for (rir, snapshot_ratio, delta_ratio) in [
|
||||
("afrinic", 2.0_f64, 3.0_f64),
|
||||
("apnic", 1.5_f64, 2.5_f64),
|
||||
("arin", 1.1_f64, 1.6_f64),
|
||||
("lacnic", 3.8_f64, 4.8_f64),
|
||||
("ripe", 2.7_f64, 2.6_f64),
|
||||
] {
|
||||
let rir_dir = case_root.join(rir);
|
||||
std::fs::create_dir_all(&rir_dir).expect("create rir dir");
|
||||
let report = serde_json::json!({
|
||||
"rir": rir,
|
||||
"snapshot": {
|
||||
"match": true,
|
||||
"ours_seconds": 10.0,
|
||||
"routinator_seconds": 5.0,
|
||||
"ratio": snapshot_ratio,
|
||||
"compare": {"only_in_ours": 0, "only_in_record": 0}
|
||||
},
|
||||
"delta": {
|
||||
"match": true,
|
||||
"ours_seconds": 12.0,
|
||||
"routinator_seconds": 6.0,
|
||||
"ratio": delta_ratio,
|
||||
"compare": {"only_in_ours": 0, "only_in_record": 0}
|
||||
}
|
||||
});
|
||||
std::fs::write(
|
||||
rir_dir.join(format!("{rir}_case_report.json")),
|
||||
serde_json::to_vec_pretty(&report).expect("serialize report"),
|
||||
)
|
||||
.expect("write report");
|
||||
}
|
||||
|
||||
let out_md = dir.path().join("summary.md");
|
||||
let out_json = dir.path().join("summary.json");
|
||||
let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("scripts/payload_replay/write_multi_rir_summary.py");
|
||||
let out = Command::new("python3")
|
||||
.arg(&script)
|
||||
.args([
|
||||
"--case-root",
|
||||
case_root.to_string_lossy().as_ref(),
|
||||
"--out-md",
|
||||
out_md.to_string_lossy().as_ref(),
|
||||
"--out-json",
|
||||
out_json.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run summary script");
|
||||
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"script failed: status={}\nstdout={}\nstderr={}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
|
||||
let json: serde_json::Value =
|
||||
serde_json::from_slice(&std::fs::read(&out_json).expect("read summary json"))
|
||||
.expect("parse summary json");
|
||||
assert_eq!(json["cases"].as_array().map(|v| v.len()), Some(5));
|
||||
assert_eq!(json["summary"]["snapshot_all_match"].as_bool(), Some(true));
|
||||
assert_eq!(json["summary"]["delta_all_match"].as_bool(), Some(true));
|
||||
assert!(json["summary"]["all_ratio_geomean"].as_f64().unwrap_or(0.0) > 0.0);
|
||||
|
||||
let md = std::fs::read_to_string(&out_md).expect("read summary md");
|
||||
assert!(md.contains("Multi-RIR Replay Summary"), "{md}");
|
||||
assert!(md.contains("| afrinic | true | 10.000 | 5.000 | 2.000 | true | 12.000 | 6.000 | 3.000 |"), "{md}");
|
||||
}
|
||||
|
||||
33
tests/test_signed_object_ber_indefinite.rs
Normal file
33
tests/test_signed_object_ber_indefinite.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use base64::Engine;
|
||||
use rpki::data_model::roa::RoaObject;
|
||||
use rpki::data_model::signed_object::RpkiSignedObject;
|
||||
|
||||
fn extract_publish_bytes(xml: &str, uri: &str) -> Vec<u8> {
|
||||
let needle = format!("<publish uri=\"{uri}\">");
|
||||
let start = xml.find(&needle).expect("publish uri present") + needle.len();
|
||||
let end = xml[start..].find("</publish>").expect("publish end") + start;
|
||||
let b64 = xml[start..end].trim();
|
||||
base64::engine::general_purpose::STANDARD
|
||||
.decode(b64)
|
||||
.expect("decode publish base64")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_object_decode_accepts_real_arin_ber_indefinite_roa_from_multi_rir_bundle() {
|
||||
let xml_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../rpki/target/live/20260316-112341-multi-final3/arin/base-payload-archive/v1/captures/arin-base-arin/rrdp/repos/8bd1405242614aed9f13321d266fe3193db0ca842e0cbffda2b3df05481c331b/4a394319-7460-4141-a416-1addb69284ff/snapshot-160090-880434ae2a6f0e5fff224391c65a22ab037e09ac1d3ebad0ceda18558b49b13e.xml",
|
||||
);
|
||||
assert!(xml_path.is_file(), "xml path missing: {}", xml_path.display());
|
||||
let xml = std::fs::read_to_string(&xml_path).expect("read snapshot xml");
|
||||
let uri = "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/2a246947-2d62-4a6c-ba05-87187f0099b2/9ed5ce80-224e-46ab-94f1-1afce8ccf13f/0b13beb5-6bbb-3994-a254-02c5b10175c5.roa";
|
||||
let der = extract_publish_bytes(&xml, uri);
|
||||
|
||||
assert_eq!(der.first().copied(), Some(0x30));
|
||||
assert_eq!(der.get(1).copied(), Some(0x80));
|
||||
|
||||
let signed_object = RpkiSignedObject::decode_der(&der).expect("decode BER-indefinite CMS signed object");
|
||||
assert_eq!(signed_object.signed_data.encap_content_info.econtent_type, "1.2.840.113549.1.9.16.1.24");
|
||||
|
||||
let roa = RoaObject::decode_der(&der).expect("decode ROA object from BER-indefinite CMS");
|
||||
assert!(!roa.roa.ip_addr_blocks.is_empty());
|
||||
}
|
||||
Reference in New Issue
Block a user