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#[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
22pub trait BackupRunnerExecutor {
27 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 fn stop_canister(&mut self, canister_id: &str) -> Result<(), BackupRunnerCommandError>;
38
39 fn start_canister(&mut self, canister_id: &str) -> Result<(), BackupRunnerCommandError>;
41
42 fn create_snapshot(
44 &mut self,
45 canister_id: &str,
46 ) -> Result<BackupRunnerSnapshotReceipt, BackupRunnerCommandError>;
47
48 fn download_snapshot(
50 &mut self,
51 canister_id: &str,
52 snapshot_id: &str,
53 artifact_path: &Path,
54 ) -> Result<(), BackupRunnerCommandError>;
55}
56
57#[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#[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#[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#[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#[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}