laminar_core/cluster/control/topology/
activation.rs1use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6use super::{TopologyAdmissionPhase, TopologyAdmissionStatus, TopologyError};
7use crate::checkpoint::{CheckpointAssignmentFence, LeaderProof};
8use crate::cluster::control::LocalProcessAuthorityIdentity;
9
10pub const TOPOLOGY_INSTALLATION_PROTOCOL_VERSION: u16 = 5;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(deny_unknown_fields)]
17pub struct TopologyInstallationReceipt {
18 pub process: LocalProcessAuthorityIdentity,
20 pub runtime_id: Uuid,
22 pub protocol_version: u16,
24 pub authority_sequence: u64,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(deny_unknown_fields)]
32pub struct TopologyRelease {
33 pub authority_sequence: u64,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(deny_unknown_fields)]
41pub struct TopologyActivation {
42 pub leader: LeaderProof,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub recovery_round: Option<crate::cluster::control::RecoveryRoundId>,
48 pub assignment: CheckpointAssignmentFence,
50 pub processes: Vec<LocalProcessAuthorityIdentity>,
52 pub authority_sequence: u64,
54 pub installations: Vec<TopologyInstallationReceipt>,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub release: Option<TopologyRelease>,
59}
60
61impl TopologyActivation {
62 #[must_use]
64 pub fn installation_complete(&self) -> bool {
65 self.installations.len() == self.processes.len()
66 && self
67 .installations
68 .iter()
69 .zip(&self.processes)
70 .all(|(receipt, process)| receipt.process == *process)
71 }
72
73 pub(crate) fn validate(&self, commit: u64, status: u64) -> Result<(), TopologyError> {
74 if !self.leader.is_canonical()
75 || self.recovery_round.is_some_and(|round| {
76 round.generation == 0
77 || round.nonce.is_nil()
78 || round.driver.0 != self.leader.owner.node_id
79 })
80 || !self.assignment.is_canonical()
81 || self.authority_sequence <= commit
82 || self.authority_sequence > status
83 || self.processes.len() != self.assignment.participants.len()
84 || !self
85 .processes
86 .iter()
87 .zip(&self.assignment.participants)
88 .all(|(process, participant)| {
89 process.participant == *participant && process.process_term != 0
90 })
91 || !self.processes.iter().any(|process| {
92 process.participant.node_id == self.leader.owner.node_id
93 && process.participant.boot_incarnation == self.leader.owner.boot_id
94 })
95 || self.installations.is_empty()
96 || self.installations.len() > self.processes.len()
97 || !self
98 .installations
99 .iter()
100 .any(|receipt| receipt.authority_sequence == self.authority_sequence)
101 || self
102 .installations
103 .iter()
104 .enumerate()
105 .any(|(index, receipt)| {
106 self.installations[..index]
107 .iter()
108 .any(|prior| prior.authority_sequence == receipt.authority_sequence)
109 })
110 || !self.installations.windows(2).all(|pair| {
111 pair[0].process.participant.node_id < pair[1].process.participant.node_id
112 })
113 || self.installations.iter().any(|receipt| {
114 receipt.protocol_version != TOPOLOGY_INSTALLATION_PROTOCOL_VERSION
115 || receipt.runtime_id.is_nil()
116 || !self.processes.contains(&receipt.process)
117 || receipt.authority_sequence < self.authority_sequence
118 || receipt.authority_sequence > status
119 })
120 || self.release.as_ref().is_some_and(|release| {
121 !self.installation_complete()
122 || release.authority_sequence > status
123 || self
124 .installations
125 .iter()
126 .any(|receipt| receipt.authority_sequence >= release.authority_sequence)
127 })
128 {
129 return Err(TopologyError::Invalid(
130 "invalid exact-runtime installation or Release roster".into(),
131 ));
132 }
133 Ok(())
134 }
135}
136
137impl TopologyAdmissionStatus {
138 pub(crate) fn validate_activation(&self) -> Result<(), TopologyError> {
139 match (&self.activation, self.phase) {
140 (None, TopologyAdmissionPhase::Activating | TopologyAdmissionPhase::Active) => Err(
141 TopologyError::Invalid("activated phase has no installation round".into()),
142 ),
143 (None, _) => Ok(()),
144 (
145 Some(activation),
146 TopologyAdmissionPhase::Activating | TopologyAdmissionPhase::Active,
147 ) => {
148 let commit = self.commit.as_ref().ok_or_else(|| {
149 TopologyError::Invalid("installation has no topology Commit".into())
150 })?;
151 activation.validate(commit.authority_sequence, self.status_sequence)?;
152 let latest_evidence = activation.release.as_ref().map_or_else(
153 || {
154 activation
155 .installations
156 .iter()
157 .map(|receipt| receipt.authority_sequence)
158 .max()
159 },
160 |release| Some(release.authority_sequence),
161 );
162 if (self.phase == TopologyAdmissionPhase::Active) != activation.release.is_some()
163 || latest_evidence != Some(self.status_sequence)
164 {
165 return Err(TopologyError::Invalid(
166 "activation phase and Release disagree".into(),
167 ));
168 }
169 Ok(())
170 }
171 (Some(_), _) => Err(TopologyError::Invalid(
172 "installation outside committed activation".into(),
173 )),
174 }
175 }
176
177 pub(crate) fn validate_activation_successor(
178 &self,
179 after: &Self,
180 sequence: u64,
181 ) -> Result<(), TopologyError> {
182 if self.activation == after.activation {
183 return if self.has_target_commit() && self != after {
184 Err(TopologyError::Invalid(
185 "committed installation status can change only with its exact evidence append"
186 .into(),
187 ))
188 } else {
189 Ok(())
190 };
191 }
192 if self.commit.is_none()
193 || self.commit != after.commit
194 || !self.same_migration_binding(after)
195 || self.target_preparations != after.target_preparations
196 || after.status_sequence != sequence
197 {
198 return Err(TopologyError::Invalid(
199 "installation cannot change the committed migration binding".into(),
200 ));
201 }
202 after.validate_activation()?;
203 let next = after.activation.as_ref().ok_or_else(|| {
204 TopologyError::Invalid("authority cannot forget installation evidence".into())
205 })?;
206 match &self.activation {
207 None if self.phase == TopologyAdmissionPhase::Committed
208 && after.phase == TopologyAdmissionPhase::Activating
209 && next.authority_sequence == sequence
210 && next.installations.len() == 1
211 && next.installations[0].authority_sequence == sequence
212 && next.release.is_none() =>
213 {
214 Ok(())
215 }
216 Some(prior)
217 if prior.release.is_none()
218 && (prior.leader != next.leader
219 || (next.recovery_round.is_some()
220 && prior.recovery_round != next.recovery_round
221 && prior.recovery_round.is_none_or(|prior| {
222 next.recovery_round
223 .is_some_and(|next| next.generation > prior.generation)
224 })))
225 && after.phase == TopologyAdmissionPhase::Activating
226 && next.authority_sequence == sequence
227 && next.installations.len() == 1
228 && next.installations[0].authority_sequence == sequence
229 && next.release.is_none() =>
230 {
231 Ok(())
232 }
233 Some(prior)
234 if prior.leader == next.leader
235 && prior.recovery_round == next.recovery_round
236 && prior.assignment == next.assignment
237 && prior.processes == next.processes
238 && prior.authority_sequence == next.authority_sequence
239 && prior.release.is_none() =>
240 {
241 let receipt_append = after.phase == TopologyAdmissionPhase::Activating
242 && next.release.is_none()
243 && next.installations.len() == prior.installations.len() + 1
244 && prior
245 .installations
246 .iter()
247 .all(|receipt| next.installations.contains(receipt))
248 && next
249 .installations
250 .iter()
251 .filter(|receipt| !prior.installations.contains(receipt))
252 .all(|receipt| receipt.authority_sequence == sequence);
253 let release = after.phase == TopologyAdmissionPhase::Active
254 && prior.installations == next.installations
255 && next
256 .release
257 .as_ref()
258 .is_some_and(|release| release.authority_sequence == sequence);
259 if receipt_append || release {
260 Ok(())
261 } else {
262 Err(TopologyError::Invalid(
263 "authority cannot rewrite installed runtimes or Release".into(),
264 ))
265 }
266 }
267 _ => Err(TopologyError::Invalid(
268 "authority cannot rewind an installation round or Release".into(),
269 )),
270 }
271 }
272}