laminar_core/cluster/control/leader_lease/
attempt_status.rs1use crate::checkpoint::{CheckpointAssignmentFence, CheckpointAttempt, LeaderProof};
4use crate::checkpoint_decision::{CheckpointOutcome, DecisionError};
5
6use super::{ClusterCheckpointAuthorityError, LeaderLeaseStore};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum ClusterAttemptStatus {
11 Pending,
13 Settled(Box<CheckpointOutcome>),
15 CommitFenced,
21}
22
23fn settlement_from_outcomes(
24 attempt: CheckpointAttempt,
25 outcomes: &[CheckpointOutcome],
26) -> Option<CheckpointOutcome> {
27 if let Ok(index) = outcomes.binary_search_by_key(&attempt.epoch, |outcome| outcome.epoch) {
28 return Some(outcomes[index].clone());
29 }
30 outcomes
31 .last()
32 .filter(|highest| highest.checkpoint_id > attempt.checkpoint_id)
33 .cloned()
34}
35
36impl LeaderLeaseStore {
37 pub async fn cluster_attempt_settlement(
45 &self,
46 attempt: CheckpointAttempt,
47 ) -> Result<Option<CheckpointOutcome>, ClusterCheckpointAuthorityError> {
48 if !attempt.is_canonical() {
49 return Err(DecisionError::Conflict(
50 "cluster checkpoint settlement requires one nonzero canonical checkpoint ID".into(),
51 )
52 .into());
53 }
54 let outcomes = self.audited_cluster_outcomes().await?.1;
55 Ok(settlement_from_outcomes(attempt, &outcomes))
56 }
57
58 pub async fn cluster_attempt_status(
69 &self,
70 attempt: CheckpointAttempt,
71 assignment_fence: &CheckpointAssignmentFence,
72 leader_proof: &LeaderProof,
73 ) -> Result<ClusterAttemptStatus, ClusterCheckpointAuthorityError> {
74 if !attempt.is_canonical() {
75 return Err(DecisionError::Conflict(
76 "cluster checkpoint status requires one nonzero canonical checkpoint ID".into(),
77 )
78 .into());
79 }
80 if !assignment_fence.is_canonical() {
81 return Err(DecisionError::Conflict(
82 "cluster checkpoint status requires a canonical assignment fence".into(),
83 )
84 .into());
85 }
86 if !leader_proof.is_canonical() {
87 return Err(ClusterCheckpointAuthorityError::Fenced);
88 }
89 if assignment_fence.participant_incarnation(leader_proof.owner.node_id)
90 != Some(leader_proof.owner.boot_id)
91 {
92 return Err(DecisionError::Conflict(
93 "cluster checkpoint status leader is outside the assignment fence".into(),
94 )
95 .into());
96 }
97
98 let (head, outcomes) = self.audited_cluster_outcomes().await?;
99 if let Some(settlement) = settlement_from_outcomes(attempt, &outcomes) {
100 return Ok(ClusterAttemptStatus::Settled(Box::new(settlement)));
101 }
102 let head = head.ok_or(ClusterCheckpointAuthorityError::Fenced)?;
103 let active = head.active_checkpoint_artifacts.as_ref().ok_or_else(|| {
104 DecisionError::Conflict(format!(
105 "unsettled cluster checkpoint {} has no active artifact inventory",
106 attempt.checkpoint_id
107 ))
108 })?;
109 if active.attempt != attempt
110 || active.assignment_fence.as_ref() != Some(assignment_fence)
111 || head.active_checkpoint_artifact_leader_proof.as_ref() != Some(leader_proof)
112 {
113 return Err(DecisionError::Conflict(format!(
114 "unsettled cluster checkpoint {} does not match its admitted artifact authority",
115 attempt.checkpoint_id
116 ))
117 .into());
118 }
119 if head.lease.matches_proof(leader_proof) {
120 Ok(ClusterAttemptStatus::Pending)
121 } else {
122 Ok(ClusterAttemptStatus::CommitFenced)
123 }
124 }
125}