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(&mut self, canister_id: &str) -> Result<String, BackupRunnerCommandError>;
44
45 fn download_snapshot(
47 &mut self,
48 canister_id: &str,
49 snapshot_id: &str,
50 artifact_path: &Path,
51 ) -> Result<(), BackupRunnerCommandError>;
52}
53
54#[derive(Clone, Debug, Eq, PartialEq)]
59pub struct BackupRunnerCommandError {
60 pub status: String,
61 pub message: String,
62}
63
64impl BackupRunnerCommandError {
65 #[must_use]
66 pub fn failed(status: impl Into<String>, message: impl Into<String>) -> Self {
67 Self {
68 status: status.into(),
69 message: message.into(),
70 }
71 }
72}
73
74impl fmt::Display for BackupRunnerCommandError {
75 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(formatter, "{}: {}", self.status, self.message)
77 }
78}
79
80impl StdError for BackupRunnerCommandError {}
81
82#[derive(Debug, ThisError)]
87pub enum BackupRunnerError {
88 #[error("backup execution journal is locked: {lock_path}")]
89 JournalLocked { lock_path: String },
90
91 #[error("backup operation {sequence} has no target canister")]
92 MissingOperationTarget { sequence: usize },
93
94 #[error("backup operation {sequence} has no snapshot id for target {target_canister_id}")]
95 MissingSnapshotId {
96 sequence: usize,
97 target_canister_id: String,
98 },
99
100 #[error(
101 "backup operation {sequence} has no artifact journal entry for target {target_canister_id}"
102 )]
103 MissingArtifactEntry {
104 sequence: usize,
105 target_canister_id: String,
106 },
107
108 #[error("backup operation {sequence} failed: {status}: {message}")]
109 CommandFailed {
110 sequence: usize,
111 status: String,
112 message: String,
113 },
114
115 #[error("backup preflight failed: {status}: {message}")]
116 PreflightFailed { status: String, message: String },
117
118 #[error("backup execution has no operation ready to run")]
119 NoReadyOperation,
120
121 #[error("backup execution is blocked: {reasons:?}")]
122 Blocked { reasons: Vec<String> },
123
124 #[error(transparent)]
125 Io(#[from] std::io::Error),
126
127 #[error(transparent)]
128 Json(#[from] serde_json::Error),
129
130 #[error(transparent)]
131 Persistence(#[from] crate::persistence::PersistenceError),
132
133 #[error(transparent)]
134 BackupPlan(#[from] crate::plan::BackupPlanError),
135
136 #[error(transparent)]
137 ExecutionJournal(#[from] crate::execution::BackupExecutionJournalError),
138
139 #[error(transparent)]
140 Journal(#[from] crate::journal::JournalValidationError),
141
142 #[error(transparent)]
143 Checksum(#[from] crate::artifacts::ArtifactChecksumError),
144
145 #[error(transparent)]
146 Manifest(#[from] crate::manifest::ManifestValidationError),
147}
148
149#[derive(Clone, Debug, Serialize)]
154pub struct BackupRunResponse {
155 pub run_id: String,
156 pub plan_id: String,
157 pub backup_id: String,
158 pub complete: bool,
159 pub max_steps_reached: bool,
160 pub executed_operation_count: usize,
161 pub executed_operations: Vec<BackupRunExecutedOperation>,
162 pub execution: BackupExecutionResumeSummary,
163}
164
165#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
170pub struct BackupRunExecutedOperation {
171 pub sequence: usize,
172 pub operation_id: String,
173 pub kind: String,
174 pub target_canister_id: Option<String>,
175 pub outcome: String,
176}
177
178impl BackupRunExecutedOperation {
179 pub(super) fn completed(operation: &BackupExecutionJournalOperation) -> Self {
180 Self::from_operation(operation, "completed")
181 }
182
183 pub(super) fn failed(operation: &BackupExecutionJournalOperation) -> Self {
184 Self::from_operation(operation, "failed")
185 }
186
187 fn from_operation(operation: &BackupExecutionJournalOperation, outcome: &str) -> Self {
188 Self {
189 sequence: operation.sequence,
190 operation_id: operation.operation_id.clone(),
191 kind: format!("{:?}", operation.kind),
192 target_canister_id: operation.target_canister_id.clone(),
193 outcome: outcome.to_string(),
194 }
195 }
196}