///|
pub(all) enum AdmissionMode {
AllOf
AnyOf
} derive(Eq, Debug)
///|
pub(all) struct EngineRule {
rule_name : String
kind : LimitKind
token_bucket : TokenBucket
fixed_window : FixedWindow
sliding_log : SlidingLog
quota_window : QuotaWindow
} derive(Debug)
///|
pub fn EngineRule::token_bucket(
name : String,
capacity : Int,
refill_tokens : Int,
refill_period_ms : Int,
) -> EngineRule {
EngineRule::{
rule_name: name,
kind: TokenBucket,
token_bucket: TokenBucket::new(capacity, refill_tokens, refill_period_ms),
fixed_window: FixedWindow::new(1, 1000),
sliding_log: SlidingLog::new(1, 1000),
quota_window: QuotaWindow::new(1, 1000),
}
}
///|
pub fn EngineRule::fixed_window(
name : String,
limit : Int,
window_ms : Int,
) -> EngineRule {
EngineRule::{
rule_name: name,
kind: FixedWindow,
token_bucket: TokenBucket::new(1, 1, 1000),
fixed_window: FixedWindow::new(limit, window_ms),
sliding_log: SlidingLog::new(1, 1000),
quota_window: QuotaWindow::new(1, 1000),
}
}
///|
pub fn EngineRule::sliding_log(
name : String,
limit : Int,
window_ms : Int,
) -> EngineRule {
EngineRule::{
rule_name: name,
kind: SlidingLog,
token_bucket: TokenBucket::new(1, 1, 1000),
fixed_window: FixedWindow::new(1, 1000),
sliding_log: SlidingLog::new(limit, window_ms),
quota_window: QuotaWindow::new(1, 1000),
}
}
///|
pub fn EngineRule::quota(
name : String,
quota : Int,
period_ms : Int,
) -> EngineRule {
EngineRule::{
rule_name: name,
kind: GcraSpec,
token_bucket: TokenBucket::new(1, 1, 1000),
fixed_window: FixedWindow::new(1, 1000),
sliding_log: SlidingLog::new(1, 1000),
quota_window: QuotaWindow::new(quota, period_ms),
}
}
///|
pub fn EngineRule::name(self : EngineRule) -> String {
self.rule_name
}
///|
pub fn EngineRule::allow_at(
self : EngineRule,
now_ms : Int,
cost? : Int = 1,
) -> Decision {
match self.kind {
TokenBucket => self.token_bucket.allow_at(now_ms, cost~)
FixedWindow => self.fixed_window.allow_at(now_ms, cost~)
SlidingLog => self.sliding_log.allow_at(now_ms, cost~)
GcraSpec => self.quota_window.allow_at(now_ms, cost~)
}
}
///|
pub fn EngineRule::describe(self : EngineRule) -> String {
"\{self.rule_name}:\{self.kind_name()}"
}
///|
pub fn EngineRule::kind_name(self : EngineRule) -> String {
match self.kind {
TokenBucket => "token_bucket"
FixedWindow => "fixed_window"
SlidingLog => "sliding_log"
GcraSpec => "quota"
}
}
///|
pub(all) struct PolicyEngine {
mode : AdmissionMode
mut rules : Array[EngineRule]
} derive(Debug)
///|
pub fn PolicyEngine::all() -> PolicyEngine {
PolicyEngine::{ mode: AllOf, rules: [] }
}
///|
pub fn PolicyEngine::any() -> PolicyEngine {
PolicyEngine::{ mode: AnyOf, rules: [] }
}
///|
pub fn PolicyEngine::add_rule(self : PolicyEngine, rule : EngineRule) -> Unit {
self.rules.push(rule)
}
///|
pub fn PolicyEngine::add_token_bucket(
self : PolicyEngine,
name : String,
capacity : Int,
refill_tokens : Int,
refill_period_ms : Int,
) -> Unit {
self.add_rule(
EngineRule::token_bucket(name, capacity, refill_tokens, refill_period_ms),
)
}
///|
pub fn PolicyEngine::add_fixed_window(
self : PolicyEngine,
name : String,
limit : Int,
window_ms : Int,
) -> Unit {
self.add_rule(EngineRule::fixed_window(name, limit, window_ms))
}
///|
pub fn PolicyEngine::add_sliding_log(
self : PolicyEngine,
name : String,
limit : Int,
window_ms : Int,
) -> Unit {
self.add_rule(EngineRule::sliding_log(name, limit, window_ms))
}
///|
pub fn PolicyEngine::add_quota(
self : PolicyEngine,
name : String,
quota : Int,
period_ms : Int,
) -> Unit {
self.add_rule(EngineRule::quota(name, quota, period_ms))
}
///|
pub fn PolicyEngine::len(self : PolicyEngine) -> Int {
self.rules.length()
}
///|
pub fn PolicyEngine::allow_at(
self : PolicyEngine,
now_ms : Int,
cost? : Int = 1,
) -> Decision {
if self.rules.length() == 0 {
return Allowed({ remaining: 0, reset_after_ms: 0 })
}
match self.mode {
AllOf => {
for rule in self.rules {
let decision = rule.allow_at(now_ms, cost~)
if !decision.is_allowed() {
return decision
}
}
Allowed({ remaining: 0, reset_after_ms: 0 })
}
AnyOf => {
let mut best_retry = 0
for rule in self.rules {
let decision = rule.allow_at(now_ms, cost~)
if decision.is_allowed() {
return decision
}
if decision.retry_after_ms() > best_retry {
best_retry = decision.retry_after_ms()
}
}
Rejected({
retry_after_ms: best_retry,
reason: "all policy alternatives rejected",
})
}
}
}
///|
pub fn PolicyEngine::describe(self : PolicyEngine) -> String {
let mode = match self.mode {
AllOf => "all"
AnyOf => "any"
}
let mut out = "mode=\{mode} rules=\{self.rules.length()}"
for rule in self.rules {
out = out + " " + rule.describe()
}
out
}
///|
pub fn PolicyEngine::simulate(
self : PolicyEngine,
workload : Workload,
) -> SimulationReport {
let report = SimulationReport::new()
for event in workload.events {
report.record(event.at_ms, self.allow_at(event.at_ms, cost=event.cost))
}
report
}