20260407 & 20260408 基于cir 三方replay对齐,并且materialize 使用hard link优化
This commit is contained in:
151
tests/test_cir_matrix_m9.rs
Normal file
151
tests/test_cir_matrix_m9.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use rpki::cir::{
|
||||
encode_cir, materialize_cir, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal,
|
||||
CIR_VERSION_V1,
|
||||
};
|
||||
|
||||
fn apnic_tal_path() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/tal/apnic-rfc7730-https.tal")
|
||||
}
|
||||
|
||||
fn apnic_ta_path() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/ta/apnic-ta.cer")
|
||||
}
|
||||
|
||||
fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
|
||||
let tal_bytes = std::fs::read(apnic_tal_path()).expect("read tal");
|
||||
let ta_bytes = std::fs::read(apnic_ta_path()).expect("read ta");
|
||||
let tal = rpki::data_model::tal::Tal::decode_bytes(&tal_bytes).expect("decode tal");
|
||||
let ta_rsync_uri = tal
|
||||
.ta_uris
|
||||
.iter()
|
||||
.find(|uri| uri.scheme() == "rsync")
|
||||
.expect("tal has rsync uri")
|
||||
.as_str()
|
||||
.to_string();
|
||||
let ta_hash = {
|
||||
use sha2::{Digest, Sha256};
|
||||
Sha256::digest(&ta_bytes).to_vec()
|
||||
};
|
||||
(
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V1,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
"2026-04-07T00:00:00Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: vec![CirObject {
|
||||
rsync_uri: ta_rsync_uri,
|
||||
sha256: ta_hash,
|
||||
}],
|
||||
tals: vec![CirTal {
|
||||
tal_uri: "https://example.test/root.tal".to_string(),
|
||||
tal_bytes,
|
||||
}],
|
||||
},
|
||||
ta_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
fn write_static(root: &Path, date: &str, bytes: &[u8]) {
|
||||
use sha2::{Digest, Sha256};
|
||||
let hash = hex::encode(Sha256::digest(bytes));
|
||||
let dir = root.join(date).join(&hash[0..2]).join(&hash[2..4]);
|
||||
std::fs::create_dir_all(&dir).expect("mkdir static");
|
||||
std::fs::write(dir.join(hash), bytes).expect("write static object");
|
||||
}
|
||||
|
||||
fn prepare_reference_ccr(work: &Path, cir: &CanonicalInputRepresentation, mirror_root: &Path) -> PathBuf {
|
||||
let reference_ccr = work.join("reference.ccr");
|
||||
let rpki_bin = env!("CARGO_BIN_EXE_rpki");
|
||||
let wrapper = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/cir-rsync-wrapper");
|
||||
let tal_path = apnic_tal_path();
|
||||
let ta_path = apnic_ta_path();
|
||||
let out = Command::new(rpki_bin)
|
||||
.env("REAL_RSYNC_BIN", "/usr/bin/rsync")
|
||||
.env("CIR_MIRROR_ROOT", mirror_root)
|
||||
.args([
|
||||
"--db",
|
||||
work.join("reference-db").to_string_lossy().as_ref(),
|
||||
"--tal-path",
|
||||
tal_path.to_string_lossy().as_ref(),
|
||||
"--ta-path",
|
||||
ta_path.to_string_lossy().as_ref(),
|
||||
"--disable-rrdp",
|
||||
"--rsync-command",
|
||||
wrapper.to_string_lossy().as_ref(),
|
||||
"--validation-time",
|
||||
&cir.validation_time
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap(),
|
||||
"--max-depth",
|
||||
"0",
|
||||
"--max-instances",
|
||||
"1",
|
||||
"--ccr-out",
|
||||
reference_ccr.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run reference rpki");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
reference_ccr
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cir_replay_matrix_script_matches_reference_for_all_participants() {
|
||||
if !Path::new("/usr/bin/rsync").exists()
|
||||
|| !Path::new("/home/yuyr/dev/rust_playground/routinator/target/debug/routinator").exists()
|
||||
|| !Path::new("/home/yuyr/dev/rpki-client-9.7/build-m5/src/rpki-client").exists()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let static_root = td.path().join("static");
|
||||
let cir_path = td.path().join("sample.cir");
|
||||
let mirror_root = td.path().join("mirror");
|
||||
let out_dir = td.path().join("matrix-out");
|
||||
|
||||
let (cir, ta_bytes) = build_ta_only_cir();
|
||||
std::fs::write(&cir_path, encode_cir(&cir).expect("encode cir")).expect("write cir");
|
||||
write_static(&static_root, "20260407", &ta_bytes);
|
||||
materialize_cir(&cir, &static_root, &mirror_root, true).expect("materialize");
|
||||
let reference_ccr = prepare_reference_ccr(td.path(), &cir, &mirror_root);
|
||||
|
||||
let script = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/run_cir_replay_matrix.sh");
|
||||
let out = Command::new(script)
|
||||
.args([
|
||||
"--cir",
|
||||
cir_path.to_string_lossy().as_ref(),
|
||||
"--static-root",
|
||||
static_root.to_string_lossy().as_ref(),
|
||||
"--out-dir",
|
||||
out_dir.to_string_lossy().as_ref(),
|
||||
"--reference-ccr",
|
||||
reference_ccr.to_string_lossy().as_ref(),
|
||||
"--rpki-client-build-dir",
|
||||
"/home/yuyr/dev/rpki-client-9.7/build-m5",
|
||||
"--rpki-bin",
|
||||
env!("CARGO_BIN_EXE_rpki"),
|
||||
])
|
||||
.output()
|
||||
.expect("run cir matrix script");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
|
||||
let summary: serde_json::Value =
|
||||
serde_json::from_slice(&std::fs::read(out_dir.join("summary.json")).expect("read summary"))
|
||||
.expect("parse summary");
|
||||
assert_eq!(summary["allMatch"], true);
|
||||
let participants = summary["participants"].as_array().expect("participants array");
|
||||
assert_eq!(participants.len(), 3);
|
||||
for participant in participants {
|
||||
assert_eq!(participant["exitCode"], 0);
|
||||
assert_eq!(participant["match"], true);
|
||||
assert_eq!(participant["vrps"]["match"], true);
|
||||
assert_eq!(participant["vaps"]["match"], true);
|
||||
}
|
||||
}
|
||||
182
tests/test_cir_peer_replay_m8.rs
Normal file
182
tests/test_cir_peer_replay_m8.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V1, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal, encode_cir,
|
||||
materialize_cir,
|
||||
};
|
||||
|
||||
fn apnic_tal_path() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/tal/apnic-rfc7730-https.tal")
|
||||
}
|
||||
|
||||
fn apnic_ta_path() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/ta/apnic-ta.cer")
|
||||
}
|
||||
|
||||
fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
|
||||
let tal_bytes = std::fs::read(apnic_tal_path()).expect("read tal");
|
||||
let ta_bytes = std::fs::read(apnic_ta_path()).expect("read ta");
|
||||
let tal = rpki::data_model::tal::Tal::decode_bytes(&tal_bytes).expect("decode tal");
|
||||
let ta_rsync_uri = tal
|
||||
.ta_uris
|
||||
.iter()
|
||||
.find(|uri| uri.scheme() == "rsync")
|
||||
.expect("tal has rsync uri")
|
||||
.as_str()
|
||||
.to_string();
|
||||
let ta_hash = {
|
||||
use sha2::{Digest, Sha256};
|
||||
Sha256::digest(&ta_bytes).to_vec()
|
||||
};
|
||||
(
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V1,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
"2026-04-07T00:00:00Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: vec![CirObject {
|
||||
rsync_uri: ta_rsync_uri,
|
||||
sha256: ta_hash,
|
||||
}],
|
||||
tals: vec![CirTal {
|
||||
tal_uri: "https://example.test/root.tal".to_string(),
|
||||
tal_bytes,
|
||||
}],
|
||||
},
|
||||
ta_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
fn write_static(root: &Path, date: &str, bytes: &[u8]) {
|
||||
use sha2::{Digest, Sha256};
|
||||
let hash = hex::encode(Sha256::digest(bytes));
|
||||
let dir = root.join(date).join(&hash[0..2]).join(&hash[2..4]);
|
||||
std::fs::create_dir_all(&dir).expect("mkdir static");
|
||||
std::fs::write(dir.join(hash), bytes).expect("write static object");
|
||||
}
|
||||
|
||||
fn prepare_reference_ccr(work: &Path, cir: &CanonicalInputRepresentation, mirror_root: &Path) -> PathBuf {
|
||||
let reference_ccr = work.join("reference.ccr");
|
||||
let rpki_bin = env!("CARGO_BIN_EXE_rpki");
|
||||
let wrapper = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/cir-rsync-wrapper");
|
||||
let tal_path = apnic_tal_path();
|
||||
let ta_path = apnic_ta_path();
|
||||
let out = Command::new(rpki_bin)
|
||||
.env("REAL_RSYNC_BIN", "/usr/bin/rsync")
|
||||
.env("CIR_MIRROR_ROOT", mirror_root)
|
||||
.args([
|
||||
"--db",
|
||||
work.join("reference-db").to_string_lossy().as_ref(),
|
||||
"--tal-path",
|
||||
tal_path.to_string_lossy().as_ref(),
|
||||
"--ta-path",
|
||||
ta_path.to_string_lossy().as_ref(),
|
||||
"--disable-rrdp",
|
||||
"--rsync-command",
|
||||
wrapper.to_string_lossy().as_ref(),
|
||||
"--validation-time",
|
||||
&cir.validation_time
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap(),
|
||||
"--max-depth",
|
||||
"0",
|
||||
"--max-instances",
|
||||
"1",
|
||||
"--ccr-out",
|
||||
reference_ccr.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run reference rpki");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
reference_ccr
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cir_routinator_script_matches_reference_on_ta_only_cir() {
|
||||
if !Path::new("/usr/bin/rsync").exists()
|
||||
|| !Path::new("/home/yuyr/dev/rust_playground/routinator/target/debug/routinator").exists()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let static_root = td.path().join("static");
|
||||
let cir_path = td.path().join("sample.cir");
|
||||
let mirror_root = td.path().join("mirror");
|
||||
let out_dir = td.path().join("routinator-out");
|
||||
|
||||
let (cir, ta_bytes) = build_ta_only_cir();
|
||||
std::fs::write(&cir_path, encode_cir(&cir).expect("encode cir")).expect("write cir");
|
||||
write_static(&static_root, "20260407", &ta_bytes);
|
||||
materialize_cir(&cir, &static_root, &mirror_root, true).expect("materialize");
|
||||
let reference_ccr = prepare_reference_ccr(td.path(), &cir, &mirror_root);
|
||||
|
||||
let script = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/run_cir_replay_routinator.sh");
|
||||
let out = Command::new(script)
|
||||
.args([
|
||||
"--cir",
|
||||
cir_path.to_string_lossy().as_ref(),
|
||||
"--static-root",
|
||||
static_root.to_string_lossy().as_ref(),
|
||||
"--out-dir",
|
||||
out_dir.to_string_lossy().as_ref(),
|
||||
"--reference-ccr",
|
||||
reference_ccr.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run routinator cir script");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
let summary: serde_json::Value = serde_json::from_slice(
|
||||
&std::fs::read(out_dir.join("compare-summary.json")).expect("read summary"),
|
||||
)
|
||||
.expect("parse summary");
|
||||
assert_eq!(summary["vrps"]["match"], true);
|
||||
assert_eq!(summary["vaps"]["match"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cir_rpki_client_script_matches_reference_on_ta_only_cir() {
|
||||
if !Path::new("/usr/bin/rsync").exists()
|
||||
|| !Path::new("/home/yuyr/dev/rpki-client-9.7/build-m5/src/rpki-client").exists()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let static_root = td.path().join("static");
|
||||
let cir_path = td.path().join("sample.cir");
|
||||
let mirror_root = td.path().join("mirror");
|
||||
let out_dir = td.path().join("rpki-client-out");
|
||||
|
||||
let (cir, ta_bytes) = build_ta_only_cir();
|
||||
std::fs::write(&cir_path, encode_cir(&cir).expect("encode cir")).expect("write cir");
|
||||
write_static(&static_root, "20260407", &ta_bytes);
|
||||
materialize_cir(&cir, &static_root, &mirror_root, true).expect("materialize");
|
||||
let reference_ccr = prepare_reference_ccr(td.path(), &cir, &mirror_root);
|
||||
|
||||
let script = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/run_cir_replay_rpki_client.sh");
|
||||
let out = Command::new(script)
|
||||
.args([
|
||||
"--cir",
|
||||
cir_path.to_string_lossy().as_ref(),
|
||||
"--static-root",
|
||||
static_root.to_string_lossy().as_ref(),
|
||||
"--out-dir",
|
||||
out_dir.to_string_lossy().as_ref(),
|
||||
"--reference-ccr",
|
||||
reference_ccr.to_string_lossy().as_ref(),
|
||||
"--build-dir",
|
||||
"/home/yuyr/dev/rpki-client-9.7/build-m5",
|
||||
])
|
||||
.output()
|
||||
.expect("run rpki-client cir script");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
let summary: serde_json::Value = serde_json::from_slice(
|
||||
&std::fs::read(out_dir.join("compare-summary.json")).expect("read summary"),
|
||||
)
|
||||
.expect("parse summary");
|
||||
assert_eq!(summary["vrps"]["match"], true);
|
||||
assert_eq!(summary["vaps"]["match"], true);
|
||||
}
|
||||
190
tests/test_cir_wrapper_m6.rs
Normal file
190
tests/test_cir_wrapper_m6.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
fn wrapper_path() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/cir-rsync-wrapper")
|
||||
}
|
||||
|
||||
fn real_rsync() -> Option<String> {
|
||||
let candidate = "/usr/bin/rsync";
|
||||
if std::path::Path::new(candidate).exists() {
|
||||
return Some(candidate.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cir_rsync_wrapper_passes_through_help() {
|
||||
let Some(real) = real_rsync() else {
|
||||
return;
|
||||
};
|
||||
let out = Command::new(wrapper_path())
|
||||
.env("REAL_RSYNC_BIN", real)
|
||||
.arg("-h")
|
||||
.output()
|
||||
.expect("run wrapper -h");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(stdout.contains("rsync") || stderr.contains("rsync"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cir_rsync_wrapper_rewrites_rsync_source_to_mirror_tree() {
|
||||
let Some(real) = real_rsync() else {
|
||||
return;
|
||||
};
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let mirror_root = td.path().join("mirror");
|
||||
let dest_root = td.path().join("dest");
|
||||
let repo_root = mirror_root.join("example.net").join("repo");
|
||||
std::fs::create_dir_all(repo_root.join("nested")).expect("mkdirs");
|
||||
std::fs::write(repo_root.join("a.roa"), b"roa").expect("write roa");
|
||||
std::fs::write(repo_root.join("nested").join("b.txt"), b"txt").expect("write txt");
|
||||
std::fs::create_dir_all(&dest_root).expect("mkdir dest");
|
||||
|
||||
let out = Command::new(wrapper_path())
|
||||
.env("REAL_RSYNC_BIN", real)
|
||||
.env("CIR_MIRROR_ROOT", &mirror_root)
|
||||
.args([
|
||||
"-rt",
|
||||
"--address",
|
||||
"127.0.0.1",
|
||||
"--contimeout=10",
|
||||
"--include=*/",
|
||||
"--include=*.roa",
|
||||
"--exclude=*",
|
||||
"rsync://example.net/repo/",
|
||||
dest_root.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run wrapper rewrite");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
assert_eq!(std::fs::read(dest_root.join("a.roa")).expect("read copied roa"), b"roa");
|
||||
assert!(!dest_root.join("nested").join("b.txt").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cir_rsync_wrapper_rewrites_module_root_without_trailing_slash_as_contents() {
|
||||
let Some(real) = real_rsync() else {
|
||||
return;
|
||||
};
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let mirror_root = td.path().join("mirror");
|
||||
let dest_root = td.path().join("dest");
|
||||
let repo_root = mirror_root.join("example.net").join("repo");
|
||||
std::fs::create_dir_all(repo_root.join("sub")).expect("mkdirs");
|
||||
std::fs::write(repo_root.join("root.cer"), b"cer").expect("write cer");
|
||||
std::fs::write(repo_root.join("sub").join("child.roa"), b"roa").expect("write roa");
|
||||
std::fs::create_dir_all(&dest_root).expect("mkdir dest");
|
||||
|
||||
let out = Command::new(wrapper_path())
|
||||
.env("REAL_RSYNC_BIN", real)
|
||||
.env("CIR_MIRROR_ROOT", &mirror_root)
|
||||
.args([
|
||||
"-rt",
|
||||
"--include=*/",
|
||||
"--include=*.cer",
|
||||
"--include=*.roa",
|
||||
"--exclude=*",
|
||||
"rsync://example.net/repo",
|
||||
dest_root.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run wrapper rewrite");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
assert_eq!(std::fs::read(dest_root.join("root.cer")).expect("read copied root cer"), b"cer");
|
||||
assert_eq!(
|
||||
std::fs::read(dest_root.join("sub").join("child.roa")).expect("read copied child roa"),
|
||||
b"roa"
|
||||
);
|
||||
assert!(!dest_root.join("repo").exists(), "module root must not be nested under destination");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cir_rsync_wrapper_requires_mirror_root_for_rsync_source() {
|
||||
let Some(real) = real_rsync() else {
|
||||
return;
|
||||
};
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let dest_root = td.path().join("dest");
|
||||
std::fs::create_dir_all(&dest_root).expect("mkdir dest");
|
||||
|
||||
let out = Command::new(wrapper_path())
|
||||
.env("REAL_RSYNC_BIN", real)
|
||||
.args(["-rt", "rsync://example.net/repo/", dest_root.to_string_lossy().as_ref()])
|
||||
.output()
|
||||
.expect("run wrapper missing env");
|
||||
assert!(!out.status.success());
|
||||
assert!(String::from_utf8_lossy(&out.stderr).contains("CIR_MIRROR_ROOT"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cir_rsync_wrapper_leaves_local_source_untouched() {
|
||||
let Some(real) = real_rsync() else {
|
||||
return;
|
||||
};
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let src_root = td.path().join("src");
|
||||
let dest_root = td.path().join("dest");
|
||||
std::fs::create_dir_all(&src_root).expect("mkdir src");
|
||||
std::fs::create_dir_all(&dest_root).expect("mkdir dest");
|
||||
std::fs::write(src_root.join("x.cer"), b"x").expect("write source");
|
||||
|
||||
let out = Command::new(wrapper_path())
|
||||
.env("REAL_RSYNC_BIN", real)
|
||||
.args([
|
||||
"-rt",
|
||||
src_root.to_string_lossy().as_ref(),
|
||||
dest_root.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run wrapper local passthrough");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
assert_eq!(std::fs::read(dest_root.join("src").join("x.cer")).expect("read copied file"), b"x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cir_rsync_wrapper_local_link_mode_uses_hardlinks_for_rewritten_sources() {
|
||||
let Some(real) = real_rsync() else {
|
||||
return;
|
||||
};
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let mirror_root = td.path().join("mirror");
|
||||
let dest_root = td.path().join("dest");
|
||||
let repo_root = mirror_root.join("example.net").join("repo");
|
||||
std::fs::create_dir_all(repo_root.join("nested")).expect("mkdirs");
|
||||
let src_file = repo_root.join("a.roa");
|
||||
let src_nested = repo_root.join("nested").join("b.cer");
|
||||
std::fs::write(&src_file, b"roa").expect("write roa");
|
||||
std::fs::write(&src_nested, b"cer").expect("write cer");
|
||||
std::fs::create_dir_all(&dest_root).expect("mkdir dest");
|
||||
|
||||
let out = Command::new(wrapper_path())
|
||||
.env("REAL_RSYNC_BIN", real)
|
||||
.env("CIR_MIRROR_ROOT", &mirror_root)
|
||||
.env("CIR_LOCAL_LINK_MODE", "1")
|
||||
.args([
|
||||
"-rt",
|
||||
"--delete",
|
||||
"--include=*/",
|
||||
"--include=*.roa",
|
||||
"--include=*.cer",
|
||||
"--exclude=*",
|
||||
"rsync://example.net/repo/",
|
||||
dest_root.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run wrapper local-link mode");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
|
||||
let dst_file = dest_root.join("a.roa");
|
||||
let dst_nested = dest_root.join("nested").join("b.cer");
|
||||
assert_eq!(std::fs::read(&dst_file).expect("read dest roa"), b"roa");
|
||||
assert_eq!(std::fs::read(&dst_nested).expect("read dest cer"), b"cer");
|
||||
|
||||
let src_meta = std::fs::metadata(&src_file).expect("src metadata");
|
||||
let dst_meta = std::fs::metadata(&dst_file).expect("dst metadata");
|
||||
assert_eq!(src_meta.ino(), dst_meta.ino(), "expected hardlinked destination file");
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::process::Command;
|
||||
|
||||
#[test]
|
||||
fn cli_run_offline_mode_executes_and_writes_json_and_ccr() {
|
||||
let db_dir = tempfile::tempdir().expect("db tempdir");
|
||||
@@ -80,3 +82,168 @@ fn cli_run_offline_mode_writes_decodable_ccr() {
|
||||
let ccr = rpki::ccr::decode_content_info(&bytes).expect("decode ccr");
|
||||
assert!(ccr.content.tas.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_run_offline_mode_writes_cir_and_static_pool() {
|
||||
let db_dir = tempfile::tempdir().expect("db tempdir");
|
||||
let repo_dir = tempfile::tempdir().expect("repo tempdir");
|
||||
let out_dir = tempfile::tempdir().expect("out tempdir");
|
||||
let cir_path = out_dir.path().join("result.cir");
|
||||
let static_root = out_dir.path().join("static");
|
||||
|
||||
let policy_path = out_dir.path().join("policy.toml");
|
||||
std::fs::write(&policy_path, "sync_preference = \"rsync_only\"\n").expect("write policy");
|
||||
|
||||
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 argv = vec![
|
||||
"rpki".to_string(),
|
||||
"--db".to_string(),
|
||||
db_dir.path().to_string_lossy().to_string(),
|
||||
"--policy".to_string(),
|
||||
policy_path.to_string_lossy().to_string(),
|
||||
"--tal-path".to_string(),
|
||||
tal_path.to_string_lossy().to_string(),
|
||||
"--ta-path".to_string(),
|
||||
ta_path.to_string_lossy().to_string(),
|
||||
"--rsync-local-dir".to_string(),
|
||||
repo_dir.path().to_string_lossy().to_string(),
|
||||
"--max-depth".to_string(),
|
||||
"0".to_string(),
|
||||
"--max-instances".to_string(),
|
||||
"1".to_string(),
|
||||
"--cir-enable".to_string(),
|
||||
"--cir-out".to_string(),
|
||||
cir_path.to_string_lossy().to_string(),
|
||||
"--cir-static-root".to_string(),
|
||||
static_root.to_string_lossy().to_string(),
|
||||
"--cir-tal-uri".to_string(),
|
||||
"https://example.test/root.tal".to_string(),
|
||||
];
|
||||
|
||||
rpki::cli::run(&argv).expect("cli run");
|
||||
|
||||
let bytes = std::fs::read(&cir_path).expect("read cir");
|
||||
let cir = rpki::cir::decode_cir(&bytes).expect("decode cir");
|
||||
assert_eq!(cir.tals.len(), 1);
|
||||
assert_eq!(cir.tals[0].tal_uri, "https://example.test/root.tal");
|
||||
assert!(cir
|
||||
.objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri.contains("apnic-rpki-root-iana-origin.cer")));
|
||||
|
||||
let mut file_count = 0usize;
|
||||
let mut stack = vec![static_root.clone()];
|
||||
while let Some(path) = stack.pop() {
|
||||
for entry in std::fs::read_dir(path).expect("read_dir") {
|
||||
let entry = entry.expect("entry");
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
stack.push(path);
|
||||
} else {
|
||||
file_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(file_count >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_run_blackbox_rsync_wrapper_mode_matches_reference_ccr_without_ta_path() {
|
||||
let real_rsync = std::path::Path::new("/usr/bin/rsync");
|
||||
if !real_rsync.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let db_dir = tempfile::tempdir().expect("db tempdir");
|
||||
let out_dir = tempfile::tempdir().expect("out tempdir");
|
||||
let mirror_root = out_dir.path().join("mirror");
|
||||
let ref_ccr_path = out_dir.path().join("reference.ccr");
|
||||
let actual_ccr_path = out_dir.path().join("actual.ccr");
|
||||
|
||||
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 ta_bytes = std::fs::read(&ta_path).expect("read ta");
|
||||
|
||||
std::fs::create_dir_all(mirror_root.join("rpki.apnic.net").join("repository"))
|
||||
.expect("mkdir mirror");
|
||||
std::fs::write(
|
||||
mirror_root
|
||||
.join("rpki.apnic.net")
|
||||
.join("repository")
|
||||
.join("apnic-rpki-root-iana-origin.cer"),
|
||||
ta_bytes,
|
||||
)
|
||||
.expect("write ta into mirror");
|
||||
|
||||
let bin = env!("CARGO_BIN_EXE_rpki");
|
||||
let wrapper = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("scripts/cir/cir-rsync-wrapper");
|
||||
let reference = Command::new(bin)
|
||||
.env("REAL_RSYNC_BIN", real_rsync)
|
||||
.env("CIR_MIRROR_ROOT", &mirror_root)
|
||||
.args([
|
||||
"--db",
|
||||
db_dir.path().join("reference-db").to_string_lossy().as_ref(),
|
||||
"--tal-path",
|
||||
tal_path.to_string_lossy().as_ref(),
|
||||
"--ta-path",
|
||||
ta_path.to_string_lossy().as_ref(),
|
||||
"--disable-rrdp",
|
||||
"--rsync-command",
|
||||
wrapper.to_string_lossy().as_ref(),
|
||||
"--validation-time",
|
||||
"2026-04-07T00:00:00Z",
|
||||
"--max-depth",
|
||||
"0",
|
||||
"--max-instances",
|
||||
"1",
|
||||
"--ccr-out",
|
||||
ref_ccr_path.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run reference wrapper mode");
|
||||
assert!(reference.status.success(), "stderr={}", String::from_utf8_lossy(&reference.stderr));
|
||||
|
||||
let out = Command::new(bin)
|
||||
.env("REAL_RSYNC_BIN", real_rsync)
|
||||
.env("CIR_MIRROR_ROOT", &mirror_root)
|
||||
.args([
|
||||
"--db",
|
||||
db_dir.path().join("actual-db").to_string_lossy().as_ref(),
|
||||
"--tal-path",
|
||||
tal_path.to_string_lossy().as_ref(),
|
||||
"--disable-rrdp",
|
||||
"--rsync-command",
|
||||
wrapper.to_string_lossy().as_ref(),
|
||||
"--validation-time",
|
||||
"2026-04-07T00:00:00Z",
|
||||
"--max-depth",
|
||||
"0",
|
||||
"--max-instances",
|
||||
"1",
|
||||
"--ccr-out",
|
||||
actual_ccr_path.to_string_lossy().as_ref(),
|
||||
])
|
||||
.output()
|
||||
.expect("run blackbox wrapper mode");
|
||||
assert!(out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
|
||||
let reference = rpki::ccr::decode_content_info(&std::fs::read(&ref_ccr_path).unwrap())
|
||||
.expect("decode reference ccr");
|
||||
let actual = rpki::ccr::decode_content_info(&std::fs::read(&actual_ccr_path).unwrap())
|
||||
.expect("decode actual ccr");
|
||||
|
||||
assert_eq!(actual.content.version, reference.content.version);
|
||||
assert_eq!(actual.content.hash_alg, reference.content.hash_alg);
|
||||
assert_eq!(actual.content.mfts, reference.content.mfts);
|
||||
assert_eq!(actual.content.vrps, reference.content.vrps);
|
||||
assert_eq!(actual.content.vaps, reference.content.vaps);
|
||||
assert_eq!(actual.content.tas, reference.content.tas);
|
||||
assert_eq!(actual.content.rks, reference.content.rks);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user