laminar_core/cluster/control/leader_lease/
topology_retention.rs1use super::topology_admission::{topology_checkpoint_error, CONTROL_TIMEOUT};
4use super::{read_authority_record, CheckpointDecisionStore, LeaderLeaseStore};
5use crate::checkpoint::{CommittedCheckpointIndex, CommittedCheckpointRef};
6use crate::checkpoint_decision::CheckpointOutcome;
7use crate::cluster::control::{TopologyAdmissionStatus, TopologyError};
8
9impl LeaderLeaseStore {
10 pub async fn retained_topology_checkpoints(
17 &self,
18 ) -> Result<Vec<CommittedCheckpointRef>, TopologyError> {
19 tokio::time::timeout(CONTROL_TIMEOUT, async {
20 let before = self.load_record().await?.ok_or(TopologyError::Fenced)?;
21 let mut roots = Vec::new();
22 for operation in before
23 .topology_operations
24 .iter()
25 .filter(|operation| operation.has_target_commit())
26 {
27 Box::pin(self.audit_topology_operation(operation)).await?;
28 let (outcome, _) =
29 Box::pin(self.load_retained_topology_root_checkpoint(operation)).await?;
30 roots.push(outcome.committed_checkpoint.ok_or(TopologyError::Fenced)?);
31 }
32 let after = self.load_record().await?.ok_or(TopologyError::Fenced)?;
33 if before.lease.proof() != after.lease.proof()
34 || before.topology_operations != after.topology_operations
35 {
36 return Err(TopologyError::Fenced);
37 }
38 roots.sort_by_key(|reference| (reference.epoch, reference.checkpoint_id));
39 roots.dedup();
40 Ok(roots)
41 })
42 .await
43 .map_err(|_| TopologyError::ReadTimedOut)?
44 }
45
46 pub(super) async fn load_retained_topology_root_checkpoint(
49 &self,
50 operation: &TopologyAdmissionStatus,
51 ) -> Result<(CheckpointOutcome, CommittedCheckpointIndex), TopologyError> {
52 if !operation.has_target_commit() {
53 return Err(TopologyError::Fenced);
54 }
55 let cut = operation.cut.as_ref().ok_or(TopologyError::Fenced)?;
56 let commit = cut.committed.as_ref().ok_or(TopologyError::Fenced)?;
57 let record = read_authority_record(self.store.as_ref(), commit.authority_sequence)
58 .await?
59 .ok_or_else(|| {
60 TopologyError::Invalid("retained root Commit anchor is missing".into())
61 })?;
62 let outcome = record.checkpoint_outcome.ok_or(TopologyError::Fenced)?;
63 if !outcome.is_commit()
64 || outcome.committed_checkpoint.as_ref() != Some(&commit.checkpoint)
65 || outcome.assignment_fence != cut.inventory.assignment_fence
66 || outcome.deployment_id != cut.inventory.deployment_id
67 {
68 return Err(TopologyError::Invalid(
69 "retained root differs from its exact immutable Commit".into(),
70 ));
71 }
72 let index = CheckpointDecisionStore::new(self.store.clone())
73 .validate_committed_checkpoint_for_outcome(&outcome)
74 .await
75 .map_err(|error| topology_checkpoint_error(error.into()))?;
76 if index.pipeline_identity != cut.inventory.pipeline_identity {
77 return Err(TopologyError::Invalid(
78 "retained root differs from its historical pipeline identity".into(),
79 ));
80 }
81 Ok((outcome, index))
82 }
83}
84
85impl LeaderLeaseStore {
86 pub(super) async fn retain_topology_history(
87 &self,
88 head: &super::LeaderAuthorityRecord,
89 retained: &mut std::collections::BTreeSet<u64>,
90 ) -> Result<(), super::LeaseError> {
91 if let Some(baseline) = head.topology_baseline.as_ref() {
92 self.audit_topology_adoption(baseline).await?;
93 retained.insert(baseline.authority_sequence);
94 }
95 for operation in &head.topology_operations {
96 self.audit_topology_operation(operation).await?;
97 retained.insert(operation.admitted_sequence);
98 retained.insert(operation.status_sequence);
99 retained.extend(
100 operation
101 .target_preparations
102 .iter()
103 .map(|receipt| receipt.authority_sequence),
104 );
105 if let Some(commit) = &operation.commit {
106 retained.insert(commit.authority_sequence);
107 }
108 if let Some(activation) = &operation.activation {
109 retained.insert(activation.authority_sequence);
110 retained.extend(
111 activation
112 .installations
113 .iter()
114 .map(|receipt| receipt.authority_sequence),
115 );
116 if let Some(release) = &activation.release {
117 retained.insert(release.authority_sequence);
118 }
119 }
120 if let Some(root) = &operation.migration_root {
121 retained.insert(root.authority_sequence);
122 }
123 if let Some(preparation) = &operation.preparation {
124 retained.extend(
125 preparation
126 .certificates
127 .iter()
128 .map(|certificate| certificate.authority_sequence),
129 );
130 }
131 if let Some(cut) = &operation.cut {
132 retained.insert(cut.bound_sequence);
133 if let Some(commit) = &cut.committed {
134 retained.insert(commit.authority_sequence);
135 }
136 }
137 }
138 Ok(())
139 }
140}
141
142impl super::LeaderAuthorityRecord {
143 pub(super) fn cleanup_is_pinned(&self, protected: &CommittedCheckpointRef) -> bool {
144 if self.assignment_handoff_pin.as_ref().is_some_and(|pin| {
145 protected.epoch > pin.checkpoint.epoch
146 && protected.checkpoint_id > pin.checkpoint.checkpoint_id
147 }) {
148 return true;
149 }
150 if self.topology_cut_blocks_cleanup(protected) {
151 return true;
152 }
153 if self
154 .outcome_floor
155 .as_ref()
156 .is_some_and(|floor| floor.artifact_before_epoch >= protected.epoch)
157 {
158 return true;
159 }
160 false
161 }
162}
163
164impl LeaderLeaseStore {
165 pub(super) async fn audit_topology_cleanup_roots(&self) -> Result<(), super::DecisionError> {
166 Box::pin(self.retained_topology_checkpoints())
167 .await
168 .map_err(|error| super::DecisionError::Conflict(error.to_string()))?;
169 Ok(())
170 }
171}