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