use super::{
lazy_broker_connection::LazyBrokerConnection, BrokerList, ClientError, MetadataClient, Result,
};
use crate::{
clients::ClientConfig,
models::{NodeId, PartitionId, TopicName},
};
use itertools::Itertools;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::RwLock;
#[derive(Debug, Clone)]
pub struct TopicPartitionLeaders {
cache: Arc<RwLock<HashMap<(TopicName, PartitionId), LazyBrokerConnection>>>,
metadata_client: MetadataClient,
config: Arc<ClientConfig>,
}
impl TopicPartitionLeaders {
pub async fn create(config: ClientConfig) -> Result<Self> {
let metadata_client = MetadataClient::new(config.clone());
let cache = Default::default();
Ok(TopicPartitionLeaders {
cache,
metadata_client,
config: Arc::new(config),
})
}
pub async fn get_connection_to_leader(
&self,
topic_name: &TopicName,
partition_id: PartitionId,
) -> Result<LazyBrokerConnection> {
let key = (topic_name.clone(), partition_id);
let read_lock = self.cache.read().await;
if let Some(conn) = read_lock.get(&key).cloned() {
Ok(conn)
} else {
drop(read_lock);
let topic_leaders = self.find_topic_leaders(topic_name).await?;
let mut write_lock = self.cache.write().await;
for (node_id, conn) in topic_leaders {
write_lock.insert((topic_name.clone(), node_id), conn);
}
write_lock
.get(&key)
.cloned()
.ok_or_else(|| ClientError::TopicPartitionLeaderNotFound {
topic_name: topic_name.clone(),
partition_id,
})
}
}
pub async fn reset_cache(&self, topic_names: &[&TopicName]) -> Result<()> {
let mut lock = self.cache.write().await;
let keys_to_remove = lock
.keys()
.cloned()
.filter(|(name, _)| topic_names.contains(&name))
.collect_vec();
for key in keys_to_remove {
lock.remove(&key);
}
Ok(())
}
async fn find_topic_leaders(
&self,
topic_name: &TopicName,
) -> Result<Vec<(PartitionId, LazyBrokerConnection)>> {
let metadata = self
.metadata_client
.get_metadata([topic_name.clone()])
.await?;
let brokers: HashMap<NodeId, LazyBrokerConnection> = metadata
.brokers
.into_iter()
.map(|broker| {
let config = ClientConfig {
broker_list: BrokerList::from_hostnames_and_ports([(
broker.host,
broker.port as u16,
)]),
client_id: self.config.client_id.clone(),
};
let conn = LazyBrokerConnection::new(config);
(broker.id, conn)
})
.collect();
let res = metadata
.topics
.into_iter()
.filter(|topic| &topic.name == topic_name)
.flat_map(|topic| topic.partitions)
.map(|partition| {
let conn = brokers.get(&partition.leader).cloned().ok_or(
ClientError::NodeMetadataNotFound {
node_id: partition.leader,
},
)?;
Result::Ok((partition.id, conn))
})
.try_collect()?;
Ok(res)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct TopicPartition {
pub topic_name: TopicName,
pub partition_id: PartitionId,
}