use std::io;
use ic_platform_types::principal::ExternalPrincipal as Principal;
use super::{
CachedPocketIcBaseline, CanisterSnapshotTarget, FailureDisposition, PocketIcBaselineRecipe,
PreparedBaseline, RebuildReason, ResetPolicies, ResetReceipt, ResetRequirements,
SnapshotRestoreFunding, StandaloneCanisterFixture, is_dead_pocket_ic_transport_error,
};
pub struct StandaloneCanisterRecipe<B> {
requirements: ResetRequirements,
build: B,
}
impl<B> StandaloneCanisterRecipe<B> {
#[must_use]
pub fn new(funding: SnapshotRestoreFunding, build: B) -> Self {
Self {
requirements: ResetRequirements {
funding,
policies: ResetPolicies::default(),
},
build,
}
}
}
impl<B, Fut> PocketIcBaselineRecipe for StandaloneCanisterRecipe<B>
where
B: Fn() -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = io::Result<StandaloneCanisterFixture>> + Send,
{
type Metadata = Principal;
type Error = io::Error;
fn reset_requirements(&self) -> &ResetRequirements {
&self.requirements
}
async fn build(&self) -> Result<CachedPocketIcBaseline<Principal>, io::Error> {
let (pocket_ic, canister_id) = (self.build)().await?.into_parts();
if let Err(source) = pocket_ic.instance().stop_canister(canister_id, None).await {
let error = io::Error::other(super::ControllerSnapshotError::CaptureFailed {
canister_id,
sender: None,
source: Box::new(source),
cleanup_failures: Vec::new(),
});
if let Err(cleanup) = pocket_ic.close().await {
tracing::warn!("PocketIC deletion after stop failure failed: {cleanup}");
}
return Err(error);
}
let baseline = CachedPocketIcBaseline::capture(
pocket_ic,
[CanisterSnapshotTarget {
canister_id,
sender: None,
}],
canister_id,
)
.await
.map_err(io::Error::other)?;
if let Err(error) = self.drive_to_readiness(&baseline).await {
if let Err(cleanup) = baseline.close().await {
tracing::warn!("PocketIC deletion after readiness failure failed: {cleanup}");
}
return Err(error);
}
Ok(baseline)
}
fn reset_non_snapshot_state(
&self,
_baseline: &CachedPocketIcBaseline<Principal>,
) -> impl std::future::Future<Output = Result<ResetReceipt, io::Error>> + Send {
std::future::ready(Ok(ResetReceipt::default()))
}
async fn prepare_restore(
&self,
baseline: &CachedPocketIcBaseline<Principal>,
) -> Result<(), io::Error> {
baseline
.pocket_ic()
.stop_canister(*baseline.metadata(), None)
.await
.map_err(io::Error::other)
}
async fn drive_to_readiness(
&self,
baseline: &CachedPocketIcBaseline<Principal>,
) -> Result<(), io::Error> {
baseline
.pocket_ic()
.start_canister(*baseline.metadata(), None)
.await
.map_err(io::Error::other)
}
fn validate(
&self,
_baseline: &CachedPocketIcBaseline<Principal>,
_preparation: &PreparedBaseline,
) -> impl std::future::Future<Output = Result<(), io::Error>> + Send {
std::future::ready(Ok(()))
}
fn classify_failure(
&self,
_stage: super::BaselinePreparationStage,
error: &(dyn std::error::Error + 'static),
) -> FailureDisposition {
if is_dead_pocket_ic_transport_error(error) {
FailureDisposition::Rebuild(RebuildReason::DeadPocketIcTransport)
} else {
FailureDisposition::Fatal
}
}
}
#[cfg(test)]
mod tests;