krabka_raft/kraft/controller/
control_state.rs1use {
6 super::{KraftControlState, records::decode_control_record},
7 crate::{
8 error::{MetadataReplayError, RaftError},
9 kraft::types::NodeId,
10 voter_wire::{from_wire, record, to_wire},
11 },
12 krabka_ids::Offset,
13 krabka_metadata::{
14 KRaftVersionRecord, MetadataRecord, VoterSet, VotersRecord,
15 voters::{KRaftVersionRange, Voter},
16 },
17 krabka_protocol::{
18 owned::voters_record::VotersRecord as WireVotersRecord,
19 records::{RecordBatch, metadata::control::ControlRecord},
20 },
21 krabka_verified::raft,
22 std::collections::{BTreeMap, BTreeSet},
23 uuid::Uuid,
24};
25
26fn history_value_before<T>(history: &BTreeMap<i64, T>, frontier: i64) -> Option<&T> {
27 let offsets: Vec<i64> = history.keys().copied().collect();
28 let prefix_len = raft::control_history_frontier(&offsets, frontier);
29 let last = offsets.get(prefix_len.checked_sub(1)?)?;
30 history.get(last)
31}
32
33fn truncate_history<T>(history: &mut BTreeMap<i64, T>, frontier: i64) {
34 let offsets: Vec<i64> = history.keys().copied().collect();
35 let prefix_len = raft::control_history_frontier(&offsets, frontier);
36 if let Some(first_dropped) = offsets.get(prefix_len) {
37 drop(history.split_off(first_dropped));
38 }
39}
40
41impl KraftControlState {
42 pub fn new(voters: VoterSet, version: u16) -> Self {
43 Self {
44 voter_history: maplit::btreemap! {-1 => voters.clone()},
45 version_history: maplit::btreemap! {-1 => version},
46 committed_voters: voters,
47 committed_version: version,
48 }
49 }
50
51 pub fn latest_voters(&self) -> &VoterSet {
52 self.voter_history
53 .last_key_value()
54 .map_or(&self.committed_voters, |(_, voters)| voters)
55 }
56
57 pub fn latest_version(&self) -> u16 {
58 self.version_history
59 .last_key_value()
60 .map_or(self.committed_version, |(_, version)| *version)
61 }
62
63 pub fn voters_at(&self, end_offset: Offset) -> VoterSet {
64 history_value_before(&self.voter_history, end_offset.0)
65 .map_or_else(|| self.committed_voters.clone(), Clone::clone)
66 }
67
68 pub fn version_at(&self, end_offset: Offset) -> u16 {
69 history_value_before(&self.version_history, end_offset.0)
70 .map_or(self.committed_version, |version| *version)
71 }
72
73 pub fn apply(&mut self, offset: i64, record: &ControlRecord) -> Result<(), RaftError> {
74 match record {
75 ControlRecord::KRaftVersion(record) => {
76 let version = u16::try_from(record.k_raft_version).map_err(|_| {
77 RaftError::ChangeRejected("negative kraft.version control record".into())
78 })?;
79 self.version_history.insert(offset, version);
80 }
81 ControlRecord::Voters(record) => {
82 self.voter_history
83 .insert(offset, voter_set_from_wire(record)?);
84 }
85 _ => {}
86 }
87 Ok(())
88 }
89
90 pub fn truncate_to(&mut self, offset: i64) {
91 truncate_history(&mut self.voter_history, offset);
92 truncate_history(&mut self.version_history, offset);
93 }
94
95 pub fn commit_to(&mut self, high_watermark: i64) -> bool {
96 let voters = history_value_before(&self.voter_history, high_watermark)
97 .map_or_else(|| self.committed_voters.clone(), Clone::clone);
98 let version = history_value_before(&self.version_history, high_watermark)
99 .map_or(self.committed_version, |version| *version);
100 let changed = voters != self.committed_voters || version != self.committed_version;
101 self.committed_voters = voters;
102 self.committed_version = version;
103 changed
104 }
105}
106
107pub fn voter_supports_version(voter: &Voter, version: u16) -> bool {
108 voter.kraft_version.min <= version && version <= voter.kraft_version.max
109}
110
111pub fn voter_set_to_wire(voters: &VoterSet) -> WireVotersRecord {
112 let voters = voters
113 .iter()
114 .map(|voter| {
115 to_wire(
116 voter,
117 i32::try_from(voter.id.0).unwrap_or(i32::MAX),
118 i16::try_from(voter.kraft_version.min).unwrap_or(i16::MAX),
119 i16::try_from(voter.kraft_version.max).unwrap_or(i16::MAX),
120 )
121 })
122 .collect();
123 record(voters)
124}
125
126pub fn voter_set_from_wire(record: &WireVotersRecord) -> Result<VoterSet, RaftError> {
127 let mut voter_ids = BTreeSet::new();
128 let voter_ids_unique = record
129 .voters
130 .iter()
131 .all(|voter| voter_ids.insert(voter.voter_id));
132 match krabka_verified::voter_set_wire_decision(
133 record.version,
134 record.voters.len(),
135 voter_ids_unique,
136 ) {
137 krabka_verified::VoterSetWireDecision::UnsupportedRecordVersion => {
138 return Err(RaftError::InvalidVoterUpdate(format!(
139 "unsupported VotersRecord version {}",
140 record.version
141 )));
142 }
143 krabka_verified::VoterSetWireDecision::Empty => {
144 return Err(RaftError::InvalidVoterUpdate(
145 "empty voter set in VotersRecord".into(),
146 ));
147 }
148 krabka_verified::VoterSetWireDecision::DuplicateId => {
149 return Err(RaftError::InvalidVoterUpdate(
150 "duplicate voter id in VotersRecord".into(),
151 ));
152 }
153 krabka_verified::VoterSetWireDecision::Accept => {}
154 }
155 let voters = record
156 .voters
157 .iter()
158 .map(|voter| {
159 let directory_id = Uuid::from_bytes(voter.voter_directory_id.0);
160 let directory_id_exact = directory_id.as_bytes() == &voter.voter_directory_id.0;
161 let mut endpoint_names = BTreeSet::new();
162 let endpoints_valid = voter.endpoints.iter().all(|endpoint| {
163 !endpoint.name.is_empty()
164 && !endpoint.host.is_empty()
165 && endpoint.port != 0
166 && endpoint_names.insert(endpoint.name.as_str())
167 });
168 match krabka_verified::voter_wire_decision(
169 voter.voter_id,
170 directory_id_exact,
171 voter.endpoints.len(),
172 endpoints_valid,
173 voter.k_raft_version_feature.min_supported_version,
174 voter.k_raft_version_feature.max_supported_version,
175 ) {
176 krabka_verified::VoterWireDecision::NegativeId => {
177 return Err(RaftError::InvalidVoterUpdate(
178 "negative voter id in VotersRecord".into(),
179 ));
180 }
181 krabka_verified::VoterWireDecision::DirectoryMismatch => {
182 return Err(RaftError::InvalidVoterUpdate(
183 "voter directory id changed during decoding".into(),
184 ));
185 }
186 krabka_verified::VoterWireDecision::InvalidEndpoint => {
187 return Err(RaftError::InvalidVoterUpdate(
188 "voter endpoints must be nonempty, uniquely named, and have a name, host, and nonzero port".into(),
189 ));
190 }
191 krabka_verified::VoterWireDecision::InvalidVersionRange => {
192 return Err(RaftError::InvalidVoterUpdate(
193 "invalid kraft.version range in VotersRecord".into(),
194 ));
195 }
196 krabka_verified::VoterWireDecision::Accept => {}
197 }
198 let id = u64::try_from(voter.voter_id).map_err(|_| {
199 RaftError::InvalidVoterUpdate("voter id must be nonnegative".into())
200 })?;
201 let invalid_range =
202 |_| RaftError::InvalidVoterUpdate("invalid kraft.version range in VotersRecord".into());
203 let min = u16::try_from(voter.k_raft_version_feature.min_supported_version)
204 .map_err(invalid_range)?;
205 let max = u16::try_from(voter.k_raft_version_feature.max_supported_version)
206 .map_err(invalid_range)?;
207 Ok(from_wire(
208 voter,
209 NodeId(id),
210 directory_id,
211 KRaftVersionRange { min, max },
212 ))
213 })
214 .collect::<Result<Vec<_>, RaftError>>()?;
215 Ok(VoterSet::from_voters(voters))
216}
217
218pub fn control_batch_image_records(
236 batch: &RecordBatch,
237) -> Result<Vec<MetadataRecord>, MetadataReplayError> {
238 if !batch.attributes.is_control_batch() {
239 return Ok(Vec::new());
240 }
241 let mut records = Vec::new();
242 for record in &batch.records {
243 let offset = batch
244 .base_offset
245 .saturating_add(i64::from(record.offset_delta));
246 let invalid = |reason: String| MetadataReplayError::InvalidControlRecord { offset, reason };
247 match decode_control_record(record).map_err(|error| invalid(error.to_string()))? {
248 ControlRecord::KRaftVersion(record) => {
249 let kraft_version = u16::try_from(record.k_raft_version).map_err(|_| {
250 invalid(format!("negative kraft.version {}", record.k_raft_version))
251 })?;
252 records.push(MetadataRecord::V1KRaftVersion(KRaftVersionRecord {
253 kraft_version,
254 }));
255 }
256 ControlRecord::Voters(record) => {
257 records.push(MetadataRecord::V1Voters(VotersRecord {
258 voters: voter_set_from_wire(&record)
259 .map_err(|error| invalid(error.to_string()))?,
260 }));
261 }
262 _ => {}
263 }
264 }
265 Ok(records)
266}
267
268#[cfg(test)]
269mod tests {
270 use {
271 super::*,
272 krabka_metadata::voters::{KRaftVersionRange, Voter, VoterEndpoint},
273 };
274
275 fn versioned_voter() -> Voter {
276 let ep = VoterEndpoint {
277 name: "CONTROLLER".into(),
278 host: "127.0.0.1".into(),
279 port: 9092,
280 };
281 Voter {
282 id: NodeId(1),
283 directory_id: uuid::Uuid::nil(),
284 endpoints: vec![ep],
285 kraft_version: KRaftVersionRange { min: 2, max: 4 },
286 }
287 }
288
289 #[test]
290 fn voter_supports_version_checks_both_bounds() {
291 let voter = versioned_voter();
292 assert2::check!(!voter_supports_version(&voter, 1));
293 assert2::check!(voter_supports_version(&voter, 2));
294 assert2::check!(voter_supports_version(&voter, 3));
295 assert2::check!(voter_supports_version(&voter, 4));
296 assert2::check!(!voter_supports_version(&voter, 5));
297 }
298
299 #[test]
300 fn voter_set_to_wire_encodes_version_and_features() {
301 let voter = versioned_voter();
302 let set = VoterSet::from_voters([voter]);
303 let wire = voter_set_to_wire(&set);
304 assert2::assert!(wire.version == 0);
305 assert2::assert!(wire.voters[0].k_raft_version_feature.min_supported_version == 2);
306 assert2::assert!(wire.voters[0].k_raft_version_feature.max_supported_version == 4);
307 }
308}