1use crate::{
4 cdk::types::Cycles,
5 ids::{ComponentInstanceId, FleetSubnetCanisterPoolConfig},
6};
7use candid::{CandidType, Principal};
8use serde::{Deserialize, Serialize};
9
10#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
12pub struct CanisterPoolClaim {
13 pub component: ComponentInstanceId,
14 pub operation_id: [u8; 32],
15}
16
17#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
19pub enum CanisterPoolRecycleReset {
20 Pending,
21 Ready,
22 Failed { reason: String },
23}
24
25#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
27pub enum CanisterPoolAssetOrigin {
28 InfrastructureStore,
29 Created,
30 Imported,
31 Recycled,
32}
33
34#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
36pub enum CanisterPoolAssetStatus {
37 Store,
38 StoreDeletionPending {
39 operation_id: [u8; 32],
40 },
41 PendingReset,
42 Ready,
43 Claimed {
44 claim: CanisterPoolClaim,
45 },
46 Workload {
47 claim: CanisterPoolClaim,
48 },
49 Recycling {
50 claim: CanisterPoolClaim,
51 reset: CanisterPoolRecycleReset,
52 },
53 HandingOff {
54 recipient: Principal,
55 },
56 Failed {
57 reason: String,
58 },
59}
60
61#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
63pub struct CanisterPoolAsset {
64 pub canister_id: Principal,
65 pub cycles: Cycles,
66 pub origin: CanisterPoolAssetOrigin,
67 pub status: CanisterPoolAssetStatus,
68 pub added_at_ns: u64,
69 pub updated_at_ns: u64,
70}
71
72#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
74pub struct CanisterPoolHandoff {
75 pub canister_id: Principal,
76 pub recipient: Principal,
77 pub prepared_at_ns: u64,
78}
79
80#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
82pub enum CanisterPoolCreationFailure {
83 UnresolvedAfterLedgerWindow,
84 LedgerCreationFailed,
85 LedgerRejected,
86}
87
88#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
90pub enum CanisterPoolCreationProgress {
91 Intent {
92 uncertain_result: bool,
93 },
94 Created {
95 block_index: u64,
96 canister_id: Principal,
97 },
98 Blocked {
99 failure: CanisterPoolCreationFailure,
100 },
101}
102
103#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
105pub struct CanisterPoolCreation {
106 pub operation_id: [u8; 32],
107 pub cycles_ledger: Principal,
108 pub placement_subnet: Principal,
109 pub root: Principal,
110 pub ledger_amount: Cycles,
111 pub created_at_time_ns: u64,
112 pub progress: CanisterPoolCreationProgress,
113}
114
115#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
117pub struct CanisterPoolStatusRequest {
118 pub start_after: Option<Principal>,
119 pub limit: u16,
120}
121
122#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
124pub struct CanisterPoolResponse {
125 pub config: FleetSubnetCanisterPoolConfig,
126 pub tracked: u32,
127 pub store: u32,
128 pub store_deletion_pending: u32,
129 pub pooled: u32,
130 pub workload: u32,
131 pub surplus: u32,
132 pub ready: u32,
133 pub pending_reset: u32,
134 pub claimed: u32,
135 pub recycling: u32,
136 pub handing_off: u32,
137 pub failed: u32,
138 pub completed_handoffs: u64,
139 pub pending_creation: Option<CanisterPoolCreation>,
140 pub pending_handoff: Option<CanisterPoolHandoff>,
141 pub entries: Vec<CanisterPoolAsset>,
142 pub next_start_after: Option<Principal>,
143}
144
145#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
147pub enum PoolAdminCommand {
148 Maintain,
149 RetryRefill,
150 Import {
151 canister_id: Principal,
152 },
153 RetryReset {
154 canister_id: Principal,
155 },
156 Handoff {
157 canister_id: Principal,
158 recipient: Principal,
159 },
160}
161
162#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
164pub enum PoolAdminResponse {
165 Maintained,
166 MaintenancePaused {
167 reason: String,
168 },
169 Created {
170 canister_id: Principal,
171 },
172 RefillWaitingForCycles {
173 available: Cycles,
174 creation_amount: Cycles,
175 },
176 RefillPending {
177 operation_id: [u8; 32],
178 uncertain_result: bool,
179 },
180 RefillBlocked {
181 operation_id: [u8; 32],
182 failure: CanisterPoolCreationFailure,
183 },
184 RefillRetryScheduled {
185 previous_operation_id: [u8; 32],
186 },
187 Imported {
188 canister_id: Principal,
189 },
190 ResetQueued {
191 canister_id: Principal,
192 },
193 ResetReady {
194 canister_id: Principal,
195 },
196 HandedOff {
197 canister_id: Principal,
198 recipient: Principal,
199 },
200 ResetFailed {
201 canister_id: Principal,
202 reason: String,
203 },
204}
205
206#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn pool_status_and_admin_contracts_round_trip_through_candid() {
216 let canister_id = Principal::from_slice(&[7; 29]);
217 let response = CanisterPoolResponse {
218 config: FleetSubnetCanisterPoolConfig {
219 minimum_size: 3,
220 maximum_size: 10,
221 canister_cycles: Cycles::new(5_000_000_000_000),
222 },
223 tracked: 1,
224 store: 0,
225 store_deletion_pending: 0,
226 pooled: 1,
227 workload: 0,
228 surplus: 0,
229 ready: 0,
230 pending_reset: 0,
231 claimed: 0,
232 recycling: 0,
233 handing_off: 0,
234 failed: 1,
235 completed_handoffs: 0,
236 pending_creation: Some(CanisterPoolCreation {
237 operation_id: [8; 32],
238 cycles_ledger: Principal::from_slice(&[6; 29]),
239 placement_subnet: Principal::from_slice(&[5; 29]),
240 root: Principal::from_slice(&[4; 29]),
241 ledger_amount: Cycles::new(5_500_000_000_000),
242 created_at_time_ns: 12,
243 progress: CanisterPoolCreationProgress::Blocked {
244 failure: CanisterPoolCreationFailure::LedgerCreationFailed,
245 },
246 }),
247 pending_handoff: None,
248 entries: vec![CanisterPoolAsset {
249 canister_id,
250 cycles: Cycles::new(4_000_000_000_000),
251 origin: CanisterPoolAssetOrigin::Recycled,
252 status: CanisterPoolAssetStatus::Failed {
253 reason: "below configured cycles".to_string(),
254 },
255 added_at_ns: 10,
256 updated_at_ns: 11,
257 }],
258 next_start_after: None,
259 };
260 let bytes = candid::encode_one(&response).expect("encode pool status");
261 assert_eq!(
262 candid::decode_one::<CanisterPoolResponse>(&bytes).expect("decode pool status"),
263 response,
264 );
265
266 let command = PoolAdminCommand::Import { canister_id };
267 let bytes = candid::encode_one(&command).expect("encode pool command");
268 assert_eq!(
269 candid::decode_one::<PoolAdminCommand>(&bytes).expect("decode pool command"),
270 command,
271 );
272
273 let command = PoolAdminCommand::RetryRefill;
274 let bytes = candid::encode_one(&command).expect("encode refill retry command");
275 assert_eq!(
276 candid::decode_one::<PoolAdminCommand>(&bytes).expect("decode refill retry command"),
277 command,
278 );
279 }
280}