use std::cmp::Ordering;
use std::collections::HashSet;
use rocketmq_model::common::constant::PermName;
use rocketmq_model::common::message::message_queue::MessageQueue;
use rocketmq_model::common::mix_all;
use rocketmq_protocol::protocol::route::topic_route_data::TopicRouteData;
use rocketmq_transport::api::ClientMetadata;
use tracing::warn;
use crate::producer::producer_impl::topic_publish_info::TopicPublishInfo;
pub(super) fn cap_default_topic_route_queue_nums(topic_route_data: &mut TopicRouteData, default_topic_queue_nums: u32) {
for data in &mut topic_route_data.queue_datas {
let queue_nums = default_topic_queue_nums.min(data.read_queue_nums);
data.read_queue_nums = queue_nums;
data.write_queue_nums = queue_nums;
}
}
pub fn topic_route_data2topic_publish_info(topic: &str, route: &mut TopicRouteData) -> TopicPublishInfo {
let mut info = TopicPublishInfo {
topic_route_data: Some(route.clone()),
..Default::default()
};
if let Some(order_topic_conf) = route.order_topic_conf.as_ref().filter(|conf| !conf.is_empty()) {
for broker in order_topic_conf.split_char(';') {
let item = broker.split(':').collect::<Vec<&str>>();
if item.len() != 2 {
continue;
}
let queue_num = match item[1].parse::<i32>() {
Ok(queue_num) => queue_num,
Err(error) => {
warn!(
"ignore invalid order topic conf entry for topic={}, broker={}, queue_nums={}, error={}",
topic, item[0], item[1], error
);
continue;
}
};
for i in 0..queue_num {
info.message_queue_list
.push(MessageQueue::from_parts(topic, item[0], i));
}
}
info.order_topic = true;
} else if route.order_topic_conf.is_none()
&& route
.topic_queue_mapping_by_broker
.as_ref()
.is_some_and(|mapping| !mapping.is_empty())
{
info.order_topic = false;
if let Some(endpoints) = ClientMetadata::topic_route_data2endpoints_for_static_topic(topic, route) {
info.message_queue_list.extend(endpoints.into_keys());
}
info.message_queue_list
.sort_by(|left, right| match left.queue_id().cmp(&right.queue_id()) {
Ordering::Less => Ordering::Less,
Ordering::Equal => Ordering::Equal,
Ordering::Greater => Ordering::Greater,
});
} else {
route.queue_datas.sort();
for queue_data in &route.queue_datas {
if !PermName::is_writeable(queue_data.perm) {
continue;
}
let Some(broker_data) = route
.broker_datas
.iter()
.find(|data| data.broker_name() == queue_data.broker_name.as_str())
else {
continue;
};
if !broker_data.broker_addrs().contains_key(&mix_all::MASTER_ID) {
continue;
}
for queue_id in 0..queue_data.write_queue_nums {
info.message_queue_list.push(MessageQueue::from_parts(
topic,
queue_data.broker_name.as_str(),
queue_id as i32,
));
}
}
}
info
}
pub fn topic_route_data2_topic_publish_info(topic: &str, route: &mut TopicRouteData) -> TopicPublishInfo {
topic_route_data2topic_publish_info(topic, route)
}
pub fn topic_route_data2topic_subscribe_info(topic: &str, route: &TopicRouteData) -> HashSet<MessageQueue> {
if route
.topic_queue_mapping_by_broker
.as_ref()
.is_some_and(|mapping| !mapping.is_empty())
{
return ClientMetadata::topic_route_data2endpoints_for_static_topic(topic, route)
.unwrap_or_default()
.into_keys()
.collect();
}
let mut queues = HashSet::new();
for queue_data in &route.queue_datas {
if PermName::is_readable(queue_data.perm) {
for queue_id in 0..queue_data.read_queue_nums {
queues.insert(MessageQueue::from_parts(
topic,
queue_data.broker_name.as_str(),
queue_id as i32,
));
}
}
}
queues
}
pub fn topic_route_data2_topic_subscribe_info(topic: &str, route: &TopicRouteData) -> HashSet<MessageQueue> {
topic_route_data2topic_subscribe_info(topic, route)
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use cheetah_string::CheetahString;
use rocketmq_protocol::protocol::route::route_data_view::BrokerData;
use rocketmq_protocol::protocol::route::route_data_view::QueueData;
use super::*;
#[test]
fn topic_route_data2topic_publish_info_selects_writable_queue_with_master_broker() {
let mut route = TopicRouteData {
order_topic_conf: None,
queue_datas: vec![QueueData::new(
CheetahString::from("broker-a"),
2,
3,
PermName::PERM_READ | PermName::PERM_WRITE,
0,
)],
broker_datas: vec![BrokerData::new(
CheetahString::from("cluster-a"),
CheetahString::from("broker-a"),
HashMap::from([(mix_all::MASTER_ID, CheetahString::from("127.0.0.1:10911"))]),
None,
)],
..Default::default()
};
let info = topic_route_data2topic_publish_info("TopicA", &mut route);
let mut queues = info.message_queue_list.clone();
queues.sort_by_key(|queue| queue.queue_id());
assert_eq!(
queues,
vec![
MessageQueue::from_parts("TopicA", "broker-a", 0),
MessageQueue::from_parts("TopicA", "broker-a", 1),
MessageQueue::from_parts("TopicA", "broker-a", 2),
]
);
}
#[test]
fn topic_route_data2topic_publish_info_excludes_non_writable_and_master_missing_queues() {
let mut route = TopicRouteData {
order_topic_conf: None,
queue_datas: vec![
QueueData::new(CheetahString::from("broker-readonly"), 2, 2, PermName::PERM_READ, 0),
QueueData::new(
CheetahString::from("broker-unknown"),
2,
2,
PermName::PERM_READ | PermName::PERM_WRITE,
0,
),
QueueData::new(
CheetahString::from("broker-slave-only"),
2,
2,
PermName::PERM_READ | PermName::PERM_WRITE,
0,
),
],
broker_datas: vec![
BrokerData::new(
CheetahString::from("cluster-a"),
CheetahString::from("broker-readonly"),
HashMap::from([(mix_all::MASTER_ID, CheetahString::from("127.0.0.1:10911"))]),
None,
),
BrokerData::new(
CheetahString::from("cluster-a"),
CheetahString::from("broker-slave-only"),
HashMap::from([(1u64, CheetahString::from("127.0.0.1:10912"))]),
None,
),
],
..Default::default()
};
let info = topic_route_data2topic_publish_info("TopicA", &mut route);
assert!(info.message_queue_list.is_empty());
}
#[test]
fn topic_route_data2topic_subscribe_info_selects_readable_queues_without_master_requirement() {
let route = TopicRouteData {
order_topic_conf: None,
queue_datas: vec![
QueueData::new(
CheetahString::from("broker-no-master-metadata"),
2,
2,
PermName::PERM_READ | PermName::PERM_WRITE,
0,
),
QueueData::new(CheetahString::from("broker-write-only"), 2, 2, PermName::PERM_WRITE, 0),
],
broker_datas: vec![],
..Default::default()
};
let queues = topic_route_data2topic_subscribe_info("TopicA", &route);
assert_eq!(
queues,
HashSet::from([
MessageQueue::from_parts("TopicA", "broker-no-master-metadata", 0),
MessageQueue::from_parts("TopicA", "broker-no-master-metadata", 1),
])
);
}
#[test]
fn topic_route_data2topic_publish_and_subscribe_info_respect_independent_queue_counts() {
let mut route = TopicRouteData {
order_topic_conf: None,
queue_datas: vec![QueueData::new(
CheetahString::from("broker-a"),
0,
3,
PermName::PERM_READ | PermName::PERM_WRITE,
0,
)],
broker_datas: vec![BrokerData::new(
CheetahString::from("cluster-a"),
CheetahString::from("broker-a"),
HashMap::from([(mix_all::MASTER_ID, CheetahString::from("127.0.0.1:10911"))]),
None,
)],
..Default::default()
};
let publish_info = topic_route_data2topic_publish_info("TopicA", &mut route);
let mut publish_queues = publish_info.message_queue_list.clone();
publish_queues.sort_by_key(|queue| queue.queue_id());
assert_eq!(
publish_queues,
vec![
MessageQueue::from_parts("TopicA", "broker-a", 0),
MessageQueue::from_parts("TopicA", "broker-a", 1),
MessageQueue::from_parts("TopicA", "broker-a", 2),
]
);
let subscribe_info = topic_route_data2topic_subscribe_info("TopicA", &route);
assert!(subscribe_info.is_empty());
}
}