use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[derive(Clone)]
pub struct S3FaultInterceptor {
ops: Arc<Vec<String>>,
every: u64,
skip: u64,
seen: Arc<AtomicU64>,
injected: Arc<AtomicU64>,
}
impl fmt::Debug for S3FaultInterceptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "S3FaultInterceptor(ops={:?}, every={}, skip={})", self.ops, self.every, self.skip)
}
}
impl S3FaultInterceptor {
pub fn new(ops: &[&str], every: u64, skip: u64) -> Self {
Self {
ops: Arc::new(ops.iter().map(|s| s.to_string()).collect()),
every,
skip,
seen: Arc::new(AtomicU64::new(0)),
injected: Arc::new(AtomicU64::new(0)),
}
}
pub fn from_env() -> Option<Self> {
let ops = std::env::var("HYPERDIR_FAULT_OPS").ok()?;
let ops: Vec<String> = ops.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect();
if ops.is_empty() {
return None;
}
let num = |k: &str, d: u64| std::env::var(k).ok().and_then(|v| v.parse().ok()).unwrap_or(d);
Some(Self {
ops: Arc::new(ops),
every: num("HYPERDIR_FAULT_EVERY", 1),
skip: num("HYPERDIR_FAULT_SKIP", 0),
seen: Arc::new(AtomicU64::new(0)),
injected: Arc::new(AtomicU64::new(0)),
})
}
pub fn injected(&self) -> u64 {
self.injected.load(Ordering::Relaxed)
}
pub fn seen(&self) -> u64 {
self.seen.load(Ordering::Relaxed)
}
}
impl aws_sdk_s3::config::Intercept for S3FaultInterceptor {
fn name(&self) -> &'static str {
"S3FaultInterceptor"
}
fn modify_before_transmit(
&self,
_ctx: &mut aws_sdk_s3::config::interceptors::BeforeTransmitInterceptorContextMut<'_>,
_: &aws_sdk_s3::config::RuntimeComponents,
cfg: &mut aws_sdk_s3::config::ConfigBag,
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
if self.every == 0 {
return Ok(());
}
let op = cfg
.load::<aws_smithy_runtime_api::client::orchestrator::Metadata>()
.map(|m| m.name().to_string())
.unwrap_or_default();
if !self.ops.iter().any(|o| o == &op) {
return Ok(());
}
let n = self.seen.fetch_add(1, Ordering::Relaxed) + 1;
if n <= self.skip || !(n - self.skip).is_multiple_of(self.every) {
return Ok(());
}
self.injected.fetch_add(1, Ordering::Relaxed);
Err(format!("injected fault: {} request #{}", op, n).into())
}
}