use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
use std::time::{Duration, Instant};
use crate::telemetry::metrics;
const REPORT_EVERY: Duration = Duration::from_secs(60);
const REMEMBERED: usize = 1024;
static REPORTED: LazyLock<Mutex<Reported>> = LazyLock::new(|| Mutex::new(Reported::default()));
pub(crate) fn denied(condition: Unenforceable, store: &'static str, namespace: &str) -> bool {
metrics::record_policy_unenforceable_denial(condition.label(), store);
let mut reported = match REPORTED.lock() {
Ok(reported) => reported,
Err(poisoned) => poisoned.into_inner(),
};
reported.should_report(condition, store, namespace, Instant::now())
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) const STORES: &[&str] = &[BUDGET_REDIS, BUDGET_POSTGRES, RATE_LIMIT_REDIS];
pub(crate) const BUDGET_REDIS: &str = "budget:redis";
pub(crate) const BUDGET_POSTGRES: &str = "budget:postgres";
pub(crate) const RATE_LIMIT_REDIS: &str = "rate_limit:redis";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum Unenforceable {
Ungoverned,
Layout,
}
impl Unenforceable {
const fn label(self) -> &'static str {
match self {
Self::Ungoverned => "ungoverned",
Self::Layout => "layout",
}
}
}
#[derive(Debug, Default)]
struct Reported(HashMap<(Unenforceable, &'static str, String), Instant>);
impl Reported {
fn should_report(
&mut self,
condition: Unenforceable,
store: &'static str,
namespace: &str,
now: Instant,
) -> bool {
if let Some(last) = self.0.get_mut(&(condition, store, namespace.to_owned())) {
if now.duration_since(*last) < REPORT_EVERY {
return false;
}
*last = now;
return true;
}
if self.0.len() >= REMEMBERED {
self.0.clear();
}
self.0.insert((condition, store, namespace.to_owned()), now);
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_namespace_that_stays_ungoverned_is_explained_once_per_interval() {
let mut reported = Reported::default();
let start = Instant::now();
assert!(reported.should_report(Unenforceable::Ungoverned, "redis", "alpha", start));
assert!(!reported.should_report(Unenforceable::Ungoverned, "redis", "alpha", start));
assert!(!reported.should_report(
Unenforceable::Ungoverned,
"redis",
"alpha",
start + REPORT_EVERY - Duration::from_millis(1)
));
assert!(reported.should_report(
Unenforceable::Ungoverned,
"redis",
"alpha",
start + REPORT_EVERY
));
}
#[test]
fn each_condition_store_and_namespace_is_explained_on_its_own() {
let mut reported = Reported::default();
let start = Instant::now();
assert!(reported.should_report(Unenforceable::Ungoverned, "budget:redis", "alpha", start));
assert!(reported.should_report(Unenforceable::Ungoverned, "budget:redis", "beta", start));
assert!(reported.should_report(
Unenforceable::Ungoverned,
"budget:postgres",
"alpha",
start
));
assert!(reported.should_report(
Unenforceable::Ungoverned,
"rate_limit:redis",
"alpha",
start
));
assert!(reported.should_report(Unenforceable::Layout, "budget:redis", "alpha", start));
}
#[test]
fn every_denial_is_counted_under_a_catalogued_condition() {
for condition in [Unenforceable::Ungoverned, Unenforceable::Layout] {
for store in STORES {
crate::telemetry::catalog::validate_label_value(
"axond.policy.unenforceable_denials",
"axond.policy.condition",
condition.label(),
)
.expect("the condition is catalogued");
crate::telemetry::catalog::validate_label_value(
"axond.policy.unenforceable_denials",
"axond.policy.store",
store,
)
.expect("the store is catalogued");
denied(condition, store, "counted");
assert!(!denied(condition, store, "counted"));
}
}
}
#[test]
fn the_record_cannot_grow_without_bound() {
let mut reported = Reported::default();
let start = Instant::now();
for namespace in 0..REMEMBERED {
assert!(reported.should_report(
Unenforceable::Ungoverned,
"redis",
&namespace.to_string(),
start
));
}
assert_eq!(reported.0.len(), REMEMBERED);
assert!(reported.should_report(Unenforceable::Ungoverned, "redis", "one-too-many", start));
assert_eq!(reported.0.len(), 1);
}
}