krabka_verified/reconfiguration.rs
1//! `KRaft` voter-reconfiguration admission and result shape.
2//!
3//! The decision follows the order in which Kafka's `KafkaRaftClient`,
4//! `AddVoterHandler`, `RemoveVoterHandler`, `UpdateVoterHandler`, and
5//! `LeaderState.maybeAppendUpgradedKRaftVersion` reject a request, and it
6//! admits a change only while KIP-853's one-change-at-a-time rule holds: the
7//! node leads, its epoch is committed, no earlier change is pending, and no
8//! control record is uncommitted.
9
10#[cfg(creusot)]
11use creusot_std::prelude::*;
12
13/// The KIP-853 control operation being validated.
14#[krabka_macros::model_type]
15pub enum VoterChangeKind {
16 Add,
17 Remove,
18 Update,
19 FinalizeKraftVersion,
20}
21
22/// The receiving node's leadership state for one reconfiguration.
23#[krabka_macros::model_type]
24pub struct ReconfigurationLeadership {
25 /// The node is the current `KRaft` leader.
26 pub is_leader: bool,
27 /// No earlier voter change is waiting for its record to commit.
28 pub no_pending_change: bool,
29 /// The leader has committed its own epoch, so Kafka's leader HWM exists.
30 pub epoch_committed: bool,
31}
32
33/// The committed voter set the change applies to.
34#[krabka_macros::model_type]
35pub struct CurrentVoterSet {
36 pub voter_count: usize,
37 /// The committed `kraft.version`.
38 pub kraft_version: u16,
39 /// The latest voter set and `kraft.version` in the log equal the
40 /// committed ones: no control record is uncommitted.
41 pub latest_controls_committed: bool,
42 /// Every current voter's supported range covers the requested
43 /// `kraft.version`. Only a finalization reads it.
44 pub all_voters_support_requested: bool,
45}
46
47/// One requested control operation.
48#[krabka_macros::model_type]
49pub struct VoterChangeRequest {
50 pub kind: VoterChangeKind,
51 /// The `kraft.version` a finalization asks for.
52 pub requested_kraft_version: u16,
53}
54
55/// How the request's voter key relates to the current voter set.
56#[krabka_macros::model_type]
57pub enum TargetMembership {
58 /// No current voter has the request's voter id.
59 Absent,
60 /// The voter id is current, and its stored directory id is unknown.
61 PresentUnknownDirectory,
62 /// The voter id is current, with the request's directory id.
63 PresentSameDirectory,
64 /// The voter id is current, with a different known directory id.
65 PresentOtherDirectory,
66}
67
68/// Facts about the voter an add, remove, or update names. A finalization
69/// names no voter and does not read them.
70#[krabka_macros::model_type]
71pub struct TargetVoter {
72 pub membership: TargetMembership,
73 /// The voter's supported range covers the committed `kraft.version`.
74 pub version_compatible: bool,
75 /// The voter has fetched up to the leader's log end.
76 pub caught_up: bool,
77}
78
79/// The exact control-record shape of an admitted change.
80#[krabka_macros::model_type]
81pub struct VoterReconfigurationPlan {
82 pub next_voter_count: usize,
83 pub next_kraft_version: u16,
84 pub write_voters: bool,
85 pub write_kraft_version: bool,
86 pub preflight_only: bool,
87}
88
89#[krabka_macros::model_type]
90pub enum VoterReconfigurationDecision {
91 NotLeader,
92 InProgress,
93 EpochUncommitted,
94 EmptyCurrentVoterSet,
95 UnsupportedKraftVersion,
96 DuplicateVoter,
97 IncompatibleVoter,
98 VoterNotCaughtUp,
99 VoterNotFound,
100 DirectoryMismatch,
101 InvalidVersionTransition,
102 Admit(VoterReconfigurationPlan),
103}
104
105/// The leadership and committed voter-set snapshot captured for one control change.
106pub type ReconfigurationState = (ReconfigurationLeadership, CurrentVoterSet);
107
108mod admitted_plan;
109#[cfg(creusot)]
110pub use admitted_plan::{
111 admitted_plan, is_admit, may_reconfigure, update_key_matches, voter_reconfiguration_rejection,
112 voter_set_removes,
113};
114
115mod voter_reconfiguration_decision;
116pub use voter_reconfiguration_decision::voter_reconfiguration_decision;
117
118#[cfg(test)]
119mod tests;
120
121#[cfg(test)]
122pub(crate) mod test_support;