20260326 完成数据库model迁移;20260327 增加一键replay脚本

This commit is contained in:
2026-03-27 11:24:34 +08:00
parent fe8b89d829
commit cd0ba15286
28 changed files with 1956 additions and 930 deletions

View File

@@ -7,7 +7,9 @@ use rpki::data_model::rc::{
use rpki::data_model::roa::RoaObject;
use rpki::storage::PackFile;
use rpki::storage::RawByHashEntry;
use rpki::storage::RocksStore;
use sha2::Digest;
use std::path::{Path, PathBuf};
use std::time::Instant;
@@ -304,7 +306,12 @@ fn landing_packfile_cbor_put(store: &RocksStore, obj_type: ObjType, sample: &str
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);
store.put_raw(&key, &encoded).expect("store put_raw");
let sha256_hex = hex::encode(sha2::Sha256::digest(&encoded));
let mut entry = RawByHashEntry::from_bytes(sha256_hex, encoded);
entry.origin_uris.push(key);
entry.object_type = Some("cbor".to_string());
entry.encoding = Some("cbor".to_string());
store.put_raw_by_hash_entry(&entry).expect("store raw_by_hash");
}
#[derive(Clone, Debug, serde::Serialize)]
@@ -560,7 +567,7 @@ fn stage2_decode_validate_and_landing_benchmark_selected_der_v2() {
}
if mode.do_landing() {
// Landing benchmark: PackFile(from_bytes_compute_sha256) + CBOR + RocksDB put_raw.
// Landing benchmark: PackFile(from_bytes_compute_sha256) + CBOR + RocksDB current-state landing.
let mut per_round_ns_per_op = Vec::with_capacity(rounds as usize);
for _ in 0..warmup_iters {
landing_packfile_cbor_put(&store, s.obj_type, &s.name, bytes.as_slice());
@@ -617,7 +624,7 @@ fn stage2_decode_validate_and_landing_benchmark_selected_der_v2() {
println!();
}
if mode.do_landing() {
println!("## landing (PackFile::from_bytes_compute_sha256 + CBOR + RocksDB put_raw)");
println!("## landing (PackFile::from_bytes_compute_sha256 + CBOR + RocksDB current-state landing)");
println!();
println!("| type | sample | size_bytes | complexity | avg ns/op | ops/s |");
println!("|---|---|---:|---:|---:|---:|");
@@ -693,7 +700,7 @@ fn stage2_decode_validate_and_landing_benchmark_selected_der_v2() {
if let Some(path) = out_md_landing {
let md = render_markdown(
"Stage2 landing benchmark (PackFile CBOR + RocksDB put_raw)",
"Stage2 landing benchmark (PackFile CBOR + RocksDB current-state landing)",
&landing_rows,
);
write_text_file(&path, &md);

View File

@@ -168,18 +168,17 @@ fn apnic_live_delta_only_from_persistent_db() {
.as_deref()
.expect("APNIC root must have rrdpNotification");
let state_bytes = store
.get_rrdp_state(rrdp_notification_uri)
.expect("get rrdp_state")
let source = store
.get_rrdp_source_record(rrdp_notification_uri)
.expect("get rrdp source")
.unwrap_or_else(|| {
panic!(
"missing rrdp_state for APNIC notification URI; run bootstrap test first. db_dir={}",
"missing RRDP source state for APNIC notification URI; run bootstrap test first. db_dir={}",
db_dir.display()
)
});
let state = rpki::sync::rrdp::RrdpState::decode(&state_bytes).expect("decode rrdp_state");
let old_serial = state.serial;
let old_session = state.session_id;
let old_serial = source.last_serial.expect("last serial");
let old_session = source.last_session_id.expect("last session");
let max_wait_secs: u64 = std::env::var("RPKI_LIVE_MAX_WAIT_SECS")
.ok()

View File

@@ -212,11 +212,12 @@ fn apnic_tree_full_stats_serial() {
let mut raw_total = 0usize;
let mut raw_by_ext: BTreeMap<String, usize> = BTreeMap::new();
for (k, _v) in store.raw_iter_all().expect("raw_iter_all") {
for entry in store
.list_repository_view_entries_with_prefix("rsync://")
.expect("list repository view")
{
raw_total += 1;
if let Ok(uri) = std::str::from_utf8(&k) {
*raw_by_ext.entry(ext_of_uri(uri)).or_insert(0) += 1;
}
*raw_by_ext.entry(ext_of_uri(&entry.rsync_uri)).or_insert(0) += 1;
}
println!("APNIC Stage2 full-tree serial stats");
@@ -270,7 +271,7 @@ fn apnic_tree_full_stats_serial() {
);
println!();
println!(
"rocksdb_raw_objects_total={} raw_by_ext={:?}",
"current_repository_view_total={} by_ext={:?}",
raw_total, raw_by_ext
);

View File

@@ -5,7 +5,8 @@ use sha2::Digest;
use rpki::data_model::manifest::ManifestObject;
use rpki::policy::{CaFailedFetchPolicy, Policy};
use rpki::storage::{
PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult, ValidatedManifestMeta,
PackTime, RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore,
ValidatedCaInstanceResult, ValidatedManifestMeta,
VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary,
VcirInstanceGate, VcirRelatedArtifact, VcirSummary,
};
@@ -115,6 +116,26 @@ fn store_validated_manifest_baseline(
.expect("store validated manifest baseline");
}
fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
let sha256_hex = hex::encode(sha2::Sha256::digest(&bytes));
let mut raw_entry = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes);
raw_entry.origin_uris.push(rsync_uri.to_string());
raw_entry.object_type = Some(object_type.to_string());
raw_entry.encoding = Some("der".to_string());
store
.put_raw_by_hash_entry(&raw_entry)
.expect("store raw_by_hash");
store
.put_repository_view_entry(&RepositoryViewEntry {
rsync_uri: rsync_uri.to_string(),
current_hash: Some(sha256_hex),
repository_source: Some("https://example.test/notification.xml".to_string()),
object_type: Some(object_type.to_string()),
state: RepositoryViewState::Present,
})
.expect("store repository view");
}
#[test]
fn manifest_success_returns_validated_publication_point_data() {
let manifest_path = Path::new(
@@ -131,9 +152,7 @@ fn manifest_success_returns_validated_publication_point_data() {
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
let entries = manifest
.manifest
.parse_files()
@@ -146,7 +165,7 @@ fn manifest_success_returns_validated_publication_point_data() {
let bytes = std::fs::read(&file_path)
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
put_current_object(&store, &rsync_uri, bytes, rsync_uri.rsplit('.').next().unwrap_or("bin"));
}
let policy = Policy::default();
@@ -186,9 +205,7 @@ fn manifest_hash_mismatch_reuses_current_instance_vcir_when_enabled() {
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
let entries = manifest
.manifest
.parse_files()
@@ -201,7 +218,7 @@ fn manifest_hash_mismatch_reuses_current_instance_vcir_when_enabled() {
let bytes = std::fs::read(&file_path)
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
put_current_object(&store, &rsync_uri, bytes, rsync_uri.rsplit('.').next().unwrap_or("bin"));
}
let policy = Policy::default();
@@ -234,11 +251,11 @@ fn manifest_hash_mismatch_reuses_current_instance_vcir_when_enabled() {
let victim = entries.first().expect("non-empty file list");
let victim_uri = format!("{publication_point_rsync_uri}{}", victim.file_name);
let mut tampered = store
.get_raw(&victim_uri)
.expect("get victim raw")
.load_current_object_bytes_by_uri(&victim_uri)
.expect("load victim raw")
.expect("victim raw exists");
tampered[0] ^= 0xFF;
store.put_raw(&victim_uri, &tampered).expect("tamper raw");
put_current_object(&store, &victim_uri, tampered, victim_uri.rsplit('.').next().unwrap_or("bin"));
let second = process_manifest_publication_point(
&store,
@@ -274,9 +291,7 @@ fn manifest_failed_fetch_stop_all_output() {
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
let entries = manifest
.manifest
.parse_files()
@@ -289,7 +304,7 @@ fn manifest_failed_fetch_stop_all_output() {
let bytes = std::fs::read(&file_path)
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
put_current_object(&store, &rsync_uri, bytes, rsync_uri.rsplit('.').next().unwrap_or("bin"));
}
let mut policy = Policy::default();
@@ -313,11 +328,11 @@ fn manifest_failed_fetch_stop_all_output() {
let victim = entries.first().expect("non-empty file list");
let victim_uri = format!("{publication_point_rsync_uri}{}", victim.file_name);
let mut tampered = store
.get_raw(&victim_uri)
.expect("get victim raw")
.load_current_object_bytes_by_uri(&victim_uri)
.expect("load victim raw")
.expect("victim raw exists");
tampered[0] ^= 0xFF;
store.put_raw(&victim_uri, &tampered).expect("tamper raw");
put_current_object(&store, &victim_uri, tampered, victim_uri.rsplit('.').next().unwrap_or("bin"));
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::StopAllOutput;
let err = process_manifest_publication_point(
@@ -350,9 +365,7 @@ fn manifest_failed_fetch_rejects_stale_current_instance_vcir() {
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
let entries = manifest
.manifest
.parse_files()
@@ -365,7 +378,7 @@ fn manifest_failed_fetch_rejects_stale_current_instance_vcir() {
let bytes = std::fs::read(&file_path)
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
put_current_object(&store, &rsync_uri, bytes, rsync_uri.rsplit('.').next().unwrap_or("bin"));
}
let policy = Policy::default();
@@ -391,8 +404,16 @@ fn manifest_failed_fetch_rejects_stale_current_instance_vcir() {
);
store
.delete_raw(&manifest_rsync_uri)
.expect("delete manifest raw to force fallback");
.delete_raw_by_hash_entry(
store
.get_repository_view_entry(&manifest_rsync_uri)
.expect("get repository view")
.expect("manifest repository view exists")
.current_hash
.as_deref()
.expect("manifest current hash"),
)
.expect("delete manifest raw_by_hash to force missing current object");
let err = process_manifest_publication_point(
&store,
@@ -425,9 +446,7 @@ fn manifest_revalidation_with_unchanged_manifest_is_fresh() {
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
let entries = manifest
.manifest
.parse_files()
@@ -440,7 +459,7 @@ fn manifest_revalidation_with_unchanged_manifest_is_fresh() {
let bytes = std::fs::read(&file_path)
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
put_current_object(&store, &rsync_uri, bytes, rsync_uri.rsplit('.').next().unwrap_or("bin"));
}
let policy = Policy::default();
@@ -507,9 +526,7 @@ fn manifest_rollback_is_treated_as_failed_fetch_and_reuses_current_instance_vcir
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
let entries = manifest
.manifest
.parse_files()
@@ -522,7 +539,7 @@ fn manifest_rollback_is_treated_as_failed_fetch_and_reuses_current_instance_vcir
let bytes = std::fs::read(&file_path)
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
put_current_object(&store, &rsync_uri, bytes, rsync_uri.rsplit('.').next().unwrap_or("bin"));
}
let policy = Policy::default();

View File

@@ -1,8 +1,10 @@
use std::path::Path;
use sha2::Digest;
use rpki::data_model::manifest::ManifestObject;
use rpki::policy::Policy;
use rpki::storage::RocksStore;
use rpki::storage::{RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore};
use rpki::validation::manifest::{
ManifestProcessError, PublicationPointSource, process_manifest_publication_point,
};
@@ -40,6 +42,26 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
s
}
fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
let sha256_hex = hex::encode(sha2::Sha256::digest(&bytes));
let mut raw_entry = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes);
raw_entry.origin_uris.push(rsync_uri.to_string());
raw_entry.object_type = Some(object_type.to_string());
raw_entry.encoding = Some("der".to_string());
store
.put_raw_by_hash_entry(&raw_entry)
.expect("store raw_by_hash");
store
.put_repository_view_entry(&RepositoryViewEntry {
rsync_uri: rsync_uri.to_string(),
current_hash: Some(sha256_hex),
repository_source: Some("https://example.test/notification.xml".to_string()),
object_type: Some(object_type.to_string()),
state: RepositoryViewState::Present,
})
.expect("store repository view");
}
#[test]
fn manifest_outside_publication_point_yields_no_usable_cache() {
let manifest_path = Path::new(
@@ -56,9 +78,7 @@ fn manifest_outside_publication_point_yields_no_usable_cache() {
let store = RocksStore::open(temp.path()).expect("open rocksdb");
// Store manifest and its locked files so Fresh would otherwise succeed.
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
for entry in manifest
.manifest
.parse_files()
@@ -71,7 +91,12 @@ fn manifest_outside_publication_point_yields_no_usable_cache() {
.join(entry.file_name.as_str());
let bytes = std::fs::read(&file_path).expect("read referenced file");
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
put_current_object(
&store,
&rsync_uri,
bytes,
rsync_uri.rsplit('.').next().unwrap_or("bin"),
);
}
let policy = Policy::default();
@@ -112,9 +137,7 @@ fn manifest_outside_publication_point_detects_current_instance_snapshot_pp_misma
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
for entry in manifest
.manifest
.parse_files()
@@ -127,7 +150,12 @@ fn manifest_outside_publication_point_detects_current_instance_snapshot_pp_misma
.join(entry.file_name.as_str());
let bytes = std::fs::read(&file_path).expect("read referenced file");
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
put_current_object(
&store,
&rsync_uri,
bytes,
rsync_uri.rsplit('.').next().unwrap_or("bin"),
);
}
let policy = Policy::default();

View File

@@ -5,7 +5,8 @@ use sha2::Digest;
use rpki::data_model::manifest::ManifestObject;
use rpki::policy::{CaFailedFetchPolicy, Policy};
use rpki::storage::{
PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult, ValidatedManifestMeta,
PackTime, RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore,
ValidatedCaInstanceResult, ValidatedManifestMeta,
VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary,
VcirInstanceGate, VcirRelatedArtifact, VcirSummary,
};
@@ -118,6 +119,26 @@ fn store_validated_manifest_baseline(
.expect("store validated manifest baseline");
}
fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
let sha256_hex = hex::encode(sha2::Sha256::digest(&bytes));
let mut raw_entry = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes);
raw_entry.origin_uris.push(rsync_uri.to_string());
raw_entry.object_type = Some(object_type.to_string());
raw_entry.encoding = Some("der".to_string());
store
.put_raw_by_hash_entry(&raw_entry)
.expect("store raw_by_hash");
store
.put_repository_view_entry(&RepositoryViewEntry {
rsync_uri: rsync_uri.to_string(),
current_hash: Some(sha256_hex),
repository_source: Some("https://example.test/notification.xml".to_string()),
object_type: Some(object_type.to_string()),
state: RepositoryViewState::Present,
})
.expect("store repository view");
}
fn store_manifest_and_locked_files(
store: &RocksStore,
manifest_path: &Path,
@@ -126,9 +147,7 @@ fn store_manifest_and_locked_files(
manifest: &ManifestObject,
manifest_bytes: &[u8],
) {
store
.put_raw(manifest_rsync_uri, manifest_bytes)
.expect("store manifest");
put_current_object(store, manifest_rsync_uri, manifest_bytes.to_vec(), "mft");
for entry in manifest
.manifest
.parse_files()
@@ -142,7 +161,7 @@ fn store_manifest_and_locked_files(
let bytes = std::fs::read(&file_path)
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
put_current_object(&store, &rsync_uri, bytes, rsync_uri.rsplit('.').next().unwrap_or("bin"));
}
}
@@ -258,9 +277,7 @@ fn manifest_missing_locked_file_is_treated_as_failed_fetch() {
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest only (no locked files)");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
let issuer_ca_der = issuer_ca_fixture_der();
let policy = Policy::default();
@@ -279,7 +296,8 @@ fn manifest_missing_locked_file_is_treated_as_failed_fetch() {
"{err}"
);
assert!(
err.to_string().contains("file missing in raw_objects"),
err.to_string()
.contains("file missing in current repository view"),
"unexpected error: {err}"
);
}

View File

@@ -1,8 +1,10 @@
use std::path::Path;
use sha2::Digest;
use rpki::data_model::manifest::ManifestObject;
use rpki::policy::{CaFailedFetchPolicy, Policy};
use rpki::storage::RocksStore;
use rpki::storage::{RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore};
use rpki::validation::manifest::process_manifest_publication_point;
fn issuer_ca_fixture() -> Vec<u8> {
@@ -16,6 +18,26 @@ fn issuer_ca_rsync_uri() -> &'static str {
"rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"
}
fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
let sha256_hex = hex::encode(sha2::Sha256::digest(&bytes));
let mut raw_entry = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes);
raw_entry.origin_uris.push(rsync_uri.to_string());
raw_entry.object_type = Some(object_type.to_string());
raw_entry.encoding = Some("der".to_string());
store
.put_raw_by_hash_entry(&raw_entry)
.expect("store raw_by_hash");
store
.put_repository_view_entry(&RepositoryViewEntry {
rsync_uri: rsync_uri.to_string(),
current_hash: Some(sha256_hex),
repository_source: Some("https://example.test/notification.xml".to_string()),
object_type: Some(object_type.to_string()),
state: RepositoryViewState::Present,
})
.expect("store repository view");
}
#[test]
fn manifest_outside_publication_point_is_failed_fetch_rfc9286_section6_1() {
let fixture_manifest_path = Path::new(
@@ -35,9 +57,7 @@ fn manifest_outside_publication_point_is_failed_fetch_rfc9286_section6_1() {
let store = RocksStore::open(temp.path()).expect("open rocksdb");
// Store the manifest at its rsync URI.
store
.put_raw(manifest_rsync_uri, &manifest_bytes)
.expect("store manifest raw");
put_current_object(&store, manifest_rsync_uri, manifest_bytes.clone(), "mft");
// Store all referenced files under the (different) publication point so that §6.4/§6.5
// would otherwise succeed if §6.1 was not enforced.
@@ -50,7 +70,8 @@ fn manifest_outside_publication_point_is_failed_fetch_rfc9286_section6_1() {
let bytes = std::fs::read(&file_path)
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file raw");
let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin");
put_current_object(&store, &rsync_uri, bytes, object_type);
}
let mut policy = Policy::default();

View File

@@ -1,10 +1,14 @@
use std::path::Path;
use sha2::Digest;
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::manifest::ManifestObject;
use rpki::data_model::rc::ResourceCertificate;
use rpki::policy::{Policy, SignedObjectFailurePolicy};
use rpki::storage::{PackFile, RocksStore};
use rpki::storage::{
PackFile, RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore,
};
use rpki::validation::manifest::process_manifest_publication_point;
use rpki::validation::objects::process_publication_point_snapshot_for_issuer;
@@ -30,6 +34,26 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
s
}
fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
let sha256_hex = hex::encode(sha2::Sha256::digest(&bytes));
let mut raw_entry = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes);
raw_entry.origin_uris.push(rsync_uri.to_string());
raw_entry.object_type = Some(object_type.to_string());
raw_entry.encoding = Some("der".to_string());
store
.put_raw_by_hash_entry(&raw_entry)
.expect("store raw_by_hash");
store
.put_repository_view_entry(&RepositoryViewEntry {
rsync_uri: rsync_uri.to_string(),
current_hash: Some(sha256_hex),
repository_source: Some("https://example.test/notification.xml".to_string()),
object_type: Some(object_type.to_string()),
state: RepositoryViewState::Present,
})
.expect("store repository view");
}
fn build_cernet_pack_and_validation_time() -> (
rpki::validation::publication_point::PublicationPointSnapshot,
time::OffsetDateTime,
@@ -48,9 +72,7 @@ fn build_cernet_pack_and_validation_time() -> (
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
let entries = manifest
.manifest
.parse_files()
@@ -63,7 +85,8 @@ fn build_cernet_pack_and_validation_time() -> (
let bytes = std::fs::read(&file_path)
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin");
put_current_object(&store, &rsync_uri, bytes, object_type);
}
let issuer_ca_der = std::fs::read(

View File

@@ -1,10 +1,14 @@
use std::path::Path;
use sha2::Digest;
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::manifest::ManifestObject;
use rpki::data_model::rc::ResourceCertificate;
use rpki::policy::{Policy, SignedObjectFailurePolicy};
use rpki::storage::{PackFile, RocksStore};
use rpki::storage::{
PackFile, RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore,
};
use rpki::validation::manifest::process_manifest_publication_point;
use rpki::validation::objects::process_publication_point_snapshot_for_issuer;
@@ -30,6 +34,26 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
s
}
fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
let sha256_hex = hex::encode(sha2::Sha256::digest(&bytes));
let mut raw_entry = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes);
raw_entry.origin_uris.push(rsync_uri.to_string());
raw_entry.object_type = Some(object_type.to_string());
raw_entry.encoding = Some("der".to_string());
store
.put_raw_by_hash_entry(&raw_entry)
.expect("store raw_by_hash");
store
.put_repository_view_entry(&RepositoryViewEntry {
rsync_uri: rsync_uri.to_string(),
current_hash: Some(sha256_hex),
repository_source: Some("https://example.test/notification.xml".to_string()),
object_type: Some(object_type.to_string()),
state: RepositoryViewState::Present,
})
.expect("store repository view");
}
fn build_cernet_pack_and_validation_time() -> (
rpki::validation::publication_point::PublicationPointSnapshot,
time::OffsetDateTime,
@@ -48,9 +72,7 @@ fn build_cernet_pack_and_validation_time() -> (
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw(&manifest_rsync_uri, &manifest_bytes)
.expect("store manifest");
put_current_object(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
let entries = manifest
.manifest
.parse_files()
@@ -63,7 +85,8 @@ fn build_cernet_pack_and_validation_time() -> (
let bytes = std::fs::read(&file_path)
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
store.put_raw(&rsync_uri, &bytes).expect("store file");
let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin");
put_current_object(&store, &rsync_uri, bytes, object_type);
}
let issuer_ca_der = std::fs::read(

View File

@@ -95,7 +95,7 @@ fn build_publication_point_snapshot_from_local_rsync_fixture(
None,
None,
)
.expect("sync into raw_objects");
.expect("sync into current repository view");
let pp = process_manifest_publication_point(
&store,

View File

@@ -20,6 +20,15 @@ impl Fetcher for MapFetcher {
}
}
fn assert_current_object(store: &RocksStore, uri: &str, expected: &[u8]) {
assert_eq!(
store
.load_current_object_bytes_by_uri(uri)
.expect("load current object"),
Some(expected.to_vec())
);
}
struct CountingRsyncFetcher<F> {
inner: F,
calls: Arc<Mutex<usize>>,
@@ -92,13 +101,7 @@ fn repo_sync_uses_rrdp_when_available() {
assert_eq!(out.objects_written, 2);
assert_eq!(*calls.lock().unwrap(), 0);
assert_eq!(
store
.get_raw("rsync://example.net/repo/obj1.cer")
.unwrap()
.unwrap(),
b"abc"
);
assert_current_object(&store, "rsync://example.net/repo/obj1.cer", b"abc");
}
#[test]
@@ -166,13 +169,7 @@ fn repo_sync_skips_snapshot_when_state_unchanged() {
"expected no rsync fallback calls"
);
assert_eq!(
store
.get_raw("rsync://example.net/repo/obj1.cer")
.unwrap()
.unwrap(),
b"abc"
);
assert_current_object(&store, "rsync://example.net/repo/obj1.cer", b"abc");
}
#[test]
@@ -221,17 +218,11 @@ fn repo_sync_falls_back_to_rsync_on_rrdp_failure() {
.any(|r| r.0 == "RFC 8182 §3.4.5")
);
assert_eq!(
store
.get_raw("rsync://example.net/repo/sub/obj.cer")
.unwrap()
.unwrap(),
b"hello"
);
assert_current_object(&store, "rsync://example.net/repo/sub/obj.cer", b"hello");
}
#[test]
fn repo_sync_rsync_populates_raw_objects() {
fn repo_sync_rsync_populates_current_repository_view() {
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
@@ -263,18 +254,6 @@ fn repo_sync_rsync_populates_raw_objects() {
assert_eq!(out.source, RepoSyncSource::Rsync);
assert_eq!(out.objects_written, 2);
assert_eq!(
store
.get_raw("rsync://example.net/repo/a/one.cer")
.unwrap()
.unwrap(),
b"1"
);
assert_eq!(
store
.get_raw("rsync://example.net/repo/a/b/two.crl")
.unwrap()
.unwrap(),
b"2"
);
assert_current_object(&store, "rsync://example.net/repo/a/one.cer", b"1");
assert_current_object(&store, "rsync://example.net/repo/a/b/two.crl", b"2");
}

View File

@@ -17,6 +17,15 @@ impl Fetcher for MapFetcher {
}
}
fn assert_current_object(store: &RocksStore, uri: &str, expected: &[u8]) {
assert_eq!(
store
.load_current_object_bytes_by_uri(uri)
.expect("load current object"),
Some(expected.to_vec())
);
}
#[test]
fn notification_parses_and_snapshot_is_applied_and_state_written() {
let notification_xml =
@@ -47,28 +56,18 @@ fn notification_parses_and_snapshot_is_applied_and_state_written() {
.expect("sync");
assert_eq!(published, 2);
let obj1 = store
.get_raw("rsync://example.net/repo/obj1.cer")
.expect("get obj1")
.expect("obj1 exists");
assert_eq!(obj1, b"abc");
assert_current_object(&store, "rsync://example.net/repo/obj1.cer", b"abc");
assert_current_object(&store, "rsync://example.net/repo/obj2.crl", b"def");
let obj2 = store
.get_raw("rsync://example.net/repo/obj2.crl")
.expect("get obj2")
.expect("obj2 exists");
assert_eq!(obj2, b"def");
let state_bytes = store
.get_rrdp_state("https://example.net/rrdp/notification.xml")
.expect("get state")
.expect("state exists");
let state = rpki::sync::rrdp::RrdpState::decode(&state_bytes).expect("decode state");
let source = store
.get_rrdp_source_record("https://example.net/rrdp/notification.xml")
.expect("get source")
.expect("source exists");
assert_eq!(
state.session_id,
source.last_session_id.expect("session id"),
"9df4b597-af9e-4dca-bdda-719cce2c4e28".to_string()
);
assert_eq!(state.serial, 1);
assert_eq!(source.last_serial, Some(1));
}
#[test]

View File

@@ -2,7 +2,8 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{Duration, Instant};
use rpki::storage::RocksStore;
use rpki::storage::{RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore};
use sha2::Digest;
const RSYNC_BASE_URI: &str = "rsync://rpki.luys.cloud/repo/LY-RPKI/1/";
@@ -90,7 +91,31 @@ fn rsync_fallback_breakdown_luys_cloud() {
let t3 = Instant::now();
for (uri, bytes) in &objects {
store.put_raw(uri, bytes).expect("put_raw");
let sha256_hex = hex::encode(sha2::Sha256::digest(bytes));
let mut raw = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes.clone());
raw.origin_uris.push(uri.clone());
raw.object_type = Some(
uri.rsplit('.')
.next()
.unwrap_or("bin")
.to_ascii_lowercase(),
);
raw.encoding = Some("der".to_string());
store.put_raw_by_hash_entry(&raw).expect("put raw_by_hash");
store
.put_repository_view_entry(&RepositoryViewEntry {
rsync_uri: uri.clone(),
current_hash: Some(sha256_hex),
repository_source: Some(rsync_base_uri.clone()),
object_type: Some(
uri.rsplit('.')
.next()
.unwrap_or("bin")
.to_ascii_lowercase(),
),
state: RepositoryViewState::Present,
})
.expect("put repository view");
}
let write_wall = t3.elapsed();
@@ -114,7 +139,7 @@ fn rsync_fallback_breakdown_luys_cloud() {
pct(read_wall)
);
println!(
"| rocksdb_put_raw | {:>11.3} | {:>5.1}% | write raw_objects ({} keys) |",
"| rocksdb_put_current | {:>11.3} | {:>5.1}% | write repository_view + raw_by_hash ({} keys) |",
write_wall.as_secs_f64() * 1000.0,
pct(write_wall),
objects.len()

View File

@@ -1,21 +1,37 @@
use rpki::storage::RocksStore;
use rpki::storage::{RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore};
use sha2::Digest;
fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: &[u8], object_type: &str) {
let sha256_hex = hex::encode(sha2::Sha256::digest(bytes));
let mut raw = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes.to_vec());
raw.origin_uris.push(rsync_uri.to_string());
raw.object_type = Some(object_type.to_string());
raw.encoding = Some("der".to_string());
store.put_raw_by_hash_entry(&raw).expect("put raw_by_hash");
store
.put_repository_view_entry(&RepositoryViewEntry {
rsync_uri: rsync_uri.to_string(),
current_hash: Some(sha256_hex),
repository_source: Some("https://example.test/notification.xml".to_string()),
object_type: Some(object_type.to_string()),
state: RepositoryViewState::Present,
})
.expect("put repository view");
}
#[test]
fn storage_iter_all_lists_raw_entries() {
fn storage_iter_all_lists_repository_view_entries() {
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw("rsync://example.test/repo/a.cer", b"a")
.expect("put_raw a");
store
.put_raw("rsync://example.test/repo/b.roa", b"b")
.expect("put_raw b");
put_current_object(&store, "rsync://example.test/repo/a.cer", b"a", "cer");
put_current_object(&store, "rsync://example.test/repo/b.roa", b"b", "roa");
let raw_keys = store
.raw_iter_all()
.expect("raw_iter_all")
.map(|(k, _v)| String::from_utf8(k.to_vec()).expect("utf8 key"))
.list_repository_view_entries_with_prefix("rsync://")
.expect("list repository_view")
.into_iter()
.map(|entry| entry.rsync_uri)
.collect::<Vec<_>>();
assert_eq!(raw_keys.len(), 2);
assert!(raw_keys.contains(&"rsync://example.test/repo/a.cer".to_string()));

View File

@@ -1,32 +1,24 @@
use rocksdb::WriteBatch;
use rpki::storage::RocksStore;
#[test]
fn storage_delete_rrdp_state_works() {
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
use rpki::storage::{RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore};
use sha2::Digest;
fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: &[u8], object_type: &str) {
let sha256_hex = hex::encode(sha2::Sha256::digest(bytes));
let mut raw = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes.to_vec());
raw.origin_uris.push(rsync_uri.to_string());
raw.object_type = Some(object_type.to_string());
raw.encoding = Some("der".to_string());
store.put_raw_by_hash_entry(&raw).expect("put raw_by_hash");
store
.put_rrdp_state("https://example.net/rrdp/notification.xml", b"state")
.expect("put state");
assert_eq!(
store
.get_rrdp_state("https://example.net/rrdp/notification.xml")
.unwrap()
.unwrap(),
b"state"
);
store
.delete_rrdp_state("https://example.net/rrdp/notification.xml")
.expect("delete state");
assert!(
store
.get_rrdp_state("https://example.net/rrdp/notification.xml")
.unwrap()
.is_none()
);
.put_repository_view_entry(&RepositoryViewEntry {
rsync_uri: rsync_uri.to_string(),
current_hash: Some(sha256_hex),
repository_source: Some("https://example.net/notification.xml".to_string()),
object_type: Some(object_type.to_string()),
state: RepositoryViewState::Present,
})
.expect("put repository view");
}
#[test]
@@ -34,25 +26,20 @@ fn storage_raw_iter_prefix_filters_by_prefix() {
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
store
.put_raw("rsync://example.net/repo/a/1.cer", b"1")
.unwrap();
store
.put_raw("rsync://example.net/repo/a/2.cer", b"2")
.unwrap();
store
.put_raw("rsync://example.net/repo/b/1.cer", b"3")
.unwrap();
put_current_object(&store, "rsync://example.net/repo/a/1.cer", b"1", "cer");
put_current_object(&store, "rsync://example.net/repo/a/2.cer", b"2", "cer");
put_current_object(&store, "rsync://example.net/repo/b/1.cer", b"3", "cer");
let prefix = b"rsync://example.net/repo/a/";
let prefix = "rsync://example.net/repo/a/";
let items = store
.raw_iter_prefix(prefix)
.list_repository_view_entries_with_prefix(prefix)
.expect("iter")
.map(|(k, v)| (String::from_utf8_lossy(&k).to_string(), v.to_vec()))
.into_iter()
.map(|entry| entry.rsync_uri)
.collect::<Vec<_>>();
assert_eq!(items.len(), 2);
for (k, _v) in &items {
for k in &items {
assert!(k.starts_with("rsync://example.net/repo/a/"));
}
}

View File

@@ -1,6 +1,28 @@
use std::path::Path;
use rpki::storage::RocksStore;
use rpki::storage::{
PackTime, RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore,
RrdpSourceRecord, RrdpSourceSyncState,
};
use sha2::Digest;
fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: &[u8], object_type: &str) {
let sha256_hex = hex::encode(sha2::Sha256::digest(bytes));
let mut raw = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes.to_vec());
raw.origin_uris.push(rsync_uri.to_string());
raw.object_type = Some(object_type.to_string());
raw.encoding = Some("der".to_string());
store.put_raw_by_hash_entry(&raw).expect("put raw_by_hash");
store
.put_repository_view_entry(&RepositoryViewEntry {
rsync_uri: rsync_uri.to_string(),
current_hash: Some(sha256_hex),
repository_source: Some("https://example.invalid/notification.xml".to_string()),
object_type: Some(object_type.to_string()),
state: RepositoryViewState::Present,
})
.expect("put repository view");
}
#[test]
fn storage_opens_and_creates_column_families() {
@@ -9,32 +31,65 @@ fn storage_opens_and_creates_column_families() {
}
#[test]
fn raw_objects_roundtrip_by_rsync_uri() {
fn current_object_roundtrip_by_rsync_uri() {
let dir = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(dir.path()).expect("open rocksdb");
let key = "rsync://example.invalid/repo/a.cer";
let value = b"hello";
store.put_raw(key, value).expect("put raw");
let got = store.get_raw(key).expect("get raw");
put_current_object(&store, key, value, "cer");
let got = store
.load_current_object_bytes_by_uri(key)
.expect("get current object");
assert_eq!(got.as_deref(), Some(value.as_slice()));
store.delete_raw(key).expect("delete raw");
let got = store.get_raw(key).expect("get raw after delete");
let current_hash = store
.get_repository_view_entry(key)
.expect("get repository view")
.expect("view exists")
.current_hash
.expect("current hash");
store
.delete_repository_view_entry(key)
.expect("delete repository view");
store
.delete_raw_by_hash_entry(&current_hash)
.expect("delete raw_by_hash");
let got = store
.load_current_object_bytes_by_uri(key)
.expect("get current object after delete");
assert!(got.is_none());
}
#[test]
fn rrdp_state_roundtrip_by_notification_uri() {
fn rrdp_source_roundtrip_by_notification_uri() {
let dir = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(dir.path()).expect("open rocksdb");
let notif = "https://example.invalid/rrdp/notification.xml";
let state = b"{\"session_id\":\"00000000-0000-0000-0000-000000000000\",\"last_serial\":1}";
store.put_rrdp_state(notif, state).expect("put rrdp_state");
let record = RrdpSourceRecord {
notify_uri: notif.to_string(),
last_session_id: Some("00000000-0000-0000-0000-000000000000".to_string()),
last_serial: Some(1),
first_seen_at: PackTime::from_utc_offset_datetime(time::OffsetDateTime::now_utc()),
last_seen_at: PackTime::from_utc_offset_datetime(time::OffsetDateTime::now_utc()),
last_sync_at: None,
sync_state: RrdpSourceSyncState::SnapshotOnly,
last_snapshot_uri: None,
last_snapshot_hash: None,
last_error: None,
};
store.put_rrdp_source_record(&record).expect("put rrdp_source");
let got = store.get_rrdp_state(notif).expect("get rrdp_state");
assert_eq!(got.as_deref(), Some(state.as_slice()));
let got = store
.get_rrdp_source_record(notif)
.expect("get rrdp_source")
.expect("rrdp_source present");
assert_eq!(got.last_serial, Some(1));
assert_eq!(
got.last_session_id.as_deref(),
Some("00000000-0000-0000-0000-000000000000")
);
}
#[test]
@@ -43,14 +98,12 @@ fn store_is_reopenable() {
let path: &Path = dir.path();
let store = RocksStore::open(path).expect("open rocksdb");
store
.put_raw("rsync://example.invalid/repo/x", b"x")
.expect("put");
put_current_object(&store, "rsync://example.invalid/repo/x", b"x", "bin");
drop(store);
let store = RocksStore::open(path).expect("reopen rocksdb");
let got = store
.get_raw("rsync://example.invalid/repo/x")
.load_current_object_bytes_by_uri("rsync://example.invalid/repo/x")
.expect("get after reopen");
assert_eq!(got.as_deref(), Some(b"x".as_slice()));
}