1use crate::plan::BackupOperationKind;
8
9use serde::{Deserialize, Serialize};
10use thiserror::Error as ThisError;
11
12#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
20#[serde(deny_unknown_fields)]
21pub struct BackupExecutionJournal {
22 pub journal_version: u16,
23 pub plan_id: String,
24 pub run_id: String,
25 pub preflight_id: Option<String>,
26 pub preflight_accepted: bool,
27 pub restart_required: bool,
28 pub operations: Vec<BackupExecutionJournalOperation>,
29 pub operation_receipts: Vec<BackupExecutionOperationReceipt>,
30}
31
32#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
40#[serde(deny_unknown_fields)]
41pub struct BackupExecutionJournalOperation {
42 pub sequence: usize,
43 pub operation_id: String,
44 pub kind: BackupOperationKind,
45 pub target_canister_id: Option<String>,
46 pub state: BackupExecutionOperationState,
47 pub state_updated_at: Option<String>,
48 pub blocking_reasons: Vec<String>,
49}
50
51#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
59#[serde(rename_all = "kebab-case")]
60pub enum BackupExecutionOperationState {
61 Ready,
62 Pending,
63 Blocked,
64 Completed,
65 Failed,
66 Skipped,
67}
68
69#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
77#[serde(deny_unknown_fields)]
78pub struct BackupExecutionOperationReceipt {
79 pub plan_id: String,
80 pub run_id: String,
81 pub preflight_id: Option<String>,
82 pub sequence: usize,
83 pub operation_id: String,
84 pub kind: BackupOperationKind,
85 pub target_canister_id: Option<String>,
86 pub outcome: BackupExecutionOperationReceiptOutcome,
87 pub updated_at: Option<String>,
88 pub snapshot_id: Option<String>,
89 #[serde(default)]
90 pub snapshot_taken_at_timestamp: Option<u64>,
91 #[serde(default)]
92 pub snapshot_total_size_bytes: Option<u64>,
93 pub artifact_path: Option<String>,
94 pub checksum: Option<String>,
95 pub failure_reason: Option<String>,
96}
97
98#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
106#[serde(rename_all = "kebab-case")]
107pub enum BackupExecutionOperationReceiptOutcome {
108 Completed,
109 Failed,
110 Skipped,
111}
112
113#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
121#[serde(deny_unknown_fields)]
122pub struct BackupExecutionResumeSummary {
123 pub plan_id: String,
124 pub run_id: String,
125 pub preflight_id: Option<String>,
126 pub preflight_accepted: bool,
127 pub restart_required: bool,
128 pub total_operations: usize,
129 pub ready_operations: usize,
130 pub pending_operations: usize,
131 pub blocked_operations: usize,
132 pub completed_operations: usize,
133 pub failed_operations: usize,
134 pub skipped_operations: usize,
135 pub next_operation: Option<BackupExecutionJournalOperation>,
136}
137
138#[derive(Debug, ThisError)]
146pub enum BackupExecutionJournalError {
147 #[error("invalid backup plan for execution journal: {0}")]
148 InvalidPlan(String),
149
150 #[error("unsupported backup execution journal version {0}")]
151 UnsupportedVersion(u16),
152
153 #[error("backup execution journal field {0} is required")]
154 MissingField(&'static str),
155
156 #[error("backup execution journal has duplicate operation sequence {0}")]
157 DuplicateSequence(usize),
158
159 #[error("backup execution journal is missing operation sequence {0}")]
160 MissingSequence(usize),
161
162 #[error("accepted preflight is missing preflight_id")]
163 AcceptedPreflightMissingId,
164
165 #[error("restart_required does not match execution operation state")]
166 RestartRequiredMismatch,
167
168 #[error("preflight already accepted as {existing}, cannot accept {attempted}")]
169 PreflightAlreadyAccepted { existing: String, attempted: String },
170
171 #[error("preflight receipt plan id {actual} does not match execution journal plan {expected}")]
172 PreflightPlanMismatch { expected: String, actual: String },
173
174 #[error("mutating operation {sequence} is ready before preflight acceptance")]
175 MutationReadyBeforePreflight { sequence: usize },
176
177 #[error("mutating operation {sequence} cannot run before preflight acceptance")]
178 MutationBeforePreflightAccepted { sequence: usize },
179
180 #[error("operation {0} is missing a blocking or failure reason")]
181 OperationMissingReason(usize),
182
183 #[error("operation {0} cannot have blocking reasons in its current state")]
184 UnblockedOperationHasReasons(usize),
185
186 #[error("operation {0} was not found")]
187 OperationNotFound(usize),
188
189 #[error("operation {sequence} cannot transition from {from:?} to {to:?}")]
190 InvalidOperationTransition {
191 sequence: usize,
192 from: BackupExecutionOperationState,
193 to: BackupExecutionOperationState,
194 },
195
196 #[error("operation {requested} cannot advance before operation {next}")]
197 OutOfOrderOperationTransition { requested: usize, next: usize },
198
199 #[error("no operation can be advanced")]
200 NoTransitionableOperation,
201
202 #[error("operation {0} is not failed")]
203 OperationNotFailed(usize),
204
205 #[error("operation receipt references missing operation {0}")]
206 ReceiptOperationNotFound(usize),
207
208 #[error("operation receipt does not match operation {sequence}")]
209 ReceiptOperationMismatch { sequence: usize },
210
211 #[error("operation receipt does not match journal {sequence}")]
212 ReceiptJournalMismatch { sequence: usize },
213
214 #[error("operation receipt does not match accepted preflight {sequence}")]
215 ReceiptPreflightMismatch { sequence: usize },
216
217 #[error("operation receipt {sequence} has no pending operation")]
218 ReceiptWithoutPendingOperation { sequence: usize },
219}