11use futures:: { StreamExt , stream:: FuturesOrdered } ;
22use ic_agent:: { Agent , AgentError , export:: Principal } ;
33use ic_management_canister_types:: {
4- CanisterId , ChunkHash , UpgradeFlags , UploadChunkArgs , WasmMemoryPersistence ,
4+ CanisterId , CanisterStatusType , ChunkHash , UpgradeFlags , UploadChunkArgs , WasmMemoryPersistence ,
55} ;
66use ic_utils:: interfaces:: {
77 ManagementCanister , management_canister:: builders:: CanisterInstallMode ,
@@ -10,7 +10,7 @@ use icp::context::TermWriter;
1010use sha2:: { Digest , Sha256 } ;
1111use snafu:: { ResultExt , Snafu } ;
1212use std:: sync:: Arc ;
13- use tracing:: debug;
13+ use tracing:: { debug, warn } ;
1414
1515use crate :: progress:: { ProgressManager , ProgressManagerSettings } ;
1616
@@ -23,6 +23,18 @@ pub enum InstallOperationError {
2323
2424 #[ snafu( display( "agent error: {source}" ) ) ]
2525 Agent { source : AgentError } ,
26+
27+ #[ snafu( display( "Failed to stop canister '{canister_name}' before upgrade" ) ) ]
28+ StopCanister {
29+ canister_name : String ,
30+ source : AgentError ,
31+ } ,
32+
33+ #[ snafu( display( "Failed to start canister '{canister_name}' after upgrade" ) ) ]
34+ StartCanister {
35+ canister_name : String ,
36+ source : AgentError ,
37+ } ,
2638}
2739
2840#[ derive( Debug , Snafu ) ]
@@ -41,28 +53,26 @@ struct InstallFailure {
4153/// Resolve a mode string ("auto", "install", "reinstall", "upgrade") into
4254/// a [`CanisterInstallMode`]. For "auto", queries `canister_status` to
4355/// determine whether the canister already has code installed.
44- pub ( crate ) async fn resolve_install_mode (
56+ pub ( crate ) async fn resolve_install_mode_and_status (
4557 agent : & Agent ,
4658 canister_name : & str ,
4759 canister_id : & Principal ,
4860 mode : & str ,
49- ) -> Result < CanisterInstallMode , ResolveInstallModeError > {
61+ ) -> Result < ( CanisterInstallMode , CanisterStatusType ) , ResolveInstallModeError > {
62+ let mgmt = ManagementCanister :: create ( agent) ;
63+ let ( status, ) = mgmt
64+ . canister_status ( canister_id)
65+ . await
66+ . context ( ResolveInstallModeSnafu { canister_name } ) ?;
5067 match mode {
51- "auto" => {
52- let mgmt = ManagementCanister :: create ( agent) ;
53- let ( status, ) = mgmt
54- . canister_status ( canister_id)
55- . await
56- . context ( ResolveInstallModeSnafu { canister_name } ) ?;
57- Ok ( if status. module_hash . is_some ( ) {
58- CanisterInstallMode :: Upgrade ( None )
59- } else {
60- CanisterInstallMode :: Install
61- } )
62- }
63- "install" => Ok ( CanisterInstallMode :: Install ) ,
64- "reinstall" => Ok ( CanisterInstallMode :: Reinstall ) ,
65- "upgrade" => Ok ( CanisterInstallMode :: Upgrade ( None ) ) ,
68+ "auto" => Ok ( if status. module_hash . is_some ( ) {
69+ ( CanisterInstallMode :: Upgrade ( None ) , status. status )
70+ } else {
71+ ( CanisterInstallMode :: Install , status. status )
72+ } ) ,
73+ "install" => Ok ( ( CanisterInstallMode :: Install , status. status ) ) ,
74+ "reinstall" => Ok ( ( CanisterInstallMode :: Reinstall , status. status ) ) ,
75+ "upgrade" => Ok ( ( CanisterInstallMode :: Upgrade ( None ) , status. status ) ) ,
6676 _ => panic ! ( "invalid install mode: {mode}" ) ,
6777 }
6878}
@@ -80,6 +90,7 @@ pub(crate) async fn install_canister(
8090 canister_name : & str ,
8191 wasm : & [ u8 ] ,
8292 mode : CanisterInstallMode ,
93+ status : CanisterStatusType ,
8394 init_args : Option < & [ u8 ] > ,
8495) -> Result < ( ) , InstallOperationError > {
8596 let mode = match mode {
@@ -106,7 +117,16 @@ pub(crate) async fn install_canister(
106117 canister_name, mode
107118 ) ;
108119
109- do_install_operation ( agent, canister_id, canister_name, wasm, mode, init_args) . await
120+ do_install_operation (
121+ agent,
122+ canister_id,
123+ canister_name,
124+ wasm,
125+ mode,
126+ status,
127+ init_args,
128+ )
129+ . await
110130}
111131
112132async fn do_install_operation (
@@ -115,6 +135,7 @@ async fn do_install_operation(
115135 canister_name : & str ,
116136 wasm : & [ u8 ] ,
117137 mode : CanisterInstallMode ,
138+ status : CanisterStatusType ,
118139 init_args : Option < & [ u8 ] > ,
119140) -> Result < ( ) , InstallOperationError > {
120141 let mgmt = ManagementCanister :: create ( agent) ;
@@ -143,9 +164,12 @@ async fn do_install_operation(
143164 builder = builder. with_raw_arg ( args. into ( ) ) ;
144165 }
145166
146- builder
147- . await
148- . map_err ( |source| InstallOperationError :: Agent { source } ) ?;
167+ stop_and_start_if_upgrade ( & mgmt, canister_id, canister_name, mode, status, async {
168+ builder
169+ . await
170+ . map_err ( |source| InstallOperationError :: Agent { source } )
171+ } )
172+ . await ?;
149173 } else {
150174 // Large wasm: use chunked installation
151175 debug ! ( "Installing wasm for {canister_name} using chunked installation" ) ;
@@ -197,31 +221,91 @@ async fn do_install_operation(
197221 builder = builder. with_raw_arg ( args. to_vec ( ) ) ;
198222 }
199223
200- builder
201- . await
202- . map_err ( |source| InstallOperationError :: Agent { source } ) ?;
224+ let install_res =
225+ stop_and_start_if_upgrade ( & mgmt, canister_id, canister_name, mode, status, async {
226+ builder
227+ . await
228+ . map_err ( |source| InstallOperationError :: Agent { source } )
229+ } )
230+ . await ;
203231
204232 // Clear chunk store after successful installation to free up storage
205- mgmt. clear_chunk_store ( canister_id)
233+ let clear_res = mgmt
234+ . clear_chunk_store ( canister_id)
206235 . await
207- . map_err ( |source| InstallOperationError :: Agent { source } ) ?;
236+ . map_err ( |source| InstallOperationError :: Agent { source } ) ;
237+
238+ if let Err ( clear_error) = clear_res {
239+ if let Err ( install_error) = install_res {
240+ warn ! ( "Failed to clear chunk store after failed install: {clear_error}" ) ;
241+ return Err ( install_error) ;
242+ } else {
243+ return Err ( clear_error) ;
244+ }
245+ }
246+ install_res?;
208247 }
209248
210249 Ok ( ( ) )
211250}
212251
252+ async fn stop_and_start_if_upgrade (
253+ mgmt : & ManagementCanister < ' _ > ,
254+ canister_id : & Principal ,
255+ canister_name : & str ,
256+ mode : CanisterInstallMode ,
257+ status : CanisterStatusType ,
258+ f : impl Future < Output = Result < ( ) , InstallOperationError > > ,
259+ ) -> Result < ( ) , InstallOperationError > {
260+ let should_guard = matches ! (
261+ mode,
262+ CanisterInstallMode :: Upgrade ( _) | CanisterInstallMode :: Reinstall
263+ ) && matches ! ( status, CanisterStatusType :: Running ) ;
264+ // Stop the canister before proceeding
265+ if should_guard {
266+ mgmt. stop_canister ( canister_id)
267+ . await
268+ . context ( StopCanisterSnafu { canister_name } ) ?;
269+ }
270+ // Install the canister
271+ let install_result = f. await ;
272+ // Restart the canister whether or not the installation succeeded
273+ if should_guard {
274+ let start_result = mgmt. start_canister ( canister_id) . await ;
275+ if let Err ( start_error) = start_result {
276+ // If both install and start failed, report the install error since it's more likely to be the root cause
277+ if let Err ( install_error) = install_result {
278+ warn ! ( "Failed to start canister after failed upgrade: {start_error}" ) ;
279+ return Err ( install_error) ;
280+ } else {
281+ return Err ( start_error) . context ( StartCanisterSnafu { canister_name } ) ;
282+ }
283+ }
284+ }
285+
286+ install_result
287+ }
288+
213289/// Installs code to multiple canisters and displays progress bars.
214290pub ( crate ) async fn install_many (
215291 agent : Agent ,
216- canisters : impl IntoIterator < Item = ( String , Principal , CanisterInstallMode , Option < Vec < u8 > > ) > ,
292+ canisters : impl IntoIterator <
293+ Item = (
294+ String ,
295+ Principal ,
296+ CanisterInstallMode ,
297+ CanisterStatusType ,
298+ Option < Vec < u8 > > ,
299+ ) ,
300+ > ,
217301 artifacts : Arc < dyn icp:: store_artifact:: Access > ,
218302 term : Arc < TermWriter > ,
219303 debug : bool ,
220304) -> Result < ( ) , InstallManyError > {
221305 let mut futs = FuturesOrdered :: new ( ) ;
222306 let progress_manager = ProgressManager :: new ( ProgressManagerSettings { hidden : debug } ) ;
223307
224- for ( name, cid, mode, init_args) in canisters {
308+ for ( name, cid, mode, status , init_args) in canisters {
225309 let pb = progress_manager. create_progress_bar ( & name) ;
226310 let agent = agent. clone ( ) ;
227311 let install_fn = {
@@ -238,7 +322,16 @@ pub(crate) async fn install_many(
238322 }
239323 } ) ?;
240324
241- install_canister ( & agent, & cid, & name, & wasm, mode, init_args. as_deref ( ) ) . await
325+ install_canister (
326+ & agent,
327+ & cid,
328+ & name,
329+ & wasm,
330+ mode,
331+ status,
332+ init_args. as_deref ( ) ,
333+ )
334+ . await
242335 }
243336 } ;
244337
0 commit comments