20260616 增加产品UI查询服务
This commit is contained in:
@@ -3,8 +3,12 @@ use std::path::Path;
|
||||
|
||||
use serde::Serialize;
|
||||
use serde::ser::SerializeSeq;
|
||||
use sha2::Digest;
|
||||
|
||||
use crate::audit::{AspaOutput, AuditRunMeta, AuditWarning, VrpOutput};
|
||||
use crate::audit::{
|
||||
AspaOutput, AuditRunMeta, AuditWarning, QueryAuditManifest, ValidationEvent,
|
||||
ValidationEventCounts, VrpOutput,
|
||||
};
|
||||
use crate::ccr::canonical_vrp_prefix;
|
||||
|
||||
use super::{PostValidationShared, RunStageTiming};
|
||||
@@ -49,6 +53,8 @@ struct BorrowedAuditReportV2<'a> {
|
||||
downloads: &'a [crate::audit::AuditDownloadEvent],
|
||||
download_stats: &'a crate::audit::AuditDownloadStats,
|
||||
repo_sync_stats: crate::audit::AuditRepoSyncStats,
|
||||
#[serde(rename = "queryAudit", skip_serializing_if = "Option::is_none")]
|
||||
query_audit: Option<QueryAuditManifest>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -125,6 +131,7 @@ pub(super) fn write_report_json_from_shared(
|
||||
.format(&Rfc3339)
|
||||
.expect("format validation_time");
|
||||
let repo_sync_stats = super::build_repo_sync_stats(shared.publication_points.as_ref());
|
||||
let query_audit = write_validation_events_sidecar(path, &validation_time_rfc3339_utc, shared)?;
|
||||
let report = BorrowedAuditReportV2 {
|
||||
format_version: 2,
|
||||
meta: AuditRunMeta {
|
||||
@@ -142,6 +149,7 @@ pub(super) fn write_report_json_from_shared(
|
||||
downloads: shared.downloads.as_ref(),
|
||||
download_stats: &shared.download_stats,
|
||||
repo_sync_stats,
|
||||
query_audit: Some(query_audit),
|
||||
};
|
||||
let build_ms = build_started.elapsed().as_millis() as u64;
|
||||
|
||||
@@ -153,6 +161,148 @@ pub(super) fn write_report_json_from_shared(
|
||||
})
|
||||
}
|
||||
|
||||
fn write_validation_events_sidecar(
|
||||
report_path: &Path,
|
||||
validation_time: &str,
|
||||
shared: &PostValidationShared,
|
||||
) -> Result<QueryAuditManifest, String> {
|
||||
let events_path = report_path.with_file_name("validation-events.jsonl");
|
||||
if let Some(parent) = events_path.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.map_err(|e| format!("create validation events parent failed: {e}"))?;
|
||||
}
|
||||
let mut writer = BufWriter::new(std::fs::File::create(&events_path).map_err(|e| {
|
||||
format!(
|
||||
"create validation events failed: {}: {e}",
|
||||
events_path.display()
|
||||
)
|
||||
})?);
|
||||
let mut seq = 0u64;
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
emit_validation_events(validation_time, shared, &mut seq, &mut |event| {
|
||||
let mut line = serde_json::to_vec(&event)
|
||||
.map_err(|e| format!("serialize validation event failed: {e}"))?;
|
||||
line.push(b'\n');
|
||||
std::io::Write::write_all(&mut writer, &line)
|
||||
.map_err(|e| format!("write validation event failed: {e}"))?;
|
||||
hasher.update(&line);
|
||||
Ok(())
|
||||
})?;
|
||||
std::io::Write::flush(&mut writer)
|
||||
.map_err(|e| format!("flush validation events failed: {e}"))?;
|
||||
let events_count = seq;
|
||||
let events_sha256 = hex::encode(hasher.finalize());
|
||||
Ok(QueryAuditManifest {
|
||||
schema_version: 1,
|
||||
status: "complete".to_string(),
|
||||
events_path: events_path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("validation-events.jsonl")
|
||||
.to_string(),
|
||||
events_count,
|
||||
events_sha256,
|
||||
writer_version: 1,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn emit_validation_events(
|
||||
validation_time: &str,
|
||||
shared: &PostValidationShared,
|
||||
seq: &mut u64,
|
||||
emit: &mut impl FnMut(ValidationEvent) -> Result<(), String>,
|
||||
) -> Result<(), String> {
|
||||
emit(next_event(seq, "run_summary", validation_time, |event| {
|
||||
event.counts = Some(ValidationEventCounts {
|
||||
objects: Some(
|
||||
shared
|
||||
.publication_points
|
||||
.iter()
|
||||
.map(|pp| pp.objects.len() as u64)
|
||||
.sum(),
|
||||
),
|
||||
warnings: Some(
|
||||
(shared.tree_warnings.len()
|
||||
+ shared
|
||||
.publication_points
|
||||
.iter()
|
||||
.map(|pp| pp.warnings.len())
|
||||
.sum::<usize>()) as u64,
|
||||
),
|
||||
vrps: Some(shared.vrps.len() as u64),
|
||||
aspas: Some(shared.aspas.len() as u64),
|
||||
});
|
||||
}))?;
|
||||
for pp in shared.publication_points.iter() {
|
||||
emit(next_event(
|
||||
seq,
|
||||
"publication_point",
|
||||
validation_time,
|
||||
|event| {
|
||||
event.pp_node_id = pp.node_id;
|
||||
event.pp_manifest_uri = Some(pp.manifest_rsync_uri.clone());
|
||||
event.pp_rsync_base_uri = Some(pp.rsync_base_uri.clone());
|
||||
event.repo_sync_phase = pp.repo_sync_phase.clone();
|
||||
event.repo_terminal_state = Some(pp.repo_terminal_state.clone());
|
||||
event.counts = Some(ValidationEventCounts {
|
||||
objects: Some(pp.objects.len() as u64),
|
||||
warnings: Some(pp.warnings.len() as u64),
|
||||
vrps: None,
|
||||
aspas: None,
|
||||
});
|
||||
},
|
||||
))?;
|
||||
for object in &pp.objects {
|
||||
emit(next_event(seq, "object", validation_time, |event| {
|
||||
event.pp_node_id = pp.node_id;
|
||||
event.pp_manifest_uri = Some(pp.manifest_rsync_uri.clone());
|
||||
event.object_uri = Some(object.rsync_uri.clone());
|
||||
event.sha256 = Some(object.sha256_hex.clone());
|
||||
event.object_type = Some(object.kind.clone());
|
||||
event.result = Some(object.result.clone());
|
||||
event.reason = object.detail.clone();
|
||||
}))?;
|
||||
}
|
||||
for warning in &pp.warnings {
|
||||
emit(next_event(seq, "warning", validation_time, |event| {
|
||||
event.pp_node_id = pp.node_id;
|
||||
event.pp_manifest_uri = Some(pp.manifest_rsync_uri.clone());
|
||||
event.reason = Some(warning.message.clone());
|
||||
}))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next_event(
|
||||
seq: &mut u64,
|
||||
event_type: &str,
|
||||
validation_time: &str,
|
||||
fill: impl FnOnce(&mut ValidationEvent),
|
||||
) -> ValidationEvent {
|
||||
*seq += 1;
|
||||
let mut event = ValidationEvent {
|
||||
schema_version: 1,
|
||||
seq: *seq,
|
||||
event_type: event_type.to_string(),
|
||||
validation_time: validation_time.to_string(),
|
||||
pp_node_id: None,
|
||||
pp_manifest_uri: None,
|
||||
pp_rsync_base_uri: None,
|
||||
repo_sync_phase: None,
|
||||
repo_terminal_state: None,
|
||||
object_uri: None,
|
||||
sha256: None,
|
||||
object_type: None,
|
||||
result: None,
|
||||
reason: None,
|
||||
counts: None,
|
||||
};
|
||||
fill(&mut event);
|
||||
event
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct CompareViewTaskOutput {
|
||||
pub(super) build_ms: Option<u64>,
|
||||
|
||||
@@ -1452,6 +1452,14 @@ fn synthetic_post_validation_shared() -> PostValidationShared {
|
||||
let mut pp1 = crate::audit::PublicationPointAudit::default();
|
||||
pp1.source = "fresh".to_string();
|
||||
pp1.rrdp_notification_uri = Some("https://example.test/n1.xml".to_string());
|
||||
pp1.manifest_rsync_uri = "rsync://example.test/repo/pp1/manifest.mft".to_string();
|
||||
pp1.objects.push(crate::audit::ObjectAuditEntry {
|
||||
rsync_uri: "rsync://example.test/repo/pp1/a.roa".to_string(),
|
||||
sha256_hex: "11".repeat(32),
|
||||
kind: crate::audit::AuditObjectKind::Roa,
|
||||
result: crate::audit::AuditObjectResult::Ok,
|
||||
detail: None,
|
||||
});
|
||||
let mut pp2 = crate::audit::PublicationPointAudit::default();
|
||||
pp2.source = "fresh".to_string();
|
||||
pp2.rrdp_notification_uri = Some("https://example.test/n1.xml".to_string());
|
||||
@@ -1549,6 +1557,19 @@ fn run_report_task_and_stage_timing_work() {
|
||||
serde_json::from_str(&report_json).expect("parse compact report json");
|
||||
assert_eq!(report["vrps"].as_array().unwrap().len(), 2);
|
||||
assert_eq!(report["aspas"].as_array().unwrap().len(), 1);
|
||||
assert_eq!(report["queryAudit"]["status"].as_str(), Some("complete"));
|
||||
assert!(report["queryAudit"]["eventsCount"].as_u64().unwrap() > 0);
|
||||
let events_path = dir.path().join(
|
||||
report["queryAudit"]["eventsPath"]
|
||||
.as_str()
|
||||
.expect("events path"),
|
||||
);
|
||||
let events = std::fs::read_to_string(events_path).expect("read validation events");
|
||||
assert!(
|
||||
events
|
||||
.lines()
|
||||
.any(|line| line.contains("\"eventType\":\"object\""))
|
||||
);
|
||||
|
||||
let stage_timing = RunStageTiming {
|
||||
validation_ms: 1,
|
||||
@@ -1858,6 +1879,7 @@ fn write_json_writes_report() {
|
||||
downloads: Vec::new(),
|
||||
download_stats: crate::audit::AuditDownloadStats::default(),
|
||||
repo_sync_stats: crate::audit::AuditRepoSyncStats::default(),
|
||||
query_audit: None,
|
||||
};
|
||||
|
||||
let dir = tempfile::tempdir().expect("tmpdir");
|
||||
|
||||
Reference in New Issue
Block a user