将fetch pp cache改成使用vcir结构,跑通apnic全量同步
This commit is contained in:
@@ -1,21 +1,36 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rocksdb::{ColumnFamilyDescriptor, DB, IteratorMode, Options};
|
||||
use rocksdb::{DB, IteratorMode, Options};
|
||||
use rpki::storage::{
|
||||
ALL_COLUMN_FAMILY_NAMES, CF_AUDIT_RULE_INDEX, CF_RAW_BY_HASH, CF_RAW_OBJECTS,
|
||||
CF_REPOSITORY_VIEW, CF_RRDP_OBJECT_INDEX, CF_RRDP_SOURCE, CF_RRDP_SOURCE_MEMBER, CF_RRDP_STATE,
|
||||
CF_RRDP_URI_OWNER, CF_VCIR, column_family_descriptors,
|
||||
};
|
||||
|
||||
const CF_RAW_OBJECTS: &str = "raw_objects";
|
||||
const CF_FETCH_CACHE_PP: &str = "fetch_cache_pp";
|
||||
const CF_RRDP_STATE: &str = "rrdp_state";
|
||||
const CF_RRDP_OBJECT_INDEX: &str = "rrdp_object_index";
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum DbStatsMode {
|
||||
Estimate,
|
||||
Exact,
|
||||
}
|
||||
|
||||
fn enable_blobdb_if_supported(opts: &mut Options) {
|
||||
// Keep this in sync with `rpki::storage`:
|
||||
// blob files are CF-level options; readers should open CFs with blob enabled too.
|
||||
#[allow(dead_code)]
|
||||
fn _set(opts: &mut Options) {
|
||||
opts.set_enable_blob_files(true);
|
||||
opts.set_min_blob_size(1024);
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum CfGroup {
|
||||
CurrentRepositoryView,
|
||||
CurrentValidationState,
|
||||
CurrentRrdpState,
|
||||
LegacyCompatibility,
|
||||
}
|
||||
|
||||
impl CfGroup {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::CurrentRepositoryView => "current_repository_view",
|
||||
Self::CurrentValidationState => "current_validation_state",
|
||||
Self::CurrentRrdpState => "current_rrdp_state",
|
||||
Self::LegacyCompatibility => "legacy_compatibility",
|
||||
}
|
||||
}
|
||||
_set(opts);
|
||||
}
|
||||
|
||||
fn usage() -> String {
|
||||
@@ -29,21 +44,16 @@ Options:
|
||||
--db <path> RocksDB directory
|
||||
--exact Iterate to count keys (slower; default uses RocksDB estimates)
|
||||
--help Show this help
|
||||
|
||||
Output groups:
|
||||
- current_repository_view: repository_view + raw_by_hash
|
||||
- current_validation_state: vcir + audit_rule_index
|
||||
- current_rrdp_state: rrdp_state + rrdp_source + rrdp_source_member + rrdp_uri_owner
|
||||
- legacy_compatibility: raw_objects + rrdp_object_index
|
||||
"
|
||||
)
|
||||
}
|
||||
|
||||
fn cf_descriptors() -> Vec<ColumnFamilyDescriptor> {
|
||||
let mut cf_opts = Options::default();
|
||||
enable_blobdb_if_supported(&mut cf_opts);
|
||||
vec![
|
||||
ColumnFamilyDescriptor::new(CF_RAW_OBJECTS, cf_opts.clone()),
|
||||
ColumnFamilyDescriptor::new(CF_FETCH_CACHE_PP, cf_opts.clone()),
|
||||
ColumnFamilyDescriptor::new(CF_RRDP_STATE, cf_opts.clone()),
|
||||
ColumnFamilyDescriptor::new(CF_RRDP_OBJECT_INDEX, cf_opts),
|
||||
]
|
||||
}
|
||||
|
||||
fn estimate_keys(db: &DB, cf_name: &str) -> Result<Option<u64>, Box<dyn std::error::Error>> {
|
||||
let cf = db
|
||||
.cf_handle(cf_name)
|
||||
@@ -64,6 +74,35 @@ fn exact_keys(db: &DB, cf_name: &str) -> Result<u64, Box<dyn std::error::Error>>
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn cf_group(cf_name: &str) -> CfGroup {
|
||||
match cf_name {
|
||||
CF_REPOSITORY_VIEW | CF_RAW_BY_HASH => CfGroup::CurrentRepositoryView,
|
||||
CF_VCIR | CF_AUDIT_RULE_INDEX => CfGroup::CurrentValidationState,
|
||||
CF_RRDP_STATE | CF_RRDP_SOURCE | CF_RRDP_SOURCE_MEMBER | CF_RRDP_URI_OWNER => {
|
||||
CfGroup::CurrentRrdpState
|
||||
}
|
||||
CF_RAW_OBJECTS | CF_RRDP_OBJECT_INDEX => CfGroup::LegacyCompatibility,
|
||||
_ => CfGroup::LegacyCompatibility,
|
||||
}
|
||||
}
|
||||
|
||||
fn summarize_counts<'a>(
|
||||
counts: impl IntoIterator<Item = (&'a str, u64)>,
|
||||
) -> BTreeMap<CfGroup, u64> {
|
||||
let mut grouped = BTreeMap::new();
|
||||
for (cf_name, count) in counts {
|
||||
*grouped.entry(cf_group(cf_name)).or_insert(0) += count;
|
||||
}
|
||||
grouped
|
||||
}
|
||||
|
||||
fn mode_label(mode: DbStatsMode) -> &'static str {
|
||||
match mode {
|
||||
DbStatsMode::Estimate => "estimate",
|
||||
DbStatsMode::Exact => "exact",
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let argv: Vec<String> = std::env::args().collect();
|
||||
if argv.iter().any(|a| a == "--help" || a == "-h") {
|
||||
@@ -72,7 +111,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
let mut db_path: Option<PathBuf> = None;
|
||||
let mut exact = false;
|
||||
let mut mode = DbStatsMode::Estimate;
|
||||
let mut i = 1usize;
|
||||
while i < argv.len() {
|
||||
match argv[i].as_str() {
|
||||
@@ -81,7 +120,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let v = argv.get(i).ok_or("--db requires a value")?;
|
||||
db_path = Some(PathBuf::from(v));
|
||||
}
|
||||
"--exact" => exact = true,
|
||||
"--exact" => mode = DbStatsMode::Exact,
|
||||
other => return Err(format!("unknown argument: {other}\n\n{}", usage()).into()),
|
||||
}
|
||||
i += 1;
|
||||
@@ -93,33 +132,75 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
opts.create_if_missing(false);
|
||||
opts.create_missing_column_families(false);
|
||||
|
||||
let db = DB::open_cf_descriptors(&opts, &db_path, cf_descriptors())?;
|
||||
|
||||
let cf_names = [
|
||||
CF_RAW_OBJECTS,
|
||||
CF_FETCH_CACHE_PP,
|
||||
CF_RRDP_STATE,
|
||||
CF_RRDP_OBJECT_INDEX,
|
||||
];
|
||||
let db = DB::open_cf_descriptors(&opts, &db_path, column_family_descriptors())?;
|
||||
|
||||
println!("db={}", db_path.display());
|
||||
println!("mode={}", if exact { "exact" } else { "estimate" });
|
||||
println!("mode={}", mode_label(mode));
|
||||
|
||||
let mut per_cf = Vec::with_capacity(ALL_COLUMN_FAMILY_NAMES.len());
|
||||
let mut total: u64 = 0;
|
||||
for name in cf_names {
|
||||
let n = if exact {
|
||||
exact_keys(&db, name)?
|
||||
} else {
|
||||
estimate_keys(&db, name)?.unwrap_or(0)
|
||||
for &name in ALL_COLUMN_FAMILY_NAMES {
|
||||
let n = match mode {
|
||||
DbStatsMode::Exact => exact_keys(&db, name)?,
|
||||
DbStatsMode::Estimate => estimate_keys(&db, name)?.unwrap_or(0),
|
||||
};
|
||||
total = total.saturating_add(n);
|
||||
per_cf.push((name, n));
|
||||
println!("{name}={n}");
|
||||
}
|
||||
println!("total={total}");
|
||||
|
||||
// Also print # of SST files (useful sanity signal).
|
||||
for (group, count) in summarize_counts(per_cf.iter().copied()) {
|
||||
println!("group_{}={count}", group.as_str());
|
||||
}
|
||||
|
||||
let live = db.live_files()?;
|
||||
println!("sst_files={}", live.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cf_group_classifies_current_and_legacy_keyspaces() {
|
||||
assert_eq!(cf_group(CF_REPOSITORY_VIEW), CfGroup::CurrentRepositoryView);
|
||||
assert_eq!(cf_group(CF_RAW_BY_HASH), CfGroup::CurrentRepositoryView);
|
||||
assert_eq!(cf_group(CF_VCIR), CfGroup::CurrentValidationState);
|
||||
assert_eq!(
|
||||
cf_group(CF_AUDIT_RULE_INDEX),
|
||||
CfGroup::CurrentValidationState
|
||||
);
|
||||
assert_eq!(cf_group(CF_RRDP_SOURCE), CfGroup::CurrentRrdpState);
|
||||
assert_eq!(cf_group(CF_RRDP_URI_OWNER), CfGroup::CurrentRrdpState);
|
||||
assert_eq!(cf_group(CF_RAW_OBJECTS), CfGroup::LegacyCompatibility);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn summarize_counts_accumulates_by_group() {
|
||||
let grouped = summarize_counts([
|
||||
(CF_REPOSITORY_VIEW, 5),
|
||||
(CF_RAW_BY_HASH, 7),
|
||||
(CF_VCIR, 11),
|
||||
(CF_AUDIT_RULE_INDEX, 13),
|
||||
(CF_RRDP_STATE, 17),
|
||||
(CF_RRDP_SOURCE_MEMBER, 19),
|
||||
(CF_RRDP_OBJECT_INDEX, 29),
|
||||
]);
|
||||
|
||||
assert_eq!(grouped.get(&CfGroup::CurrentRepositoryView), Some(&12));
|
||||
assert_eq!(grouped.get(&CfGroup::CurrentValidationState), Some(&24));
|
||||
assert_eq!(grouped.get(&CfGroup::CurrentRrdpState), Some(&36));
|
||||
assert_eq!(grouped.get(&CfGroup::LegacyCompatibility), Some(&29));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_mentions_grouped_output_and_exact_mode() {
|
||||
let text = usage();
|
||||
assert!(text.contains("--exact"), "{text}");
|
||||
assert!(text.contains("current_validation_state"), "{text}");
|
||||
assert!(text.contains("legacy_compatibility"), "{text}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,40 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rocksdb::{ColumnFamilyDescriptor, DB, IteratorMode, Options};
|
||||
use rocksdb::{DB, IteratorMode, Options};
|
||||
use rpki::storage::{
|
||||
CF_RRDP_SOURCE, CF_RRDP_SOURCE_MEMBER, CF_RRDP_STATE, CF_RRDP_URI_OWNER,
|
||||
RrdpSourceMemberRecord, RrdpSourceRecord, RrdpUriOwnerRecord, column_family_descriptors,
|
||||
};
|
||||
use rpki::sync::rrdp::RrdpState;
|
||||
|
||||
fn enable_blobdb_if_supported(opts: &mut Options) {
|
||||
// Keep this in sync with `rpki::storage`:
|
||||
// blob files are CF-level options; readers should open CFs with blob enabled too.
|
||||
#[allow(dead_code)]
|
||||
fn _set(opts: &mut Options) {
|
||||
opts.set_enable_blob_files(true);
|
||||
opts.set_min_blob_size(1024);
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum DumpView {
|
||||
LegacyState,
|
||||
Source,
|
||||
Members,
|
||||
Owners,
|
||||
All,
|
||||
}
|
||||
|
||||
impl DumpView {
|
||||
fn parse(value: &str) -> Result<Self, String> {
|
||||
match value {
|
||||
"legacy-state" => Ok(Self::LegacyState),
|
||||
"source" => Ok(Self::Source),
|
||||
"members" => Ok(Self::Members),
|
||||
"owners" => Ok(Self::Owners),
|
||||
"all" => Ok(Self::All),
|
||||
other => Err(format!(
|
||||
"invalid --view: {other} (expected one of: legacy-state, source, members, owners, all)"
|
||||
)),
|
||||
}
|
||||
}
|
||||
_set(opts);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DumpArgs {
|
||||
db_path: PathBuf,
|
||||
view: DumpView,
|
||||
}
|
||||
|
||||
fn usage() -> String {
|
||||
@@ -18,23 +42,23 @@ fn usage() -> String {
|
||||
format!(
|
||||
"\
|
||||
Usage:
|
||||
{bin} --db <path>
|
||||
{bin} --db <path> [--view <legacy-state|source|members|owners|all>]
|
||||
|
||||
Options:
|
||||
--db <path> RocksDB directory
|
||||
--view <name> Dump one RRDP view; default is all
|
||||
--help Show this help
|
||||
"
|
||||
)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let argv: Vec<String> = std::env::args().collect();
|
||||
fn parse_args(argv: &[String]) -> Result<DumpArgs, String> {
|
||||
if argv.iter().any(|a| a == "--help" || a == "-h") {
|
||||
print!("{}", usage());
|
||||
return Ok(());
|
||||
return Err(usage());
|
||||
}
|
||||
|
||||
let mut db_path: Option<PathBuf> = None;
|
||||
let mut view = DumpView::All;
|
||||
let mut i = 1usize;
|
||||
while i < argv.len() {
|
||||
match argv[i].as_str() {
|
||||
@@ -43,43 +67,280 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let v = argv.get(i).ok_or("--db requires a value")?;
|
||||
db_path = Some(PathBuf::from(v));
|
||||
}
|
||||
other => return Err(format!("unknown argument: {other}\n\n{}", usage()).into()),
|
||||
"--view" => {
|
||||
i += 1;
|
||||
let v = argv.get(i).ok_or("--view requires a value")?;
|
||||
view = DumpView::parse(v)?;
|
||||
}
|
||||
other => return Err(format!("unknown argument: {other}\n\n{}", usage())),
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let db_path = db_path.ok_or_else(|| format!("--db is required\n\n{}", usage()))?;
|
||||
Ok(DumpArgs {
|
||||
db_path: db_path.ok_or_else(|| format!("--db is required\n\n{}", usage()))?,
|
||||
view,
|
||||
})
|
||||
}
|
||||
|
||||
fn open_db(path: &std::path::Path) -> Result<DB, Box<dyn std::error::Error>> {
|
||||
let mut opts = Options::default();
|
||||
opts.create_if_missing(false);
|
||||
opts.create_missing_column_families(false);
|
||||
Ok(DB::open_cf_descriptors(
|
||||
&opts,
|
||||
path,
|
||||
column_family_descriptors(),
|
||||
)?)
|
||||
}
|
||||
|
||||
// Open only the column families we need.
|
||||
let mut cf_opts = Options::default();
|
||||
enable_blobdb_if_supported(&mut cf_opts);
|
||||
let cfs = vec![
|
||||
ColumnFamilyDescriptor::new("raw_objects", cf_opts.clone()),
|
||||
ColumnFamilyDescriptor::new("fetch_cache_pp", cf_opts.clone()),
|
||||
ColumnFamilyDescriptor::new("rrdp_state", cf_opts.clone()),
|
||||
ColumnFamilyDescriptor::new("rrdp_object_index", cf_opts),
|
||||
];
|
||||
let db = DB::open_cf_descriptors(&opts, &db_path, cfs)?;
|
||||
fn collect_legacy_state(db: &DB) -> Result<Vec<(String, RrdpState)>, Box<dyn std::error::Error>> {
|
||||
let cf = db
|
||||
.cf_handle("rrdp_state")
|
||||
.cf_handle(CF_RRDP_STATE)
|
||||
.ok_or("missing column family: rrdp_state")?;
|
||||
|
||||
let mut out: Vec<(String, u64, String)> = Vec::new();
|
||||
let mut out = Vec::new();
|
||||
for res in db.iterator_cf(cf, IteratorMode::Start) {
|
||||
let (k, v) = res?;
|
||||
let k = String::from_utf8_lossy(&k).to_string();
|
||||
let st = rpki::sync::rrdp::RrdpState::decode(&v)
|
||||
.map_err(|e| format!("decode rrdp_state failed for {k}: {e}"))?;
|
||||
out.push((k, st.serial, st.session_id));
|
||||
let notify_uri = String::from_utf8_lossy(&k).to_string();
|
||||
let state = RrdpState::decode(&v)
|
||||
.map_err(|e| format!("decode rrdp_state failed for {notify_uri}: {e}"))?;
|
||||
out.push((notify_uri, state));
|
||||
}
|
||||
|
||||
out.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
for (k, serial, session) in out {
|
||||
println!("{k}\t{serial}\t{session}");
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn collect_source_records(db: &DB) -> Result<Vec<RrdpSourceRecord>, Box<dyn std::error::Error>> {
|
||||
let cf = db
|
||||
.cf_handle(CF_RRDP_SOURCE)
|
||||
.ok_or("missing column family: rrdp_source")?;
|
||||
let mut out = Vec::new();
|
||||
for res in db.iterator_cf(cf, IteratorMode::Start) {
|
||||
let (_k, v) = res?;
|
||||
let record: RrdpSourceRecord = serde_cbor::from_slice(&v)?;
|
||||
record.validate_internal()?;
|
||||
out.push(record);
|
||||
}
|
||||
out.sort_by(|a, b| a.notify_uri.cmp(&b.notify_uri));
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn collect_source_member_records(
|
||||
db: &DB,
|
||||
) -> Result<Vec<RrdpSourceMemberRecord>, Box<dyn std::error::Error>> {
|
||||
let cf = db
|
||||
.cf_handle(CF_RRDP_SOURCE_MEMBER)
|
||||
.ok_or("missing column family: rrdp_source_member")?;
|
||||
let mut out = Vec::new();
|
||||
for res in db.iterator_cf(cf, IteratorMode::Start) {
|
||||
let (_k, v) = res?;
|
||||
let record: RrdpSourceMemberRecord = serde_cbor::from_slice(&v)?;
|
||||
record.validate_internal()?;
|
||||
out.push(record);
|
||||
}
|
||||
out.sort_by(|a, b| {
|
||||
a.notify_uri
|
||||
.cmp(&b.notify_uri)
|
||||
.then(a.rsync_uri.cmp(&b.rsync_uri))
|
||||
});
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn collect_uri_owner_records(
|
||||
db: &DB,
|
||||
) -> Result<Vec<RrdpUriOwnerRecord>, Box<dyn std::error::Error>> {
|
||||
let cf = db
|
||||
.cf_handle(CF_RRDP_URI_OWNER)
|
||||
.ok_or("missing column family: rrdp_uri_owner")?;
|
||||
let mut out = Vec::new();
|
||||
for res in db.iterator_cf(cf, IteratorMode::Start) {
|
||||
let (_k, v) = res?;
|
||||
let record: RrdpUriOwnerRecord = serde_cbor::from_slice(&v)?;
|
||||
record.validate_internal()?;
|
||||
out.push(record);
|
||||
}
|
||||
out.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn print_legacy_state(entries: &[(String, RrdpState)]) {
|
||||
println!("[legacy-state]");
|
||||
println!("notify_uri\tserial\tsession_id");
|
||||
for (notify_uri, state) in entries {
|
||||
println!("{notify_uri}\t{}\t{}", state.serial, state.session_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_source_records(entries: &[RrdpSourceRecord]) {
|
||||
println!("[source]");
|
||||
println!("notify_uri\tlast_serial\tlast_session_id\tsync_state\tlast_snapshot_uri\tlast_error");
|
||||
for record in entries {
|
||||
println!(
|
||||
"{}\t{}\t{}\t{:?}\t{}\t{}",
|
||||
record.notify_uri,
|
||||
record
|
||||
.last_serial
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_else(|| "-".to_string()),
|
||||
record.last_session_id.as_deref().unwrap_or("-"),
|
||||
record.sync_state,
|
||||
record.last_snapshot_uri.as_deref().unwrap_or("-"),
|
||||
record.last_error.as_deref().unwrap_or("-"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_source_member_records(entries: &[RrdpSourceMemberRecord]) {
|
||||
println!("[members]");
|
||||
println!("notify_uri\trsync_uri\tpresent\thash\tsession_id\tserial");
|
||||
for record in entries {
|
||||
println!(
|
||||
"{}\t{}\t{}\t{}\t{}\t{}",
|
||||
record.notify_uri,
|
||||
record.rsync_uri,
|
||||
record.present,
|
||||
record.current_hash.as_deref().unwrap_or("-"),
|
||||
record.last_confirmed_session_id,
|
||||
record.last_confirmed_serial,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_uri_owner_records(entries: &[RrdpUriOwnerRecord]) {
|
||||
println!("[owners]");
|
||||
println!("rsync_uri\tnotify_uri\towner_state\thash\tsession_id\tserial");
|
||||
for record in entries {
|
||||
println!(
|
||||
"{}\t{}\t{:?}\t{}\t{}\t{}",
|
||||
record.rsync_uri,
|
||||
record.notify_uri,
|
||||
record.owner_state,
|
||||
record.current_hash.as_deref().unwrap_or("-"),
|
||||
record.last_confirmed_session_id,
|
||||
record.last_confirmed_serial,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let argv: Vec<String> = std::env::args().collect();
|
||||
let args = parse_args(&argv).map_err(|e| -> Box<dyn std::error::Error> { e.into() })?;
|
||||
let db = open_db(&args.db_path)?;
|
||||
|
||||
match args.view {
|
||||
DumpView::LegacyState => print_legacy_state(&collect_legacy_state(&db)?),
|
||||
DumpView::Source => print_source_records(&collect_source_records(&db)?),
|
||||
DumpView::Members => print_source_member_records(&collect_source_member_records(&db)?),
|
||||
DumpView::Owners => print_uri_owner_records(&collect_uri_owner_records(&db)?),
|
||||
DumpView::All => {
|
||||
print_legacy_state(&collect_legacy_state(&db)?);
|
||||
print_source_records(&collect_source_records(&db)?);
|
||||
print_source_member_records(&collect_source_member_records(&db)?);
|
||||
print_uri_owner_records(&collect_uri_owner_records(&db)?);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rpki::storage::{PackTime, RocksStore, RrdpSourceSyncState, RrdpUriOwnerState};
|
||||
|
||||
#[test]
|
||||
fn parse_args_accepts_view_and_db() {
|
||||
let args = parse_args(&[
|
||||
"rrdp_state_dump".to_string(),
|
||||
"--db".to_string(),
|
||||
"db".to_string(),
|
||||
"--view".to_string(),
|
||||
"owners".to_string(),
|
||||
])
|
||||
.expect("parse args");
|
||||
assert_eq!(args.db_path, PathBuf::from("db"));
|
||||
assert_eq!(args.view, DumpView::Owners);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_args_rejects_invalid_view() {
|
||||
let err = parse_args(&[
|
||||
"rrdp_state_dump".to_string(),
|
||||
"--db".to_string(),
|
||||
"db".to_string(),
|
||||
"--view".to_string(),
|
||||
"nope".to_string(),
|
||||
])
|
||||
.unwrap_err();
|
||||
assert!(err.contains("invalid --view"), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_rrdp_views_reads_legacy_and_current_records() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let store = RocksStore::open(dir.path()).expect("open store");
|
||||
let legacy_state = RrdpState {
|
||||
session_id: "session-1".to_string(),
|
||||
serial: 42,
|
||||
};
|
||||
store
|
||||
.put_rrdp_state(
|
||||
"https://example.test/notify.xml",
|
||||
&legacy_state.encode().expect("encode legacy state"),
|
||||
)
|
||||
.expect("put legacy state");
|
||||
store
|
||||
.put_rrdp_source_record(&RrdpSourceRecord {
|
||||
notify_uri: "https://example.test/notify.xml".to_string(),
|
||||
last_session_id: Some("session-1".to_string()),
|
||||
last_serial: Some(42),
|
||||
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::DeltaReady,
|
||||
last_snapshot_uri: Some("https://example.test/snapshot.xml".to_string()),
|
||||
last_snapshot_hash: None,
|
||||
last_error: None,
|
||||
})
|
||||
.expect("put source record");
|
||||
store
|
||||
.put_rrdp_source_member_record(&RrdpSourceMemberRecord {
|
||||
notify_uri: "https://example.test/notify.xml".to_string(),
|
||||
rsync_uri: "rsync://example.test/repo/a.roa".to_string(),
|
||||
current_hash: Some("11".repeat(32)),
|
||||
object_type: Some("roa".to_string()),
|
||||
present: true,
|
||||
last_confirmed_session_id: "session-1".to_string(),
|
||||
last_confirmed_serial: 42,
|
||||
last_changed_at: PackTime::from_utc_offset_datetime(time::OffsetDateTime::now_utc()),
|
||||
})
|
||||
.expect("put member record");
|
||||
store
|
||||
.put_rrdp_uri_owner_record(&RrdpUriOwnerRecord {
|
||||
rsync_uri: "rsync://example.test/repo/a.roa".to_string(),
|
||||
notify_uri: "https://example.test/notify.xml".to_string(),
|
||||
current_hash: Some("11".repeat(32)),
|
||||
last_confirmed_session_id: "session-1".to_string(),
|
||||
last_confirmed_serial: 42,
|
||||
last_changed_at: PackTime::from_utc_offset_datetime(time::OffsetDateTime::now_utc()),
|
||||
owner_state: RrdpUriOwnerState::Active,
|
||||
})
|
||||
.expect("put owner record");
|
||||
drop(store);
|
||||
|
||||
let db = open_db(dir.path()).expect("open db");
|
||||
let legacy = collect_legacy_state(&db).expect("legacy dump");
|
||||
let sources = collect_source_records(&db).expect("source dump");
|
||||
let members = collect_source_member_records(&db).expect("members dump");
|
||||
let owners = collect_uri_owner_records(&db).expect("owners dump");
|
||||
|
||||
assert_eq!(legacy.len(), 1);
|
||||
assert_eq!(legacy[0].0, "https://example.test/notify.xml");
|
||||
assert_eq!(legacy[0].1.serial, 42);
|
||||
assert_eq!(sources.len(), 1);
|
||||
assert_eq!(sources[0].sync_state, RrdpSourceSyncState::DeltaReady);
|
||||
assert_eq!(members.len(), 1);
|
||||
assert_eq!(members[0].rsync_uri, "rsync://example.test/repo/a.roa");
|
||||
assert_eq!(owners.len(), 1);
|
||||
assert_eq!(owners[0].owner_state, RrdpUriOwnerState::Active);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user