手动执行全量同步
This commit is contained in:
@@ -3,8 +3,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
fn default_samples_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/benchmark/selected_der")
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/benchmark/selected_der")
|
||||
}
|
||||
|
||||
fn read_samples(dir: &Path) -> Vec<Sample> {
|
||||
@@ -40,9 +39,7 @@ fn env_u64(name: &str, default: u64) -> u64 {
|
||||
}
|
||||
|
||||
fn env_u64_opt(name: &str) -> Option<u64> {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
std::env::var(name).ok().and_then(|s| s.parse::<u64>().ok())
|
||||
}
|
||||
|
||||
fn env_bool(name: &str) -> bool {
|
||||
@@ -101,7 +98,10 @@ fn manifest_decode_profile_benchmark_selected_der() {
|
||||
|
||||
if let Some(filter) = sample_filter.as_deref() {
|
||||
samples.retain(|s| s.name == filter);
|
||||
assert!(!samples.is_empty(), "no sample matched BENCH_SAMPLE={filter}");
|
||||
assert!(
|
||||
!samples.is_empty(),
|
||||
"no sample matched BENCH_SAMPLE={filter}"
|
||||
);
|
||||
}
|
||||
|
||||
println!("# Manifest decode + profile validate benchmark (debug build)");
|
||||
@@ -161,11 +161,7 @@ fn manifest_decode_profile_benchmark_selected_der() {
|
||||
let iters = if let Some(n) = fixed_iters {
|
||||
n
|
||||
} else {
|
||||
choose_iters_adaptive(
|
||||
bytes.as_slice(),
|
||||
min_round_ms,
|
||||
max_adaptive_iters,
|
||||
)
|
||||
choose_iters_adaptive(bytes.as_slice(), min_round_ms, max_adaptive_iters)
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -208,9 +204,9 @@ fn manifest_decode_profile_benchmark_selected_der() {
|
||||
}
|
||||
|
||||
if out_md.is_some() || out_json.is_some() {
|
||||
let timestamp_utc =
|
||||
time::OffsetDateTime::now_utc().format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
let timestamp_utc = time::OffsetDateTime::now_utc()
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
let cfg = RunConfig {
|
||||
dir: dir.display().to_string(),
|
||||
sample: sample_filter,
|
||||
|
||||
@@ -6,7 +6,9 @@ use std::path::{Path, PathBuf};
|
||||
use rpki::data_model::aspa::AspaObject;
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::data_model::rc::{AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertificate};
|
||||
use rpki::data_model::rc::{
|
||||
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertificate,
|
||||
};
|
||||
use rpki::data_model::roa::RoaObject;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
@@ -243,7 +245,9 @@ fn scan_pack_candidates(
|
||||
|
||||
let cand = CandidateRef {
|
||||
pack_index,
|
||||
part: Part::StoredObject { index: object_index },
|
||||
part: Part::StoredObject {
|
||||
index: object_index,
|
||||
},
|
||||
size_bytes: content_len as u32,
|
||||
};
|
||||
if let Some(ext) = ext {
|
||||
@@ -272,7 +276,8 @@ struct Extracted {
|
||||
}
|
||||
|
||||
fn extract_candidate(pack_path: &Path, cand: CandidateRef) -> Result<Extracted, String> {
|
||||
let bytes = std::fs::read(pack_path).map_err(|e| format!("read {}: {e}", pack_path.display()))?;
|
||||
let bytes =
|
||||
std::fs::read(pack_path).map_err(|e| format!("read {}: {e}", pack_path.display()))?;
|
||||
let mut cur = ByteCursor::new(bytes.as_slice());
|
||||
|
||||
let _version = cur.read_u8()?;
|
||||
@@ -349,9 +354,7 @@ fn ip_resource_count(set: &IpResourceSet) -> u32 {
|
||||
for fam in &set.families {
|
||||
match &fam.choice {
|
||||
IpAddressChoice::Inherit => n = n.saturating_add(1),
|
||||
IpAddressChoice::AddressesOrRanges(items) => {
|
||||
n = n.saturating_add(items.len() as u32)
|
||||
}
|
||||
IpAddressChoice::AddressesOrRanges(items) => n = n.saturating_add(items.len() as u32),
|
||||
}
|
||||
}
|
||||
n
|
||||
@@ -556,7 +559,9 @@ fn stage2_collect_selected_der_v2_from_routinator_store() {
|
||||
(Part::PackCrl, Part::PackCrl) => std::cmp::Ordering::Equal,
|
||||
(Part::PackCrl, Part::StoredObject { .. }) => std::cmp::Ordering::Less,
|
||||
(Part::StoredObject { .. }, Part::PackCrl) => std::cmp::Ordering::Greater,
|
||||
(Part::StoredObject { index: ai }, Part::StoredObject { index: bi }) => ai.cmp(&bi),
|
||||
(Part::StoredObject { index: ai }, Part::StoredObject { index: bi }) => {
|
||||
ai.cmp(&bi)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@@ -791,11 +796,18 @@ fn stage2_collect_selected_der_v2_from_routinator_store() {
|
||||
}
|
||||
}
|
||||
|
||||
per_type_out.insert(format!("{:?}", obj_type).to_lowercase(), selected_sha.len() as u32);
|
||||
per_type_out.insert(
|
||||
format!("{:?}", obj_type).to_lowercase(),
|
||||
selected_sha.len() as u32,
|
||||
);
|
||||
println!();
|
||||
}
|
||||
|
||||
samples.sort_by(|a, b| a.obj_type.cmp(&b.obj_type).then_with(|| a.label.cmp(&b.label)));
|
||||
samples.sort_by(|a, b| {
|
||||
a.obj_type
|
||||
.cmp(&b.obj_type)
|
||||
.then_with(|| a.label.cmp(&b.label))
|
||||
});
|
||||
|
||||
let created_at_rfc3339_utc = fmt_rfc3339_utc(time::OffsetDateTime::now_utc());
|
||||
let manifest = SamplesManifest {
|
||||
@@ -885,9 +897,8 @@ impl<'a> ByteCursor<'a> {
|
||||
|
||||
fn read_time_utc_i64_be(&mut self) -> Result<time::OffsetDateTime, String> {
|
||||
let ts = self.read_i64_be()?;
|
||||
time::OffsetDateTime::from_unix_timestamp(ts).map_err(|e| {
|
||||
format!("invalid unix timestamp {ts}: {e}")
|
||||
})
|
||||
time::OffsetDateTime::from_unix_timestamp(ts)
|
||||
.map_err(|e| format!("invalid unix timestamp {ts}: {e}"))
|
||||
}
|
||||
|
||||
fn read_bytes_u32(&mut self) -> Result<&'a [u8], String> {
|
||||
@@ -917,17 +928,15 @@ impl<'a> ByteCursor<'a> {
|
||||
|
||||
fn skip_bytes_u64(&mut self) -> Result<(), String> {
|
||||
let len_u64 = self.read_u64_be()?;
|
||||
let len = usize::try_from(len_u64).map_err(|_| {
|
||||
format!("data block too large for this system: {len_u64}")
|
||||
})?;
|
||||
let len = usize::try_from(len_u64)
|
||||
.map_err(|_| format!("data block too large for this system: {len_u64}"))?;
|
||||
self.skip(len)
|
||||
}
|
||||
|
||||
fn read_bytes_u64_vec(&mut self) -> Result<Vec<u8>, String> {
|
||||
let len_u64 = self.read_u64_be()?;
|
||||
let len = usize::try_from(len_u64).map_err(|_| {
|
||||
format!("data block too large for this system: {len_u64}")
|
||||
})?;
|
||||
let len = usize::try_from(len_u64)
|
||||
.map_err(|_| format!("data block too large for this system: {len_u64}"))?;
|
||||
Ok(self.read_exact(len)?.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use rpki::data_model::aspa::AspaObject;
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::data_model::rc::{AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertificate};
|
||||
use rpki::data_model::rc::{
|
||||
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertificate,
|
||||
};
|
||||
use rpki::data_model::roa::RoaObject;
|
||||
|
||||
use rpki::storage::pack::PackFile;
|
||||
use rpki::storage::RocksStore;
|
||||
use rpki::storage::pack::PackFile;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
@@ -76,10 +78,18 @@ fn read_samples(root: &Path) -> Vec<Sample> {
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
out.push(Sample { obj_type, name, path });
|
||||
out.push(Sample {
|
||||
obj_type,
|
||||
name,
|
||||
path,
|
||||
});
|
||||
}
|
||||
}
|
||||
out.sort_by(|a, b| a.obj_type.cmp(&b.obj_type).then_with(|| a.name.cmp(&b.name)));
|
||||
out.sort_by(|a, b| {
|
||||
a.obj_type
|
||||
.cmp(&b.obj_type)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
});
|
||||
out
|
||||
}
|
||||
|
||||
@@ -260,8 +270,8 @@ fn complexity_main(obj_type: ObjType, bytes: &[u8]) -> u64 {
|
||||
fn decode_validate(obj_type: ObjType, bytes: &[u8]) {
|
||||
match obj_type {
|
||||
ObjType::Cer => {
|
||||
let decoded = ResourceCertificate::decode_der(std::hint::black_box(bytes))
|
||||
.expect("decode cert");
|
||||
let decoded =
|
||||
ResourceCertificate::decode_der(std::hint::black_box(bytes)).expect("decode cert");
|
||||
std::hint::black_box(decoded);
|
||||
}
|
||||
ObjType::Crl => {
|
||||
@@ -284,13 +294,13 @@ fn decode_validate(obj_type: ObjType, bytes: &[u8]) {
|
||||
}
|
||||
}
|
||||
|
||||
fn landing_packfile_cbor_put(
|
||||
store: &RocksStore,
|
||||
obj_type: ObjType,
|
||||
sample: &str,
|
||||
bytes: &[u8],
|
||||
) {
|
||||
let rsync_uri = format!("rsync://bench.invalid/{}/{}.{}", obj_type.as_str(), sample, obj_type.ext());
|
||||
fn landing_packfile_cbor_put(store: &RocksStore, obj_type: ObjType, sample: &str, bytes: &[u8]) {
|
||||
let rsync_uri = format!(
|
||||
"rsync://bench.invalid/{}/{}.{}",
|
||||
obj_type.as_str(),
|
||||
sample,
|
||||
obj_type.ext()
|
||||
);
|
||||
let pf = PackFile::from_bytes_compute_sha256(rsync_uri, bytes.to_vec());
|
||||
let encoded = serde_cbor::to_vec(std::hint::black_box(&pf)).expect("cbor encode packfile");
|
||||
let key = format!("bench:packfile:{}:{}", obj_type.as_str(), sample);
|
||||
@@ -414,7 +424,10 @@ fn stage2_decode_validate_and_landing_benchmark_selected_der_v2() {
|
||||
}
|
||||
if let Some(filter) = sample_filter.as_deref() {
|
||||
samples.retain(|s| s.name == filter);
|
||||
assert!(!samples.is_empty(), "no sample matched BENCH_SAMPLE={filter}");
|
||||
assert!(
|
||||
!samples.is_empty(),
|
||||
"no sample matched BENCH_SAMPLE={filter}"
|
||||
);
|
||||
}
|
||||
|
||||
println!("# Stage2 decode+validate benchmark (selected_der_v2)");
|
||||
@@ -473,7 +486,8 @@ fn stage2_decode_validate_and_landing_benchmark_selected_der_v2() {
|
||||
let mut landing_rows: Vec<ResultRow> = Vec::with_capacity(samples.len());
|
||||
|
||||
for s in &samples {
|
||||
let bytes = std::fs::read(&s.path).unwrap_or_else(|e| panic!("read {}: {e}", s.path.display()));
|
||||
let bytes =
|
||||
std::fs::read(&s.path).unwrap_or_else(|e| panic!("read {}: {e}", s.path.display()));
|
||||
let size_bytes = bytes.len();
|
||||
let complexity = if mode.do_decode() {
|
||||
complexity_main(s.obj_type, bytes.as_slice())
|
||||
@@ -637,7 +651,10 @@ fn stage2_decode_validate_and_landing_benchmark_selected_der_v2() {
|
||||
};
|
||||
|
||||
if let Some(path) = out_md {
|
||||
let md = render_markdown("Stage2 decode+validate benchmark (selected_der_v2)", &decode_rows);
|
||||
let md = render_markdown(
|
||||
"Stage2 decode+validate benchmark (selected_der_v2)",
|
||||
&decode_rows,
|
||||
);
|
||||
write_text_file(&path, &md);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
|
||||
@@ -228,8 +228,6 @@ fn stage2_inventory_routinator_sap_store() {
|
||||
if let Some(path) = out_json.as_ref() {
|
||||
create_parent_dirs(path);
|
||||
let bytes = serde_json::to_vec_pretty(&report).expect("encode json");
|
||||
std::fs::write(path, bytes)
|
||||
.unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
|
||||
std::fs::write(path, bytes).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -199,9 +199,8 @@ impl<'a> Cursor<'a> {
|
||||
|
||||
fn read_time_utc_i64_be(&mut self) -> Result<time::OffsetDateTime, SapDecodeError> {
|
||||
let ts = self.read_i64_be()?;
|
||||
time::OffsetDateTime::from_unix_timestamp(ts).map_err(|e| {
|
||||
SapDecodeError::Decode(format!("invalid unix timestamp {ts}: {e}"))
|
||||
})
|
||||
time::OffsetDateTime::from_unix_timestamp(ts)
|
||||
.map_err(|e| SapDecodeError::Decode(format!("invalid unix timestamp {ts}: {e}")))
|
||||
}
|
||||
|
||||
fn read_string_u32(&mut self) -> Result<String, SapDecodeError> {
|
||||
@@ -252,4 +251,3 @@ impl<'a> Cursor<'a> {
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ use rpki::fetch::http::{BlockingHttpFetcher, HttpFetcherConfig};
|
||||
use rpki::fetch::rsync::{RsyncFetchError, RsyncFetcher};
|
||||
use rpki::policy::{CaFailedFetchPolicy, Policy, SyncPreference};
|
||||
use rpki::storage::{FetchCachePpKey, RocksStore};
|
||||
use rpki::sync::rrdp::{Fetcher, parse_notification, sync_from_notification};
|
||||
use rpki::sync::repo::{RepoSyncSource, sync_publication_point};
|
||||
use rpki::sync::rrdp::{Fetcher, parse_notification, sync_from_notification};
|
||||
use rpki::validation::from_tal::discover_root_ca_instance_from_tal_url;
|
||||
use rpki::validation::manifest::{PublicationPointSource, process_manifest_publication_point};
|
||||
|
||||
@@ -36,8 +36,13 @@ fn live_http_fetcher() -> BlockingHttpFetcher {
|
||||
struct AlwaysFailRsyncFetcher;
|
||||
|
||||
impl RsyncFetcher for AlwaysFailRsyncFetcher {
|
||||
fn fetch_objects(&self, _rsync_base_uri: &str) -> Result<Vec<(String, Vec<u8>)>, RsyncFetchError> {
|
||||
Err(RsyncFetchError::Fetch("rsync disabled for this test".to_string()))
|
||||
fn fetch_objects(
|
||||
&self,
|
||||
_rsync_base_uri: &str,
|
||||
) -> Result<Vec<(String, Vec<u8>)>, RsyncFetchError> {
|
||||
Err(RsyncFetchError::Fetch(
|
||||
"rsync disabled for this test".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +113,7 @@ fn apnic_live_bootstrap_snapshot_and_fetch_cache_pp_pack_to_persistent_db() {
|
||||
&ca_instance.rsync_base_uri,
|
||||
&http,
|
||||
&rsync,
|
||||
None,
|
||||
)
|
||||
.expect("repo sync");
|
||||
|
||||
@@ -130,13 +136,16 @@ fn apnic_live_bootstrap_snapshot_and_fetch_cache_pp_pack_to_persistent_db() {
|
||||
assert_eq!(pp.source, PublicationPointSource::Fresh);
|
||||
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&ca_instance.manifest_rsync_uri);
|
||||
let cached = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp");
|
||||
let cached = store.get_fetch_cache_pp(&key).expect("get fetch_cache_pp");
|
||||
assert!(cached.is_some(), "expected fetch_cache_pp to be stored");
|
||||
|
||||
eprintln!("OK: bootstrap complete; persistent db at: {}", db_dir.display());
|
||||
eprintln!("Next: run `cargo test --release -q --test test_apnic_rrdp_delta_live_20260226 -- --ignored` later to exercise delta sync.");
|
||||
eprintln!(
|
||||
"OK: bootstrap complete; persistent db at: {}",
|
||||
db_dir.display()
|
||||
);
|
||||
eprintln!(
|
||||
"Next: run `cargo test --release -q --test test_apnic_rrdp_delta_live_20260226 -- --ignored` later to exercise delta sync."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -184,9 +193,7 @@ fn apnic_live_delta_only_from_persistent_db() {
|
||||
if start.elapsed() > Duration::from_secs(max_wait_secs) {
|
||||
panic!(
|
||||
"timed out waiting for APNIC RRDP serial to advance for delta sync; old_session={} old_serial={} waited={}s",
|
||||
old_session,
|
||||
old_serial,
|
||||
max_wait_secs
|
||||
old_session, old_serial, max_wait_secs
|
||||
);
|
||||
}
|
||||
|
||||
@@ -198,8 +205,7 @@ fn apnic_live_delta_only_from_persistent_db() {
|
||||
if notif.session_id.to_string() != old_session {
|
||||
panic!(
|
||||
"RRDP session_id changed; this delta-only test assumes same snapshot baseline. old_session={} new_session={}",
|
||||
old_session,
|
||||
notif.session_id
|
||||
old_session, notif.session_id
|
||||
);
|
||||
}
|
||||
|
||||
@@ -217,10 +223,7 @@ fn apnic_live_delta_only_from_persistent_db() {
|
||||
if notif.deltas.is_empty() || min_delta > want_first {
|
||||
panic!(
|
||||
"notification deltas do not cover required serial gap for delta-only sync; old_serial={} want_first={} min_delta={} current_serial={}. rerun bootstrap to refresh snapshot baseline.",
|
||||
old_serial,
|
||||
want_first,
|
||||
min_delta,
|
||||
notif.serial
|
||||
old_serial, want_first, min_delta, notif.serial
|
||||
);
|
||||
}
|
||||
|
||||
@@ -276,9 +279,7 @@ fn apnic_root_repo_sync_failure_uses_fetch_cache_pp_pack() {
|
||||
|
||||
// Ensure cache exists (created by bootstrap).
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&ca_instance.manifest_rsync_uri);
|
||||
let cached = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp");
|
||||
let cached = store.get_fetch_cache_pp(&key).expect("get fetch_cache_pp");
|
||||
assert!(
|
||||
cached.is_some(),
|
||||
"missing fetch_cache_pp; run bootstrap test first. db_dir={}",
|
||||
@@ -303,8 +304,9 @@ fn apnic_root_repo_sync_failure_uses_fetch_cache_pp_pack() {
|
||||
|
||||
assert_eq!(pp.source, PublicationPointSource::FetchCachePp);
|
||||
assert!(
|
||||
pp.warnings.iter().any(|w| w.message.contains("using fetch_cache_pp")),
|
||||
pp.warnings
|
||||
.iter()
|
||||
.any(|w| w.message.contains("using fetch_cache_pp")),
|
||||
"expected cache-use warning"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -166,6 +166,12 @@ fn apnic_tree_full_stats_serial() {
|
||||
http_fetcher: &http,
|
||||
rsync_fetcher: &rsync,
|
||||
validation_time,
|
||||
timing: None,
|
||||
revalidate_only: false,
|
||||
rrdp_dedup: true,
|
||||
rrdp_repo_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
|
||||
rsync_dedup: true,
|
||||
rsync_repo_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
|
||||
};
|
||||
|
||||
let stats = RefCell::new(LiveStats::default());
|
||||
@@ -190,6 +196,7 @@ fn apnic_tree_full_stats_serial() {
|
||||
&TreeRunConfig {
|
||||
max_depth,
|
||||
max_instances,
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree");
|
||||
|
||||
@@ -36,6 +36,7 @@ fn apnic_tree_depth1_processes_more_than_root() {
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(1),
|
||||
max_instances: Some(2),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree from tal");
|
||||
@@ -74,6 +75,7 @@ fn apnic_tree_root_only_processes_root_with_long_timeouts() {
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run APNIC root-only");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use rpki::data_model::oid::{OID_AD_CA_REPOSITORY, OID_AD_RPKI_MANIFEST, OID_AD_RPKI_NOTIFY};
|
||||
use rpki::data_model::rc::{
|
||||
AccessDescription, ResourceCertKind, ResourceCertificate, SubjectInfoAccess, SubjectInfoAccessCa,
|
||||
AccessDescription, ResourceCertKind, ResourceCertificate, SubjectInfoAccess,
|
||||
SubjectInfoAccessCa,
|
||||
};
|
||||
use rpki::validation::ca_instance::{CaInstanceUrisError, ca_instance_uris_from_ca_certificate};
|
||||
|
||||
@@ -12,8 +13,9 @@ fn apnic_child_ca_fixture_der() -> Vec<u8> {
|
||||
}
|
||||
|
||||
fn set_sia_ca(cert: &mut ResourceCertificate, ads: Vec<AccessDescription>) {
|
||||
cert.tbs.extensions.subject_info_access =
|
||||
Some(SubjectInfoAccess::Ca(SubjectInfoAccessCa { access_descriptions: ads }));
|
||||
cert.tbs.extensions.subject_info_access = Some(SubjectInfoAccess::Ca(SubjectInfoAccessCa {
|
||||
access_descriptions: ads,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -48,7 +50,10 @@ fn ca_instance_uris_success_and_error_branches() {
|
||||
}],
|
||||
);
|
||||
let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
|
||||
assert!(matches!(err, CaInstanceUrisError::MissingCaRepository), "{err}");
|
||||
assert!(
|
||||
matches!(err, CaInstanceUrisError::MissingCaRepository),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
set_sia_ca(
|
||||
&mut cert,
|
||||
@@ -58,7 +63,10 @@ fn ca_instance_uris_success_and_error_branches() {
|
||||
}],
|
||||
);
|
||||
let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
|
||||
assert!(matches!(err, CaInstanceUrisError::MissingRpkiManifest), "{err}");
|
||||
assert!(
|
||||
matches!(err, CaInstanceUrisError::MissingRpkiManifest),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
// Scheme validation branches.
|
||||
set_sia_ca(
|
||||
@@ -69,21 +77,29 @@ fn ca_instance_uris_success_and_error_branches() {
|
||||
}],
|
||||
);
|
||||
let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
|
||||
assert!(matches!(err, CaInstanceUrisError::CaRepositoryNotRsync(_)), "{err}");
|
||||
assert!(
|
||||
matches!(err, CaInstanceUrisError::CaRepositoryNotRsync(_)),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
set_sia_ca(
|
||||
&mut cert,
|
||||
vec![AccessDescription {
|
||||
access_method_oid: OID_AD_CA_REPOSITORY.to_string(),
|
||||
access_location: "rsync://example.test/repo/".to_string(),
|
||||
},
|
||||
AccessDescription {
|
||||
access_method_oid: OID_AD_RPKI_MANIFEST.to_string(),
|
||||
access_location: "http://example.test/repo/x.mft".to_string(),
|
||||
}],
|
||||
vec![
|
||||
AccessDescription {
|
||||
access_method_oid: OID_AD_CA_REPOSITORY.to_string(),
|
||||
access_location: "rsync://example.test/repo/".to_string(),
|
||||
},
|
||||
AccessDescription {
|
||||
access_method_oid: OID_AD_RPKI_MANIFEST.to_string(),
|
||||
access_location: "http://example.test/repo/x.mft".to_string(),
|
||||
},
|
||||
],
|
||||
);
|
||||
let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
|
||||
assert!(matches!(err, CaInstanceUrisError::RpkiManifestNotRsync(_)), "{err}");
|
||||
assert!(
|
||||
matches!(err, CaInstanceUrisError::RpkiManifestNotRsync(_)),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
set_sia_ca(
|
||||
&mut cert,
|
||||
@@ -103,7 +119,10 @@ fn ca_instance_uris_success_and_error_branches() {
|
||||
],
|
||||
);
|
||||
let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
|
||||
assert!(matches!(err, CaInstanceUrisError::RpkiNotifyNotHttps(_)), "{err}");
|
||||
assert!(
|
||||
matches!(err, CaInstanceUrisError::RpkiNotifyNotHttps(_)),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
// ManifestNotUnderPublicationPoint.
|
||||
set_sia_ca(
|
||||
@@ -120,6 +139,11 @@ fn ca_instance_uris_success_and_error_branches() {
|
||||
],
|
||||
);
|
||||
let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
|
||||
assert!(matches!(err, CaInstanceUrisError::ManifestNotUnderPublicationPoint { .. }), "{err}");
|
||||
assert!(
|
||||
matches!(
|
||||
err,
|
||||
CaInstanceUrisError::ManifestNotUnderPublicationPoint { .. }
|
||||
),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ impl PublicationPointRunner for SinglePackRunner {
|
||||
ca.effective_ip_resources.as_ref(),
|
||||
ca.effective_as_resources.as_ref(),
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(PublicationPointRunResult {
|
||||
@@ -81,7 +82,9 @@ fn crl_mismatch_drops_publication_point_and_cites_rfc_sections() {
|
||||
|
||||
let root = CaInstanceHandle {
|
||||
depth: 0,
|
||||
ca_certificate_der: vec![0x01, 0x02, 0x03],
|
||||
// Use a real, parseable CA certificate DER so objects processing can reach CRL selection.
|
||||
// The test only asserts CRLDP/locked-pack error handling, not signature chaining.
|
||||
ca_certificate_der: fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
ca_certificate_rsync_uri: None,
|
||||
effective_ip_resources: None,
|
||||
effective_as_resources: None,
|
||||
@@ -99,6 +102,7 @@ fn crl_mismatch_drops_publication_point_and_cites_rfc_sections() {
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree audit");
|
||||
|
||||
@@ -213,7 +213,10 @@ fn cached_pack_revalidation_rejects_hash_mismatch_against_manifest_filelist() {
|
||||
)
|
||||
.expect_err("cache pack hash mismatch must be rejected");
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("cached fetch_cache_pp file hash mismatch"), "{msg}");
|
||||
assert!(
|
||||
msg.contains("cached fetch_cache_pp file hash mismatch"),
|
||||
"{msg}"
|
||||
);
|
||||
assert!(msg.contains("RFC 9286 §6.5"), "{msg}");
|
||||
}
|
||||
|
||||
|
||||
@@ -36,3 +36,47 @@ fn system_rsync_fetcher_can_sync_from_local_directory_for_tests() {
|
||||
assert!(saw_a, "expected a.txt");
|
||||
assert!(saw_b, "expected sub/b.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_rsync_fetcher_can_reuse_persistent_mirror_directory() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
let src = tmp.path().join("src");
|
||||
let nested = src.join("sub");
|
||||
fs::create_dir_all(&nested).expect("mkdir");
|
||||
fs::write(src.join("a.txt"), b"aaa").expect("write a");
|
||||
fs::write(nested.join("b.txt"), b"bbb").expect("write b");
|
||||
|
||||
let mirror_root = tmp.path().join("mirror");
|
||||
let config = SystemRsyncConfig {
|
||||
rsync_bin: "rsync".into(),
|
||||
mirror_root: Some(mirror_root.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
let fetcher = SystemRsyncFetcher::new(config);
|
||||
|
||||
let base = src.to_string_lossy().to_string();
|
||||
|
||||
// First sync creates the mirror directory.
|
||||
let out1 = fetcher.fetch_objects(&base).expect("fetch objects #1");
|
||||
assert!(out1.iter().any(|(u, _)| u.ends_with("a.txt")));
|
||||
assert!(out1.iter().any(|(u, _)| u.ends_with("sub/b.txt")));
|
||||
|
||||
let dirs1: Vec<_> = std::fs::read_dir(&mirror_root)
|
||||
.expect("read mirror root")
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().is_dir())
|
||||
.collect();
|
||||
assert_eq!(dirs1.len(), 1, "expected exactly one mirror directory");
|
||||
|
||||
// Second sync should reuse the same mirror directory (stable hash mapping).
|
||||
let out2 = fetcher.fetch_objects(&base).expect("fetch objects #2");
|
||||
assert!(out2.iter().any(|(u, _)| u.ends_with("a.txt")));
|
||||
assert!(out2.iter().any(|(u, _)| u.ends_with("sub/b.txt")));
|
||||
|
||||
let dirs2: Vec<_> = std::fs::read_dir(&mirror_root)
|
||||
.expect("read mirror root")
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().is_dir())
|
||||
.collect();
|
||||
assert_eq!(dirs2.len(), 1, "expected mirror directory to be reused");
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ fn discover_root_ca_instance_from_tal_url_succeeds_with_apnic_fixtures() {
|
||||
.map
|
||||
.insert(tal_url.to_string(), Ok(tal_bytes.clone()));
|
||||
for u in &tal.ta_uris {
|
||||
fetcher.map.insert(u.as_str().to_string(), Ok(ta_der.clone()));
|
||||
fetcher
|
||||
.map
|
||||
.insert(u.as_str().to_string(), Ok(ta_der.clone()));
|
||||
}
|
||||
|
||||
let out = discover_root_ca_instance_from_tal_url(&fetcher, tal_url).expect("discover root");
|
||||
@@ -49,9 +51,7 @@ fn discover_root_ca_instance_from_tal_url_returns_ta_fetch_error_when_all_candid
|
||||
let tal = Tal::decode_bytes(&tal_bytes).expect("decode tal fixture");
|
||||
|
||||
let mut fetcher = MapFetcher::default();
|
||||
fetcher
|
||||
.map
|
||||
.insert(tal_url.to_string(), Ok(tal_bytes));
|
||||
fetcher.map.insert(tal_url.to_string(), Ok(tal_bytes));
|
||||
for u in &tal.ta_uris {
|
||||
fetcher.map.insert(
|
||||
u.as_str().to_string(),
|
||||
@@ -67,4 +67,3 @@ fn discover_root_ca_instance_from_tal_url_returns_ta_fetch_error_when_all_candid
|
||||
other => panic!("unexpected error: {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -385,7 +385,10 @@ fn manifest_revalidation_with_unchanged_manifest_is_fresh() {
|
||||
assert_eq!(second.source, PublicationPointSource::Fresh);
|
||||
assert!(second.warnings.is_empty());
|
||||
assert_eq!(second.pack.manifest_bytes, first.pack.manifest_bytes);
|
||||
assert_eq!(second.pack.manifest_number_be, first.pack.manifest_number_be);
|
||||
assert_eq!(
|
||||
second.pack.manifest_number_be,
|
||||
first.pack.manifest_number_be
|
||||
);
|
||||
assert_eq!(second.pack.files, first.pack.files);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ use std::path::Path;
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::policy::Policy;
|
||||
use rpki::storage::RocksStore;
|
||||
use rpki::validation::manifest::{ManifestProcessError, PublicationPointSource, process_manifest_publication_point};
|
||||
use rpki::validation::manifest::{
|
||||
ManifestProcessError, PublicationPointSource, process_manifest_publication_point,
|
||||
};
|
||||
|
||||
fn issuer_ca_fixture_der() -> Vec<u8> {
|
||||
std::fs::read(
|
||||
@@ -89,7 +91,10 @@ fn manifest_outside_publication_point_yields_no_usable_cache() {
|
||||
.unwrap_err();
|
||||
|
||||
// With no cached pack available for this wrong publication point, we get NoUsableCache.
|
||||
assert!(matches!(err, ManifestProcessError::NoUsableCache { .. }), "{err}");
|
||||
assert!(
|
||||
matches!(err, ManifestProcessError::NoUsableCache { .. }),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -154,6 +159,8 @@ fn manifest_outside_publication_point_detects_cached_pack_pp_mismatch() {
|
||||
validation_time,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, ManifestProcessError::NoUsableCache { .. }), "{err}");
|
||||
assert!(
|
||||
matches!(err, ManifestProcessError::NoUsableCache { .. }),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@ use std::path::Path;
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::policy::{CaFailedFetchPolicy, Policy};
|
||||
use rpki::storage::{FetchCachePpKey, FetchCachePpPack, RocksStore};
|
||||
use rpki::validation::manifest::{ManifestProcessError, PublicationPointSource, process_manifest_publication_point, process_manifest_publication_point_after_repo_sync};
|
||||
use rpki::validation::manifest::{
|
||||
ManifestProcessError, PublicationPointSource, process_manifest_publication_point,
|
||||
process_manifest_publication_point_after_repo_sync,
|
||||
};
|
||||
|
||||
fn issuer_ca_fixture_der() -> Vec<u8> {
|
||||
std::fs::read(
|
||||
@@ -522,7 +525,8 @@ fn cached_pack_missing_file_is_rejected_during_revalidation() {
|
||||
"{err}"
|
||||
);
|
||||
assert!(
|
||||
err.to_string().contains("cached fetch_cache_pp missing file"),
|
||||
err.to_string()
|
||||
.contains("cached fetch_cache_pp missing file"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
@@ -597,7 +601,8 @@ fn cached_pack_hash_mismatch_is_rejected_during_revalidation() {
|
||||
"{err}"
|
||||
);
|
||||
assert!(
|
||||
err.to_string().contains("cached fetch_cache_pp file hash mismatch"),
|
||||
err.to_string()
|
||||
.contains("cached fetch_cache_pp file hash mismatch"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -319,9 +319,7 @@ struct ManifestEContentPretty {
|
||||
|
||||
impl From<&ManifestEContent> for ManifestEContentPretty {
|
||||
fn from(v: &ManifestEContent) -> Self {
|
||||
let entries = v
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
let entries = v.parse_files().expect("parse validated manifest fileList");
|
||||
Self {
|
||||
version: v.version,
|
||||
manifest_number: v.manifest_number.to_hex_upper(),
|
||||
|
||||
@@ -121,6 +121,7 @@ fn missing_crl_causes_roas_to_be_dropped_under_drop_object_policy() {
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
assert!(out.vrps.is_empty());
|
||||
assert!(!out.warnings.is_empty());
|
||||
@@ -146,6 +147,7 @@ fn wrong_issuer_ca_cert_causes_roas_to_be_dropped_under_drop_object_policy() {
|
||||
None,
|
||||
None,
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
assert!(out.vrps.is_empty());
|
||||
assert!(!out.warnings.is_empty());
|
||||
@@ -173,6 +175,7 @@ fn invalid_aspa_object_is_reported_as_warning_under_drop_object_policy() {
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(
|
||||
|
||||
@@ -144,6 +144,7 @@ fn drop_object_policy_drops_only_failing_object() {
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(
|
||||
@@ -186,6 +187,7 @@ fn drop_publication_point_policy_drops_the_publication_point() {
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
assert!(out.stats.publication_point_dropped);
|
||||
assert!(out.vrps.is_empty(), "expected publication point dropped");
|
||||
|
||||
@@ -91,6 +91,7 @@ fn build_fetch_cache_pp_from_local_rsync_fixture(
|
||||
rsync_base_uri,
|
||||
&NoopHttpFetcher,
|
||||
&LocalDirRsyncFetcher::new(dir),
|
||||
None,
|
||||
)
|
||||
.expect("sync into raw_objects");
|
||||
|
||||
@@ -134,6 +135,7 @@ fn process_pack_for_issuer_extracts_vrps_from_real_cernet_fixture() {
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(
|
||||
@@ -188,6 +190,7 @@ fn signed_object_failure_policy_drop_object_drops_only_bad_object() {
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(out.vrps.len() > 0);
|
||||
@@ -250,6 +253,7 @@ fn signed_object_failure_policy_drop_publication_point_drops_all_output() {
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(out.vrps.is_empty());
|
||||
@@ -304,6 +308,7 @@ fn process_pack_for_issuer_without_crl_drops_publication_point() {
|
||||
None,
|
||||
None,
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(out.vrps.is_empty());
|
||||
@@ -346,6 +351,7 @@ fn process_pack_for_issuer_handles_invalid_aspa_bytes() {
|
||||
None,
|
||||
None,
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
assert!(out.aspas.is_empty());
|
||||
assert!(!out.warnings.is_empty());
|
||||
@@ -388,6 +394,7 @@ fn process_pack_for_issuer_drop_publication_point_on_invalid_aspa_bytes() {
|
||||
None,
|
||||
None,
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
assert!(out.vrps.is_empty());
|
||||
assert!(out.aspas.is_empty());
|
||||
|
||||
@@ -68,6 +68,7 @@ fn process_pack_drop_object_on_wrong_issuer_ca_for_roa() {
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(out.stats.roa_total, 1);
|
||||
@@ -130,6 +131,7 @@ fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_roa_skips_rest() {
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
assert!(out.stats.publication_point_dropped);
|
||||
assert_eq!(out.warnings.len(), 1);
|
||||
@@ -179,6 +181,7 @@ fn process_pack_drop_object_on_wrong_issuer_ca_for_aspa() {
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(out.stats.aspa_total, 1);
|
||||
@@ -234,6 +237,7 @@ fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_aspa_skips_rest()
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
assert!(out.stats.publication_point_dropped);
|
||||
}
|
||||
@@ -265,11 +269,12 @@ fn process_pack_for_issuer_marks_objects_skipped_when_missing_issuer_crl() {
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&[0x01, 0x02, 0x03],
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(out.stats.publication_point_dropped);
|
||||
@@ -307,11 +312,12 @@ fn process_pack_for_issuer_drop_object_records_errors_and_continues() {
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&[0x01, 0x02, 0x03],
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!out.stats.publication_point_dropped);
|
||||
@@ -348,11 +354,12 @@ fn process_pack_for_issuer_drop_publication_point_records_skips_for_rest() {
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&[0x01, 0x02, 0x03],
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(out.stats.publication_point_dropped);
|
||||
@@ -398,11 +405,12 @@ fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_roa() {
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&[0x01, 0x02, 0x03],
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(out.stats.roa_total, 1);
|
||||
@@ -439,11 +447,12 @@ fn process_pack_for_issuer_rejects_roa_when_crldp_crl_missing() {
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&[0x01, 0x02, 0x03],
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(out.stats.roa_total, 1);
|
||||
@@ -494,11 +503,12 @@ fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_aspa() {
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&[0x01, 0x02, 0x03],
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(out.stats.aspa_total, 1);
|
||||
|
||||
@@ -83,6 +83,7 @@ fn repo_sync_uses_rrdp_when_available() {
|
||||
"rsync://example.net/repo/",
|
||||
&http_fetcher,
|
||||
&rsync_fetcher,
|
||||
None,
|
||||
)
|
||||
.expect("sync");
|
||||
|
||||
@@ -135,6 +136,7 @@ fn repo_sync_skips_snapshot_when_state_unchanged() {
|
||||
"rsync://example.net/repo/",
|
||||
&http_fetcher,
|
||||
&rsync_fetcher,
|
||||
None,
|
||||
)
|
||||
.expect("sync 1");
|
||||
assert_eq!(out1.source, RepoSyncSource::Rrdp);
|
||||
@@ -147,6 +149,7 @@ fn repo_sync_skips_snapshot_when_state_unchanged() {
|
||||
"rsync://example.net/repo/",
|
||||
&http_fetcher,
|
||||
&rsync_fetcher,
|
||||
None,
|
||||
)
|
||||
.expect("sync 2");
|
||||
assert_eq!(out2.source, RepoSyncSource::Rrdp);
|
||||
@@ -199,6 +202,7 @@ fn repo_sync_falls_back_to_rsync_on_rrdp_failure() {
|
||||
"rsync://example.net/repo/",
|
||||
&http_fetcher,
|
||||
&rsync_fetcher,
|
||||
None,
|
||||
)
|
||||
.expect("fallback sync");
|
||||
|
||||
@@ -247,6 +251,7 @@ fn repo_sync_rsync_populates_raw_objects() {
|
||||
"rsync://example.net/repo/",
|
||||
&http_fetcher,
|
||||
&rsync_fetcher,
|
||||
None,
|
||||
)
|
||||
.expect("rsync-only sync");
|
||||
|
||||
|
||||
@@ -30,4 +30,3 @@ fn rpki_bin_without_args_exits_2_and_prints_error() {
|
||||
"expected non-empty stderr, got empty"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
128
tests/test_rsync_fallback_breakdown_live.rs
Normal file
128
tests/test_rsync_fallback_breakdown_live.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use rpki::storage::RocksStore;
|
||||
|
||||
const RSYNC_BASE_URI: &str = "rsync://rpki.luys.cloud/repo/LY-RPKI/1/";
|
||||
|
||||
fn run_rsync(src: &str, dst: &Path, timeout: Duration) -> Result<(), String> {
|
||||
let timeout_secs = timeout.as_secs().max(1).to_string();
|
||||
let out = Command::new("rsync")
|
||||
.arg("-rt")
|
||||
.arg("--delete")
|
||||
.arg("--timeout")
|
||||
.arg(timeout_secs)
|
||||
.arg(src)
|
||||
.arg(dst)
|
||||
.output()
|
||||
.map_err(|e| format!("rsync spawn failed: {e}"))?;
|
||||
if !out.status.success() {
|
||||
return Err(format!(
|
||||
"rsync failed: status={} stdout={} stderr={}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stdout).trim(),
|
||||
String::from_utf8_lossy(&out.stderr).trim()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn normalize_rsync_base_uri(s: &str) -> String {
|
||||
if s.ends_with('/') {
|
||||
s.to_string()
|
||||
} else {
|
||||
format!("{s}/")
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_files(root: &Path, current: &Path, out: &mut Vec<PathBuf>) -> Result<(), String> {
|
||||
for entry in std::fs::read_dir(current).map_err(|e| e.to_string())? {
|
||||
let entry = entry.map_err(|e| e.to_string())?;
|
||||
let path = entry.path();
|
||||
let meta = entry.metadata().map_err(|e| e.to_string())?;
|
||||
if meta.is_dir() {
|
||||
walk_files(root, &path, out)?;
|
||||
} else if meta.is_file() {
|
||||
out.push(path);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "live network rsync breakdown; prints timing table"]
|
||||
fn rsync_fallback_breakdown_luys_cloud() {
|
||||
let rsync_base_uri = normalize_rsync_base_uri(RSYNC_BASE_URI);
|
||||
let timeout = Duration::from_secs(30 * 60);
|
||||
|
||||
let temp_dir = tempfile::tempdir().expect("tempdir");
|
||||
let dst = temp_dir.path().join("rsync");
|
||||
std::fs::create_dir_all(&dst).expect("create dst dir");
|
||||
|
||||
let t0 = Instant::now();
|
||||
run_rsync(&rsync_base_uri, &dst, timeout).expect("rsync fetch");
|
||||
let rsync_wall = t0.elapsed();
|
||||
|
||||
let t1 = Instant::now();
|
||||
let mut files: Vec<PathBuf> = Vec::new();
|
||||
walk_files(&dst, &dst, &mut files).expect("walk files");
|
||||
let walk_wall = t1.elapsed();
|
||||
|
||||
let t2 = Instant::now();
|
||||
let mut objects: Vec<(String, Vec<u8>)> = Vec::with_capacity(files.len());
|
||||
let mut total_bytes: u64 = 0;
|
||||
for path in &files {
|
||||
let rel = path
|
||||
.strip_prefix(&dst)
|
||||
.expect("strip prefix")
|
||||
.to_string_lossy()
|
||||
.replace('\\', "/");
|
||||
let uri = format!("{rsync_base_uri}{rel}");
|
||||
let bytes = std::fs::read(path).expect("read file");
|
||||
total_bytes = total_bytes.saturating_add(bytes.len() as u64);
|
||||
objects.push((uri, bytes));
|
||||
}
|
||||
let read_wall = t2.elapsed();
|
||||
|
||||
let db_dir = tempfile::tempdir().expect("db tempdir");
|
||||
let store = RocksStore::open(db_dir.path()).expect("open rocksdb");
|
||||
|
||||
let t3 = Instant::now();
|
||||
for (uri, bytes) in &objects {
|
||||
store.put_raw(uri, bytes).expect("put_raw");
|
||||
}
|
||||
let write_wall = t3.elapsed();
|
||||
|
||||
println!("rsync_base_uri: {rsync_base_uri}");
|
||||
println!("| phase | duration(ms) | pct | notes |\n|---|---:|---:|---|");
|
||||
let total = rsync_wall + walk_wall + read_wall + write_wall;
|
||||
let pct = |d: Duration| (d.as_secs_f64() / total.as_secs_f64()) * 100.0;
|
||||
println!(
|
||||
"| rsync_network | {:>11.3} | {:>5.1}% | rsync command wall time |",
|
||||
rsync_wall.as_secs_f64() * 1000.0,
|
||||
pct(rsync_wall)
|
||||
);
|
||||
println!(
|
||||
"| walk_files | {:>11.3} | {:>5.1}% | enumerate local files |",
|
||||
walk_wall.as_secs_f64() * 1000.0,
|
||||
pct(walk_wall)
|
||||
);
|
||||
println!(
|
||||
"| read_files | {:>11.3} | {:>5.1}% | read all bytes into memory |",
|
||||
read_wall.as_secs_f64() * 1000.0,
|
||||
pct(read_wall)
|
||||
);
|
||||
println!(
|
||||
"| rocksdb_put_raw | {:>11.3} | {:>5.1}% | write raw_objects ({} keys) |",
|
||||
write_wall.as_secs_f64() * 1000.0,
|
||||
pct(write_wall),
|
||||
objects.len()
|
||||
);
|
||||
println!(
|
||||
"| TOTAL | {:>11.3} | 100.0% | files={} bytes={} |",
|
||||
total.as_secs_f64() * 1000.0,
|
||||
objects.len(),
|
||||
total_bytes
|
||||
);
|
||||
}
|
||||
@@ -91,6 +91,7 @@ fn run_tree_from_tal_url_entry_executes_and_records_failure_when_repo_empty() {
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree");
|
||||
@@ -134,6 +135,7 @@ fn run_tree_from_tal_and_ta_der_entry_executes_and_records_failure_when_repo_emp
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree");
|
||||
@@ -185,6 +187,7 @@ fn run_tree_from_tal_url_audit_entry_collects_no_publication_points_when_repo_em
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree audit");
|
||||
@@ -224,6 +227,7 @@ fn run_tree_from_tal_and_ta_der_audit_entry_collects_no_publication_points_when_
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree audit");
|
||||
|
||||
@@ -236,6 +236,7 @@ fn tree_respects_max_depth_and_max_instances() {
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: None,
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree depth-limited");
|
||||
@@ -248,6 +249,7 @@ fn tree_respects_max_depth_and_max_instances() {
|
||||
&TreeRunConfig {
|
||||
max_depth: None,
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree instance-limited");
|
||||
@@ -316,3 +318,69 @@ fn tree_audit_includes_parent_and_discovered_from_for_non_root_nodes() {
|
||||
.expect("child discovered_from");
|
||||
assert_eq!(df.parent_manifest_rsync_uri, root_manifest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_revalidate_only_enqueues_children_from_fetch_cache_pp() {
|
||||
let root_manifest = "rsync://example.test/repo/root.mft";
|
||||
let child_manifest = "rsync://example.test/repo/child.mft";
|
||||
|
||||
let runner = MockRunner::default()
|
||||
.with(
|
||||
root_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::FetchCachePp,
|
||||
pack: empty_pack(root_manifest, "rsync://example.test/repo/"),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
},
|
||||
audit: PublicationPointAudit::default(),
|
||||
discovered_children: vec![discovered_child(root_manifest, child_manifest)],
|
||||
},
|
||||
)
|
||||
.with(
|
||||
child_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::FetchCachePp,
|
||||
pack: empty_pack(child_manifest, "rsync://example.test/repo/child/"),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
},
|
||||
audit: PublicationPointAudit::default(),
|
||||
discovered_children: Vec::new(),
|
||||
},
|
||||
);
|
||||
|
||||
let out = run_tree_serial(
|
||||
ca_handle(root_manifest),
|
||||
&runner,
|
||||
&TreeRunConfig {
|
||||
max_depth: None,
|
||||
max_instances: None,
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree");
|
||||
assert_eq!(out.instances_processed, 1);
|
||||
|
||||
let out = run_tree_serial(
|
||||
ca_handle(root_manifest),
|
||||
&runner,
|
||||
&TreeRunConfig {
|
||||
max_depth: None,
|
||||
max_instances: None,
|
||||
revalidate_only: true,
|
||||
},
|
||||
)
|
||||
.expect("run tree");
|
||||
assert_eq!(out.instances_processed, 2);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user