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