1use crate::plan::BackupOperationKind;
2use serde::{Deserialize, Serialize};
3use thiserror::Error as ThisError;
4
5#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
10pub struct BackupExecutionJournal {
11 pub journal_version: u16,
12 pub plan_id: String,
13 pub run_id: String,
14 pub preflight_id: Option<String>,
15 pub preflight_accepted: bool,
16 pub restart_required: bool,
17 pub operations: Vec<BackupExecutionJournalOperation>,
18 pub operation_receipts: Vec<BackupExecutionOperationReceipt>,
19}
20
21#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
26pub struct BackupExecutionJournalOperation {
27 pub sequence: usize,
28 pub operation_id: String,
29 pub kind: BackupOperationKind,
30 pub target_canister_id: Option<String>,
31 pub state: BackupExecutionOperationState,
32 pub state_updated_at: Option<String>,
33 pub blocking_reasons: Vec<String>,
34}
35
36#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
41#[serde(rename_all = "kebab-case")]
42pub enum BackupExecutionOperationState {
43 Ready,
44 Pending,
45 Blocked,
46 Completed,
47 Failed,
48 Skipped,
49}
50
51#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
56pub struct BackupExecutionOperationReceipt {
57 pub plan_id: String,
58 pub run_id: String,
59 pub preflight_id: Option<String>,
60 pub sequence: usize,
61 pub operation_id: String,
62 pub kind: BackupOperationKind,
63 pub target_canister_id: Option<String>,
64 pub outcome: BackupExecutionOperationReceiptOutcome,
65 pub updated_at: Option<String>,
66 pub snapshot_id: Option<String>,
67 #[serde(default)]
68 pub snapshot_taken_at_timestamp: Option<u64>,
69 #[serde(default)]
70 pub snapshot_total_size_bytes: Option<u64>,
71 pub artifact_path: Option<String>,
72 pub checksum: Option<String>,
73 pub failure_reason: Option<String>,
74}
75
76#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
81#[serde(rename_all = "kebab-case")]
82pub enum BackupExecutionOperationReceiptOutcome {
83 Completed,
84 Failed,
85 Skipped,
86}
87
88#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
93pub struct BackupExecutionResumeSummary {
94 pub plan_id: String,
95 pub run_id: String,
96 pub preflight_id: Option<String>,
97 pub preflight_accepted: bool,
98 pub restart_required: bool,
99 pub total_operations: usize,
100 pub ready_operations: usize,
101 pub pending_operations: usize,
102 pub blocked_operations: usize,
103 pub completed_operations: usize,
104 pub failed_operations: usize,
105 pub skipped_operations: usize,
106 pub next_operation: Option<BackupExecutionJournalOperation>,
107}
108
109#[derive(Debug, ThisError)]
114pub enum BackupExecutionJournalError {
115 #[error("invalid backup plan for execution journal: {0}")]
116 InvalidPlan(String),
117
118 #[error("unsupported backup execution journal version {0}")]
119 UnsupportedVersion(u16),
120
121 #[error("backup execution journal field {0} is required")]
122 MissingField(&'static str),
123
124 #[error("backup execution journal has duplicate operation sequence {0}")]
125 DuplicateSequence(usize),
126
127 #[error("backup execution journal is missing operation sequence {0}")]
128 MissingSequence(usize),
129
130 #[error("accepted preflight is missing preflight_id")]
131 AcceptedPreflightMissingId,
132
133 #[error("restart_required does not match execution operation state")]
134 RestartRequiredMismatch,
135
136 #[error("preflight already accepted as {existing}, cannot accept {attempted}")]
137 PreflightAlreadyAccepted { existing: String, attempted: String },
138
139 #[error("preflight receipt plan id {actual} does not match execution journal plan {expected}")]
140 PreflightPlanMismatch { expected: String, actual: String },
141
142 #[error("mutating operation {sequence} is ready before preflight acceptance")]
143 MutationReadyBeforePreflight { sequence: usize },
144
145 #[error("mutating operation {sequence} cannot run before preflight acceptance")]
146 MutationBeforePreflightAccepted { sequence: usize },
147
148 #[error("operation {0} is missing a blocking or failure reason")]
149 OperationMissingReason(usize),
150
151 #[error("operation {0} cannot have blocking reasons in its current state")]
152 UnblockedOperationHasReasons(usize),
153
154 #[error("operation {0} was not found")]
155 OperationNotFound(usize),
156
157 #[error("operation {sequence} cannot transition from {from:?} to {to:?}")]
158 InvalidOperationTransition {
159 sequence: usize,
160 from: BackupExecutionOperationState,
161 to: BackupExecutionOperationState,
162 },
163
164 #[error("operation {requested} cannot advance before operation {next}")]
165 OutOfOrderOperationTransition { requested: usize, next: usize },
166
167 #[error("no operation can be advanced")]
168 NoTransitionableOperation,
169
170 #[error("operation {0} is not failed")]
171 OperationNotFailed(usize),
172
173 #[error("operation receipt references missing operation {0}")]
174 ReceiptOperationNotFound(usize),
175
176 #[error("operation receipt does not match operation {sequence}")]
177 ReceiptOperationMismatch { sequence: usize },
178
179 #[error("operation receipt does not match journal {sequence}")]
180 ReceiptJournalMismatch { sequence: usize },
181
182 #[error("operation receipt does not match accepted preflight {sequence}")]
183 ReceiptPreflightMismatch { sequence: usize },
184
185 #[error("operation receipt {sequence} has no pending operation")]
186 ReceiptWithoutPendingOperation { sequence: usize },
187}