ic_backup/policy/snapshot_inventory_delta/
mod.rs1use crate::model::{
4 ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
5 ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
6};
7use thiserror::Error;
8
9#[derive(Debug)]
16pub struct SnapshotInventoryDeltaView<'a> {
17 capture: &'a IcManagementRequestRecord,
18 baseline: &'a IcSnapshotReply<'a>,
19 observed: &'a IcSnapshotReply<'a>,
20 candidates: Vec<&'a IcSnapshotInfo>,
21}
22
23impl<'a> SnapshotInventoryDeltaView<'a> {
24 #[must_use]
26 pub const fn capture(&self) -> &'a IcManagementRequestRecord {
27 self.capture
28 }
29
30 #[must_use]
32 pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
33 self.baseline
34 }
35
36 #[must_use]
38 pub const fn observed(&self) -> &'a IcSnapshotReply<'a> {
39 self.observed
40 }
41
42 #[must_use]
47 pub fn candidates(&self) -> &[&'a IcSnapshotInfo] {
48 &self.candidates
49 }
50}
51
52pub fn compare<'a>(
67 capture: &'a IcManagementRequestRecord,
68 baseline: &'a IcSnapshotReply<'a>,
69 observed: &'a IcSnapshotReply<'a>,
70) -> Result<SnapshotInventoryDeltaView<'a>, SnapshotInventoryDeltaError> {
71 if capture.method() != IcManagementMethodRecord::TakeCanisterSnapshot {
72 return Err(SnapshotInventoryDeltaError::WrongCaptureMethod);
73 }
74 let candidates = compare_inventories(capture.target(), baseline, observed)?;
75 Ok(SnapshotInventoryDeltaView {
76 capture,
77 baseline,
78 observed,
79 candidates,
80 })
81}
82
83pub(crate) fn compare_inventories<'a>(
85 target: &str,
86 baseline: &IcSnapshotReply<'_>,
87 observed: &'a IcSnapshotReply<'_>,
88) -> Result<Vec<&'a IcSnapshotInfo>, SnapshotInventoryDeltaError> {
89 for reply in [baseline, observed] {
90 if reply.request().method() != IcManagementMethodRecord::ListCanisterSnapshots {
91 return Err(SnapshotInventoryDeltaError::WrongInventoryMethod);
92 }
93 if reply.request().target() != target {
94 return Err(SnapshotInventoryDeltaError::TargetMismatch);
95 }
96 }
97 let mut candidates = Vec::new();
98 let mut previous = baseline.snapshots().iter().peekable();
99 for snapshot in observed.snapshots() {
100 match previous.peek() {
101 Some(old) if old.id() < snapshot.id() => {
102 return Err(SnapshotInventoryDeltaError::LostBaseline);
103 }
104 Some(old) if old.id() == snapshot.id() => {
105 if *old != snapshot {
106 return Err(SnapshotInventoryDeltaError::ChangedBaselineMetadata);
107 }
108 previous.next();
109 }
110 _ => candidates.push(snapshot),
111 }
112 }
113 if previous.next().is_some() {
114 return Err(SnapshotInventoryDeltaError::LostBaseline);
115 }
116 Ok(candidates)
117}
118
119#[derive(Debug, Error, Eq, PartialEq)]
121pub enum SnapshotInventoryDeltaError {
122 #[error("inventory comparison requires a new-snapshot capture request")]
124 WrongCaptureMethod,
125 #[error("inventory comparison requires two snapshot-list replies")]
127 WrongInventoryMethod,
128 #[error("snapshot inventory target differs from capture target")]
130 TargetMismatch,
131 #[error("snapshot inventory lost a baseline identifier")]
133 LostBaseline,
134 #[error("snapshot inventory changed baseline metadata")]
136 ChangedBaselineMetadata,
137}
138
139#[cfg(test)]
140mod tests;