use async_trait::async_trait;
use slog::{info, Logger};
use std::sync::{Arc, RwLock};
use uuid::Uuid;
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum MithrilEvent {
SnapshotDownloadStarted {
digest: String,
download_id: String,
size: u64,
},
SnapshotDownloadProgress {
download_id: String,
downloaded_bytes: u64,
size: u64,
},
SnapshotDownloadCompleted {
download_id: String,
},
CertificateChainValidationStarted {
certificate_chain_validation_id: String,
},
CertificateValidated {
certificate_chain_validation_id: String,
certificate_hash: String,
},
CertificateChainValidated {
certificate_chain_validation_id: String,
},
}
impl MithrilEvent {
pub fn new_snapshot_download_id() -> String {
Uuid::new_v4().to_string()
}
pub fn new_certificate_chain_validation_id() -> String {
Uuid::new_v4().to_string()
}
#[cfg(test)]
pub(crate) fn event_id(&self) -> &str {
match self {
MithrilEvent::SnapshotDownloadStarted { download_id, .. } => download_id,
MithrilEvent::SnapshotDownloadProgress { download_id, .. } => download_id,
MithrilEvent::SnapshotDownloadCompleted { download_id } => download_id,
MithrilEvent::CertificateChainValidationStarted {
certificate_chain_validation_id,
} => certificate_chain_validation_id,
MithrilEvent::CertificateValidated {
certificate_chain_validation_id,
..
} => certificate_chain_validation_id,
MithrilEvent::CertificateChainValidated {
certificate_chain_validation_id,
} => certificate_chain_validation_id,
}
}
}
#[derive(Clone)]
pub struct FeedbackSender {
receivers: Vec<Arc<dyn FeedbackReceiver>>,
}
impl FeedbackSender {
pub fn new(receivers: &[Arc<dyn FeedbackReceiver>]) -> FeedbackSender {
Self {
receivers: receivers.to_vec(),
}
}
pub async fn send_event(&self, event: MithrilEvent) {
for receiver in &self.receivers {
receiver.handle_event(event.clone()).await;
}
}
}
#[async_trait]
pub trait FeedbackReceiver: Sync + Send {
async fn handle_event(&self, event: MithrilEvent);
}
pub struct SlogFeedbackReceiver {
logger: Logger,
}
impl SlogFeedbackReceiver {
pub fn new(logger: Logger) -> SlogFeedbackReceiver {
Self { logger }
}
}
#[async_trait]
impl FeedbackReceiver for SlogFeedbackReceiver {
async fn handle_event(&self, event: MithrilEvent) {
match event {
MithrilEvent::SnapshotDownloadStarted {
digest,
download_id,
size,
} => {
info!(
self.logger,
"Snapshot download started";
"size" => size,
"digest" => digest,
"download_id" => download_id,
);
}
MithrilEvent::SnapshotDownloadProgress {
download_id,
downloaded_bytes,
size,
} => {
info!(
self.logger,
"Snapshot download in progress ...";
"downloaded bytes" => downloaded_bytes,
"size" => size,
"download_id" => download_id,
);
}
MithrilEvent::SnapshotDownloadCompleted { download_id } => {
info!(self.logger, "Snapshot download completed"; "download_id" => download_id);
}
MithrilEvent::CertificateChainValidationStarted {
certificate_chain_validation_id,
} => {
info!(
self.logger,
"Certificate chain validation started";
"certificate_chain_validation_id" => certificate_chain_validation_id,
);
}
MithrilEvent::CertificateValidated {
certificate_hash,
certificate_chain_validation_id,
} => {
info!(
self.logger,
"Certificate validated";
"certificate_hash" => certificate_hash,
"certificate_chain_validation_id" => certificate_chain_validation_id,
);
}
MithrilEvent::CertificateChainValidated {
certificate_chain_validation_id,
} => {
info!(
self.logger,
"Certificate chain validated";
"certificate_chain_validation_id" => certificate_chain_validation_id,
);
}
};
}
}
pub struct StackFeedbackReceiver {
stacked_events: RwLock<Vec<MithrilEvent>>,
}
impl StackFeedbackReceiver {
pub fn new() -> StackFeedbackReceiver {
Self {
stacked_events: RwLock::new(vec![]),
}
}
pub fn stacked_events(&self) -> Vec<MithrilEvent> {
let events = self.stacked_events.read().unwrap();
events.clone()
}
}
impl Default for StackFeedbackReceiver {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl FeedbackReceiver for StackFeedbackReceiver {
async fn handle_event(&self, event: MithrilEvent) {
let mut events = self.stacked_events.write().unwrap();
events.push(event);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::feedback::MithrilEvent::{SnapshotDownloadCompleted, SnapshotDownloadStarted};
use std::time::Duration;
use tokio::task::JoinSet;
#[tokio::test]
async fn send_event_same_thread() {
let receiver = Arc::new(StackFeedbackReceiver::new());
let sender = FeedbackSender::new(&[receiver.clone()]);
sender
.send_event(SnapshotDownloadStarted {
digest: "digest".to_string(),
download_id: "download_id".to_string(),
size: 10,
})
.await;
sender
.send_event(SnapshotDownloadCompleted {
download_id: "download_id".to_string(),
})
.await;
assert_eq!(
receiver.stacked_events(),
vec![
SnapshotDownloadStarted {
digest: "digest".to_string(),
download_id: "download_id".to_string(),
size: 10
},
SnapshotDownloadCompleted {
download_id: "download_id".to_string()
}
]
);
}
#[tokio::test]
async fn send_event_multiple_thread() {
let receiver = Arc::new(StackFeedbackReceiver::new());
let sender = FeedbackSender::new(&[
receiver.clone(),
Arc::new(SlogFeedbackReceiver::new(crate::test_utils::test_logger())),
]);
let sender2 = sender.clone();
let mut join_set = JoinSet::new();
join_set.spawn(async move {
sender
.send_event(SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1,
})
.await;
tokio::time::sleep(Duration::from_millis(2)).await;
sender
.send_event(SnapshotDownloadCompleted {
download_id: "download3".to_string(),
})
.await;
sender
.send_event(SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2,
})
.await;
});
join_set.spawn(async move {
sender2
.send_event(SnapshotDownloadCompleted {
download_id: "download1".to_string(),
})
.await;
sender2
.send_event(SnapshotDownloadStarted {
digest: "digest3".to_string(),
download_id: "download3".to_string(),
size: 3,
})
.await;
tokio::time::sleep(Duration::from_millis(5)).await;
sender2
.send_event(SnapshotDownloadCompleted {
download_id: "download2".to_string(),
})
.await;
});
while let Some(res) = join_set.join_next().await {
res.unwrap();
}
assert_eq!(
receiver.stacked_events(),
vec![
SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1
},
SnapshotDownloadCompleted {
download_id: "download1".to_string()
},
SnapshotDownloadStarted {
digest: "digest3".to_string(),
download_id: "download3".to_string(),
size: 3
},
SnapshotDownloadCompleted {
download_id: "download3".to_string()
},
SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2
},
SnapshotDownloadCompleted {
download_id: "download2".to_string()
},
]
);
}
#[tokio::test]
async fn send_event_in_one_thread_and_receive_in_another_thread() {
let receiver = Arc::new(StackFeedbackReceiver::new());
let receiver2 = receiver.clone();
let sender = FeedbackSender::new(&[receiver.clone()]);
let mut join_set = JoinSet::new();
join_set.spawn(async move {
sender
.send_event(SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1,
})
.await;
tokio::time::sleep(Duration::from_millis(10)).await;
sender
.send_event(SnapshotDownloadCompleted {
download_id: "download1".to_string(),
})
.await;
sender
.send_event(SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2,
})
.await;
tokio::time::sleep(Duration::from_millis(10)).await;
sender
.send_event(SnapshotDownloadCompleted {
download_id: "download2".to_string(),
})
.await;
sender
.send_event(SnapshotDownloadStarted {
digest: "digest3".to_string(),
download_id: "download3".to_string(),
size: 3,
})
.await;
tokio::time::sleep(Duration::from_millis(10)).await;
sender
.send_event(SnapshotDownloadCompleted {
download_id: "download3".to_string(),
})
.await;
});
join_set.spawn(async move {
tokio::time::sleep(Duration::from_millis(3)).await;
assert_eq!(
receiver2.stacked_events(),
vec![SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1
},]
);
tokio::time::sleep(Duration::from_millis(10)).await;
assert_eq!(
receiver2.stacked_events(),
vec![
SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1
},
SnapshotDownloadCompleted {
download_id: "download1".to_string()
},
SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2
},
]
);
tokio::time::sleep(Duration::from_millis(10)).await;
assert_eq!(
receiver2.stacked_events(),
vec![
SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1
},
SnapshotDownloadCompleted {
download_id: "download1".to_string()
},
SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2
},
SnapshotDownloadCompleted {
download_id: "download2".to_string()
},
SnapshotDownloadStarted {
digest: "digest3".to_string(),
download_id: "download3".to_string(),
size: 3
},
]
);
});
while let Some(res) = join_set.join_next().await {
res.unwrap();
}
assert_eq!(
receiver.stacked_events(),
vec![
SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1
},
SnapshotDownloadCompleted {
download_id: "download1".to_string()
},
SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2
},
SnapshotDownloadCompleted {
download_id: "download2".to_string()
},
SnapshotDownloadStarted {
digest: "digest3".to_string(),
download_id: "download3".to_string(),
size: 3
},
SnapshotDownloadCompleted {
download_id: "download3".to_string()
},
]
);
}
}