1use std::collections::HashMap;
22
23use kafka_protocol::messages::MetadataResponse;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct BrokerAddr {
28 pub node_id: i32,
29 pub host: String,
30 pub port: i32,
31}
32
33impl BrokerAddr {
34 #[must_use]
36 pub fn addr(&self) -> String {
37 format!("{}:{}", self.host, self.port)
38 }
39}
40
41pub type PartitionKey = (String, i32);
43
44#[derive(Debug, Default)]
46pub struct Metadata {
47 brokers: HashMap<i32, BrokerAddr>,
48 leaders: HashMap<PartitionKey, i32>,
49 partition_counts: HashMap<String, i32>,
59 controller: Option<i32>,
65}
66
67impl Metadata {
68 #[must_use]
69 pub fn new() -> Self {
70 Self::default()
71 }
72
73 pub fn update(&mut self, resp: &MetadataResponse) {
81 if resp.controller_id.0 >= 0 {
83 self.controller = Some(resp.controller_id.0);
84 }
85 for broker in &resp.brokers {
86 self.brokers.insert(
87 broker.node_id.0,
88 BrokerAddr {
89 node_id: broker.node_id.0,
90 host: broker.host.to_string(),
91 port: broker.port,
92 },
93 );
94 }
95 for topic in &resp.topics {
96 if topic.error_code != 0 {
97 continue;
98 }
99 let Some(name) = topic.name.as_ref() else {
100 continue;
101 };
102 self.partition_counts.insert(
103 name.0.to_string(),
104 i32::try_from(topic.partitions.len()).unwrap_or(i32::MAX),
105 );
106 for partition in &topic.partitions {
107 if partition.error_code != 0 || partition.leader_id.0 < 0 {
108 continue;
109 }
110 self.leaders.insert(
111 (name.0.to_string(), partition.partition_index),
112 partition.leader_id.0,
113 );
114 }
115 }
116 }
117
118 #[must_use]
120 pub fn leader_for(&self, topic: &str, partition: i32) -> Option<&BrokerAddr> {
121 let node = self.leaders.get(&(topic.to_owned(), partition))?;
122 self.brokers.get(node)
123 }
124
125 #[must_use]
128 pub fn broker(&self, node_id: i32) -> Option<&BrokerAddr> {
129 self.brokers.get(&node_id)
130 }
131
132 #[must_use]
134 pub fn controller(&self) -> Option<&BrokerAddr> {
135 self.brokers.get(&self.controller?)
136 }
137
138 pub fn invalidate_controller(&mut self) {
140 self.controller = None;
141 }
142
143 pub fn invalidate_partition(&mut self, topic: &str, partition: i32) {
145 self.leaders.remove(&(topic.to_owned(), partition));
146 }
147
148 #[must_use]
154 pub fn partition_count(&self, topic: &str) -> i32 {
155 self.partition_counts.get(topic).copied().unwrap_or(0)
156 }
157
158 pub fn forget_topic(&mut self, topic: &str) {
163 self.partition_counts.remove(topic);
164 self.leaders.retain(|(name, _), _| name != topic);
165 }
166
167 pub fn brokers(&self) -> impl Iterator<Item = &BrokerAddr> {
169 self.brokers.values()
170 }
171
172 #[must_use]
173 pub fn is_empty(&self) -> bool {
174 self.brokers.is_empty()
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use kafka_protocol::messages::metadata_response::{
182 MetadataResponseBroker, MetadataResponsePartition, MetadataResponseTopic,
183 };
184 use kafka_protocol::messages::{BrokerId, TopicName};
185 use kafka_protocol::protocol::StrBytes;
186
187 fn broker(node_id: i32, host: &str, port: i32) -> MetadataResponseBroker {
188 let mut b = MetadataResponseBroker::default();
189 b.node_id = BrokerId(node_id);
190 b.host = StrBytes::from_string(host.to_owned());
191 b.port = port;
192 b
193 }
194
195 fn topic(
196 name: &str,
197 error_code: i16,
198 partitions: Vec<MetadataResponsePartition>,
199 ) -> MetadataResponseTopic {
200 let mut t = MetadataResponseTopic::default();
201 t.name = Some(TopicName(StrBytes::from_string(name.to_owned())));
202 t.error_code = error_code;
203 t.partitions = partitions;
204 t
205 }
206
207 fn partition(index: i32, leader: i32, error_code: i16) -> MetadataResponsePartition {
208 let mut p = MetadataResponsePartition::default();
209 p.partition_index = index;
210 p.leader_id = BrokerId(leader);
211 p.error_code = error_code;
212 p
213 }
214
215 fn response(
216 brokers: Vec<MetadataResponseBroker>,
217 topics: Vec<MetadataResponseTopic>,
218 ) -> MetadataResponse {
219 let mut r = MetadataResponse::default();
220 r.brokers = brokers;
221 r.topics = topics;
222 r
223 }
224
225 #[test]
226 fn a_leader_is_resolved_to_an_address() {
227 let mut md = Metadata::new();
228 md.update(&response(
229 vec![broker(1, "kafka-1", 9092), broker(2, "kafka-2", 9092)],
230 vec![topic("t", 0, vec![partition(0, 2, 0)])],
231 ));
232 assert_eq!(md.leader_for("t", 0).unwrap().addr(), "kafka-2:9092");
233 assert!(md.leader_for("t", 1).is_none());
234 assert!(md.leader_for("other", 0).is_none());
235 }
236
237 #[test]
240 fn an_update_for_one_topic_leaves_the_others_alone() {
241 let mut md = Metadata::new();
242 md.update(&response(
243 vec![broker(1, "kafka-1", 9092)],
244 vec![topic("a", 0, vec![partition(0, 1, 0)])],
245 ));
246 md.update(&response(
247 vec![broker(1, "kafka-1", 9092)],
248 vec![topic("b", 0, vec![partition(0, 1, 0)])],
249 ));
250 assert!(md.leader_for("a", 0).is_some(), "topic a was forgotten");
251 assert!(md.leader_for("b", 0).is_some());
252 }
253
254 #[test]
256 fn a_moved_leader_is_picked_up() {
257 let mut md = Metadata::new();
258 md.update(&response(
259 vec![broker(1, "kafka-1", 9092), broker(2, "kafka-2", 9092)],
260 vec![topic("t", 0, vec![partition(0, 1, 0)])],
261 ));
262 assert_eq!(md.leader_for("t", 0).unwrap().node_id, 1);
263
264 md.update(&response(
265 vec![broker(1, "kafka-1", 9092), broker(2, "kafka-2", 9092)],
266 vec![topic("t", 0, vec![partition(0, 2, 0)])],
267 ));
268 assert_eq!(md.leader_for("t", 0).unwrap().node_id, 2);
269 }
270
271 #[test]
274 fn an_errored_partition_is_not_recorded() {
275 let mut md = Metadata::new();
276 md.update(&response(
277 vec![broker(1, "kafka-1", 9092)],
278 vec![topic("t", 0, vec![partition(0, 1, 9)])],
279 ));
280 assert!(md.leader_for("t", 0).is_none());
281 }
282
283 #[test]
285 fn a_leaderless_partition_is_not_recorded() {
286 let mut md = Metadata::new();
287 md.update(&response(
288 vec![broker(1, "kafka-1", 9092)],
289 vec![topic("t", 0, vec![partition(0, -1, 0)])],
290 ));
291 assert!(md.leader_for("t", 0).is_none());
292 }
293
294 #[test]
297 fn an_errored_topic_still_yields_its_brokers() {
298 let mut md = Metadata::new();
299 md.update(&response(
300 vec![broker(7, "kafka-7", 9092)],
301 vec![topic("t", 3, vec![partition(0, 7, 0)])],
302 ));
303 assert!(md.leader_for("t", 0).is_none());
304 assert_eq!(md.broker(7).unwrap().addr(), "kafka-7:9092");
305 }
306
307 #[test]
309 fn the_partition_count_follows_metadata() {
310 let mut md = Metadata::new();
311 assert_eq!(md.partition_count("t"), 0);
312 md.update(&response(
313 vec![broker(1, "kafka-1", 9092)],
314 vec![topic(
315 "t",
316 0,
317 vec![partition(0, 1, 0), partition(1, 1, 0), partition(2, 1, 0)],
318 )],
319 ));
320 assert_eq!(md.partition_count("t"), 3);
321 assert_eq!(md.partition_count("other"), 0);
322 }
323
324 #[test]
327 fn invalidating_one_partition_keeps_the_others() {
328 let mut md = Metadata::new();
329 md.update(&response(
330 vec![broker(1, "kafka-1", 9092)],
331 vec![topic("t", 0, vec![partition(0, 1, 0), partition(1, 1, 0)])],
332 ));
333 md.invalidate_partition("t", 0);
334 assert!(md.leader_for("t", 0).is_none());
335 assert!(md.leader_for("t", 1).is_some());
336 assert!(
337 md.broker(1).is_some(),
338 "the broker itself is still reachable and its connection stays open"
339 );
340 }
341}