laminar_core/cluster/control/topology/
checkpoint_continuity.rs1use super::{
4 ClusterTopologyObjectTransition, ClusterTopologyValidation, TopologyError,
5 TopologyMigrationRoot,
6};
7use crate::checkpoint::{CheckpointScope, CommittedCheckpointIndex};
8use crate::cluster::control::CatalogObjectKind;
9
10impl TopologyMigrationRoot {
11 pub fn validate_target_checkpoint_predecessor(
19 &self,
20 descriptor: &ClusterTopologyValidation,
21 target: &CommittedCheckpointIndex,
22 parent: &CommittedCheckpointIndex,
23 ) -> Result<(), TopologyError> {
24 target.validate().map_err(TopologyError::Invalid)?;
25 parent.validate().map_err(TopologyError::Invalid)?;
26 let (_, parent_ref) = parent
27 .encode_and_reference()
28 .map_err(TopologyError::Invalid)?;
29 let parent_assignment = parent
30 .assignment_fence
31 .as_ref()
32 .ok_or(TopologyError::Fenced)?;
33 let target_assignment = target
34 .assignment_fence
35 .as_ref()
36 .ok_or(TopologyError::Fenced)?;
37 let sources = descriptor
38 .objects
39 .iter()
40 .filter(|object| {
41 object.kind == CatalogObjectKind::Source
42 && object.transition != ClusterTopologyObjectTransition::Remove
43 })
44 .map(|object| object.name.clone())
45 .collect::<Vec<_>>();
46 let parent_sources = descriptor
47 .objects
48 .iter()
49 .filter(|object| {
50 object.kind == CatalogObjectKind::Source
51 && object.transition != ClusterTopologyObjectTransition::AddFutureOnly
52 })
53 .map(|object| object.name.clone())
54 .collect::<Vec<_>>();
55 if self.cut.checkpoint != parent_ref
56 || target.predecessor.as_ref() != Some(&parent_ref)
57 || target.version < parent.version
58 || target.epoch <= parent.epoch
59 || parent.pipeline_identity != descriptor.parent_pipeline
60 || target.pipeline_identity != descriptor.target_pipeline
61 || parent.deployment_id != descriptor.deployment_id
62 || target.deployment_id != descriptor.deployment_id
63 || parent.scope != CheckpointScope::Cluster
64 || target.scope != CheckpointScope::Cluster
65 || parent.vnode_count != target.vnode_count
66 || parent.source_names != parent_sources
67 || target.source_names != sources
68 || target_assignment.assignment_version < parent_assignment.assignment_version
69 || target_assignment.assignment_digest != parent_assignment.assignment_digest
70 || target_assignment.vnode_count != parent_assignment.vnode_count
71 || target_assignment.partitioning_abi_version
72 != parent_assignment.partitioning_abi_version
73 || !target_assignment
74 .participants
75 .iter()
76 .map(|participant| participant.node_id)
77 .eq(parent_assignment
78 .participants
79 .iter()
80 .map(|participant| participant.node_id))
81 {
82 return Err(TopologyError::Invalid(
83 "first target checkpoint does not continue the exact authorized migration root"
84 .into(),
85 ));
86 }
87 let inherited_sources = self
88 .preserved_objects
89 .iter()
90 .filter(|object| object.kind == CatalogObjectKind::Source)
91 .map(|object| object.name.clone())
92 .collect::<Vec<_>>();
93 target
94 .validate_source_watermark_continuity(parent, &inherited_sources)
95 .and_then(|()| target.validate_view_frontier_continuity(parent))
96 .map_err(TopologyError::Invalid)
97 }
98}