use serde::{Deserialize, Serialize};
use shikumi::TieredConfig;
use crate::error::ConfigError;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SchedulerConfig {
pub strategy: SchedulerStrategyKind,
pub namespace: String,
pub tick_interval_seconds: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SchedulerStrategyKind {
RoundRobin,
BinPack,
Affinity,
}
impl TieredConfig for SchedulerConfig {
fn bare() -> Self {
Self {
strategy: SchedulerStrategyKind::RoundRobin,
namespace: String::new(),
tick_interval_seconds: 0,
}
}
fn prescribed_default() -> Self {
Self {
strategy: SchedulerStrategyKind::RoundRobin,
namespace: String::new(),
tick_interval_seconds: 5,
}
}
fn extend(self, base: &Self) -> Self {
Self {
strategy: self.strategy,
namespace: if self.namespace.is_empty() {
base.namespace.clone()
} else {
self.namespace
},
tick_interval_seconds: if self.tick_interval_seconds == 0 {
base.tick_interval_seconds
} else {
self.tick_interval_seconds
},
}
}
}
impl SchedulerConfig {
pub fn validate(&self) -> Result<(), ConfigError> {
if self.tick_interval_seconds == 0 {
return Err(ConfigError::InvalidField {
field: "scheduler.tick_interval_seconds".into(),
reason: "tick interval must be > 0 (zero would hot-loop)".into(),
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prescribed_default_validates() {
SchedulerConfig::prescribed_default().validate().unwrap();
}
#[test]
fn bare_fails_validation_due_to_zero_tick() {
assert!(SchedulerConfig::bare().validate().is_err());
}
#[test]
fn strategy_kind_serializes_snake_case() {
let json = serde_json::to_string(&SchedulerStrategyKind::BinPack).unwrap();
assert_eq!(json, "\"bin_pack\"");
}
}