use std::time::Duration;
use super::declarations::QueueDeclaration;
use super::taxonomy::QueueServiceReason;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PoolCensus {
pub workers_in_pool: usize,
pub workers_serving_activity: usize,
pub compatible_workers: usize,
pub eligible_compatible_workers: usize,
pub compatible_workers_reachability_lost: usize,
pub last_compatible_poller_age: Option<Duration>,
}
impl PoolCensus {
#[must_use]
pub const fn is_served(&self) -> bool {
self.compatible_workers > 0
}
#[must_use]
pub const fn will_be_served(&self) -> bool {
self.eligible_compatible_workers > 0
|| (self.compatible_workers > 0 && self.compatible_workers_reachability_lost == 0)
}
}
#[must_use]
pub fn classify(declaration: QueueDeclaration, census: &PoolCensus) -> Option<QueueServiceReason> {
if census.eligible_compatible_workers > 0 {
return None;
}
if declaration == QueueDeclaration::NotDeclared {
return Some(QueueServiceReason::NoQueueDeclaration);
}
if census.compatible_workers > 0 {
if census.compatible_workers_reachability_lost > 0 {
return Some(QueueServiceReason::PollersUnreachable);
}
return None;
}
if census.workers_in_pool > 0 {
return Some(QueueServiceReason::PollersIncompatible);
}
Some(QueueServiceReason::NoLivePollers)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_served_address_is_not_classified() {
let census = PoolCensus {
workers_in_pool: 1,
workers_serving_activity: 1,
compatible_workers: 1,
eligible_compatible_workers: 1,
compatible_workers_reachability_lost: 0,
last_compatible_poller_age: Some(Duration::ZERO),
};
assert_eq!(classify(QueueDeclaration::Declared, &census), None);
assert_eq!(classify(QueueDeclaration::NotDeclared, &census), None);
}
#[test]
fn an_undeclared_queue_is_structural() {
assert_eq!(
classify(QueueDeclaration::NotDeclared, &PoolCensus::default()),
Some(QueueServiceReason::NoQueueDeclaration)
);
}
#[test]
fn an_empty_pool_on_a_declared_queue_has_no_live_pollers() {
assert_eq!(
classify(QueueDeclaration::Declared, &PoolCensus::default()),
Some(QueueServiceReason::NoLivePollers)
);
}
#[test]
fn an_unknowable_declaration_never_produces_a_structural_refusal() {
assert_eq!(
classify(QueueDeclaration::Unknown, &PoolCensus::default()),
Some(QueueServiceReason::NoLivePollers)
);
}
fn pool(compatible: usize, eligible: usize, lost: usize) -> PoolCensus {
PoolCensus {
workers_in_pool: compatible,
workers_serving_activity: compatible,
compatible_workers: compatible,
eligible_compatible_workers: eligible,
compatible_workers_reachability_lost: lost,
last_compatible_poller_age: Some(Duration::ZERO),
}
}
#[test]
fn a_pool_excluded_only_by_an_opening_probation_is_not_reported() {
assert_eq!(classify(QueueDeclaration::Declared, &pool(2, 0, 0)), None);
}
#[test]
fn a_pool_that_lost_reachability_is_reported() {
assert_eq!(
classify(QueueDeclaration::Declared, &pool(2, 0, 2)),
Some(QueueServiceReason::PollersUnreachable)
);
}
#[test]
fn the_exclusion_cause_is_what_decides() {
let probation = pool(2, 0, 0);
let unreachable = pool(2, 0, 2);
assert_eq!(
probation.workers_in_pool, unreachable.workers_in_pool,
"precondition: the two pools differ ONLY in the exclusion cause"
);
assert_eq!(probation.compatible_workers, unreachable.compatible_workers);
assert_eq!(
probation.eligible_compatible_workers,
unreachable.eligible_compatible_workers
);
assert_ne!(
classify(QueueDeclaration::Declared, &probation),
classify(QueueDeclaration::Declared, &unreachable),
"a pool serving its probation and a pool that lost reachability must not get the \
same answer: one clears itself in seconds and the other never does, and the \
operator's remedies are different"
);
}
#[test]
fn a_genuinely_empty_pool_is_still_reported_as_having_no_pollers() {
assert_eq!(
classify(QueueDeclaration::Declared, &PoolCensus::default()),
Some(QueueServiceReason::NoLivePollers)
);
}
#[test]
fn workers_that_do_not_serve_the_activity_are_still_incompatible_not_unreachable() {
let census = PoolCensus {
workers_in_pool: 3,
workers_serving_activity: 0,
compatible_workers: 0,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
last_compatible_poller_age: None,
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersIncompatible)
);
}
#[test]
fn one_eligible_worker_means_the_address_is_served() {
assert_eq!(classify(QueueDeclaration::Declared, &pool(3, 1, 2)), None);
}
#[test]
fn pollers_that_do_not_cover_the_activity_are_incompatible() {
let census = PoolCensus {
workers_in_pool: 2,
workers_serving_activity: 0,
compatible_workers: 0,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
last_compatible_poller_age: None,
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersIncompatible)
);
}
#[test]
fn pollers_off_the_pinned_node_are_incompatible() {
let census = PoolCensus {
workers_in_pool: 1,
workers_serving_activity: 1,
compatible_workers: 0,
eligible_compatible_workers: 0,
compatible_workers_reachability_lost: 0,
last_compatible_poller_age: Some(Duration::from_secs(9)),
};
assert_eq!(
classify(QueueDeclaration::Declared, &census),
Some(QueueServiceReason::PollersIncompatible)
);
}
}