Skip to main content

canic_backup/runner/
types.rs

1use crate::{
2    execution::{BackupExecutionJournalOperation, BackupExecutionResumeSummary},
3    plan::{BackupExecutionPreflightReceipts, BackupPlan},
4};
5use serde::Serialize;
6use std::{error::Error as StdError, fmt, path::Path, path::PathBuf};
7use thiserror::Error as ThisError;
8
9///
10/// BackupRunnerConfig
11///
12
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct BackupRunnerConfig {
15    pub out: PathBuf,
16    pub max_steps: Option<usize>,
17    pub updated_at: Option<String>,
18    pub tool_name: String,
19    pub tool_version: String,
20}
21
22///
23/// BackupRunnerExecutor
24///
25
26pub trait BackupRunnerExecutor {
27    /// Prove execution preflights before any mutating operation runs.
28    fn preflight_receipts(
29        &mut self,
30        plan: &BackupPlan,
31        preflight_id: &str,
32        validated_at: &str,
33        expires_at: &str,
34    ) -> Result<BackupExecutionPreflightReceipts, BackupRunnerCommandError>;
35
36    /// Stop one selected canister.
37    fn stop_canister(&mut self, canister_id: &str) -> Result<(), BackupRunnerCommandError>;
38
39    /// Start one selected canister.
40    fn start_canister(&mut self, canister_id: &str) -> Result<(), BackupRunnerCommandError>;
41
42    /// Create one selected canister snapshot and return the typed snapshot receipt.
43    fn create_snapshot(
44        &mut self,
45        canister_id: &str,
46    ) -> Result<BackupRunnerSnapshotReceipt, BackupRunnerCommandError>;
47
48    /// Download one selected snapshot into a temporary artifact directory.
49    fn download_snapshot(
50        &mut self,
51        canister_id: &str,
52        snapshot_id: &str,
53        artifact_path: &Path,
54    ) -> Result<(), BackupRunnerCommandError>;
55}
56
57///
58/// BackupRunnerSnapshotReceipt
59///
60
61#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct BackupRunnerSnapshotReceipt {
63    pub snapshot_id: String,
64    pub taken_at_timestamp: Option<u64>,
65    pub total_size_bytes: Option<u64>,
66}
67
68///
69/// BackupRunnerCommandError
70///
71
72#[derive(Clone, Debug, Eq, PartialEq)]
73pub struct BackupRunnerCommandError {
74    pub status: String,
75    pub message: String,
76}
77
78impl BackupRunnerCommandError {
79    #[must_use]
80    pub fn failed(status: impl Into<String>, message: impl Into<String>) -> Self {
81        Self {
82            status: status.into(),
83            message: message.into(),
84        }
85    }
86}
87
88impl fmt::Display for BackupRunnerCommandError {
89    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(formatter, "{}: {}", self.status, self.message)
91    }
92}
93
94impl StdError for BackupRunnerCommandError {}
95
96///
97/// BackupRunnerError
98///
99
100#[derive(Debug, ThisError)]
101pub enum BackupRunnerError {
102    #[error("backup execution journal is locked: {lock_path}")]
103    JournalLocked { lock_path: String },
104
105    #[error("backup operation {sequence} has no target canister")]
106    MissingOperationTarget { sequence: usize },
107
108    #[error("backup operation {sequence} has no snapshot id for target {target_canister_id}")]
109    MissingSnapshotId {
110        sequence: usize,
111        target_canister_id: String,
112    },
113
114    #[error(
115        "backup operation {sequence} has no artifact journal entry for target {target_canister_id}"
116    )]
117    MissingArtifactEntry {
118        sequence: usize,
119        target_canister_id: String,
120    },
121
122    #[error(
123        "backup operation {sequence} artifact temp path for target {target_canister_id} does not match expected runner path: journal={journal_path}, expected={expected_path}"
124    )]
125    ArtifactTempPathMismatch {
126        sequence: usize,
127        target_canister_id: String,
128        journal_path: String,
129        expected_path: String,
130    },
131
132    #[error("backup operation {sequence} failed: {status}: {message}")]
133    CommandFailed {
134        sequence: usize,
135        status: String,
136        message: String,
137    },
138
139    #[error("backup preflight failed: {status}: {message}")]
140    PreflightFailed { status: String, message: String },
141
142    #[error("backup execution has no operation ready to run")]
143    NoReadyOperation,
144
145    #[error("backup execution is blocked: {reasons:?}")]
146    Blocked { reasons: Vec<String> },
147
148    #[error(transparent)]
149    Io(#[from] std::io::Error),
150
151    #[error(transparent)]
152    Json(#[from] serde_json::Error),
153
154    #[error(transparent)]
155    Persistence(#[from] crate::persistence::PersistenceError),
156
157    #[error(transparent)]
158    BackupPlan(#[from] crate::plan::BackupPlanError),
159
160    #[error(transparent)]
161    ExecutionJournal(#[from] crate::execution::BackupExecutionJournalError),
162
163    #[error(transparent)]
164    Journal(#[from] crate::journal::JournalValidationError),
165
166    #[error(transparent)]
167    Checksum(#[from] crate::artifacts::ArtifactChecksumError),
168
169    #[error(transparent)]
170    Manifest(#[from] crate::manifest::ManifestValidationError),
171}
172
173///
174/// BackupRunResponse
175///
176
177#[derive(Clone, Debug, Serialize)]
178pub struct BackupRunResponse {
179    pub run_id: String,
180    pub plan_id: String,
181    pub backup_id: String,
182    pub complete: bool,
183    pub max_steps_reached: bool,
184    pub executed_operation_count: usize,
185    pub executed_operations: Vec<BackupRunExecutedOperation>,
186    pub execution: BackupExecutionResumeSummary,
187}
188
189///
190/// BackupRunExecutedOperation
191///
192
193#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
194pub struct BackupRunExecutedOperation {
195    pub sequence: usize,
196    pub operation_id: String,
197    pub kind: String,
198    pub target_canister_id: Option<String>,
199    pub outcome: String,
200}
201
202impl BackupRunExecutedOperation {
203    pub(super) fn completed(operation: &BackupExecutionJournalOperation) -> Self {
204        Self::from_operation(operation, "completed")
205    }
206
207    pub(super) fn failed(operation: &BackupExecutionJournalOperation) -> Self {
208        Self::from_operation(operation, "failed")
209    }
210
211    fn from_operation(operation: &BackupExecutionJournalOperation, outcome: &str) -> Self {
212        Self {
213            sequence: operation.sequence,
214            operation_id: operation.operation_id.clone(),
215            kind: format!("{:?}", operation.kind),
216            target_canister_id: operation.target_canister_id.clone(),
217            outcome: outcome.to_string(),
218        }
219    }
220}