use std::collections::HashMap;
use std::time::Duration;
use anyhow::{Result, bail};
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Tier {
Hot,
Warm,
Cold,
}
impl Tier {
pub fn parse(raw: &str) -> Result<Tier> {
match raw.trim().to_ascii_lowercase().as_str() {
"hot" => Ok(Tier::Hot),
"warm" => Ok(Tier::Warm),
"cold" => Ok(Tier::Cold),
other => bail!("unknown tier '{other}', expected hot, warm or cold"),
}
}
pub fn as_str(self) -> &'static str {
match self {
Tier::Hot => "hot",
Tier::Warm => "warm",
Tier::Cold => "cold",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TierPolicy {
pub default_tier: Tier,
pub shards: HashMap<String, Tier>,
pub warm_idle: Duration,
}
impl Default for TierPolicy {
fn default() -> Self {
Self {
default_tier: Tier::Hot,
shards: HashMap::new(),
warm_idle: Duration::from_secs(3600),
}
}
}
impl TierPolicy {
pub fn tier_of(&self, shard: &str) -> Tier {
if shard == crate::persistence::INTERNAL_SHARD {
return Tier::Hot;
}
self.shards.get(shard).copied().unwrap_or(self.default_tier)
}
pub fn idle_allowance(&self, shard: &str) -> Option<Duration> {
match self.tier_of(shard) {
Tier::Hot => None,
Tier::Warm => Some(self.warm_idle),
Tier::Cold => Some(Duration::ZERO),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn policy() -> TierPolicy {
TierPolicy {
default_tier: Tier::Warm,
shards: HashMap::from([
("myorg".to_string(), Tier::Hot),
("archive".to_string(), Tier::Cold),
]),
warm_idle: Duration::from_secs(3600),
}
}
#[test]
fn tiers_parse_from_configuration() {
assert_eq!(Tier::parse("hot").unwrap(), Tier::Hot);
assert_eq!(Tier::parse(" Warm ").unwrap(), Tier::Warm);
assert_eq!(Tier::parse("COLD").unwrap(), Tier::Cold);
assert!(Tier::parse("lukewarm").is_err());
}
#[test]
fn a_shard_takes_its_configured_tier() {
let p = policy();
assert_eq!(p.tier_of("myorg"), Tier::Hot);
assert_eq!(p.tier_of("archive"), Tier::Cold);
assert_eq!(p.tier_of("anything-else"), Tier::Warm);
}
#[test]
fn the_internal_shard_is_always_hot() {
let mut p = policy();
p.default_tier = Tier::Cold;
p.shards
.insert(crate::persistence::INTERNAL_SHARD.to_string(), Tier::Cold);
assert_eq!(p.tier_of(crate::persistence::INTERNAL_SHARD), Tier::Hot);
assert_eq!(p.idle_allowance(crate::persistence::INTERNAL_SHARD), None);
}
#[test]
fn idle_allowance_follows_the_tier() {
let p = policy();
assert_eq!(p.idle_allowance("myorg"), None);
assert_eq!(p.idle_allowance("other"), Some(Duration::from_secs(3600)));
assert_eq!(p.idle_allowance("archive"), Some(Duration::ZERO));
}
}