1use std::collections::BTreeMap;
5
6use krabka_protocol::owned::{
7 elect_leaders_request::{ElectLeadersRequest, TopicPartitions},
8 elect_leaders_response::ElectLeadersResponse,
9};
10
11use crate::{
12 AdminClient, AdminError, KafkaError, NOT_CONTROLLER, kafka_error_name, retry::ControllerRetry,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ElectionType {
18 Preferred,
21 Unclean,
24}
25
26impl ElectionType {
27 const fn wire(self) -> i8 {
28 match self {
29 Self::Preferred => 0,
30 Self::Unclean => 1,
31 }
32 }
33}
34
35pub type ElectionResults = BTreeMap<(String, i32), Result<(), KafkaError>>;
39
40fn elect_leaders_request(
44 election_type: ElectionType,
45 partitions: Option<&[(String, i32)]>,
46 timeout_ms: i32,
47) -> ElectLeadersRequest {
48 let topic_partitions = partitions.map(|partitions| {
49 let mut by_topic = BTreeMap::<&str, Vec<i32>>::new();
50 for (topic, partition) in partitions {
51 let entry = by_topic.entry(topic.as_str()).or_default();
52 if !entry.contains(partition) {
53 entry.push(*partition);
54 }
55 }
56 by_topic
57 .into_iter()
58 .map(|(topic, partitions)| TopicPartitions {
59 topic: topic.to_owned(),
60 partitions,
61 ..Default::default()
62 })
63 .collect()
64 });
65 ElectLeadersRequest {
66 election_type: election_type.wire(),
67 topic_partitions,
68 timeout_ms,
69 ..Default::default()
70 }
71}
72
73fn names_not_controller(response: &ElectLeadersResponse) -> bool {
77 response.error_code == NOT_CONTROLLER
78 || response
79 .replica_election_results
80 .iter()
81 .flat_map(|topic| &topic.partition_result)
82 .any(|partition| partition.error_code == NOT_CONTROLLER)
83}
84
85fn elect_leaders_results(response: ElectLeadersResponse) -> Result<ElectionResults, AdminError> {
89 if response.error_code != 0 {
90 return Err(AdminError::Broker {
91 api: "ElectLeaders",
92 code: response.error_code,
93 name: kafka_error_name(response.error_code),
94 message: None,
95 });
96 }
97 Ok(response
98 .replica_election_results
99 .into_iter()
100 .flat_map(|topic| {
101 let name = topic.topic;
102 topic.partition_result.into_iter().map(move |partition| {
103 let result = if partition.error_code == 0 {
104 Ok(())
105 } else {
106 Err(KafkaError {
107 code: partition.error_code,
108 name: kafka_error_name(partition.error_code),
109 message: partition.error_message,
110 })
111 };
112 ((name.clone(), partition.partition_id), result)
113 })
114 })
115 .collect())
116}
117
118impl AdminClient {
119 pub async fn elect_leaders(
146 &mut self,
147 election_type: ElectionType,
148 partitions: Option<&[(String, i32)]>,
149 ) -> Result<ElectionResults, AdminError> {
150 let mut retry = ControllerRetry::new("ElectLeaders", self.retry);
151 loop {
152 let request =
153 elect_leaders_request(election_type, partitions, retry.remaining_millis());
154 let response = match election_type {
155 ElectionType::Preferred => retry.bounded(self.conn.send(request)).await?,
156 ElectionType::Unclean => retry.bounded(self.conn.send_at_least(request, 1)).await?,
157 };
158 if !names_not_controller(&response) {
159 return elect_leaders_results(response);
160 }
161 retry.after_not_controller(self).await?;
162 }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use std::{
169 net::SocketAddr,
170 sync::{
171 Arc, Mutex,
172 atomic::{AtomicU16, Ordering},
173 },
174 };
175
176 use krabka_client_core::MockBroker;
177 use krabka_protocol::owned::{
178 api_versions_request, elect_leaders_request,
179 elect_leaders_response::{PartitionResult, ReplicaElectionResult},
180 metadata_request,
181 };
182
183 use super::*;
184 use crate::partition_leaders::test_support::{
185 ApiVersionRange, api_versions, decode_request, encode_response, fast_admin, orders_metadata,
186 };
187
188 fn orders(partitions: &[i32]) -> Vec<(String, i32)> {
189 partitions
190 .iter()
191 .map(|partition| ("orders".to_owned(), *partition))
192 .collect()
193 }
194
195 #[test]
196 fn request_groups_partitions_by_topic_or_asks_for_all() {
197 let keys = vec![
198 ("orders".to_owned(), 1),
199 ("audit".to_owned(), 0),
200 ("orders".to_owned(), 0),
201 ("orders".to_owned(), 1),
202 ];
203 for (name, election_type, partitions, expected) in [
204 (
205 "preferred for listed partitions",
206 ElectionType::Preferred,
207 Some(keys.as_slice()),
208 ElectLeadersRequest {
209 election_type: 0,
210 topic_partitions: Some(vec![
211 TopicPartitions {
212 topic: "audit".to_owned(),
213 partitions: vec![0],
214 ..Default::default()
215 },
216 TopicPartitions {
217 topic: "orders".to_owned(),
218 partitions: vec![1, 0],
219 ..Default::default()
220 },
221 ]),
222 timeout_ms: 30_000,
223 ..Default::default()
224 },
225 ),
226 (
227 "unclean for every partition",
228 ElectionType::Unclean,
229 None,
230 ElectLeadersRequest {
231 election_type: 1,
232 topic_partitions: None,
233 timeout_ms: 30_000,
234 ..Default::default()
235 },
236 ),
237 ] {
238 assert2::assert!(
239 elect_leaders_request(election_type, partitions, 30_000) == expected,
240 "case {name}"
241 );
242 }
243 }
244
245 fn election_answer(top_code: i16, code: i16) -> ElectLeadersResponse {
247 ElectLeadersResponse {
248 error_code: top_code,
249 replica_election_results: vec![ReplicaElectionResult {
250 topic: "orders".to_owned(),
251 partition_result: vec![PartitionResult {
252 partition_id: 0,
253 error_code: code,
254 error_message: (code != 0).then(|| "no election".to_owned()),
255 ..Default::default()
256 }],
257 ..Default::default()
258 }],
259 ..Default::default()
260 }
261 }
262
263 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
267 async fn elect_leaders_retries_not_controller_and_maps_each_partition() {
268 let not_needed = Err(KafkaError {
269 code: 84,
270 name: "ELECTION_NOT_NEEDED",
271 message: Some("no election".to_owned()),
272 });
273 for (name, election_type, answers, expected) in [
274 (
275 "success",
276 ElectionType::Preferred,
277 vec![(0, 0)],
278 (
279 Ok(maplit::btreemap! {
280 ("orders".to_owned(), 0) => Ok(()),
281 }),
282 1,
283 ),
284 ),
285 (
286 "election not needed is a partition result",
287 ElectionType::Unclean,
288 vec![(0, 84)],
289 (
290 Ok(maplit::btreemap! {
291 ("orders".to_owned(), 0) => not_needed,
292 }),
293 1,
294 ),
295 ),
296 (
297 "not controller finds the controller again",
298 ElectionType::Preferred,
299 vec![(41, 0), (0, NOT_CONTROLLER), (0, 0)],
300 (
301 Ok(maplit::btreemap! {
302 ("orders".to_owned(), 0) => Ok(()),
303 }),
304 3,
305 ),
306 ),
307 (
308 "cluster authorization failed is final",
309 ElectionType::Preferred,
310 vec![(31, 0)],
311 (Err(("ElectLeaders", 31)), 1),
312 ),
313 ] {
314 let requests = Arc::new(Mutex::new(Vec::<(i16, ElectLeadersRequest)>::new()));
315 let handler_requests = Arc::clone(&requests);
316 let port = Arc::new(AtomicU16::new(0));
317 let handler_port = Arc::clone(&port);
318 let broker = MockBroker::start(move |api_key, version, _, body| {
319 let own = SocketAddr::from(([127, 0, 0, 1], handler_port.load(Ordering::SeqCst)));
320 match api_key {
321 api_versions_request::API_KEY => Some(
322 api_versions()
323 .apis(vec![
324 ApiVersionRange::builder()
325 .api_key(elect_leaders_request::API_KEY)
326 .max_version(2)
327 .build(),
328 ])
329 .call(),
330 ),
331 metadata_request::API_KEY => {
332 Some(encode_response(&orders_metadata(own), version, true))
333 }
334 elect_leaders_request::API_KEY => {
335 let flexible = version >= elect_leaders_request::FLEXIBLE_MIN;
336 let mut requests = handler_requests.lock().expect("requests lock");
337 let (top, code) = answers[requests.len().min(answers.len() - 1)];
338 requests.push((version, decode_request(body, version, flexible)));
339 drop(requests);
340 Some(encode_response(
341 &election_answer(top, code),
342 version,
343 flexible,
344 ))
345 }
346 _ => None,
347 }
348 })
349 .await
350 .unwrap();
351 port.store(broker.addr.port(), Ordering::SeqCst);
352 let mut admin = fast_admin(broker.addr, krabka_units::secs(5)).await;
353
354 let result = admin
355 .elect_leaders(election_type, Some(&orders(&[0])))
356 .await
357 .map_err(|error| match error {
358 AdminError::Broker { api, code, .. } => (api, code),
359 other => panic!("case {name}: unexpected error {other:?}"),
360 });
361
362 broker.stop();
363 let requests = requests.lock().expect("requests lock").clone();
364 let shapes = requests
365 .iter()
366 .map(|(version, request)| {
367 (
368 *version,
369 ElectLeadersRequest {
370 timeout_ms: 0,
371 ..request.clone()
372 },
373 )
374 })
375 .collect::<Vec<_>>();
376 let expected_shape = (
377 2,
378 elect_leaders_request(election_type, Some(&orders(&[0])), 0),
379 );
380 assert2::assert!(
381 (result, shapes) == (expected.0, vec![expected_shape; expected.1]),
382 "case {name}"
383 );
384 assert2::assert!(
385 requests
386 .iter()
387 .all(|(_, request)| (1..=5_000).contains(&request.timeout_ms)),
388 "case {name}: the timeout is the time left of the call"
389 );
390 }
391 }
392
393 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
397 async fn unclean_election_needs_elect_leaders_v1() {
398 let requests = Arc::new(Mutex::new(0_usize));
399 let handler_requests = Arc::clone(&requests);
400 let broker = MockBroker::start(move |api_key, version, _, _| match api_key {
401 api_versions_request::API_KEY => Some(
402 api_versions()
403 .apis(vec![
404 ApiVersionRange::builder()
405 .api_key(elect_leaders_request::API_KEY)
406 .build(),
407 ])
408 .call(),
409 ),
410 elect_leaders_request::API_KEY => {
411 *handler_requests.lock().expect("requests lock") += 1;
412 Some(encode_response(&election_answer(0, 0), version, false))
413 }
414 _ => None,
415 })
416 .await
417 .unwrap();
418 let mut admin = fast_admin(broker.addr, krabka_units::secs(5)).await;
419
420 let result = admin
421 .elect_leaders(ElectionType::Unclean, Some(&orders(&[0])))
422 .await;
423
424 broker.stop();
425 assert2::assert!(matches!(
426 result,
427 Err(AdminError::Transport(
428 krabka_client_core::ClientError::IncompatibleVersion { .. }
429 ))
430 ));
431 assert2::assert!(*requests.lock().expect("requests lock") == 0);
432 }
433}