ic_testkit/pic/
snapshot.rs1use std::collections::HashMap;
2
3use candid::Principal;
4
5use super::{ControllerSnapshots, Pic};
6
7const SNAPSHOT_RESTORE_MIN_CYCLES: u128 = 200_000_000_000_000;
8
9impl Pic {
10 pub fn capture_controller_snapshots<I>(
12 &self,
13 controller_id: Principal,
14 canister_ids: I,
15 ) -> Option<ControllerSnapshots>
16 where
17 I: IntoIterator<Item = Principal>,
18 {
19 let mut snapshots = HashMap::new();
20
21 for canister_id in canister_ids {
22 let Some(snapshot) = self.try_take_controller_snapshot(controller_id, canister_id)
23 else {
24 eprintln!(
25 "capture_controller_snapshots: snapshot capture unavailable for {canister_id}"
26 );
27 return None;
28 };
29 snapshots.insert(canister_id, snapshot);
30 }
31
32 Some(ControllerSnapshots::new(snapshots))
33 }
34
35 pub fn restore_controller_snapshots(
37 &self,
38 controller_id: Principal,
39 snapshots: &ControllerSnapshots,
40 ) {
41 for (canister_id, snapshot_id, sender) in snapshots.iter() {
42 self.restore_controller_snapshot(controller_id, canister_id, sender, snapshot_id);
43 }
44 }
45
46 fn try_take_controller_snapshot(
48 &self,
49 controller_id: Principal,
50 canister_id: Principal,
51 ) -> Option<(Vec<u8>, Option<Principal>)> {
52 let candidates = controller_sender_candidates(controller_id, canister_id);
53 let mut last_err = None;
54
55 for sender in candidates {
56 match self.inner.take_canister_snapshot(canister_id, sender, None) {
57 Ok(snapshot) => return Some((snapshot.id, sender)),
58 Err(err) => last_err = Some((sender, err)),
59 }
60 }
61
62 if let Some((sender, err)) = last_err {
63 eprintln!(
64 "failed to capture canister snapshot for {canister_id} using sender {sender:?}: {err}"
65 );
66 }
67 None
68 }
69
70 fn restore_controller_snapshot(
72 &self,
73 controller_id: Principal,
74 canister_id: Principal,
75 snapshot_sender: Option<Principal>,
76 snapshot_id: &[u8],
77 ) {
78 let fallback_sender = if snapshot_sender.is_some() {
79 None
80 } else {
81 Some(controller_id)
82 };
83 let candidates = [snapshot_sender, fallback_sender];
84 let mut last_err = None;
85
86 for sender in candidates {
87 self.ensure_snapshot_restore_cycles(canister_id);
88 match self
89 .inner
90 .load_canister_snapshot(canister_id, sender, snapshot_id.to_vec())
91 {
92 Ok(()) => return,
93 Err(err) => last_err = Some((sender, err)),
94 }
95 }
96
97 let (sender, err) =
98 last_err.expect("snapshot restore must have at least one sender attempt");
99 panic!(
100 "failed to restore canister snapshot for {canister_id} using sender {sender:?}: {err}"
101 );
102 }
103
104 fn ensure_snapshot_restore_cycles(&self, canister_id: Principal) {
105 let balance = self.inner.cycle_balance(canister_id);
106 if balance < SNAPSHOT_RESTORE_MIN_CYCLES {
107 let top_up = SNAPSHOT_RESTORE_MIN_CYCLES - balance;
108 let _ = self.inner.add_cycles(canister_id, top_up);
109 }
110 }
111}
112
113fn controller_sender_candidates(
115 controller_id: Principal,
116 canister_id: Principal,
117) -> [Option<Principal>; 2] {
118 if canister_id == controller_id {
119 [None, Some(controller_id)]
120 } else {
121 [Some(controller_id), None]
122 }
123}