20260620 优化delta长尾控制面和PP cache快路径

This commit is contained in:
2026-06-20 15:31:20 +08:00
parent bd266ef2a5
commit 184d3cb95b
33 changed files with 1951 additions and 109 deletions

View File

@@ -72,6 +72,44 @@ pub fn discover_root_ca_instance_from_tal_url_with_strict_name(
)
}
pub fn discover_root_ca_instance_from_tal_url_with_fetchers(
http_fetcher: &dyn Fetcher,
rsync_fetcher: &dyn RsyncFetcher,
tal_url: &str,
) -> Result<DiscoveredRootCaInstance, FromTalError> {
let tal_bytes = http_fetcher
.fetch(tal_url)
.map_err(FromTalError::TalFetch)?;
let tal = Tal::decode_bytes(&tal_bytes)?;
discover_root_ca_instance_from_tal_with_fetchers_impl(
http_fetcher,
rsync_fetcher,
tal,
Some(tal_url.to_string()),
false,
TaUriOrder::HttpFirst,
)
}
pub fn discover_root_ca_instance_from_tal_url_with_fetchers_strict_name(
http_fetcher: &dyn Fetcher,
rsync_fetcher: &dyn RsyncFetcher,
tal_url: &str,
) -> Result<DiscoveredRootCaInstance, FromTalError> {
let tal_bytes = http_fetcher
.fetch(tal_url)
.map_err(FromTalError::TalFetch)?;
let tal = Tal::decode_bytes(&tal_bytes)?;
discover_root_ca_instance_from_tal_with_fetchers_impl(
http_fetcher,
rsync_fetcher,
tal,
Some(tal_url.to_string()),
true,
TaUriOrder::HttpFirst,
)
}
pub fn discover_root_ca_instance_from_tal(
http_fetcher: &dyn Fetcher,
tal: Tal,
@@ -150,6 +188,7 @@ pub fn discover_root_ca_instance_from_tal_with_fetchers(
tal,
tal_url,
false,
TaUriOrder::RsyncFirst,
)
}
@@ -165,15 +204,23 @@ pub fn discover_root_ca_instance_from_tal_with_fetchers_strict_name(
tal,
tal_url,
true,
TaUriOrder::RsyncFirst,
)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TaUriOrder {
HttpFirst,
RsyncFirst,
}
fn discover_root_ca_instance_from_tal_with_fetchers_impl(
http_fetcher: &dyn Fetcher,
rsync_fetcher: &dyn RsyncFetcher,
tal: Tal,
tal_url: Option<String>,
strict_name: bool,
ta_uri_order: TaUriOrder,
) -> Result<DiscoveredRootCaInstance, FromTalError> {
if tal.ta_uris.is_empty() {
return Err(FromTalError::NoTaUris);
@@ -181,7 +228,7 @@ fn discover_root_ca_instance_from_tal_with_fetchers_impl(
let mut last_err: Option<String> = None;
let mut ta_uris = tal.ta_uris.clone();
ta_uris.sort_by_key(|uri| if uri.scheme() == "rsync" { 0 } else { 1 });
ta_uris.sort_by_key(|uri| ta_uri_priority(uri, ta_uri_order));
for ta_uri in ta_uris.iter() {
let ta_der = match fetch_ta_der(http_fetcher, rsync_fetcher, ta_uri) {
Ok(b) => b,
@@ -221,6 +268,16 @@ fn discover_root_ca_instance_from_tal_with_fetchers_impl(
})))
}
fn ta_uri_priority(uri: &Url, order: TaUriOrder) -> u8 {
match (order, uri.scheme()) {
(TaUriOrder::HttpFirst, "https" | "http") => 0,
(TaUriOrder::HttpFirst, "rsync") => 1,
(TaUriOrder::RsyncFirst, "rsync") => 0,
(TaUriOrder::RsyncFirst, "https" | "http") => 1,
_ => 2,
}
}
fn fetch_ta_der(
http_fetcher: &dyn Fetcher,
rsync_fetcher: &dyn RsyncFetcher,
@@ -250,41 +307,9 @@ fn fetch_ta_der_via_rsync(
rsync_fetcher: &dyn RsyncFetcher,
ta_rsync_uri: &str,
) -> Result<Vec<u8>, String> {
let base = rsync_parent_uri(ta_rsync_uri)?;
let objects = rsync_fetcher
.fetch_objects(&base)
.map_err(|e| e.to_string())?;
objects
.into_iter()
.find(|(uri, _)| uri == ta_rsync_uri)
.map(|(_, bytes)| bytes)
.ok_or_else(|| format!("TA rsync object not found in fetched subtree: {ta_rsync_uri}"))
}
fn rsync_parent_uri(ta_rsync_uri: &str) -> Result<String, String> {
let url = Url::parse(ta_rsync_uri).map_err(|e| e.to_string())?;
if url.scheme() != "rsync" {
return Err(format!("not an rsync URI: {ta_rsync_uri}"));
}
let host = url
.host_str()
.ok_or_else(|| format!("missing host in rsync URI: {ta_rsync_uri}"))?;
let segments = url
.path_segments()
.ok_or_else(|| format!("missing path in rsync URI: {ta_rsync_uri}"))?
.collect::<Vec<_>>();
if segments.is_empty() || segments.last().copied().unwrap_or_default().is_empty() {
return Err(format!(
"rsync URI must reference a file object: {ta_rsync_uri}"
));
}
let parent_segments = &segments[..segments.len() - 1];
let mut parent = format!("rsync://{host}/");
if !parent_segments.is_empty() {
parent.push_str(&parent_segments.join("/"));
parent.push('/');
}
Ok(parent)
rsync_fetcher
.fetch_object(ta_rsync_uri)
.map_err(|e| e.to_string())
}
pub fn discover_root_ca_instance_from_tal_and_ta_der(
@@ -334,6 +359,20 @@ pub fn canonical_tal_rsync_uri_from_bytes(tal_bytes: &[u8]) -> Result<Url, FromT
mod tests {
use super::*;
use crate::fetch::rsync::LocalDirRsyncFetcher;
use std::collections::HashMap;
struct MapHttpFetcher {
map: HashMap<String, Vec<u8>>,
}
impl Fetcher for MapHttpFetcher {
fn fetch(&self, uri: &str) -> Result<Vec<u8>, String> {
self.map
.get(uri)
.cloned()
.ok_or_else(|| format!("no fixture mapped for {uri}"))
}
}
struct FailingHttpFetcher;
@@ -392,6 +431,84 @@ mod tests {
.starts_with("rsync://")
);
}
struct PanicRsyncFetcher;
impl RsyncFetcher for PanicRsyncFetcher {
fn fetch_objects(
&self,
rsync_base_uri: &str,
) -> crate::fetch::rsync::RsyncFetchResult<Vec<(String, Vec<u8>)>> {
panic!("rsync should not be used when HTTPS TA URI is available: {rsync_base_uri}")
}
}
#[test]
fn discover_root_ca_instance_from_tal_url_with_fetchers_prefers_https_ta_uri() {
let tal_url = "https://example.test/apnic.tal";
let tal_bytes = std::fs::read(
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/tal/apnic-rfc7730-https.tal"),
)
.unwrap();
let ta_der = std::fs::read(
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/ta/apnic-ta.cer"),
)
.unwrap();
let https_uri = "https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer";
let mut map = HashMap::new();
map.insert(tal_url.to_string(), tal_bytes);
map.insert(https_uri.to_string(), ta_der);
let discovery = discover_root_ca_instance_from_tal_url_with_fetchers(
&MapHttpFetcher { map },
&PanicRsyncFetcher,
tal_url,
)
.expect("discover via HTTPS TA URI");
assert_eq!(
discovery.trust_anchor.resolved_ta_uri.unwrap().as_str(),
https_uri
);
}
#[test]
fn discover_root_ca_instance_from_tal_url_with_fetchers_supports_rsync_only_tal() {
let tal_url = "https://example.test/apnic-rsync-only.tal";
let tal_bytes = std::fs::read_to_string(
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/tal/apnic-rfc7730-https.tal"),
)
.unwrap();
let rsync_uri = "rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer";
let key_material = tal_bytes
.split_once("\n\n")
.map(|(_, key)| key)
.expect("fixture contains TAL key material");
let rsync_only_tal = format!("{rsync_uri}\n\n{key_material}");
let ta_der = std::fs::read(
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/ta/apnic-ta.cer"),
)
.unwrap();
let td = tempfile::tempdir().unwrap();
let mirror_root = td.path().join("repository");
std::fs::create_dir_all(&mirror_root).unwrap();
std::fs::write(mirror_root.join("apnic-rpki-root-iana-origin.cer"), ta_der).unwrap();
let mut map = HashMap::new();
map.insert(tal_url.to_string(), rsync_only_tal.into_bytes());
let discovery = discover_root_ca_instance_from_tal_url_with_fetchers(
&MapHttpFetcher { map },
&LocalDirRsyncFetcher::new(mirror_root),
tal_url,
)
.expect("discover via rsync-only TAL");
assert_eq!(
discovery.trust_anchor.resolved_ta_uri.unwrap().as_str(),
rsync_uri
);
}
}
pub fn run_root_from_tal_url_once(
@@ -402,7 +519,8 @@ pub fn run_root_from_tal_url_once(
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
validation_time: time::OffsetDateTime,
) -> Result<RunFromTalOutput, FromTalError> {
let discovery = discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)?;
let discovery =
discover_root_ca_instance_from_tal_url_with_fetchers(http_fetcher, rsync_fetcher, tal_url)?;
let run = run_publication_point_once(
store,

View File

@@ -27,8 +27,8 @@ use crate::validation::from_tal::{
DiscoveredRootCaInstance, FromTalError, canonical_tal_rsync_uri_from_bytes,
discover_root_ca_instance_from_tal_and_ta_der,
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name,
discover_root_ca_instance_from_tal_url,
discover_root_ca_instance_from_tal_url_with_strict_name,
discover_root_ca_instance_from_tal_url_with_fetchers,
discover_root_ca_instance_from_tal_url_with_fetchers_strict_name,
discover_root_ca_instance_from_tal_with_fetchers,
discover_root_ca_instance_from_tal_with_fetchers_strict_name,
};
@@ -348,9 +348,17 @@ fn root_discovery_from_tal_input(
match &tal_input.source {
TalSource::Url(url) => {
if strict_name {
discover_root_ca_instance_from_tal_url_with_strict_name(http_fetcher, url)
discover_root_ca_instance_from_tal_url_with_fetchers_strict_name(
http_fetcher,
rsync_fetcher,
url,
)
} else {
discover_root_ca_instance_from_tal_url(http_fetcher, url)
discover_root_ca_instance_from_tal_url_with_fetchers(
http_fetcher,
rsync_fetcher,
url,
)
}
}
TalSource::DerBytes {
@@ -414,12 +422,17 @@ fn root_discovery_from_tal_input(
fn discover_root_ca_instance_from_tal_url_with_policy(
policy: &crate::policy::Policy,
http_fetcher: &dyn Fetcher,
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
tal_url: &str,
) -> Result<DiscoveredRootCaInstance, FromTalError> {
if policy.strict.name {
discover_root_ca_instance_from_tal_url_with_strict_name(http_fetcher, tal_url)
discover_root_ca_instance_from_tal_url_with_fetchers_strict_name(
http_fetcher,
rsync_fetcher,
tal_url,
)
} else {
discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)
discover_root_ca_instance_from_tal_url_with_fetchers(http_fetcher, rsync_fetcher, tal_url)
}
}
@@ -544,8 +557,12 @@ pub fn run_tree_from_tal_url_serial(
validation_time: time::OffsetDateTime,
config: &TreeRunConfig,
) -> Result<RunTreeFromTalOutput, RunTreeFromTalError> {
let discovery =
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
let discovery = discover_root_ca_instance_from_tal_url_with_policy(
policy,
http_fetcher,
rsync_fetcher,
tal_url,
)?;
let runner = make_live_runner(
store,
@@ -585,8 +602,12 @@ pub fn run_tree_from_tal_url_serial_audit(
validation_time: time::OffsetDateTime,
config: &TreeRunConfig,
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
let discovery =
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
let discovery = discover_root_ca_instance_from_tal_url_with_policy(
policy,
http_fetcher,
rsync_fetcher,
tal_url,
)?;
let download_log = DownloadLogHandle::new();
let runner = make_live_runner(
@@ -648,8 +669,12 @@ pub fn run_tree_from_tal_url_serial_audit_with_timing(
timing: &TimingHandle,
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
let _tal = timing.span_phase("tal_bootstrap");
let discovery =
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
let discovery = discover_root_ca_instance_from_tal_url_with_policy(
policy,
http_fetcher,
rsync_fetcher,
tal_url,
)?;
drop(_tal);
let download_log = DownloadLogHandle::new();
@@ -935,8 +960,12 @@ where
H: Fetcher + Clone + 'static,
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
{
let discovery =
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
let discovery = discover_root_ca_instance_from_tal_url_with_policy(
policy,
http_fetcher,
rsync_fetcher,
tal_url,
)?;
run_single_root_parallel_audit_inner(
store,
policy,
@@ -1051,8 +1080,12 @@ where
H: Fetcher + Clone + 'static,
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
{
let discovery =
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
let discovery = discover_root_ca_instance_from_tal_url_with_policy(
policy,
http_fetcher,
rsync_fetcher,
tal_url,
)?;
run_single_root_parallel_audit_inner(
store,
policy,
@@ -1086,8 +1119,12 @@ where
H: Fetcher + Clone + 'static,
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
{
let discovery =
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
let discovery = discover_root_ca_instance_from_tal_url_with_policy(
policy,
http_fetcher,
rsync_fetcher,
tal_url,
)?;
run_single_root_parallel_audit_inner(
store,
policy,

View File

@@ -405,6 +405,8 @@ struct RepoDrainMetrics {
duration_ms: u64,
}
const REPO_RESULT_DRAIN_MAX_EVENTS: usize = 64;
fn elapsed_ms(started: Instant) -> u64 {
started.elapsed().as_millis() as u64
}
@@ -1184,6 +1186,11 @@ fn stage_ready_publication_point(
) {
ParallelObjectsPrepare::Complete(mut objects) => {
metrics.prepare_ms = elapsed_ms(prepare_started);
runner.record_publication_point_step_ms(
&ready.node.handle.manifest_rsync_uri,
"fresh_objects_prepare",
metrics.prepare_ms,
);
metrics.complete_count = 1;
metrics.roa_tasks = objects.stats.roa_total;
metrics.aspa_objects = objects.stats.aspa_total;
@@ -1210,15 +1217,30 @@ fn stage_ready_publication_point(
metrics.direct_finalize_ms = finalize_metrics
.finalize_ms
.max(elapsed_ms(direct_finalize_started));
runner.record_publication_point_step_ms(
&metrics.manifest_rsync_uri.clone().unwrap_or_default(),
"fresh_direct_finalize",
metrics.direct_finalize_ms,
);
}
ParallelObjectsPrepare::Staged(objects_stage) => {
metrics.prepare_ms = elapsed_ms(prepare_started);
runner.record_publication_point_step_ms(
&ready.node.handle.manifest_rsync_uri,
"fresh_objects_prepare",
metrics.prepare_ms,
);
metrics.staged_count = 1;
metrics.locked_files = objects_stage.locked_file_count();
metrics.aspa_objects = objects_stage.aspa_task_count();
let build_tasks_started = Instant::now();
objects_stage.append_roa_tasks_to(pending_roa_dispatch);
metrics.build_roa_tasks_ms = elapsed_ms(build_tasks_started);
runner.record_publication_point_step_ms(
&ready.node.handle.manifest_rsync_uri,
"fresh_build_roa_tasks",
metrics.build_roa_tasks_ms,
);
let task_count = objects_stage.roa_task_count();
metrics.roa_tasks = task_count;
if task_count == 0 {
@@ -1275,6 +1297,12 @@ fn stage_ready_publication_point(
}
}
metrics.total_ms = elapsed_ms(publication_point_started);
if metrics.complete_count > 0 {
runner.record_publication_point_total_ms(
metrics.manifest_rsync_uri.as_deref().unwrap_or_default(),
metrics.total_ms,
);
}
emit_ready_publication_point_control_slow(
metrics.manifest_rsync_uri.as_deref().unwrap_or_default(),
metrics
@@ -1916,10 +1944,20 @@ fn finalize_publication_point_state(
.as_millis() as u64
});
let objects_processing_ms = objects_started_at.elapsed().as_millis() as u64;
runner.record_publication_point_step_ms(
&node.handle.manifest_rsync_uri,
"fresh_objects_processing_lifetime",
objects_processing_ms,
);
let reduce_started = Instant::now();
let locked_files = objects_stage.locked_file_count();
let reduce_result = reduce_parallel_roa_stage(objects_stage, results, runner.timing.as_ref());
let reduce_ms = elapsed_ms(reduce_started);
runner.record_publication_point_step_ms(
&node.handle.manifest_rsync_uri,
"fresh_roa_reduce",
reduce_ms,
);
let (result, mut metrics) = match reduce_result {
Ok(mut objects) => {
@@ -1985,6 +2023,25 @@ fn finalize_publication_point_state(
};
let finalize_worker_ms = elapsed_ms(finalize_worker_started);
metrics.finalize_worker_ms = finalize_worker_ms;
runner.record_publication_point_step_ms(
&node.handle.manifest_rsync_uri,
"fresh_finalize_worker",
finalize_worker_ms,
);
runner.record_publication_point_step_ms(
&node.handle.manifest_rsync_uri,
"fresh_finalize_queue_wait",
finalize_queue_wait_ms.unwrap_or(0),
);
runner.record_publication_point_step_ms(
&node.handle.manifest_rsync_uri,
"fresh_finalize",
metrics.finalize_ms,
);
runner.record_publication_point_total_ms(
&node.handle.manifest_rsync_uri,
started_at.elapsed().as_millis() as u64,
);
emit_finalize_breakdown(
"phase2_finalize_worker_breakdown",
node.handle.manifest_rsync_uri.as_str(),
@@ -2056,10 +2113,10 @@ fn drain_repo_events(
) -> Result<RepoDrainMetrics, TreeRunError> {
let started = Instant::now();
let mut metrics = RepoDrainMetrics::default();
if let Some(event) = repo_runtime
.recv_repo_result_timeout(timeout)
.map_err(TreeRunError::Runner)?
{
let events = repo_runtime
.drain_repo_results_timeout(timeout, REPO_RESULT_DRAIN_MAX_EVENTS)
.map_err(TreeRunError::Runner)?;
for event in events {
metrics.event_count += 1;
metrics.completions += event.completions.len();
for completion in event.completions {

View File

@@ -25,11 +25,12 @@ use crate::replay::archive::ReplayArchiveIndex;
use crate::replay::delta_archive::ReplayDeltaArchiveIndex;
use crate::report::{RfcRef, Warning};
use crate::storage::{
PackFile, PackTime, PublicationPointCacheChild, PublicationPointCacheProjection,
RawByHashEntry, RocksStore, ValidatedCaInstanceResult, VcirArtifactKind, VcirArtifactRole,
VcirArtifactValidationStatus, VcirAuditSummary, VcirCcrManifestProjection, VcirChildEntry,
VcirInstanceGate, VcirLocalOutput, VcirLocalOutputPayload, VcirOutputType, VcirRelatedArtifact,
VcirReplaceTimingBreakdown, VcirSourceObjectType, VcirSummary,
PackFile, PackTime, PublicationPointCacheChild, PublicationPointCacheOutput,
PublicationPointCacheProjection, RawByHashEntry, RocksStore, ValidatedCaInstanceResult,
VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary,
VcirCcrManifestProjection, VcirChildEntry, VcirInstanceGate, VcirLocalOutput,
VcirLocalOutputPayload, VcirOutputType, VcirRelatedArtifact, VcirReplaceTimingBreakdown,
VcirSourceObjectType, VcirSummary,
};
use crate::sync::repo::{
sync_publication_point, sync_publication_point_replay, sync_publication_point_replay_delta,
@@ -241,7 +242,7 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
}
let projection = match self
.store
.get_publication_point_cache_projection(&ca.manifest_rsync_uri)
.get_publication_point_cache_projection_cached(&ca.manifest_rsync_uri)
{
Ok(Some(projection)) => projection,
Ok(None) => {
@@ -526,16 +527,12 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
let output_reuse_count = projection.outputs.len() as u64;
let child_reuse_count = projection.children.len() as u64;
let related_object_reuse_count = projection.related_objects.len() as u64;
let to_vcir_started = std::time::Instant::now();
let mut vcir = projection.to_vcir_for_reuse(self.validation_time);
let to_vcir_ms = self.record_publication_point_cache_phase_ms(
"publication_point_cache_to_vcir_total",
to_vcir_started,
);
vcir.parent_manifest_rsync_uri = ca.parent_manifest_rsync_uri.clone();
let build_objects_started = std::time::Instant::now();
let mut objects =
build_objects_output_from_vcir(&vcir, self.validation_time, &mut warnings);
let mut objects = build_objects_output_from_publication_point_cache_projection(
&projection,
self.validation_time,
&mut warnings,
);
let build_objects_ms = self.record_publication_point_cache_phase_ms(
"publication_point_cache_build_objects_total",
build_objects_started,
@@ -563,19 +560,18 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
ccr_append_started,
);
let audit_build_started = std::time::Instant::now();
let audit = build_publication_point_audit_from_vcir(
let audit = build_publication_point_audit_from_publication_point_cache_projection(
ca,
PublicationPointSource::PublicationPointCache,
repo_sync_source,
repo_sync_phase,
Some(repo_sync_duration_ms),
repo_sync_err,
Some(&vcir),
None,
&projection,
self.validation_time,
&warnings,
&objects,
&child_audits,
&[],
);
let audit_build_ms = self.record_publication_point_cache_phase_ms(
"publication_point_cache_audit_build_total",
@@ -614,7 +610,6 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
"children_reused": child_reuse_count,
"related_objects_reused": related_object_reuse_count,
"audit_objects_reused": audit_object_count,
"to_vcir_ms": to_vcir_ms,
"build_objects_ms": build_objects_ms,
"restore_children_ms": restore_children_ms,
"restore_children_workers": child_restore_workers,
@@ -649,6 +644,27 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
nanos / 1_000_000
}
pub(crate) fn record_publication_point_total_ms(&self, manifest_rsync_uri: &str, ms: u64) {
if let Some(timing) = self.timing.as_ref() {
timing.record_publication_point_nanos(manifest_rsync_uri, ms.saturating_mul(1_000_000));
}
}
pub(crate) fn record_publication_point_step_ms(
&self,
manifest_rsync_uri: &str,
step: &'static str,
ms: u64,
) {
if let Some(timing) = self.timing.as_ref() {
timing.record_publication_point_step_nanos(
manifest_rsync_uri,
step,
ms.saturating_mul(1_000_000),
);
}
}
fn publication_point_cache_child_restore_worker_count(&self) -> usize {
self.parallel_phase2_config
.as_ref()
@@ -772,6 +788,88 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
error,
snapshot_prepare_ms,
})?;
if let Some(timing) = self.timing.as_ref() {
timing.record_phase_nanos(
"fresh_snapshot_prepare_total",
snapshot_prepare_ms.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_snapshot_manifest_load_total",
snapshot_prepare_timing
.manifest_load_ms
.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_snapshot_manifest_decode_total",
snapshot_prepare_timing
.manifest_decode_ms
.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_snapshot_replay_guard_total",
snapshot_prepare_timing
.replay_guard_ms
.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_snapshot_manifest_entries_total",
snapshot_prepare_timing
.manifest_entries_ms
.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_snapshot_pack_files_total",
snapshot_prepare_timing
.pack_files_ms
.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_snapshot_ee_path_validate_total",
snapshot_prepare_timing
.ee_path_validate_ms
.saturating_mul(1_000_000),
);
timing.record_count("fresh_publication_points", 1);
timing.record_count(
"fresh_manifest_files_total",
snapshot_prepare_timing.manifest_file_count as u64,
);
}
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_snapshot_prepare",
snapshot_prepare_ms,
);
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_snapshot_manifest_load",
snapshot_prepare_timing.manifest_load_ms,
);
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_snapshot_manifest_decode",
snapshot_prepare_timing.manifest_decode_ms,
);
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_snapshot_replay_guard",
snapshot_prepare_timing.replay_guard_ms,
);
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_snapshot_manifest_entries",
snapshot_prepare_timing.manifest_entries_ms,
);
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_snapshot_pack_files",
snapshot_prepare_timing.pack_files_ms,
);
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_snapshot_ee_path_validate",
snapshot_prepare_timing.ee_path_validate_ms,
);
let child_discovery_started = std::time::Instant::now();
let out = {
@@ -800,6 +898,26 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
),
};
let child_discovery_ms = child_discovery_started.elapsed().as_millis() as u64;
if let Some(timing) = self.timing.as_ref() {
timing.record_phase_nanos(
"fresh_child_discovery_total",
child_discovery_ms.saturating_mul(1_000_000),
);
timing.record_count(
"fresh_children_discovered",
discovered_children.len() as u64,
);
timing.record_count("fresh_child_audits", child_audits.len() as u64);
timing.record_count(
"fresh_router_keys_discovered",
discovered_router_keys.len() as u64,
);
}
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_child_discovery",
child_discovery_ms,
);
Ok(FreshPublicationPointStage {
fresh_point,
@@ -829,6 +947,17 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
let snapshot_pack_started = std::time::Instant::now();
let pack = fresh_point.to_publication_point_snapshot();
let snapshot_pack_ms = snapshot_pack_started.elapsed().as_millis() as u64;
if let Some(timing) = self.timing.as_ref() {
timing.record_phase_nanos(
"fresh_snapshot_pack_total",
snapshot_pack_ms.saturating_mul(1_000_000),
);
}
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_snapshot_pack",
snapshot_pack_ms,
);
let persist_vcir_started = std::time::Instant::now();
let persist_vcir_timing = if self.persist_vcir {
@@ -850,6 +979,64 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
PersistVcirTimingBreakdown::default()
};
let persist_vcir_ms = persist_vcir_started.elapsed().as_millis() as u64;
if let Some(timing) = self.timing.as_ref() {
timing.record_phase_nanos(
"fresh_persist_vcir_total",
persist_vcir_ms.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_persist_embedded_store_total",
persist_vcir_timing
.embedded_store_ms
.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_persist_build_vcir_total",
persist_vcir_timing.build_vcir_ms.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_persist_replace_vcir_total",
persist_vcir_timing
.replace_vcir_ms
.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_persist_local_outputs_total",
persist_vcir_timing
.build_vcir
.local_outputs_ms
.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_persist_child_entries_total",
persist_vcir_timing
.build_vcir
.child_entries_ms
.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_persist_related_artifacts_total",
persist_vcir_timing
.build_vcir
.related_artifacts_ms
.saturating_mul(1_000_000),
);
}
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_persist_vcir",
persist_vcir_ms,
);
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_persist_build_vcir",
persist_vcir_timing.build_vcir_ms,
);
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_persist_replace_vcir",
persist_vcir_timing.replace_vcir_ms,
);
// local_outputs_cache only exists to build/persist VCIR. Release it before the
// publication point result is retained for the rest of the run.
@@ -868,6 +1055,26 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
self.append_ccr_manifest_projection(&ccr_manifest_projection)?;
ccr_append_ms = ccr_append_started.elapsed().as_millis() as u64;
}
if let Some(timing) = self.timing.as_ref() {
timing.record_phase_nanos(
"fresh_ccr_projection_build_total",
ccr_projection_build_ms.saturating_mul(1_000_000),
);
timing.record_phase_nanos(
"fresh_ccr_append_total",
ccr_append_ms.saturating_mul(1_000_000),
);
}
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_ccr_projection_build",
ccr_projection_build_ms,
);
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_ccr_append",
ccr_append_ms,
);
let audit_build_started = std::time::Instant::now();
let audit = build_publication_point_audit_from_snapshot(
@@ -883,6 +1090,17 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
&child_audits,
);
let audit_build_ms = audit_build_started.elapsed().as_millis() as u64;
if let Some(timing) = self.timing.as_ref() {
timing.record_phase_nanos(
"fresh_audit_build_total",
audit_build_ms.saturating_mul(1_000_000),
);
}
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_audit_build",
audit_build_ms,
);
Ok(FreshPublicationPointFinalizeOutput {
result: PublicationPointRunResult {
@@ -1296,6 +1514,11 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
}
};
let objects_processing_ms = objects_processing_started.elapsed().as_millis() as u64;
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
"fresh_objects_processing",
objects_processing_ms,
);
objects.router_keys.extend(discovered_router_keys);
objects
@@ -2741,6 +2964,98 @@ fn build_publication_point_audit_from_vcir(
}
}
fn build_publication_point_audit_from_publication_point_cache_projection(
ca: &CaInstanceHandle,
source: PublicationPointSource,
repo_sync_source: Option<&str>,
repo_sync_phase: Option<&str>,
repo_sync_duration_ms: Option<u64>,
repo_sync_error: Option<&str>,
projection: &PublicationPointCacheProjection,
validation_time: time::OffsetDateTime,
runner_warnings: &[Warning],
objects: &crate::validation::objects::ObjectsOutput,
child_audits: &[ObjectAuditEntry],
) -> PublicationPointAudit {
let mut warnings = Vec::new();
warnings.extend(runner_warnings.iter().map(AuditWarning::from));
warnings.extend(objects.warnings.iter().map(AuditWarning::from));
let mut audit_by_uri: HashMap<String, ObjectAuditEntry> = HashMap::new();
for artifact in &projection.related_objects {
let Some(uri) = artifact.uri.as_ref() else {
continue;
};
audit_by_uri.insert(
uri.clone(),
ObjectAuditEntry {
rsync_uri: uri.clone(),
sha256_hex: artifact.sha256.clone(),
kind: kind_from_vcir_artifact_kind(artifact.artifact_kind),
result: audit_result_from_vcir_status(artifact.validation_status),
detail: None,
},
);
}
for entry in child_audits {
audit_by_uri.insert(entry.rsync_uri.clone(), entry.clone());
}
for entry in &objects.audit {
audit_by_uri.insert(entry.rsync_uri.clone(), entry.clone());
}
let mut ordered_uris: Vec<String> = projection
.related_objects
.iter()
.filter_map(|artifact| artifact.uri.clone())
.collect();
ordered_uris.sort();
ordered_uris.dedup();
let mut objects_out: Vec<ObjectAuditEntry> = Vec::with_capacity(ordered_uris.len().max(1));
if let Some(entry) = audit_by_uri.remove(&projection.manifest_rsync_uri) {
objects_out.push(entry);
} else {
objects_out.push(ObjectAuditEntry {
rsync_uri: projection.manifest_rsync_uri.clone(),
sha256_hex: hex::encode(projection.manifest_sha256),
kind: AuditObjectKind::Manifest,
result: AuditObjectResult::Ok,
detail: None,
});
}
for uri in ordered_uris {
if uri == projection.manifest_rsync_uri {
continue;
}
if let Some(entry) = audit_by_uri.remove(&uri) {
objects_out.push(entry);
}
}
PublicationPointAudit {
node_id: None,
parent_node_id: None,
discovered_from: None,
rsync_base_uri: ca.rsync_base_uri.clone(),
manifest_rsync_uri: ca.manifest_rsync_uri.clone(),
publication_point_rsync_uri: ca.publication_point_rsync_uri.clone(),
rrdp_notification_uri: ca.rrdp_notification_uri.clone(),
source: source_label(source),
repo_sync_source: repo_sync_source.map(ToString::to_string),
repo_sync_phase: repo_sync_phase.map(ToString::to_string),
repo_sync_duration_ms,
repo_sync_error: repo_sync_error.map(ToString::to_string),
repo_terminal_state: terminal_state_label(source).to_string(),
this_update_rfc3339_utc: projection.manifest_this_update.rfc3339_utc.clone(),
next_update_rfc3339_utc: projection.manifest_next_update.rfc3339_utc.clone(),
verified_at_rfc3339_utc: PackTime::from_utc_offset_datetime(validation_time).rfc3339_utc,
warnings,
objects: objects_out,
}
}
fn parse_snapshot_time_value(pack_time: &PackTime) -> Result<time::OffsetDateTime, String> {
time::OffsetDateTime::parse(
&pack_time.rfc3339_utc,
@@ -3160,6 +3475,202 @@ fn build_objects_output_from_vcir(
output
}
fn build_objects_output_from_publication_point_cache_projection(
projection: &PublicationPointCacheProjection,
validation_time: time::OffsetDateTime,
warnings: &mut Vec<Warning>,
) -> crate::validation::objects::ObjectsOutput {
let mut output = empty_objects_output();
let mut audit_by_uri: HashMap<String, ObjectAuditEntry> = HashMap::new();
let mut roa_total: HashSet<String> = HashSet::new();
let mut aspa_total: HashSet<String> = HashSet::new();
let mut roa_ok: HashSet<String> = HashSet::new();
let mut aspa_ok: HashSet<String> = HashSet::new();
for artifact in &projection.related_objects {
if artifact.artifact_role != VcirArtifactRole::SignedObject {
continue;
}
if let Some(uri) = artifact.uri.as_ref() {
match artifact.artifact_kind {
VcirArtifactKind::Roa => {
roa_total.insert(uri.clone());
}
VcirArtifactKind::Aspa => {
aspa_total.insert(uri.clone());
}
_ => {}
}
}
}
for projected in &projection.outputs {
let effective_until = match parse_snapshot_time_value(&projected.item_effective_until) {
Ok(value) => value,
Err(err) => {
warnings.push(
Warning::new(format!(
"publication-point cached local output has invalid item_effective_until: {err}"
))
.with_context(&projected.source_object_uri),
);
audit_by_uri.insert(
projected.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: projected.source_object_uri.clone(),
sha256_hex: hex::encode(projected.source_object_hash),
kind: audit_kind_for_vcir_output_type(projected.output_type),
result: AuditObjectResult::Error,
detail: Some(
"publication-point cached local output has invalid item_effective_until"
.to_string(),
),
},
);
continue;
}
};
if validation_time > effective_until {
audit_by_uri
.entry(projected.source_object_uri.clone())
.or_insert_with(|| ObjectAuditEntry {
rsync_uri: projected.source_object_uri.clone(),
sha256_hex: hex::encode(projected.source_object_hash),
kind: audit_kind_for_vcir_output_type(projected.output_type),
result: AuditObjectResult::Skipped,
detail: Some(
"skipped: publication-point cached local output expired".to_string(),
),
});
continue;
}
match projected.output_type {
VcirOutputType::Vrp => match parse_publication_point_cache_vrp_output(projected) {
Ok(vrp) => {
roa_ok.insert(projected.source_object_uri.clone());
output.vrps.push(vrp);
audit_by_uri.insert(
projected.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: projected.source_object_uri.clone(),
sha256_hex: hex::encode(projected.source_object_hash),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Ok,
detail: None,
},
);
}
Err(err) => {
warnings.push(
Warning::new(format!(
"publication-point cached ROA local output parse failed: {err}"
))
.with_context(&projected.source_object_uri),
);
audit_by_uri.insert(
projected.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: projected.source_object_uri.clone(),
sha256_hex: hex::encode(projected.source_object_hash),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Error,
detail: Some(format!(
"publication-point cached ROA local output parse failed: {err}"
)),
},
);
}
},
VcirOutputType::Aspa => match parse_publication_point_cache_aspa_output(projected) {
Ok(aspa) => {
aspa_ok.insert(projected.source_object_uri.clone());
output.aspas.push(aspa);
audit_by_uri.insert(
projected.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: projected.source_object_uri.clone(),
sha256_hex: hex::encode(projected.source_object_hash),
kind: AuditObjectKind::Aspa,
result: AuditObjectResult::Ok,
detail: None,
},
);
}
Err(err) => {
warnings.push(
Warning::new(format!(
"publication-point cached ASPA local output parse failed: {err}"
))
.with_context(&projected.source_object_uri),
);
audit_by_uri.insert(
projected.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: projected.source_object_uri.clone(),
sha256_hex: hex::encode(projected.source_object_hash),
kind: AuditObjectKind::Aspa,
result: AuditObjectResult::Error,
detail: Some(format!(
"publication-point cached ASPA local output parse failed: {err}"
)),
},
);
}
},
VcirOutputType::RouterKey => {
match parse_publication_point_cache_router_key_output(projected) {
Ok(router_key) => {
output.router_keys.push(router_key);
audit_by_uri.insert(
projected.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: projected.source_object_uri.clone(),
sha256_hex: hex::encode(projected.source_object_hash),
kind: AuditObjectKind::RouterCertificate,
result: AuditObjectResult::Ok,
detail: Some(
"publication-point cached Router Key local output restored"
.to_string(),
),
},
);
}
Err(err) => {
warnings.push(
Warning::new(format!(
"publication-point cached Router Key local output parse failed: {err}"
))
.with_context(&projected.source_object_uri),
);
audit_by_uri.insert(
projected.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: projected.source_object_uri.clone(),
sha256_hex: hex::encode(projected.source_object_hash),
kind: AuditObjectKind::RouterCertificate,
result: AuditObjectResult::Error,
detail: Some(format!(
"publication-point cached Router Key local output parse failed: {err}"
)),
},
);
}
}
}
}
}
output.stats.roa_total = roa_total.len();
output.stats.roa_ok = roa_ok.len();
output.stats.aspa_total = aspa_total.len();
output.stats.aspa_ok = aspa_ok.len();
let mut audit: Vec<_> = audit_by_uri.into_values().collect();
audit.sort_by(|left, right| left.rsync_uri.cmp(&right.rsync_uri));
output.audit = audit;
output
}
fn parse_vcir_vrp_output(local: &VcirLocalOutput) -> Result<Vrp, String> {
match &local.payload {
VcirLocalOutputPayload::Vrp {
@@ -3213,6 +3724,65 @@ fn parse_vcir_router_key_output(local: &VcirLocalOutput) -> Result<RouterKeyPayl
}
}
fn parse_publication_point_cache_vrp_output(
projected: &PublicationPointCacheOutput,
) -> Result<Vrp, String> {
match &projected.payload {
VcirLocalOutputPayload::Vrp {
asn,
afi,
prefix_len,
addr,
max_length,
} => Ok(Vrp {
asn: *asn,
prefix: crate::data_model::roa::IpPrefix {
afi: *afi,
prefix_len: *prefix_len,
addr: *addr,
},
max_length: *max_length,
}),
_ => Err("publication-point cache output payload is not VRP".to_string()),
}
}
fn parse_publication_point_cache_aspa_output(
projected: &PublicationPointCacheOutput,
) -> Result<AspaAttestation, String> {
match &projected.payload {
VcirLocalOutputPayload::Aspa {
customer_as_id,
provider_as_ids,
} => Ok(AspaAttestation {
customer_as_id: *customer_as_id,
provider_as_ids: provider_as_ids.clone(),
}),
_ => Err("publication-point cache output payload is not ASPA".to_string()),
}
}
fn parse_publication_point_cache_router_key_output(
projected: &PublicationPointCacheOutput,
) -> Result<RouterKeyPayload, String> {
match &projected.payload {
VcirLocalOutputPayload::RouterKey {
as_id,
ski,
spki_der,
} => Ok(RouterKeyPayload {
as_id: *as_id,
ski: ski.clone(),
spki_der: spki_der.clone(),
source_object_uri: projected.source_object_uri.clone(),
source_object_hash: hex::encode(projected.source_object_hash),
source_ee_cert_hash: hex::encode(projected.source_ee_cert_hash),
item_effective_until: projected.item_effective_until.clone(),
}),
_ => Err("publication-point cache output payload is not Router Key".to_string()),
}
}
fn restore_children_from_vcir(
store: &RocksStore,
ca: &CaInstanceHandle,