use crate::{
InternalError,
cdk::types::BoundedString64,
config::schema::{ScalePool, ScalingConfig},
domain::policy::PolicyError,
};
use thiserror::Error as ThisError;
pub use crate::view::placement::scaling::ScalingWorkerPlanEntry;
#[derive(Debug, ThisError)]
pub enum ScalingPolicyError {
#[error("scaling capability disabled for this canister")]
ScalingDisabled,
#[error("scaling pool '{0}' not found")]
PoolNotFound(String),
}
impl From<ScalingPolicyError> for InternalError {
fn from(err: ScalingPolicyError) -> Self {
PolicyError::from(err).into()
}
}
#[derive(Clone, Debug)]
pub struct ScalingPlan {
pub should_spawn: bool,
pub reason: String,
pub worker_entry: Option<ScalingWorkerPlanEntry>,
}
pub struct ScalingPolicy;
impl ScalingPolicy {
pub(crate) fn plan_create_worker(
pool: &str,
worker_count: u32,
scaling: Option<ScalingConfig>,
) -> Result<ScalingPlan, InternalError> {
let pool_cfg = Self::get_scaling_pool_cfg(pool, scaling)?;
let policy = pool_cfg.policy;
if policy.max_workers > 0 && worker_count >= policy.max_workers {
return Ok(ScalingPlan {
should_spawn: false,
reason: format!(
"pool '{pool}' at max_workers ({}/{})",
worker_count, policy.max_workers
),
worker_entry: None,
});
}
if worker_count < policy.min_workers {
let entry = ScalingWorkerPlanEntry {
pool: BoundedString64::new(pool),
canister_role: pool_cfg.canister_role,
};
return Ok(ScalingPlan {
should_spawn: true,
reason: format!(
"pool '{pool}' below min_workers (current {worker_count}, min {})",
policy.min_workers
),
worker_entry: Some(entry),
});
}
Ok(ScalingPlan {
should_spawn: false,
reason: format!(
"pool '{pool}' within policy bounds (current {worker_count}, min {}, max {})",
policy.min_workers, policy.max_workers
),
worker_entry: None,
})
}
fn get_scaling_pool_cfg(
pool: &str,
scaling: Option<ScalingConfig>,
) -> Result<ScalePool, InternalError> {
let Some(scaling) = scaling else {
return Err(ScalingPolicyError::ScalingDisabled.into());
};
let Some(pool_cfg) = scaling.pools.get(pool) else {
return Err(ScalingPolicyError::PoolNotFound(pool.to_string()).into());
};
Ok(pool_cfg.clone())
}
}