canic_core/workflow/placement/
scaling.rs

1//! Scaling workflow.
2//!
3//! This module performs scaling side effects:
4//! - evaluates scaling policy
5//! - creates canisters
6//! - mutates the scaling registry
7//!
8//! All async and IC interactions live here.
9
10use crate::{
11    Error, ThisError,
12    cdk::utils::time::now_secs,
13    dto::rpc::CreateCanisterParent,
14    ops::rpc::create_canister_request,
15    ops::{adapter::placement::worker_entry_from_view, storage::scaling::ScalingRegistryOps},
16    policy::placement::scaling::{ScalingPlan, ScalingPolicy},
17};
18use candid::Principal;
19
20///
21/// ScalingWorkflowError
22/// Errors raised during scaling execution
23///
24
25#[derive(Debug, ThisError)]
26pub enum ScalingWorkflowError {
27    #[error("scaling plan rejected: {0}")]
28    PlanRejected(String),
29}
30
31impl From<ScalingWorkflowError> for Error {
32    fn from(err: ScalingWorkflowError) -> Self {
33        Self::WorkflowError(err.to_string())
34    }
35}
36
37///
38/// ScalingWorkflow
39///
40pub struct ScalingWorkflow;
41
42impl ScalingWorkflow {
43    /// Create a new worker canister in the given pool, if policy allows.
44    pub async fn create_worker(pool: &str) -> Result<Principal, Error> {
45        // 1. Evaluate policy
46        let ScalingPlan {
47            should_spawn,
48            reason,
49            worker_entry,
50        } = ScalingPolicy::plan_create_worker(pool, now_secs())?;
51
52        if !should_spawn {
53            return Err(ScalingWorkflowError::PlanRejected(reason))?;
54        }
55
56        let entry_view = worker_entry.ok_or_else(|| {
57            ScalingWorkflowError::PlanRejected("worker entry missing for spawn plan".to_string())
58        })?;
59
60        let role = entry_view.canister_role.clone();
61
62        // 3. Create the canister
63        let pid = create_canister_request::<()>(&role, CreateCanisterParent::ThisCanister, None)
64            .await?
65            .new_canister_pid;
66
67        // 4. Register in memory
68        let entry = worker_entry_from_view(entry_view);
69        ScalingRegistryOps::upsert(pid, entry);
70
71        Ok(pid)
72    }
73}