laminar_core/cluster/control/topology/
recovery.rs1use super::{TopologyCommit, TopologyError, TopologyRestoreInput};
4use crate::checkpoint::CommittedCheckpointIndex;
5use crate::checkpoint_decision::CheckpointOutcome;
6
7pub const TOPOLOGY_RECOVERY_PROTOCOL_VERSION: u16 = 6;
10
11#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[serde(deny_unknown_fields)]
15pub struct TopologyRecoveryBinding {
16 protocol_version: u16,
17 commit: TopologyCommit,
18 processes: Vec<crate::cluster::control::LocalProcessAuthorityIdentity>,
19}
20
21impl TopologyRecoveryBinding {
22 pub(crate) fn new(
23 commit: TopologyCommit,
24 processes: Vec<crate::cluster::control::LocalProcessAuthorityIdentity>,
25 ) -> Result<Self, TopologyError> {
26 let binding = Self {
27 protocol_version: TOPOLOGY_RECOVERY_PROTOCOL_VERSION,
28 commit,
29 processes,
30 };
31 binding.validate()?;
32 Ok(binding)
33 }
34
35 #[must_use]
37 pub const fn commit(&self) -> &TopologyCommit {
38 &self.commit
39 }
40
41 #[must_use]
43 pub fn processes(&self) -> &[crate::cluster::control::LocalProcessAuthorityIdentity] {
44 &self.processes
45 }
46
47 pub(crate) fn validate(&self) -> Result<(), TopologyError> {
48 self.commit.validate()?;
49 if self.protocol_version != TOPOLOGY_RECOVERY_PROTOCOL_VERSION {
50 return Err(TopologyError::Protocol(
51 "topology-bound recovery requires protocol six".into(),
52 ));
53 }
54 if self.processes.is_empty()
55 || self.processes.len() > crate::checkpoint::MAX_CHECKPOINT_PARTICIPANTS
56 || self.processes.iter().any(|process| !process.is_canonical())
57 || self
58 .processes
59 .windows(2)
60 .any(|pair| pair[0].participant.node_id >= pair[1].participant.node_id)
61 {
62 return Err(TopologyError::Invalid(
63 "topology recovery process roster is not canonical".into(),
64 ));
65 }
66 Ok(())
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum TopologyRecoveryCut {
73 MigrationRoot,
75 TargetCheckpoint,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct TopologyRecoveryInput {
84 pub(crate) migration: TopologyRestoreInput,
85 pub(crate) target_checkpoint: Option<Box<(CheckpointOutcome, CommittedCheckpointIndex)>>,
86}
87
88impl TopologyRecoveryInput {
89 #[must_use]
91 pub const fn migration(&self) -> &TopologyRestoreInput {
92 &self.migration
93 }
94
95 #[must_use]
97 pub const fn cut(&self) -> TopologyRecoveryCut {
98 if self.target_checkpoint.is_some() {
99 TopologyRecoveryCut::TargetCheckpoint
100 } else {
101 TopologyRecoveryCut::MigrationRoot
102 }
103 }
104
105 #[must_use]
107 pub fn outcome(&self) -> &CheckpointOutcome {
108 self.target_checkpoint
109 .as_deref()
110 .map_or_else(|| self.migration.outcome(), |(outcome, _)| outcome)
111 }
112
113 #[must_use]
116 pub fn checkpoint(&self) -> &CommittedCheckpointIndex {
117 self.target_checkpoint
118 .as_deref()
119 .map_or_else(|| self.migration.checkpoint(), |(_, checkpoint)| checkpoint)
120 }
121
122 #[must_use]
125 pub fn same_restore_requirements(&self, other: &Self) -> bool {
126 self.migration.same_restore_requirements(&other.migration)
127 && self.target_checkpoint == other.target_checkpoint
128 }
129}