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, Copy, Debug, Deserialize, Eq, PartialEq)]
124pub struct PoolCanisterRequest {
125 pub canister_id: Principal,
126}
127
128#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
130pub struct PoolHandoffRequest {
131 pub canister_id: Principal,
132 pub recipient: Principal,
133}
134
135#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
137pub struct CanisterPoolResponse {
138 pub config: FleetSubnetCanisterPoolConfig,
139 pub tracked: u32,
140 pub store: u32,
141 pub store_deletion_pending: u32,
142 pub pooled: u32,
143 pub workload: u32,
144 pub surplus: u32,
145 pub ready: u32,
146 pub pending_reset: u32,
147 pub claimed: u32,
148 pub recycling: u32,
149 pub handing_off: u32,
150 pub failed: u32,
151 pub completed_handoffs: u64,
152 pub pending_creation: Option<CanisterPoolCreation>,
153 pub pending_handoff: Option<CanisterPoolHandoff>,
154 pub entries: Vec<CanisterPoolAsset>,
155 pub next_start_after: Option<Principal>,
156}
157
158#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
160pub enum PoolAdminCommand {
161 Maintain,
162 RetryRefill,
163 Import {
164 canister_id: Principal,
165 },
166 RetryReset {
167 canister_id: Principal,
168 },
169 Handoff {
170 canister_id: Principal,
171 recipient: Principal,
172 },
173}
174
175#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
177pub enum PoolAdminResponse {
178 Maintained,
179 MaintenancePaused {
180 reason: String,
181 },
182 Created {
183 canister_id: Principal,
184 },
185 RefillWaitingForCycles {
186 available: Cycles,
187 creation_amount: Cycles,
188 },
189 RefillPending {
190 operation_id: [u8; 32],
191 uncertain_result: bool,
192 },
193 RefillBlocked {
194 operation_id: [u8; 32],
195 failure: CanisterPoolCreationFailure,
196 },
197 RefillRetryScheduled {
198 previous_operation_id: [u8; 32],
199 },
200 Imported {
201 canister_id: Principal,
202 },
203 ResetQueued {
204 canister_id: Principal,
205 },
206 ResetReady {
207 canister_id: Principal,
208 },
209 HandedOff {
210 canister_id: Principal,
211 recipient: Principal,
212 },
213 ResetFailed {
214 canister_id: Principal,
215 reason: String,
216 },
217}
218
219#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
221pub enum PoolMaintenanceResponse {
222 Maintained,
223 MaintenancePaused {
224 reason: String,
225 },
226 Created {
227 canister_id: Principal,
228 },
229 RefillWaitingForCycles {
230 available: Cycles,
231 creation_amount: Cycles,
232 },
233 RefillPending {
234 operation_id: [u8; 32],
235 uncertain_result: bool,
236 },
237 RefillBlocked {
238 operation_id: [u8; 32],
239 failure: CanisterPoolCreationFailure,
240 },
241 ResetReady {
242 canister_id: Principal,
243 },
244 ResetFailed {
245 canister_id: Principal,
246 reason: String,
247 },
248}
249
250#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
252pub enum PoolImportResponse {
253 Imported {
254 canister_id: Principal,
255 },
256 ResetFailed {
257 canister_id: Principal,
258 reason: String,
259 },
260}
261
262#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
264pub struct PoolRefillRetryResponse {
265 pub previous_operation_id: [u8; 32],
266}
267
268#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
270pub struct PoolResetRetryResponse {
271 pub canister_id: Principal,
272}
273
274#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
276pub struct PoolHandoffResponse {
277 pub canister_id: Principal,
278 pub recipient: Principal,
279}
280
281#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn pool_status_and_admin_contracts_round_trip_through_candid() {
291 let canister_id = Principal::from_slice(&[7; 29]);
292 let response = CanisterPoolResponse {
293 config: FleetSubnetCanisterPoolConfig {
294 minimum_size: 3,
295 maximum_size: 10,
296 canister_cycles: Cycles::new(5_000_000_000_000),
297 },
298 tracked: 1,
299 store: 0,
300 store_deletion_pending: 0,
301 pooled: 1,
302 workload: 0,
303 surplus: 0,
304 ready: 0,
305 pending_reset: 0,
306 claimed: 0,
307 recycling: 0,
308 handing_off: 0,
309 failed: 1,
310 completed_handoffs: 0,
311 pending_creation: Some(CanisterPoolCreation {
312 operation_id: [8; 32],
313 cycles_ledger: Principal::from_slice(&[6; 29]),
314 placement_subnet: Principal::from_slice(&[5; 29]),
315 root: Principal::from_slice(&[4; 29]),
316 ledger_amount: Cycles::new(5_500_000_000_000),
317 created_at_time_ns: 12,
318 progress: CanisterPoolCreationProgress::Blocked {
319 failure: CanisterPoolCreationFailure::LedgerCreationFailed,
320 },
321 }),
322 pending_handoff: None,
323 entries: vec![CanisterPoolAsset {
324 canister_id,
325 cycles: Cycles::new(4_000_000_000_000),
326 origin: CanisterPoolAssetOrigin::Recycled,
327 status: CanisterPoolAssetStatus::Failed {
328 reason: "below configured cycles".to_string(),
329 },
330 added_at_ns: 10,
331 updated_at_ns: 11,
332 }],
333 next_start_after: None,
334 };
335 let bytes = candid::encode_one(&response).expect("encode pool status");
336 assert_eq!(
337 candid::decode_one::<CanisterPoolResponse>(&bytes).expect("decode pool status"),
338 response,
339 );
340
341 let command = PoolAdminCommand::Import { canister_id };
342 let bytes = candid::encode_one(&command).expect("encode pool command");
343 assert_eq!(
344 candid::decode_one::<PoolAdminCommand>(&bytes).expect("decode pool command"),
345 command,
346 );
347
348 let command = PoolAdminCommand::RetryRefill;
349 let bytes = candid::encode_one(&command).expect("encode refill retry command");
350 assert_eq!(
351 candid::decode_one::<PoolAdminCommand>(&bytes).expect("decode refill retry command"),
352 command,
353 );
354 }
355}