1#![allow(dead_code)]
16use std::collections::HashMap;
17use std::collections::HashSet;
18use std::env;
19use std::sync::Arc;
20use std::sync::OnceLock;
21use std::time::Duration;
22
23use crate::admin::mq_admin_ext_async::MQAdminExt;
24use crate::admin::mq_admin_ext_async_inner::MQAdminExtInnerImpl;
25use crate::base::client_config::ClientConfig;
26use crate::base::validators::Validators;
27use crate::common::admin_tool_result::AdminToolResult;
28use crate::common::admin_tools_result_code_enum::AdminToolsResultCodeEnum;
29use crate::consumer::consumer_impl::pull_request_ext::PullResultExt;
30use crate::consumer::pull_callback::PullCallback;
31use crate::consumer::pull_status::PullStatus;
32use crate::factory::mq_client_instance::MQClientInstance;
33use crate::implementation::communication_mode::CommunicationMode;
34use crate::implementation::mq_client_api_impl::MQClientAPIImpl;
35use crate::implementation::mq_client_manager::MQClientManager;
36use cheetah_string::CheetahString;
37use rand::seq::IndexedRandom;
38use rocketmq_common::common::attribute::attribute_parser::AttributeParser;
39use rocketmq_common::common::attribute::topic_message_type::TopicMessageType;
40use rocketmq_common::common::attribute::Attribute;
41use rocketmq_common::common::base::plain_access_config::PlainAccessConfig;
42use rocketmq_common::common::base::service_state::ServiceState;
43use rocketmq_common::common::config::TopicConfig;
44use rocketmq_common::common::constant::PermName;
45use rocketmq_common::common::message::message_decoder;
46use rocketmq_common::common::message::message_enum::MessageRequestMode;
47use rocketmq_common::common::message::message_ext::MessageExt;
48use rocketmq_common::common::message::message_queue::MessageQueue;
49use rocketmq_common::common::message::MessageConst;
50use rocketmq_common::common::message::MessageTrait;
51use rocketmq_common::common::mix_all;
52use rocketmq_common::common::mix_all::DLQ_GROUP_TOPIC_PREFIX;
53use rocketmq_common::common::mix_all::RETRY_GROUP_TOPIC_PREFIX;
54use rocketmq_common::common::sys_flag::pull_sys_flag::PullSysFlag;
55#[allow(deprecated)]
56use rocketmq_common::common::tools::broker_operator_result::BrokerOperatorResult;
57#[allow(deprecated)]
58use rocketmq_common::common::tools::message_track::MessageTrack;
59#[allow(deprecated)]
60use rocketmq_common::common::tools::track_type::TrackType;
61use rocketmq_common::common::topic::TopicValidator;
62use rocketmq_common::common::FAQUrl;
63use rocketmq_common::TopicAttributes::TopicAttributes;
64use rocketmq_error::RocketMQError;
65use rocketmq_remoting::code::response_code::ResponseCode;
66use rocketmq_remoting::protocol::admin::consume_stats::ConsumeStats;
67use rocketmq_remoting::protocol::admin::consume_stats_list::ConsumeStatsList;
68use rocketmq_remoting::protocol::admin::offset_wrapper::OffsetWrapper;
69use rocketmq_remoting::protocol::admin::rollback_stats::RollbackStats;
70use rocketmq_remoting::protocol::admin::topic_offset::TopicOffset;
71use rocketmq_remoting::protocol::admin::topic_stats_table::TopicStatsTable;
72use rocketmq_remoting::protocol::body::acl_info::AclInfo;
73use rocketmq_remoting::protocol::body::acl_info::PolicyEntryInfo;
74use rocketmq_remoting::protocol::body::acl_info::PolicyInfo;
75use rocketmq_remoting::protocol::body::broker_body::broker_member_group::BrokerMemberGroup;
76use rocketmq_remoting::protocol::body::broker_body::cluster_info::ClusterInfo;
77use rocketmq_remoting::protocol::body::broker_replicas_info::BrokerReplicasInfo;
78use rocketmq_remoting::protocol::body::check_rocksdb_cqwrite_progress_response_body::CheckRocksdbCqWriteResult;
79use rocketmq_remoting::protocol::body::consume_message_directly_result::ConsumeMessageDirectlyResult;
80use rocketmq_remoting::protocol::body::consumer_connection::ConsumerConnection;
81use rocketmq_remoting::protocol::body::consumer_running_info::ConsumerRunningInfo;
82use rocketmq_remoting::protocol::body::epoch_entry_cache::EpochEntryCache;
83use rocketmq_remoting::protocol::body::get_broker_lite_info_response_body::GetBrokerLiteInfoResponseBody;
84use rocketmq_remoting::protocol::body::get_lite_client_info_response_body::GetLiteClientInfoResponseBody;
85use rocketmq_remoting::protocol::body::get_lite_group_info_response_body::GetLiteGroupInfoResponseBody;
86use rocketmq_remoting::protocol::body::get_lite_topic_info_response_body::GetLiteTopicInfoResponseBody;
87use rocketmq_remoting::protocol::body::get_parent_topic_info_response_body::GetParentTopicInfoResponseBody;
88use rocketmq_remoting::protocol::body::group_list::GroupList;
89use rocketmq_remoting::protocol::body::ha_runtime_info::HARuntimeInfo;
90use rocketmq_remoting::protocol::body::kv_table::KVTable;
91use rocketmq_remoting::protocol::body::producer_connection::ProducerConnection;
92use rocketmq_remoting::protocol::body::producer_table_info::ProducerTableInfo;
93use rocketmq_remoting::protocol::body::query_consume_queue_response_body::QueryConsumeQueueResponseBody;
94use rocketmq_remoting::protocol::body::queue_time_span::QueueTimeSpan;
95use rocketmq_remoting::protocol::body::subscription_group_wrapper::SubscriptionGroupWrapper;
96use rocketmq_remoting::protocol::body::topic::topic_list::TopicList;
97use rocketmq_remoting::protocol::body::topic_info_wrapper::TopicConfigSerializeWrapper;
98use rocketmq_remoting::protocol::body::user_info::UserInfo;
99use rocketmq_remoting::protocol::header::consume_message_directly_result_request_header::ConsumeMessageDirectlyResultRequestHeader;
100use rocketmq_remoting::protocol::header::create_topic_request_header::CreateTopicRequestHeader;
101use rocketmq_remoting::protocol::header::delete_topic_request_header::DeleteTopicRequestHeader;
102use rocketmq_remoting::protocol::header::elect_master_response_header::ElectMasterResponseHeader;
103use rocketmq_remoting::protocol::header::get_consume_stats_in_broker_header::GetConsumeStatsInBrokerHeader;
104use rocketmq_remoting::protocol::header::get_consume_stats_request_header::GetConsumeStatsRequestHeader;
105use rocketmq_remoting::protocol::header::get_meta_data_response_header::GetMetaDataResponseHeader;
106use rocketmq_remoting::protocol::header::get_topic_stats_info_request_header::GetTopicStatsInfoRequestHeader;
107use rocketmq_remoting::protocol::header::namesrv::brokerid_change_request_header::NotifyMinBrokerIdChangeRequestHeader;
108use rocketmq_remoting::protocol::header::namesrv::topic_operation_header::DeleteTopicFromNamesrvRequestHeader;
109use rocketmq_remoting::protocol::header::namesrv::topic_operation_header::TopicRequestHeader;
110use rocketmq_remoting::protocol::header::pull_message_request_header::PullMessageRequestHeader;
111use rocketmq_remoting::protocol::header::query_consume_time_span_request_header::QueryConsumeTimeSpanRequestHeader;
112use rocketmq_remoting::protocol::header::query_subscription_by_consumer_request_header::QuerySubscriptionByConsumerRequestHeader;
113use rocketmq_remoting::protocol::header::query_topic_consume_by_who_request_header::QueryTopicConsumeByWhoRequestHeader;
114use rocketmq_remoting::protocol::header::query_topics_by_consumer_request_header::QueryTopicsByConsumerRequestHeader;
115use rocketmq_remoting::protocol::header::reset_offset_request_header::ResetOffsetRequestHeader;
116use rocketmq_remoting::protocol::header::update_consumer_offset_header::UpdateConsumerOffsetRequestHeader;
117use rocketmq_remoting::protocol::header::update_group_forbidden_request_header::UpdateGroupForbiddenRequestHeader;
118use rocketmq_remoting::protocol::header::view_broker_stats_data_request_header::ViewBrokerStatsDataRequestHeader;
119use rocketmq_remoting::protocol::header::view_message_request_header::ViewMessageRequestHeader;
120use rocketmq_remoting::protocol::heartbeat::consume_type::ConsumeType;
121use rocketmq_remoting::protocol::heartbeat::message_model::MessageModel;
122use rocketmq_remoting::protocol::heartbeat::subscription_data::SubscriptionData;
123use rocketmq_remoting::protocol::route::route_data_view::QueueData;
124use rocketmq_remoting::protocol::route::topic_route_data::TopicRouteData;
125use rocketmq_remoting::protocol::static_topic::topic_queue_mapping_detail::TopicQueueMappingDetail;
126use rocketmq_remoting::protocol::subscription::broker_stats_data::BrokerStatsData;
127use rocketmq_remoting::protocol::subscription::group_forbidden::GroupForbidden;
128use rocketmq_remoting::protocol::subscription::subscription_group_config::SubscriptionGroupConfig;
129use rocketmq_remoting::rpc::rpc_request_header::RpcRequestHeader;
130use rocketmq_remoting::runtime::RPCHook;
131use rocketmq_rust::ArcMut;
132use tracing::info;
133use tracing::warn;
134
135static SYSTEM_GROUP_SET: OnceLock<HashSet<CheetahString>> = OnceLock::new();
136
137fn get_system_group_set() -> &'static HashSet<CheetahString> {
138 SYSTEM_GROUP_SET.get_or_init(|| {
139 let mut set = HashSet::new();
140 set.insert(CheetahString::from(mix_all::DEFAULT_CONSUMER_GROUP));
141 set.insert(CheetahString::from(mix_all::DEFAULT_PRODUCER_GROUP));
142 set.insert(CheetahString::from(mix_all::TOOLS_CONSUMER_GROUP));
143 set.insert(CheetahString::from(mix_all::SCHEDULE_CONSUMER_GROUP));
144 set.insert(CheetahString::from(mix_all::FILTERSRV_CONSUMER_GROUP));
145 set.insert(CheetahString::from(mix_all::MONITOR_CONSUMER_GROUP));
146 set.insert(CheetahString::from(mix_all::CLIENT_INNER_PRODUCER_GROUP));
147 set.insert(CheetahString::from(mix_all::SELF_TEST_PRODUCER_GROUP));
148 set.insert(CheetahString::from(mix_all::SELF_TEST_CONSUMER_GROUP));
149 set.insert(CheetahString::from(mix_all::ONS_HTTP_PROXY_GROUP));
150 set.insert(CheetahString::from(mix_all::CID_ONSAPI_PERMISSION_GROUP));
151 set.insert(CheetahString::from(mix_all::CID_ONSAPI_OWNER_GROUP));
152 set.insert(CheetahString::from(mix_all::CID_ONSAPI_PULL_GROUP));
153 set.insert(CheetahString::from(mix_all::CID_SYS_RMQ_TRANS));
154 set
155 })
156}
157
158const SOCKS_PROXY_JSON: &str = "socksProxyJson";
159const NAMESPACE_ORDER_TOPIC_CONFIG: &str = "ORDER_TOPIC_CONFIG";
160const ROCKSDB_CONFIG_TYPE_CONSUMER_OFFSETS: &str = "consumerOffsets";
161
162fn encode_topic_attributes(attributes: &HashMap<CheetahString, CheetahString>) -> Option<CheetahString> {
163 if attributes.is_empty() {
164 return None;
165 }
166
167 let serialized = AttributeParser::parse_to_string(
168 &attributes
169 .iter()
170 .map(|(key, value)| (key.to_string(), value.to_string()))
171 .collect::<HashMap<String, String>>(),
172 );
173
174 if serialized.is_empty() {
175 None
176 } else {
177 Some(serialized.into())
178 }
179}
180
181fn offset_to_java_long(operation: &'static str, offset: u64) -> rocketmq_error::RocketMQResult<i64> {
182 i64::try_from(offset)
183 .map_err(|_| RocketMQError::illegal_argument(format!("{operation} offset exceeds Java long range")))
184}
185
186fn timestamp_to_java_long(operation: &'static str, timestamp: u64) -> rocketmq_error::RocketMQResult<i64> {
187 i64::try_from(timestamp)
188 .map_err(|_| RocketMQError::illegal_argument(format!("{operation} timestamp exceeds Java long range")))
189}
190
191fn timeout_millis_to_u64(timeout_millis: Duration) -> rocketmq_error::RocketMQResult<u64> {
192 u64::try_from(timeout_millis.as_millis()).map_err(|_| {
193 RocketMQError::illegal_argument("DefaultMQAdminExt timeoutMillis exceeds Rust u64 millisecond range")
194 })
195}
196
197fn java_long_to_u64(operation: &'static str, field: &'static str, value: i64) -> rocketmq_error::RocketMQResult<u64> {
198 u64::try_from(value).map_err(|_| {
199 RocketMQError::illegal_argument(format!(
200 "{operation} {field} is negative and cannot be represented as Rust u64"
201 ))
202 })
203}
204
205fn merge_consume_status_result(
206 target: &mut HashMap<CheetahString, HashMap<MessageQueue, u64>>,
207 source: HashMap<CheetahString, HashMap<MessageQueue, i64>>,
208) -> rocketmq_error::RocketMQResult<()> {
209 for (client_id, offsets) in source {
210 let target_offsets = target.entry(client_id).or_default();
211 for (mq, offset) in offsets {
212 target_offsets.insert(mq, java_long_to_u64("getConsumeStatus", "offset", offset)?);
213 }
214 }
215 Ok(())
216}
217
218fn master_flush_offset_to_java_long(master_flush_offset: u64) -> rocketmq_error::RocketMQResult<i64> {
219 offset_to_java_long("resetMasterFlushOffset", master_flush_offset)
220}
221
222fn query_consume_queue_index_to_java_long(index: u64) -> rocketmq_error::RocketMQResult<i64> {
223 offset_to_java_long("queryConsumeQueue", index)
224}
225
226fn search_offset_timestamp_to_java_long(timestamp: u64) -> rocketmq_error::RocketMQResult<i64> {
227 timestamp_to_java_long("searchOffset", timestamp)
228}
229
230fn validate_acl_file_path_for_global_white_addr_config(
231 acl_file_full_path: Option<&CheetahString>,
232) -> rocketmq_error::RocketMQResult<()> {
233 if acl_file_full_path.is_some_and(|acl_file_full_path| !acl_file_full_path.is_empty()) {
234 return Err(RocketMQError::illegal_argument(
235 "acl_file_full_path is not supported by RocketMQ ACL 2.0 global white address updates",
236 ));
237 }
238 Ok(())
239}
240
241#[allow(deprecated)]
242fn broker_operator_result(success_list: Vec<CheetahString>, failure_list: Vec<CheetahString>) -> BrokerOperatorResult {
243 let mut result = BrokerOperatorResult::new();
244 result.set_success_list(success_list);
245 result.set_failure_list(failure_list);
246 result
247}
248
249fn controller_servers_or_namesrv(
250 controller_servers: Vec<CheetahString>,
251 name_server_address_list: &[CheetahString],
252) -> Vec<CheetahString> {
253 if controller_servers.is_empty() {
254 name_server_address_list.to_vec()
255 } else {
256 controller_servers
257 }
258}
259
260fn update_consume_offset_request_header(
261 consume_group: CheetahString,
262 mq: &MessageQueue,
263 offset: u64,
264) -> rocketmq_error::RocketMQResult<UpdateConsumerOffsetRequestHeader> {
265 let commit_offset = offset_to_java_long("updateConsumeOffset", offset)?;
266
267 Ok(UpdateConsumerOffsetRequestHeader {
268 consumer_group: consume_group,
269 topic: mq.topic().clone(),
270 queue_id: mq.queue_id(),
271 commit_offset,
272 topic_request_header: Some(TopicRequestHeader {
273 lo: None,
274 rpc: Some(RpcRequestHeader {
275 namespace: None,
276 namespaced: None,
277 broker_name: Some(mq.broker_name().clone()),
278 oneway: None,
279 }),
280 }),
281 })
282}
283
284fn reset_offset_by_queue_id_request_headers(
285 consumer_group: CheetahString,
286 topic_name: CheetahString,
287 queue_id: i32,
288 reset_offset: u64,
289) -> rocketmq_error::RocketMQResult<(UpdateConsumerOffsetRequestHeader, ResetOffsetRequestHeader)> {
290 let reset_offset = offset_to_java_long("resetOffsetByQueueId", reset_offset)?;
291
292 let update_header = UpdateConsumerOffsetRequestHeader {
293 consumer_group: consumer_group.clone(),
294 topic: topic_name.clone(),
295 queue_id,
296 commit_offset: reset_offset,
297 topic_request_header: None,
298 };
299
300 let reset_header = ResetOffsetRequestHeader {
301 topic: topic_name,
302 group: consumer_group,
303 queue_id,
304 offset: Some(reset_offset),
305 timestamp: 0,
306 is_force: false,
307 topic_request_header: None,
308 };
309
310 Ok((update_header, reset_header))
311}
312
313fn lite_pull_update_consumer_offset_request_header(
314 topic: CheetahString,
315 group: CheetahString,
316 queue_id: i32,
317 offset: u64,
318) -> rocketmq_error::RocketMQResult<UpdateConsumerOffsetRequestHeader> {
319 let commit_offset = offset_to_java_long("updateLitePullConsumerOffset", offset)?;
320
321 Ok(UpdateConsumerOffsetRequestHeader {
322 consumer_group: group,
323 topic,
324 queue_id,
325 commit_offset,
326 topic_request_header: None,
327 })
328}
329
330fn optional_non_empty(value: Option<CheetahString>) -> Option<CheetahString> {
331 value.filter(|value| !value.is_empty())
332}
333
334fn notify_min_broker_id_change_request_header(
335 min_broker_id: u64,
336 min_broker_addr: CheetahString,
337 offline_broker_addr: Option<CheetahString>,
338 ha_broker_addr: Option<CheetahString>,
339) -> rocketmq_error::RocketMQResult<NotifyMinBrokerIdChangeRequestHeader> {
340 if min_broker_addr.is_empty() {
341 return Err(RocketMQError::illegal_argument(
342 "notifyMinBrokerIdChanged requires minBrokerAddr",
343 ));
344 }
345
346 Ok(NotifyMinBrokerIdChangeRequestHeader::new(
347 Some(min_broker_id),
348 None,
349 Some(min_broker_addr),
350 optional_non_empty(offline_broker_addr),
351 optional_non_empty(ha_broker_addr),
352 ))
353}
354
355fn choose_min_broker_notify_addrs(
356 broker_addrs: &HashMap<u64, CheetahString>,
357 min_broker_id: u64,
358 offline_broker_addr: Option<&CheetahString>,
359) -> Vec<CheetahString> {
360 let notify_all = broker_addrs.len() == 1
361 || offline_broker_addr
362 .map(|broker_addr| !broker_addr.is_empty())
363 .unwrap_or(false);
364 let mut entries = broker_addrs.iter().collect::<Vec<_>>();
365 entries.sort_by_key(|entry| *entry.0);
366 entries
367 .into_iter()
368 .filter(|entry| notify_all || *entry.0 != min_broker_id)
369 .map(|entry| entry.1.clone())
370 .collect()
371}
372
373fn update_group_forbidden_request_header(
374 group_name: CheetahString,
375 topic_name: CheetahString,
376 readable: Option<bool>,
377) -> UpdateGroupForbiddenRequestHeader {
378 UpdateGroupForbiddenRequestHeader {
379 group: group_name,
380 topic: topic_name,
381 readable,
382 topic_request_header: None,
383 }
384}
385
386#[derive(Debug, Clone, Copy)]
387enum BrokerCleanupOperation {
388 CleanExpiredConsumerQueue,
389 DeleteExpiredCommitLog,
390 CleanUnusedTopic,
391}
392
393impl BrokerCleanupOperation {
394 async fn execute(
395 self,
396 api: &ArcMut<MQClientAPIImpl>,
397 addr: &CheetahString,
398 timeout_millis: u64,
399 ) -> rocketmq_error::RocketMQResult<bool> {
400 match self {
401 BrokerCleanupOperation::CleanExpiredConsumerQueue => {
402 api.clean_expired_consume_queue(addr, timeout_millis).await
403 }
404 BrokerCleanupOperation::DeleteExpiredCommitLog => api.delete_expired_commit_log(addr, timeout_millis).await,
405 BrokerCleanupOperation::CleanUnusedTopic => api.clean_unused_topic(addr, timeout_millis).await,
406 }
407 }
408}
409
410fn cluster_names_for_admin_operation(cluster_info: &ClusterInfo, cluster: Option<CheetahString>) -> Vec<CheetahString> {
411 if let Some(cluster) = cluster.filter(|cluster| !cluster.is_empty()) {
412 return vec![cluster];
413 }
414
415 cluster_info
416 .cluster_addr_table
417 .as_ref()
418 .map(|table| table.keys().cloned().collect())
419 .unwrap_or_default()
420}
421
422fn broker_addrs_for_cluster(cluster_info: &ClusterInfo, cluster: &CheetahString) -> Vec<CheetahString> {
423 let Some(cluster_addr_table) = cluster_info.cluster_addr_table.as_ref() else {
424 return Vec::new();
425 };
426 let Some(broker_names) = cluster_addr_table.get(cluster) else {
427 return Vec::new();
428 };
429 let Some(broker_addr_table) = cluster_info.broker_addr_table.as_ref() else {
430 return Vec::new();
431 };
432
433 let mut addrs = Vec::new();
434 for broker_name in broker_names {
435 if let Some(broker_data) = broker_addr_table.get(broker_name) {
436 addrs.extend(broker_data.broker_addrs().values().cloned());
437 }
438 }
439 addrs
440}
441
442fn topic_list_from_lite_topic_names(
443 broker_addr: Option<CheetahString>,
444 topic_names: impl IntoIterator<Item = CheetahString>,
445) -> TopicList {
446 let mut topic_list = topic_names.into_iter().collect::<Vec<_>>();
447 topic_list.sort_by(|left, right| left.as_str().cmp(right.as_str()));
448 topic_list.dedup();
449 TopicList {
450 topic_list,
451 broker_addr,
452 }
453}
454
455fn lite_topic_list_from_broker_lite_info(
456 broker_addr: Option<CheetahString>,
457 lite_info: &GetBrokerLiteInfoResponseBody,
458) -> TopicList {
459 topic_list_from_lite_topic_names(broker_addr, lite_info.get_topic_meta().keys().cloned())
460}
461
462fn lite_subscription_group_list_from_broker_lite_info(
463 topic: &CheetahString,
464 lite_info: &GetBrokerLiteInfoResponseBody,
465) -> GroupList {
466 GroupList::new(lite_info.get_group_meta().get(topic).cloned().unwrap_or_default())
467}
468
469fn resolve_lite_pull_queue_num(
470 field_name: &'static str,
471 value: i32,
472 fallback_queue_num: i32,
473 allow_fallback: bool,
474) -> rocketmq_error::RocketMQResult<u32> {
475 let resolved = if value > 0 {
476 value
477 } else if allow_fallback && fallback_queue_num > 0 {
478 fallback_queue_num
479 } else {
480 return Err(mq_client_err!(format!("{field_name} must be positive")));
481 };
482 u32::try_from(resolved).map_err(|error| mq_client_err!(format!("{field_name} is out of range: {error}")))
483}
484
485fn lite_pull_topic_config(
486 topic: CheetahString,
487 queue_num: i32,
488 topic_sys_flag: i32,
489 read_queue_nums: i32,
490 write_queue_nums: i32,
491 update_existing: bool,
492) -> rocketmq_error::RocketMQResult<TopicConfig> {
493 if topic.is_empty() {
494 return Err(mq_client_err!("Lite pull topic cannot be empty"));
495 }
496 if topic_sys_flag < 0 {
497 return Err(mq_client_err!("topicSysFlag must be non-negative"));
498 }
499
500 let read_queue_nums = resolve_lite_pull_queue_num("readQueueNums", read_queue_nums, queue_num, !update_existing)?;
501 let write_queue_nums =
502 resolve_lite_pull_queue_num("writeQueueNums", write_queue_nums, queue_num, !update_existing)?;
503 let topic_sys_flag = u32::try_from(topic_sys_flag)
504 .map_err(|error| mq_client_err!(format!("topicSysFlag is out of range: {error}")))?;
505
506 let mut config = TopicConfig::with_sys_flag(
507 topic,
508 read_queue_nums,
509 write_queue_nums,
510 PermName::PERM_READ | PermName::PERM_WRITE,
511 topic_sys_flag,
512 );
513 config.attributes.insert(
514 TopicAttributes::topic_message_type_attribute().name().clone(),
515 CheetahString::from_string(TopicMessageType::Lite.to_string()),
516 );
517 Ok(config)
518}
519
520pub struct DefaultMQAdminExtImpl {
521 service_state: ServiceState,
522 client_instance: Option<ArcMut<MQClientInstance>>,
523 rpc_hook: Option<Arc<dyn RPCHook>>,
524 timeout_millis: Duration,
525 kv_namespace_to_delete_list: Vec<CheetahString>,
526 client_config: ArcMut<ClientConfig>,
527 admin_ext_group: CheetahString,
528 inner: Option<ArcMut<DefaultMQAdminExtImpl>>,
529}
530
531impl DefaultMQAdminExtImpl {
532 pub fn new(
533 rpc_hook: Option<Arc<dyn RPCHook>>,
534 timeout_millis: Duration,
535 client_config: ArcMut<ClientConfig>,
536 admin_ext_group: CheetahString,
537 ) -> Self {
538 DefaultMQAdminExtImpl {
539 service_state: ServiceState::CreateJust,
540 client_instance: None,
541 rpc_hook,
542 timeout_millis,
543 kv_namespace_to_delete_list: vec![CheetahString::from_static_str(NAMESPACE_ORDER_TOPIC_CONFIG)],
544 client_config,
545 admin_ext_group,
546 inner: None,
547 }
548 }
549
550 pub fn set_inner(&mut self, inner: ArcMut<DefaultMQAdminExtImpl>) {
551 self.inner = Some(inner);
552 }
553
554 pub fn has_inner(&self) -> bool {
555 self.inner.is_some()
556 }
557
558 #[inline]
559 pub fn is_use_tls(&self) -> bool {
560 self.client_config.is_use_tls()
561 }
562
563 #[inline]
564 pub fn set_use_tls(&mut self, use_tls: bool) {
565 self.client_config.set_use_tls(use_tls);
566 }
567
568 fn mq_client_api(&self) -> rocketmq_error::RocketMQResult<ArcMut<MQClientAPIImpl>> {
569 self.client_instance
570 .as_ref()
571 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?
572 .get_mq_client_api_impl()
573 }
574
575 fn remoting_timeout_millis(&self) -> rocketmq_error::RocketMQResult<u64> {
576 timeout_millis_to_u64(self.timeout_millis)
577 }
578
579 async fn query_topics_by_consumer_from_route(
580 &self,
581 group: CheetahString,
582 ) -> rocketmq_error::RocketMQResult<TopicList> {
583 let timeout = self.remoting_timeout_millis()?;
584 let retry_topic: CheetahString = mix_all::get_retry_topic(&group).into();
585 let topic_route = self
586 .mq_client_api()?
587 .get_topic_route_info_from_name_server(&retry_topic, timeout)
588 .await?;
589 let Some(route_data) = topic_route else {
590 return Ok(TopicList::default());
591 };
592
593 let mut result = TopicList::default();
594 for broker_data in &route_data.broker_datas {
595 let Some(addr) = broker_data.select_broker_addr() else {
596 continue;
597 };
598 let topic_list = self
599 .mq_client_api()?
600 .query_topics_by_consumer(&addr, QueryTopicsByConsumerRequestHeader::new(group.clone()), timeout)
601 .await?;
602 for topic in topic_list.topic_list {
603 if !result.topic_list.contains(&topic) {
604 result.topic_list.push(topic);
605 }
606 }
607 }
608
609 Ok(result)
610 }
611
612 async fn execute_broker_cleanup_operation(
613 &self,
614 cluster: Option<CheetahString>,
615 addr: Option<CheetahString>,
616 operation: BrokerCleanupOperation,
617 ) -> rocketmq_error::RocketMQResult<bool> {
618 let timeout = self.remoting_timeout_millis()?;
619 let api = self.mq_client_api()?;
620
621 if let Some(addr) = addr.filter(|addr| !addr.is_empty()) {
622 return operation.execute(&api, &addr, timeout).await;
623 }
624
625 let cluster_info = api.get_broker_cluster_info(timeout).await?;
626 let mut result = false;
627 for cluster_name in cluster_names_for_admin_operation(&cluster_info, cluster) {
628 for broker_addr in broker_addrs_for_cluster(&cluster_info, &cluster_name) {
629 result = operation.execute(&api, &broker_addr, timeout).await?;
630 }
631 }
632
633 Ok(result)
634 }
635
636 pub async fn create_acl_with_acl_info(
637 &self,
638 broker_addr: CheetahString,
639 acl_info: AclInfo,
640 ) -> rocketmq_error::RocketMQResult<()> {
641 if acl_info.subject.as_ref().is_none_or(|subject| subject.is_empty()) {
642 return Err(rocketmq_error::RocketMQError::IllegalArgument(
643 "ACL subject is required".into(),
644 ));
645 }
646
647 if let Some(ref client_instance) = self.client_instance {
648 client_instance
649 .get_mq_client_api_impl()?
650 .create_acl(broker_addr, &acl_info, self.remoting_timeout_millis()?)
651 .await
652 } else {
653 Err(rocketmq_error::RocketMQError::ClientNotStarted)
654 }
655 }
656
657 pub async fn update_acl_with_acl_info(
658 &self,
659 broker_addr: CheetahString,
660 acl_info: AclInfo,
661 ) -> rocketmq_error::RocketMQResult<()> {
662 if acl_info.subject.as_ref().is_none_or(|subject| subject.is_empty()) {
663 return Err(rocketmq_error::RocketMQError::IllegalArgument(
664 "ACL subject is required".into(),
665 ));
666 }
667
668 if let Some(ref client_instance) = self.client_instance {
669 client_instance
670 .get_mq_client_api_impl()?
671 .update_acl(broker_addr, &acl_info, self.remoting_timeout_millis()?)
672 .await
673 } else {
674 Err(rocketmq_error::RocketMQError::ClientNotStarted)
675 }
676 }
677
678 pub async fn create_user_with_user_info(
679 &self,
680 broker_addr: CheetahString,
681 user_info: UserInfo,
682 ) -> rocketmq_error::RocketMQResult<()> {
683 let username = user_info
684 .username
685 .clone()
686 .ok_or_else(|| rocketmq_error::RocketMQError::IllegalArgument("User username is required".into()))?;
687
688 let password = user_info.password.clone().unwrap_or_default();
689 let user_type = user_info.user_type.clone().unwrap_or_default();
690
691 self.create_user(broker_addr, username, password, user_type).await
692 }
693
694 pub async fn update_user_with_user_info(
695 &self,
696 broker_addr: CheetahString,
697 user_info: UserInfo,
698 ) -> rocketmq_error::RocketMQResult<()> {
699 let username = user_info
700 .username
701 .clone()
702 .ok_or_else(|| rocketmq_error::RocketMQError::IllegalArgument("User username is required".into()))?;
703
704 let password = user_info.password.clone().unwrap_or_default();
705 let user_type = user_info.user_type.clone().unwrap_or_default();
706 let user_status = user_info.user_status.clone().unwrap_or_default();
707
708 self.update_user(broker_addr, username, password, user_type, user_status)
709 .await
710 }
711
712 pub async fn pull_message_from_queue(
713 &self,
714 broker_addr: &str,
715 mq: &MessageQueue,
716 sub_expression: &str,
717 offset: i64,
718 max_nums: i32,
719 timeout_millis: u64,
720 ) -> rocketmq_error::RocketMQResult<crate::consumer::pull_result::PullResult> {
721 let sys_flag = PullSysFlag::build_sys_flag(false, false, true, false);
722
723 let request_header = PullMessageRequestHeader {
724 consumer_group: CheetahString::from_static_str(mix_all::TOOLS_CONSUMER_GROUP),
725 topic: mq.topic().clone(),
726 lite_topic: None,
727 queue_id: mq.queue_id(),
728 queue_offset: offset,
729 max_msg_nums: max_nums,
730 sys_flag: sys_flag as i32,
731 commit_offset: 0,
732 suspend_timeout_millis: 0,
733 sub_version: 0,
734 subscription: Some(CheetahString::from(sub_expression)),
735 expression_type: None,
736 max_msg_bytes: None,
737 request_source: None,
738 proxy_forward_client_id: None,
739 topic_request: None,
740 };
741
742 struct NoopPullCallback;
743 impl PullCallback for NoopPullCallback {
744 async fn on_success(&mut self, _pull_result: PullResultExt) {}
745 fn on_exception(&mut self, _e: Box<dyn std::error::Error + Send>) {}
746 }
747
748 let api_impl = self.mq_client_api()?;
749
750 let mut result = MQClientAPIImpl::pull_message(
751 api_impl,
752 CheetahString::from(broker_addr),
753 request_header,
754 timeout_millis,
755 CommunicationMode::Sync,
756 NoopPullCallback,
757 )
758 .await?
759 .ok_or_else(|| rocketmq_error::RocketMQError::Internal("pull_message returned None in sync mode".into()))?;
760
761 if result.pull_result.pull_status == PullStatus::Found {
762 if let Some(mut message_binary) = result.message_binary.take() {
763 let msg_vec = message_decoder::decodes_batch(&mut message_binary, true, true);
764 result.pull_result.msg_found_list = Some(msg_vec.into_iter().map(ArcMut::new).collect());
765 }
766 }
767
768 Ok(result.pull_result)
769 }
770
771 pub async fn query_message_by_key(
772 &self,
773 cluster_name: Option<CheetahString>,
774 topic: CheetahString,
775 key: CheetahString,
776 max_num: i32,
777 begin_timestamp: i64,
778 end_timestamp: i64,
779 _key_type: CheetahString,
780 _last_key: Option<CheetahString>,
781 ) -> rocketmq_error::RocketMQResult<crate::base::query_result::QueryResult> {
782 self.query_message_by_key_internal(cluster_name, topic, key, max_num, begin_timestamp, end_timestamp, false)
783 .await
784 }
785
786 pub async fn query_message_by_unique_key(
787 &self,
788 cluster_name: Option<CheetahString>,
789 topic: CheetahString,
790 unique_key: CheetahString,
791 max_num: i32,
792 begin_timestamp: i64,
793 end_timestamp: i64,
794 ) -> rocketmq_error::RocketMQResult<crate::base::query_result::QueryResult> {
795 self.query_message_by_key_internal(
796 cluster_name,
797 topic,
798 unique_key,
799 max_num,
800 begin_timestamp,
801 end_timestamp,
802 true,
803 )
804 .await
805 }
806
807 async fn query_message_by_key_internal(
808 &self,
809 cluster_name: Option<CheetahString>,
810 topic: CheetahString,
811 key: CheetahString,
812 max_num: i32,
813 begin_timestamp: i64,
814 end_timestamp: i64,
815 unique_key_flag: bool,
816 ) -> rocketmq_error::RocketMQResult<crate::base::query_result::QueryResult> {
817 let route_topic = cluster_name.unwrap_or_else(|| topic.clone());
818 let topic_route_data = self
819 .examine_topic_route_info(route_topic.clone())
820 .await?
821 .ok_or_else(|| {
822 rocketmq_error::RocketMQError::Internal(format!("Topic route not found for: {}", route_topic))
823 })?;
824
825 let mut message_list: Vec<MessageExt> = Vec::new();
826 let mut index_last_update_timestamp: u64 = 0;
827
828 let api_impl = self.mq_client_api()?;
829 let timeout = self.remoting_timeout_millis()?;
830
831 for broker_data in &topic_route_data.broker_datas {
832 let broker_addr = match broker_data.select_broker_addr() {
833 Some(addr) => addr,
834 None => continue,
835 };
836
837 let request_header =
838 rocketmq_remoting::protocol::header::query_message_request_header::QueryMessageRequestHeader {
839 topic: topic.clone(),
840 key: key.clone(),
841 max_num,
842 begin_timestamp,
843 end_timestamp,
844 index_type: Some(CheetahString::from_static_str(if unique_key_flag {
845 MessageConst::INDEX_UNIQUE_TYPE
846 } else {
847 MessageConst::INDEX_KEY_TYPE
848 })),
849 last_key: None,
850 topic_request_header: None,
851 };
852
853 match MQClientAPIImpl::query_message(&api_impl, &broker_addr, request_header, unique_key_flag, timeout)
854 .await
855 {
856 Ok(Some((response_header, body))) => {
857 if let Some(mut body_bytes) = body {
858 let msgs = message_decoder::decodes_batch(&mut body_bytes, true, true);
859 message_list.extend(msgs);
860 }
861 let response_index_timestamp = java_long_to_u64(
862 "queryMessage",
863 "indexLastUpdateTimestamp",
864 response_header.index_last_update_timestamp,
865 )?;
866 if response_index_timestamp > index_last_update_timestamp {
867 index_last_update_timestamp = response_index_timestamp;
868 }
869 }
870 Ok(None) => {
871 }
873 Err(e) => {
874 tracing::warn!("Failed to query message by key from broker {}: {}", broker_addr, e);
875 }
876 }
877 }
878
879 Ok(crate::base::query_result::QueryResult::new(
880 index_last_update_timestamp,
881 message_list,
882 ))
883 }
884}
885
886#[allow(unused_variables)]
887#[allow(unused_mut)]
888impl MQAdminExt for DefaultMQAdminExtImpl {
889 async fn start(&mut self) -> rocketmq_error::RocketMQResult<()> {
890 match self.service_state {
891 ServiceState::CreateJust => {
892 self.service_state = ServiceState::StartFailed;
893 self.client_config.change_instance_name_to_pid();
894 if "{}".eq(&self.client_config.socks_proxy_config) {
895 self.client_config.socks_proxy_config =
896 env::var(SOCKS_PROXY_JSON).unwrap_or_else(|_| "{}".to_string()).into();
897 }
898 self.client_instance = Some(
899 MQClientManager::get_instance()
900 .get_or_create_mq_client_instance(self.client_config.as_ref().clone(), self.rpc_hook.clone()),
901 );
902
903 let group = &self.admin_ext_group.clone();
904 let inner = self.inner.as_ref().ok_or_else(|| {
905 rocketmq_error::RocketMQError::IllegalArgument(
906 "DefaultMQAdminExtImpl inner is required before start".into(),
907 )
908 })?;
909 let register_ok = self
910 .client_instance
911 .as_mut()
912 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?
913 .register_admin_ext(group, MQAdminExtInnerImpl { inner: inner.clone() })
914 .await;
915 if !register_ok {
916 self.service_state = ServiceState::StartFailed;
917 return Err(rocketmq_error::RocketMQError::illegal_argument(format!(
918 "The adminExt group[{}] has created already, specified another name please.{}",
919 self.admin_ext_group,
920 FAQUrl::suggest_todo(FAQUrl::GROUP_NAME_DUPLICATE_URL)
921 )));
922 }
923 let arc_mut = self
924 .client_instance
925 .clone()
926 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?;
927 self.client_instance
928 .as_mut()
929 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?
930 .start(arc_mut)
931 .await?;
932 self.service_state = ServiceState::Running;
933 info!("the adminExt [{}] start OK", self.admin_ext_group);
934 Ok(())
935 }
936 ServiceState::Running | ServiceState::ShutdownAlready | ServiceState::StartFailed => {
937 Err(rocketmq_error::RocketMQError::ClientAlreadyStarted)
938 }
939 }
940 }
941
942 async fn shutdown(&mut self) {
943 match self.service_state {
944 ServiceState::CreateJust | ServiceState::ShutdownAlready | ServiceState::StartFailed => {
945 }
947 ServiceState::Running => {
948 if let Some(instance) = self.client_instance.as_mut() {
949 instance.unregister_admin_ext(&self.admin_ext_group).await;
950 instance.shutdown().await;
951 }
952 self.service_state = ServiceState::ShutdownAlready;
953 }
954 }
955 }
956
957 async fn add_broker_to_container(
958 &self,
959 broker_container_addr: CheetahString,
960 broker_config: CheetahString,
961 ) -> rocketmq_error::RocketMQResult<()> {
962 if let Some(ref mq_client_instance) = self.client_instance {
963 mq_client_instance
964 .get_mq_client_api_impl()?
965 .add_broker(&broker_container_addr, broker_config, self.remoting_timeout_millis()?)
966 .await
967 } else {
968 Err(rocketmq_error::RocketMQError::ClientNotStarted)
969 }
970 }
971
972 async fn remove_broker_from_container(
973 &self,
974 broker_container_addr: CheetahString,
975 cluster_name: CheetahString,
976 broker_name: CheetahString,
977 broker_id: u64,
978 ) -> rocketmq_error::RocketMQResult<()> {
979 if let Some(ref mq_client_instance) = self.client_instance {
980 mq_client_instance
981 .get_mq_client_api_impl()?
982 .remove_broker(
983 &broker_container_addr,
984 cluster_name,
985 broker_name,
986 broker_id,
987 self.remoting_timeout_millis()?,
988 )
989 .await
990 } else {
991 Err(rocketmq_error::RocketMQError::ClientNotStarted)
992 }
993 }
994
995 async fn update_broker_config(
996 &self,
997 broker_addr: CheetahString,
998 properties: HashMap<CheetahString, CheetahString>,
999 ) -> rocketmq_error::RocketMQResult<()> {
1000 let validator_input = properties
1001 .iter()
1002 .map(|(key, value)| (key.to_string(), value.to_string()))
1003 .collect::<HashMap<String, String>>();
1004 Validators::check_broker_config(&validator_input)?;
1005
1006 if let Some(ref mq_client_instance) = self.client_instance {
1007 mq_client_instance
1008 .get_mq_client_api_impl()?
1009 .update_broker_config(&broker_addr, properties, self.remoting_timeout_millis()?)
1010 .await
1011 } else {
1012 Err(rocketmq_error::RocketMQError::ClientNotStarted)
1013 }
1014 }
1015
1016 async fn get_broker_config(
1017 &self,
1018 broker_addr: CheetahString,
1019 ) -> rocketmq_error::RocketMQResult<HashMap<CheetahString, CheetahString>> {
1020 if let Some(ref mq_client_instance) = self.client_instance {
1021 mq_client_instance
1022 .get_mq_client_api_impl()?
1023 .get_broker_config(&broker_addr, self.remoting_timeout_millis()?)
1024 .await
1025 } else {
1026 Err(rocketmq_error::RocketMQError::ClientNotStarted)
1027 }
1028 }
1029
1030 async fn create_and_update_topic_config(
1031 &self,
1032 addr: CheetahString,
1033 config: TopicConfig,
1034 ) -> rocketmq_error::RocketMQResult<()> {
1035 let topic = config
1036 .topic_name
1037 .clone()
1038 .ok_or_else(|| rocketmq_error::RocketMQError::IllegalArgument("Topic name is required".into()))?;
1039 let attributes = encode_topic_attributes(&config.attributes);
1040 let request_header = CreateTopicRequestHeader {
1041 topic,
1042 default_topic: CheetahString::from_static_str(TopicValidator::AUTO_CREATE_TOPIC_KEY_TOPIC),
1043 read_queue_nums: config.read_queue_nums as i32,
1044 write_queue_nums: config.write_queue_nums as i32,
1045 perm: config.perm as i32,
1046 topic_filter_type: config.topic_filter_type.to_string().into(),
1047 topic_sys_flag: Some(config.topic_sys_flag as i32),
1048 order: config.order,
1049 attributes,
1050 force: Some(false),
1051 topic_request_header: None,
1052 };
1053
1054 self.mq_client_api()?
1055 .update_or_create_topic(&addr, request_header, self.remoting_timeout_millis()?)
1056 .await
1057 }
1058
1059 async fn create_and_update_topic_config_list(
1060 &self,
1061 addr: CheetahString,
1062 topic_config_list: Vec<TopicConfig>,
1063 ) -> rocketmq_error::RocketMQResult<()> {
1064 for config in topic_config_list {
1065 self.create_and_update_topic_config(addr.clone(), config).await?;
1066 }
1067 Ok(())
1068 }
1069
1070 async fn create_and_update_plain_access_config(
1071 &self,
1072 addr: CheetahString,
1073 config: PlainAccessConfig,
1074 ) -> rocketmq_error::RocketMQResult<()> {
1075 self.mq_client_api()?
1076 .create_and_update_plain_access_config(addr, &config, self.remoting_timeout_millis()?)
1077 .await
1078 }
1079
1080 async fn delete_plain_access_config(
1081 &self,
1082 addr: CheetahString,
1083 access_key: CheetahString,
1084 ) -> rocketmq_error::RocketMQResult<()> {
1085 self.mq_client_api()?
1086 .delete_plain_access_config(addr, access_key, self.remoting_timeout_millis()?)
1087 .await
1088 }
1089
1090 async fn update_global_white_addr_config(
1091 &self,
1092 addr: CheetahString,
1093 global_white_addrs: CheetahString,
1094 acl_file_full_path: Option<CheetahString>,
1095 ) -> rocketmq_error::RocketMQResult<()> {
1096 let mq_client_api = self.mq_client_api()?;
1097
1098 validate_acl_file_path_for_global_white_addr_config(acl_file_full_path.as_ref())?;
1099
1100 mq_client_api
1101 .update_global_white_addrs_config(addr, global_white_addrs, self.remoting_timeout_millis()?)
1102 .await
1103 }
1104
1105 async fn examine_broker_cluster_acl_version_info(
1106 &self,
1107 addr: CheetahString,
1108 ) -> rocketmq_error::RocketMQResult<CheetahString> {
1109 let version_info = self
1110 .mq_client_api()?
1111 .get_broker_cluster_acl_version_info(addr, self.remoting_timeout_millis()?)
1112 .await?;
1113 serde_json::to_string(&version_info)
1114 .map(CheetahString::from_string)
1115 .map_err(|error| mq_client_err!(format!("encode ClusterAclVersionInfo failed: {error}")))
1116 }
1117
1118 async fn create_and_update_subscription_group_config(
1119 &self,
1120 addr: CheetahString,
1121 config: SubscriptionGroupConfig,
1122 ) -> rocketmq_error::RocketMQResult<()> {
1123 self.client_instance
1124 .as_ref()
1125 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?
1126 .get_mq_client_api_impl()?
1127 .create_subscription_group(&addr, &config, self.remoting_timeout_millis()?)
1128 .await
1129 }
1130
1131 async fn create_and_update_subscription_group_config_list(
1132 &self,
1133 broker_addr: CheetahString,
1134 configs: Vec<SubscriptionGroupConfig>,
1135 ) -> rocketmq_error::RocketMQResult<()> {
1136 for config in configs {
1137 self.create_and_update_subscription_group_config(broker_addr.clone(), config)
1138 .await?;
1139 }
1140 Ok(())
1141 }
1142
1143 async fn examine_subscription_group_config(
1144 &self,
1145 addr: CheetahString,
1146 group: CheetahString,
1147 ) -> rocketmq_error::RocketMQResult<SubscriptionGroupConfig> {
1148 self.mq_client_api()?
1149 .get_subscription_group_config(&addr, group, self.remoting_timeout_millis()?)
1150 .await
1151 }
1152
1153 async fn examine_topic_stats(
1154 &self,
1155 topic: CheetahString,
1156 broker_addr: Option<CheetahString>,
1157 ) -> rocketmq_error::RocketMQResult<TopicStatsTable> {
1158 let timeout = self.remoting_timeout_millis()?;
1159 let request_header = GetTopicStatsInfoRequestHeader {
1160 topic: topic.clone(),
1161 topic_request_header: None,
1162 };
1163 if let Some(addr) = broker_addr {
1164 return self
1165 .mq_client_api()?
1166 .get_topic_stats_info(&addr, request_header, timeout)
1167 .await;
1168 }
1169
1170 let topic_route = self.examine_topic_route_info(topic).await?;
1171 let mut result = TopicStatsTable::new();
1172 if let Some(route_data) = topic_route {
1173 for broker_data in &route_data.broker_datas {
1174 if let Some(master_addr) = broker_data.broker_addrs().get(&mix_all::MASTER_ID) {
1175 let stats = self
1176 .mq_client_api()?
1177 .get_topic_stats_info(master_addr, request_header.clone(), timeout)
1178 .await?;
1179 result.get_offset_table_mut().extend(stats.into_offset_table());
1180 }
1181 }
1182 }
1183
1184 Ok(result)
1185 }
1186
1187 async fn examine_topic_stats_concurrent(&self, topic: CheetahString) -> AdminToolResult<TopicStatsTable> {
1188 match self.examine_topic_stats(topic, None).await {
1189 Ok(stats) => AdminToolResult::success(stats),
1190 Err(error) => AdminToolResult::failure(
1191 crate::common::admin_tools_result_code_enum::AdminToolsResultCodeEnum::RemotingError,
1192 error.to_string(),
1193 ),
1194 }
1195 }
1196
1197 async fn fetch_all_topic_list(&self) -> rocketmq_error::RocketMQResult<TopicList> {
1198 self.mq_client_api()?
1199 .get_all_topic_list_from_name_server(self.remoting_timeout_millis()?)
1200 .await
1201 }
1202
1203 async fn fetch_topics_by_cluster(&self, cluster_name: CheetahString) -> rocketmq_error::RocketMQResult<TopicList> {
1204 self.mq_client_api()?
1205 .get_topics_by_cluster(cluster_name, self.remoting_timeout_millis()?)
1206 .await
1207 }
1208
1209 async fn fetch_broker_runtime_stats(&self, broker_addr: CheetahString) -> rocketmq_error::RocketMQResult<KVTable> {
1210 self.mq_client_api()?
1211 .get_broker_runtime_info(&broker_addr, self.remoting_timeout_millis()?)
1212 .await
1213 }
1214
1215 async fn examine_consume_stats(
1216 &self,
1217 consumer_group: CheetahString,
1218 topic: Option<CheetahString>,
1219 cluster_name: Option<CheetahString>,
1220 broker_addr: Option<CheetahString>,
1221 timeout_millis: Option<u64>,
1222 ) -> rocketmq_error::RocketMQResult<ConsumeStats> {
1223 let timeout = timeout_millis.unwrap_or(self.remoting_timeout_millis()?);
1224 let topic_str = topic.clone().unwrap_or_default();
1225
1226 if let Some(addr) = broker_addr {
1227 let request_header = GetConsumeStatsRequestHeader {
1228 consumer_group,
1229 topic: topic_str,
1230 topic_request_header: None,
1231 };
1232 return self
1233 .mq_client_api()?
1234 .get_consume_stats(&addr, request_header, timeout)
1235 .await;
1236 }
1237
1238 let retry_topic: CheetahString = rocketmq_common::common::mix_all::get_retry_topic(&consumer_group).into();
1239 let topic_route = self
1240 .mq_client_api()?
1241 .get_topic_route_info_from_name_server(&retry_topic, timeout)
1242 .await?;
1243
1244 let mut result = ConsumeStats::new();
1245
1246 if let Some(route_data) = topic_route {
1247 for bd in &route_data.broker_datas {
1248 if let Some(master_addr) = bd.broker_addrs().get(&rocketmq_common::common::mix_all::MASTER_ID) {
1249 let request_header = GetConsumeStatsRequestHeader {
1250 consumer_group: consumer_group.clone(),
1251 topic: topic_str.clone(),
1252 topic_request_header: None,
1253 };
1254 let cs = self
1255 .mq_client_api()?
1256 .get_consume_stats(master_addr, request_header, timeout)
1257 .await?;
1258
1259 result.get_offset_table_mut().extend(cs.offset_table);
1260 let new_tps = result.get_consume_tps() + cs.consume_tps;
1261 result.set_consume_tps(new_tps);
1262 }
1263 }
1264 }
1265
1266 Ok(result)
1267 }
1268
1269 async fn check_rocksdb_cq_write_progress(
1270 &self,
1271 broker_addr: CheetahString,
1272 topic: CheetahString,
1273 check_store_time: i64,
1274 ) -> rocketmq_error::RocketMQResult<CheckRocksdbCqWriteResult> {
1275 self.mq_client_api()?
1276 .check_rocksdb_cq_write_progress(&broker_addr, topic, check_store_time, self.remoting_timeout_millis()?)
1277 .await
1278 }
1279
1280 async fn examine_broker_cluster_info(&self) -> rocketmq_error::RocketMQResult<ClusterInfo> {
1281 self.mq_client_api()?
1282 .get_broker_cluster_info(self.remoting_timeout_millis()?)
1283 .await
1284 }
1285
1286 async fn examine_topic_route_info(
1287 &self,
1288 topic: CheetahString,
1289 ) -> rocketmq_error::RocketMQResult<Option<TopicRouteData>> {
1290 self.mq_client_api()?
1291 .get_topic_route_info_from_name_server(&topic, self.remoting_timeout_millis()?)
1292 .await
1293 }
1294
1295 async fn examine_consumer_connection_info(
1296 &self,
1297 consumer_group: CheetahString,
1298 broker_addr: Option<CheetahString>,
1299 ) -> rocketmq_error::RocketMQResult<ConsumerConnection> {
1300 let mut result = ConsumerConnection::new();
1301 let timeout = self.remoting_timeout_millis()?;
1302
1303 let selected_addr = if let Some(broker_addr) = broker_addr {
1304 Some(broker_addr)
1305 } else {
1306 let topic = CheetahString::from_string(mix_all::get_retry_topic(consumer_group.as_str()));
1307 let topic_route_data = self
1308 .mq_client_api()?
1309 .get_topic_route_info_from_name_server(&topic, timeout)
1310 .await?;
1311
1312 topic_route_data.and_then(|topic_route_data| {
1313 topic_route_data
1314 .broker_datas
1315 .choose(&mut rand::rng())
1316 .and_then(|broker_data| broker_data.select_broker_addr())
1317 })
1318 };
1319
1320 if let Some(broker_addr) = selected_addr {
1321 result = self
1322 .mq_client_api()?
1323 .get_consumer_connection_list(broker_addr.as_str(), consumer_group.clone(), timeout)
1324 .await?;
1325 }
1326
1327 if result.get_connection_set().is_empty() {
1328 return Err(mq_client_err!(
1329 rocketmq_remoting::code::response_code::ResponseCode::ConsumerNotOnline,
1330 "Not found the consumer group connection"
1331 ));
1332 }
1333
1334 Ok(result)
1335 }
1336
1337 async fn examine_producer_connection_info(
1338 &self,
1339 producer_group: CheetahString,
1340 topic: CheetahString,
1341 ) -> rocketmq_error::RocketMQResult<ProducerConnection> {
1342 let mut result = ProducerConnection::new();
1343 let timeout = self.remoting_timeout_millis()?;
1344
1345 if let Some(topic_route_data) = self.examine_topic_route_info(topic).await? {
1346 let brokers = &topic_route_data.broker_datas;
1347 let selected_addr = brokers
1348 .choose(&mut rand::rng())
1349 .and_then(|broker_data| broker_data.select_broker_addr());
1350 if let Some(addr) = selected_addr {
1351 result = self
1352 .mq_client_api()?
1353 .get_producer_connection_list(addr.as_str(), producer_group.clone(), timeout)
1354 .await?;
1355 }
1356 }
1357
1358 if result.connection_set().is_empty() {
1359 return Err(mq_client_err!("Not found the producer group connection"));
1360 }
1361
1362 Ok(result)
1363 }
1364
1365 async fn get_all_producer_info(
1366 &self,
1367 broker_addr: CheetahString,
1368 ) -> rocketmq_error::RocketMQResult<ProducerTableInfo> {
1369 self.mq_client_api()?
1370 .get_all_producer_info(broker_addr.as_str(), self.remoting_timeout_millis()?)
1371 .await
1372 }
1373
1374 async fn get_name_server_address_list(&self) -> Vec<CheetahString> {
1375 self.client_instance
1376 .as_ref()
1377 .map(|client_instance| {
1378 client_instance
1379 .get_mq_client_api_impl()
1380 .map(|mq_client_api| mq_client_api.get_name_server_address_list().to_vec())
1381 .unwrap_or_default()
1382 })
1383 .unwrap_or_default()
1384 }
1385
1386 async fn wipe_write_perm_of_broker(
1387 &self,
1388 namesrv_addr: CheetahString,
1389 broker_name: CheetahString,
1390 ) -> rocketmq_error::RocketMQResult<i32> {
1391 self.mq_client_api()?
1392 .wipe_write_perm_of_broker(namesrv_addr, broker_name, self.remoting_timeout_millis()?)
1393 .await
1394 }
1395
1396 async fn add_write_perm_of_broker(
1397 &self,
1398 namesrv_addr: CheetahString,
1399 broker_name: CheetahString,
1400 ) -> rocketmq_error::RocketMQResult<i32> {
1401 self.mq_client_api()?
1402 .add_write_perm_of_broker(namesrv_addr, broker_name, self.remoting_timeout_millis()?)
1403 .await
1404 }
1405
1406 async fn put_kv_config(&self, namespace: CheetahString, key: CheetahString, value: CheetahString) {
1407 if let Err(error) = self.create_and_update_kv_config(namespace, key, value).await {
1408 warn!("put_kv_config failed: {}", error);
1409 }
1410 }
1411
1412 async fn get_kv_config(
1413 &self,
1414 namespace: CheetahString,
1415 key: CheetahString,
1416 ) -> rocketmq_error::RocketMQResult<CheetahString> {
1417 Ok(self
1418 .client_instance
1419 .as_ref()
1420 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?
1421 .get_mq_client_api_impl()?
1422 .get_kvconfig_value(namespace, key, self.remoting_timeout_millis()?)
1423 .await?
1424 .unwrap_or_default())
1425 }
1426
1427 async fn get_kv_list_by_namespace(&self, namespace: CheetahString) -> rocketmq_error::RocketMQResult<KVTable> {
1428 self.mq_client_api()?
1429 .get_kvlist_by_namespace(namespace, self.remoting_timeout_millis()?)
1430 .await
1431 }
1432
1433 async fn delete_topic(
1434 &self,
1435 topic_name: CheetahString,
1436 cluster_name: CheetahString,
1437 ) -> rocketmq_error::RocketMQResult<()> {
1438 let cluster_info = self.examine_broker_cluster_info().await?;
1439 let mut broker_addrs = HashSet::new();
1440 if let Some(cluster_addr_table) = cluster_info.cluster_addr_table.as_ref() {
1441 if let Some(broker_names) = cluster_addr_table.get(&cluster_name) {
1442 if let Some(broker_addr_table) = cluster_info.broker_addr_table.as_ref() {
1443 for broker_name in broker_names {
1444 if let Some(broker_data) = broker_addr_table.get(broker_name) {
1445 broker_addrs.extend(broker_data.broker_addrs().values().cloned());
1446 }
1447 }
1448 }
1449 }
1450 }
1451 self.delete_topic_in_broker(broker_addrs, topic_name.clone()).await?;
1452
1453 let namesrv_addrs: HashSet<CheetahString> = self.get_name_server_address_list().await.into_iter().collect();
1454 self.delete_topic_in_name_server(namesrv_addrs, Some(cluster_name), topic_name)
1455 .await
1456 }
1457
1458 async fn delete_topic_in_broker(
1459 &self,
1460 addrs: HashSet<CheetahString>,
1461 topic: CheetahString,
1462 ) -> rocketmq_error::RocketMQResult<()> {
1463 let request_header = DeleteTopicRequestHeader {
1464 topic: topic.clone(),
1465 topic_request_header: None,
1466 };
1467 let mut api = self.mq_client_api()?;
1468 let timeout = self.remoting_timeout_millis()?;
1469 for addr in addrs {
1470 api.delete_topic_in_broker(
1471 &addr,
1472 DeleteTopicRequestHeader {
1473 topic: request_header.topic.clone(),
1474 topic_request_header: None,
1475 },
1476 timeout,
1477 )
1478 .await?;
1479 }
1480 Ok(())
1481 }
1482
1483 async fn delete_topic_in_name_server(
1484 &self,
1485 addrs: HashSet<CheetahString>,
1486 cluster_name: Option<CheetahString>,
1487 topic: CheetahString,
1488 ) -> rocketmq_error::RocketMQResult<()> {
1489 let request_header = DeleteTopicFromNamesrvRequestHeader::new(topic, cluster_name);
1490 let api = self.mq_client_api()?;
1491 let timeout = self.remoting_timeout_millis()?;
1492 for addr in addrs {
1493 api.delete_topic_in_nameserver(&addr, request_header.clone(), timeout)
1494 .await?;
1495 }
1496 Ok(())
1497 }
1498
1499 async fn delete_subscription_group(
1500 &self,
1501 addr: CheetahString,
1502 group_name: CheetahString,
1503 remove_offset: Option<bool>,
1504 ) -> rocketmq_error::RocketMQResult<()> {
1505 self.mq_client_api()?
1506 .delete_subscription_group(
1507 &addr,
1508 group_name,
1509 remove_offset.unwrap_or(false),
1510 self.remoting_timeout_millis()?,
1511 )
1512 .await
1513 }
1514
1515 async fn create_and_update_kv_config(
1516 &self,
1517 namespace: CheetahString,
1518 key: CheetahString,
1519 value: CheetahString,
1520 ) -> rocketmq_error::RocketMQResult<()> {
1521 self.mq_client_api()?
1522 .put_kvconfig_value(namespace, key, value, self.remoting_timeout_millis()?)
1523 .await
1524 }
1525
1526 async fn delete_kv_config(
1527 &self,
1528 namespace: CheetahString,
1529 key: CheetahString,
1530 ) -> rocketmq_error::RocketMQResult<()> {
1531 self.mq_client_api()?
1532 .delete_kvconfig_value(namespace, key, self.remoting_timeout_millis()?)
1533 .await
1534 }
1535
1536 async fn reset_offset_by_timestamp(
1537 &self,
1538 cluster_name: Option<CheetahString>,
1539 topic: CheetahString,
1540 group: CheetahString,
1541 timestamp: u64,
1542 is_force: bool,
1543 ) -> rocketmq_error::RocketMQResult<HashMap<MessageQueue, u64>> {
1544 let timestamp = timestamp_to_java_long("resetOffsetByTimestamp", timestamp)?;
1545 let topic_route = self.examine_topic_route_info(topic.clone()).await?;
1546 let mut offset_table = HashMap::new();
1547 let timeout = self.remoting_timeout_millis()?;
1548
1549 if let Some(route_data) = topic_route {
1550 for broker_data in &route_data.broker_datas {
1551 if let Some(expected_cluster) = cluster_name.as_ref() {
1552 if broker_data.cluster() != expected_cluster {
1553 continue;
1554 }
1555 }
1556 if let Some(master_addr) = broker_data.broker_addrs().get(&mix_all::MASTER_ID) {
1557 let request_header = ResetOffsetRequestHeader {
1558 topic: topic.clone(),
1559 group: group.clone(),
1560 queue_id: -1,
1561 offset: Some(-1),
1562 timestamp,
1563 is_force,
1564 topic_request_header: None,
1565 };
1566 let offsets = self
1567 .mq_client_api()?
1568 .invoke_broker_to_reset_offset(master_addr, request_header, timeout)
1569 .await?;
1570 for (mq, offset) in offsets {
1571 offset_table.insert(mq, java_long_to_u64("resetOffsetByTimestamp", "offset", offset)?);
1572 }
1573 }
1574 }
1575 }
1576
1577 Ok(offset_table)
1578 }
1579
1580 async fn reset_offset_new(
1581 &self,
1582 consumer_group: CheetahString,
1583 topic: CheetahString,
1584 timestamp: u64,
1585 ) -> rocketmq_error::RocketMQResult<()> {
1586 match self
1587 .reset_offset_by_timestamp(None, topic.clone(), consumer_group.clone(), timestamp, true)
1588 .await
1589 {
1590 Ok(_) => Ok(()),
1591 Err(error) if is_consumer_not_online_error(&error) => {
1592 self.reset_offset_by_timestamp_old(None, consumer_group, topic, timestamp, true)
1593 .await?;
1594 Ok(())
1595 }
1596 Err(error) => Err(error),
1597 }
1598 }
1599
1600 async fn get_consume_status(
1601 &self,
1602 topic: CheetahString,
1603 group: CheetahString,
1604 client_addr: CheetahString,
1605 ) -> rocketmq_error::RocketMQResult<HashMap<CheetahString, HashMap<MessageQueue, u64>>> {
1606 let Some(route_data) = self.examine_topic_route_info(topic.clone()).await? else {
1607 return Ok(HashMap::new());
1608 };
1609
1610 let mut api = self.mq_client_api()?;
1611 let timeout = self.remoting_timeout_millis()?;
1612 let mut merged = HashMap::new();
1613 let mut queried = false;
1614 let mut last_error = None;
1615
1616 for broker_data in &route_data.broker_datas {
1617 let Some(addr) = broker_data.select_broker_addr() else {
1618 continue;
1619 };
1620 queried = true;
1621 match api
1622 .invoke_broker_to_get_consumer_status(
1623 addr.as_str(),
1624 topic.clone(),
1625 group.clone(),
1626 client_addr.clone(),
1627 timeout,
1628 )
1629 .await
1630 {
1631 Ok(result) => merge_consume_status_result(&mut merged, result)?,
1632 Err(error) => {
1633 warn!("get_consume_status failed on broker {}: {}", addr, error);
1634 last_error = Some(error);
1635 }
1636 }
1637 }
1638
1639 if !merged.is_empty() || last_error.is_none() || !queried {
1640 return Ok(merged);
1641 }
1642
1643 if let Some(error) = last_error {
1644 Err(error)
1645 } else {
1646 Ok(merged)
1647 }
1648 }
1649
1650 async fn create_or_update_order_conf(
1651 &self,
1652 key: CheetahString,
1653 value: CheetahString,
1654 is_cluster: bool,
1655 ) -> rocketmq_error::RocketMQResult<()> {
1656 if is_cluster {
1657 return self
1658 .mq_client_api()?
1659 .put_kvconfig_value(
1660 CheetahString::from_static_str(NAMESPACE_ORDER_TOPIC_CONFIG),
1661 key,
1662 value,
1663 self.remoting_timeout_millis()?,
1664 )
1665 .await;
1666 }
1667
1668 let existing = self
1669 .mq_client_api()?
1670 .get_kvconfig_value(
1671 CheetahString::from_static_str(NAMESPACE_ORDER_TOPIC_CONFIG),
1672 key.clone(),
1673 self.remoting_timeout_millis()?,
1674 )
1675 .await?
1676 .unwrap_or_default();
1677
1678 let merged_order_conf = merge_order_conf_entries(existing.as_str(), value.as_str());
1679
1680 self.mq_client_api()?
1681 .put_kvconfig_value(
1682 CheetahString::from_static_str(NAMESPACE_ORDER_TOPIC_CONFIG),
1683 key,
1684 merged_order_conf.into(),
1685 self.remoting_timeout_millis()?,
1686 )
1687 .await
1688 }
1689
1690 async fn query_topic_consume_by_who(&self, topic: CheetahString) -> rocketmq_error::RocketMQResult<GroupList> {
1691 let topic_route = self
1692 .mq_client_api()?
1693 .get_topic_route_info_from_name_server(&topic, self.remoting_timeout_millis()?)
1694 .await?;
1695
1696 if let Some(route_data) = topic_route {
1697 for bd in &route_data.broker_datas {
1698 if let Some(master_addr) = bd.broker_addrs().get(&rocketmq_common::common::mix_all::MASTER_ID) {
1699 let request_header = QueryTopicConsumeByWhoRequestHeader {
1700 topic: topic.clone(),
1701 topic_request_header: None,
1702 };
1703 return self
1704 .mq_client_api()?
1705 .query_topic_consume_by_who(master_addr, request_header, self.remoting_timeout_millis()?)
1706 .await;
1707 }
1708 }
1709 }
1710
1711 Ok(GroupList::default())
1712 }
1713
1714 async fn query_topics_by_consumer(&self, group: CheetahString) -> rocketmq_error::RocketMQResult<TopicList> {
1715 self.query_topics_by_consumer_from_route(group).await
1716 }
1717
1718 async fn query_topics_by_consumer_concurrent(&self, group: CheetahString) -> AdminToolResult<TopicList> {
1719 let timeout = match self.remoting_timeout_millis() {
1720 Ok(timeout) => timeout,
1721 Err(error) => return AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string()),
1722 };
1723 let retry_topic: CheetahString = mix_all::get_retry_topic(&group).into();
1724 let api = match self.mq_client_api() {
1725 Ok(api) => api,
1726 Err(error) => {
1727 return AdminToolResult::failure(AdminToolsResultCodeEnum::MQClientError, error.to_string());
1728 }
1729 };
1730 let topic_route = match api.get_topic_route_info_from_name_server(&retry_topic, timeout).await {
1731 Ok(Some(route_data)) if !route_data.broker_datas.is_empty() => route_data,
1732 Ok(_) => {
1733 return AdminToolResult::failure(
1734 AdminToolsResultCodeEnum::TopicRouteInfoNotExist,
1735 "router info not found.".to_string(),
1736 )
1737 }
1738 Err(error) => {
1739 return AdminToolResult::failure(AdminToolsResultCodeEnum::MQClientError, error.to_string());
1740 }
1741 };
1742
1743 let mut result = TopicList::default();
1744 for broker_data in &topic_route.broker_datas {
1745 let Some(addr) = broker_data.select_broker_addr() else {
1746 continue;
1747 };
1748 match api
1749 .query_topics_by_consumer(&addr, QueryTopicsByConsumerRequestHeader::new(group.clone()), timeout)
1750 .await
1751 {
1752 Ok(topic_list) => {
1753 for topic in topic_list.topic_list {
1754 if !result.topic_list.contains(&topic) {
1755 result.topic_list.push(topic);
1756 }
1757 }
1758 }
1759 Err(error) => {
1760 warn!("query_topics_by_consumer error. group={}, error={}", group, error);
1761 }
1762 }
1763 }
1764
1765 AdminToolResult::success(result)
1766 }
1767
1768 async fn query_subscription(
1769 &self,
1770 group: CheetahString,
1771 topic: CheetahString,
1772 ) -> rocketmq_error::RocketMQResult<SubscriptionData> {
1773 let timeout = self.remoting_timeout_millis()?;
1774 let api = self.mq_client_api()?;
1775 let topic_route = api.get_topic_route_info_from_name_server(&topic, timeout).await?;
1776 let Some(route_data) = topic_route else {
1777 return Err(mq_client_err!(format!("Topic route not found for: {topic}")));
1778 };
1779
1780 for broker_data in &route_data.broker_datas {
1781 let Some(addr) = broker_data.select_broker_addr() else {
1782 continue;
1783 };
1784 let request_header = QuerySubscriptionByConsumerRequestHeader {
1785 group: group.clone(),
1786 topic: topic.clone(),
1787 topic_request_header: None,
1788 };
1789 return api.query_subscription_by_consumer(&addr, request_header, timeout).await;
1790 }
1791
1792 Err(mq_client_err!(format!("Broker address not found for topic: {topic}")))
1793 }
1794
1795 async fn clean_expired_consumer_queue(
1796 &self,
1797 cluster: Option<CheetahString>,
1798 addr: Option<CheetahString>,
1799 ) -> rocketmq_error::RocketMQResult<bool> {
1800 self.execute_broker_cleanup_operation(cluster, addr, BrokerCleanupOperation::CleanExpiredConsumerQueue)
1801 .await
1802 }
1803
1804 async fn clean_expired_consumer_queue_by_addr(&self, addr: CheetahString) -> rocketmq_error::RocketMQResult<bool> {
1805 self.clean_expired_consumer_queue(None, Some(addr)).await
1806 }
1807
1808 async fn delete_expired_commit_log(
1809 &self,
1810 cluster: Option<CheetahString>,
1811 addr: Option<CheetahString>,
1812 ) -> rocketmq_error::RocketMQResult<bool> {
1813 self.execute_broker_cleanup_operation(cluster, addr, BrokerCleanupOperation::DeleteExpiredCommitLog)
1814 .await
1815 }
1816
1817 async fn delete_expired_commit_log_by_addr(&self, addr: CheetahString) -> rocketmq_error::RocketMQResult<bool> {
1818 self.delete_expired_commit_log(None, Some(addr)).await
1819 }
1820
1821 async fn clean_unused_topic(
1822 &self,
1823 cluster: Option<CheetahString>,
1824 addr: Option<CheetahString>,
1825 ) -> rocketmq_error::RocketMQResult<bool> {
1826 self.execute_broker_cleanup_operation(cluster, addr, BrokerCleanupOperation::CleanUnusedTopic)
1827 .await
1828 }
1829
1830 async fn clean_unused_topic_by_addr(&self, addr: CheetahString) -> rocketmq_error::RocketMQResult<bool> {
1831 self.clean_unused_topic(None, Some(addr)).await
1832 }
1833
1834 async fn get_consumer_running_info(
1835 &self,
1836 consumer_group: CheetahString,
1837 client_id: CheetahString,
1838 jstack: bool,
1839 _metrics: Option<bool>,
1840 ) -> rocketmq_error::RocketMQResult<ConsumerRunningInfo> {
1841 let broker_addr = self
1842 .examine_consumer_connection_info(consumer_group.clone(), None)
1843 .await?
1844 .get_connection_set()
1845 .iter()
1846 .find(|connection| connection.get_client_id() == client_id)
1847 .map(|connection| connection.get_client_addr().clone())
1848 .ok_or_else(|| {
1849 rocketmq_error::RocketMQError::IllegalArgument(format!(
1850 "Client `{}` was not found in consumer group `{}`",
1851 client_id, consumer_group
1852 ))
1853 })?;
1854
1855 self.client_instance
1856 .as_ref()
1857 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?
1858 .get_mq_client_api_impl()?
1859 .get_consumer_running_info(
1860 &broker_addr,
1861 consumer_group,
1862 client_id,
1863 jstack,
1864 self.remoting_timeout_millis()?,
1865 )
1866 .await
1867 }
1868
1869 async fn consume_message_directly(
1870 &self,
1871 consumer_group: CheetahString,
1872 client_id: CheetahString,
1873 topic: CheetahString,
1874 msg_id: CheetahString,
1875 ) -> rocketmq_error::RocketMQResult<ConsumeMessageDirectlyResult> {
1876 let consumer_connection = self
1877 .examine_consumer_connection_info(consumer_group.clone(), None)
1878 .await?;
1879 let (resolved_client_id, client_addr) =
1880 select_consumer_direct_connection(&consumer_group, &consumer_connection, Some(&client_id))?;
1881 let message = MQAdminExt::query_message(self, CheetahString::default(), topic.clone(), msg_id.clone()).await?;
1882 let request_header = ConsumeMessageDirectlyResultRequestHeader {
1883 consumer_group,
1884 client_id: Some(resolved_client_id),
1885 msg_id: Some(msg_id),
1886 broker_name: (!message.broker_name().is_empty()).then(|| message.broker_name.clone()),
1887 topic: Some(topic),
1888 topic_sys_flag: None,
1889 group_sys_flag: None,
1890 topic_request_header: None,
1891 };
1892
1893 self.client_instance
1894 .as_ref()
1895 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?
1896 .get_mq_client_api_impl()?
1897 .consume_message_directly(&client_addr, request_header, &message, self.remoting_timeout_millis()?)
1898 .await
1899 }
1900
1901 async fn consume_message_directly_ext(
1902 &self,
1903 _cluster_name: CheetahString,
1904 consumer_group: CheetahString,
1905 client_id: CheetahString,
1906 topic: CheetahString,
1907 msg_id: CheetahString,
1908 ) -> rocketmq_error::RocketMQResult<ConsumeMessageDirectlyResult> {
1909 self.consume_message_directly(consumer_group, client_id, topic, msg_id)
1910 .await
1911 }
1912
1913 async fn clone_group_offset(
1914 &self,
1915 src_group: CheetahString,
1916 dest_group: CheetahString,
1917 topic: CheetahString,
1918 is_offline: bool,
1919 ) -> rocketmq_error::RocketMQResult<()> {
1920 let retry_topic: CheetahString = mix_all::get_retry_topic(src_group.as_str()).into();
1921 let topic_route_data = self
1922 .examine_topic_route_info(retry_topic.clone())
1923 .await?
1924 .ok_or_else(|| mq_client_err!(format!("Topic route not found for retry topic: {retry_topic}")))?;
1925 let timeout = self.remoting_timeout_millis()?;
1926 let api = self.mq_client_api()?;
1927
1928 for broker_data in &topic_route_data.broker_datas {
1929 if let Some(addr) = broker_data.select_broker_addr() {
1930 api.clone_group_offset(
1931 &addr,
1932 src_group.clone(),
1933 dest_group.clone(),
1934 topic.clone(),
1935 is_offline,
1936 timeout,
1937 )
1938 .await?;
1939 }
1940 }
1941
1942 Ok(())
1943 }
1944
1945 async fn get_cluster_list(&self, topic: String) -> rocketmq_error::RocketMQResult<HashSet<CheetahString>> {
1946 self.get_topic_cluster_list(topic).await
1947 }
1948
1949 async fn get_topic_cluster_list(&self, topic: String) -> rocketmq_error::RocketMQResult<HashSet<CheetahString>> {
1950 let cluster_info = self.examine_broker_cluster_info().await?;
1951 let topic_route_data = self
1952 .examine_topic_route_info(topic.clone().into())
1953 .await?
1954 .ok_or_else(|| mq_client_err!(format!("Topic route not found for: {topic}")))?;
1955 let broker_data = topic_route_data
1956 .broker_datas
1957 .first()
1958 .ok_or_else(|| mq_client_err!("Broker datas is empty"))?;
1959 let mut cluster_set = HashSet::new();
1960 let broker_name = broker_data.broker_name();
1961 if let Some(cluster_addr_table) = cluster_info.cluster_addr_table.as_ref() {
1962 cluster_set.extend(
1963 cluster_addr_table
1964 .iter()
1965 .filter(|(cluster_name, broker_names)| broker_names.contains(broker_name))
1966 .map(|(cluster_name, broker_names)| cluster_name.clone()),
1967 );
1968 }
1969 Ok(cluster_set)
1970 }
1971
1972 async fn get_all_topic_config(
1973 &self,
1974 broker_addr: CheetahString,
1975 timeout_millis: u64,
1976 ) -> rocketmq_error::RocketMQResult<TopicConfigSerializeWrapper> {
1977 self.mq_client_api()?
1978 .get_all_topic_config(&broker_addr, timeout_millis)
1979 .await
1980 }
1981
1982 async fn get_user_topic_config(
1983 &self,
1984 broker_addr: CheetahString,
1985 special_topic: bool,
1986 timeout_millis: u64,
1987 ) -> rocketmq_error::RocketMQResult<TopicConfigSerializeWrapper> {
1988 let mut topic_config_wrapper = self.get_all_topic_config(broker_addr.clone(), timeout_millis).await?;
1989 let system_topic_list = self.get_system_topic_list_from_broker(broker_addr).await?;
1990
1991 if let Some(ref mut topic_table) = topic_config_wrapper.topic_config_table_mut() {
1992 retain_java_user_topic_config(topic_table, &system_topic_list.topic_list, special_topic);
1993 }
1994
1995 Ok(topic_config_wrapper)
1996 }
1997
1998 async fn update_consume_offset(
1999 &self,
2000 broker_addr: CheetahString,
2001 consume_group: CheetahString,
2002 mq: MessageQueue,
2003 offset: u64,
2004 ) -> rocketmq_error::RocketMQResult<()> {
2005 let request_header = update_consume_offset_request_header(consume_group, &mq, offset)?;
2006 let client_instance = self
2007 .client_instance
2008 .as_ref()
2009 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?;
2010
2011 client_instance
2012 .get_mq_client_api_impl()?
2013 .update_consumer_offset(&broker_addr, request_header, self.remoting_timeout_millis()?)
2014 .await
2015 }
2016
2017 async fn update_name_server_config(
2018 &self,
2019 properties: HashMap<CheetahString, CheetahString>,
2020 name_servers: Option<Vec<CheetahString>>,
2021 ) -> rocketmq_error::RocketMQResult<()> {
2022 self.mq_client_api()?
2023 .update_name_server_config(properties, name_servers, self.remoting_timeout_millis()?)
2024 .await
2025 }
2026
2027 async fn get_name_server_config(
2028 &self,
2029 name_servers: Vec<CheetahString>,
2030 ) -> rocketmq_error::RocketMQResult<HashMap<CheetahString, HashMap<CheetahString, CheetahString>>> {
2031 Ok(self
2032 .mq_client_api()?
2033 .get_name_server_config(Some(name_servers), self.timeout_millis)
2034 .await?
2035 .unwrap_or_default())
2036 }
2037
2038 async fn probe_name_server(&self, name_server: CheetahString) -> rocketmq_error::RocketMQResult<()> {
2039 self.client_instance
2040 .as_ref()
2041 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?
2042 .get_mq_client_api_impl()?
2043 .probe_name_server(&name_server, self.timeout_millis)
2044 .await
2045 }
2046
2047 async fn resume_check_half_message(
2048 &self,
2049 topic: CheetahString,
2050 msg_id: CheetahString,
2051 ) -> rocketmq_error::RocketMQResult<bool> {
2052 let message = self
2053 .query_message(CheetahString::default(), topic.clone(), msg_id.clone())
2054 .await?;
2055 let broker_addr = CheetahString::from_string(message.store_host().to_string());
2056 let broker_msg_id = if message
2057 .property(&CheetahString::from_static_str(
2058 MessageConst::PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX,
2059 ))
2060 .is_some()
2061 {
2062 message.msg_id().clone()
2063 } else {
2064 msg_id
2065 };
2066
2067 self.mq_client_api()?
2068 .resume_check_half_message(&broker_addr, topic, broker_msg_id, self.remoting_timeout_millis()?)
2069 .await
2070 }
2071
2072 async fn set_message_request_mode(
2073 &self,
2074 broker_addr: CheetahString,
2075 topic: CheetahString,
2076 consumer_group: CheetahString,
2077 mode: MessageRequestMode,
2078 pop_work_group_size: i32,
2079 timeout_millis: u64,
2080 ) -> rocketmq_error::RocketMQResult<()> {
2081 let mut mq_client_api = self.mq_client_api()?;
2082 match mq_client_api
2083 .set_message_request_mode(
2084 &broker_addr,
2085 &topic,
2086 &consumer_group,
2087 mode,
2088 pop_work_group_size,
2089 timeout_millis,
2090 )
2091 .await
2092 {
2093 Ok(_) => Ok(()),
2094 Err(e) => Err(e),
2095 }
2096 }
2097
2098 async fn reset_offset_by_queue_id(
2099 &self,
2100 broker_addr: CheetahString,
2101 consumer_group: CheetahString,
2102 topic_name: CheetahString,
2103 queue_id: i32,
2104 reset_offset: u64,
2105 ) -> rocketmq_error::RocketMQResult<()> {
2106 let (update_header, reset_header) =
2107 reset_offset_by_queue_id_request_headers(consumer_group, topic_name, queue_id, reset_offset)?;
2108 let client_instance = self
2109 .client_instance
2110 .as_ref()
2111 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?;
2112 let timeout_millis = self.remoting_timeout_millis()?;
2113
2114 client_instance
2115 .get_mq_client_api_impl()?
2116 .update_consumer_offset(&broker_addr, update_header, timeout_millis)
2117 .await?;
2118
2119 let offsets = client_instance
2120 .get_mq_client_api_impl()?
2121 .invoke_broker_to_reset_offset(&broker_addr, reset_header, timeout_millis)
2122 .await?;
2123
2124 for (mq, old_offset) in offsets {
2125 info!(
2126 "Reset single message queue {} offset from {} to {}",
2127 mq, old_offset, reset_offset
2128 );
2129 }
2130
2131 Ok(())
2132 }
2133
2134 async fn examine_topic_config(
2135 &self,
2136 addr: CheetahString,
2137 topic: CheetahString,
2138 ) -> rocketmq_error::RocketMQResult<TopicConfig> {
2139 self.client_instance
2140 .as_ref()
2141 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?
2142 .get_topic_config(&addr, topic, self.remoting_timeout_millis()?)
2143 .await
2144 }
2145
2146 async fn create_static_topic(
2147 &self,
2148 addr: CheetahString,
2149 default_topic: CheetahString,
2150 topic_config: TopicConfig,
2151 mapping_detail: TopicQueueMappingDetail,
2152 force: bool,
2153 ) -> rocketmq_error::RocketMQResult<()> {
2154 self.mq_client_api()?
2155 .create_static_topic(
2156 &addr,
2157 default_topic,
2158 topic_config,
2159 mapping_detail,
2160 force,
2161 self.remoting_timeout_millis()?,
2162 )
2163 .await
2164 }
2165
2166 async fn get_controller_meta_data(
2167 &self,
2168 controller_addr: CheetahString,
2169 ) -> rocketmq_error::RocketMQResult<GetMetaDataResponseHeader> {
2170 if let Some(ref mq_client_instance) = self.client_instance {
2171 Ok(mq_client_instance
2172 .get_mq_client_api_impl()?
2173 .get_controller_metadata(controller_addr, self.remoting_timeout_millis()?)
2174 .await?)
2175 } else {
2176 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2177 }
2178 }
2179
2180 async fn reset_master_flush_offset(
2181 &self,
2182 broker_addr: CheetahString,
2183 master_flush_offset: u64,
2184 ) -> rocketmq_error::RocketMQResult<()> {
2185 if let Some(ref mq_client_instance) = self.client_instance {
2186 let master_flush_offset = master_flush_offset_to_java_long(master_flush_offset)?;
2187 mq_client_instance
2188 .get_mq_client_api_impl()?
2189 .reset_master_flush_offset(&broker_addr, master_flush_offset)
2190 .await
2191 } else {
2192 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2193 }
2194 }
2195
2196 async fn get_controller_config(
2197 &self,
2198 controller_servers: Vec<CheetahString>,
2199 ) -> rocketmq_error::RocketMQResult<HashMap<CheetahString, HashMap<CheetahString, CheetahString>>> {
2200 if let Some(ref mq_client_instance) = self.client_instance {
2201 let mut result: HashMap<CheetahString, HashMap<CheetahString, CheetahString>> = HashMap::new();
2202 let mq_client_api = mq_client_instance.get_mq_client_api_impl()?;
2203 let timeout_millis = self.remoting_timeout_millis()?;
2204 let controller_servers =
2205 controller_servers_or_namesrv(controller_servers, mq_client_api.get_name_server_address_list());
2206
2207 for controller_addr in controller_servers {
2208 let config = mq_client_api
2209 .get_controller_config(controller_addr.clone(), timeout_millis)
2210 .await?;
2211 result.insert(controller_addr, config);
2212 }
2213
2214 Ok(result)
2215 } else {
2216 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2217 }
2218 }
2219
2220 async fn update_controller_config(
2221 &self,
2222 properties: HashMap<CheetahString, CheetahString>,
2223 controllers: Vec<CheetahString>,
2224 ) -> rocketmq_error::RocketMQResult<()> {
2225 if let Some(ref mq_client_instance) = self.client_instance {
2226 mq_client_instance
2227 .get_mq_client_api_impl()?
2228 .update_controller_config(properties, controllers, self.remoting_timeout_millis()?)
2229 .await
2230 } else {
2231 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2232 }
2233 }
2234
2235 async fn clean_controller_broker_data(
2236 &self,
2237 controller_addr: CheetahString,
2238 cluster_name: CheetahString,
2239 broker_name: CheetahString,
2240 broker_controller_ids_to_clean: Option<CheetahString>,
2241 is_clean_living_broker: bool,
2242 ) -> rocketmq_error::RocketMQResult<()> {
2243 if let Some(ref mq_client_instance) = self.client_instance {
2244 mq_client_instance
2245 .get_mq_client_api_impl()?
2246 .clean_controller_broker_data(
2247 controller_addr,
2248 cluster_name,
2249 broker_name,
2250 broker_controller_ids_to_clean,
2251 is_clean_living_broker,
2252 self.remoting_timeout_millis()?,
2253 )
2254 .await
2255 } else {
2256 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2257 }
2258 }
2259
2260 async fn update_cold_data_flow_ctr_group_config(
2261 &self,
2262 broker_addr: CheetahString,
2263 properties: HashMap<CheetahString, CheetahString>,
2264 ) -> rocketmq_error::RocketMQResult<()> {
2265 self.mq_client_api()?
2266 .update_cold_data_flow_ctr_group_config(broker_addr, properties, self.remoting_timeout_millis()?)
2267 .await
2268 }
2269
2270 async fn remove_cold_data_flow_ctr_group_config(
2271 &self,
2272 broker_addr: CheetahString,
2273 consumer_group: CheetahString,
2274 ) -> rocketmq_error::RocketMQResult<()> {
2275 self.mq_client_api()?
2276 .remove_cold_data_flow_ctr_group_config(broker_addr, consumer_group, self.remoting_timeout_millis()?)
2277 .await
2278 }
2279
2280 async fn get_cold_data_flow_ctr_info(
2281 &self,
2282 broker_addr: CheetahString,
2283 ) -> rocketmq_error::RocketMQResult<CheetahString> {
2284 self.mq_client_api()?
2285 .get_cold_data_flow_ctr_info(broker_addr, self.remoting_timeout_millis()?)
2286 .await
2287 }
2288
2289 async fn set_commit_log_read_ahead_mode(
2290 &self,
2291 broker_addr: CheetahString,
2292 mode: CheetahString,
2293 ) -> rocketmq_error::RocketMQResult<CheetahString> {
2294 self.mq_client_api()?
2295 .set_commit_log_read_ahead_mode(broker_addr, mode, self.remoting_timeout_millis()?)
2296 .await
2297 }
2298
2299 async fn create_user(
2300 &self,
2301 broker_addr: CheetahString,
2302 username: CheetahString,
2303 password: CheetahString,
2304 user_type: CheetahString,
2305 ) -> rocketmq_error::RocketMQResult<()> {
2306 let user_info = UserInfo {
2307 username: Some(username),
2308 user_type: Some(user_type),
2309 password: Some(password),
2310 user_status: None,
2311 };
2312
2313 if let Some(ref mq_client_instance) = self.client_instance {
2314 let mq_client_api = mq_client_instance.get_mq_client_api_impl()?;
2315 let timeout_millis = self.remoting_timeout_millis()?;
2316 mq_client_api
2317 .create_user(broker_addr, &user_info, timeout_millis)
2318 .await?;
2319 Ok(())
2320 } else {
2321 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2322 }
2323 }
2324
2325 async fn update_user(
2326 &self,
2327 broker_addr: CheetahString,
2328 username: CheetahString,
2329 password: CheetahString,
2330 user_type: CheetahString,
2331 user_status: CheetahString,
2332 ) -> rocketmq_error::RocketMQResult<()> {
2333 let mut user_info = UserInfo {
2334 username: Some(username),
2335 user_type: Some(user_type),
2336 password: Some(password),
2337 user_status: Some(user_status),
2338 };
2339
2340 if let Some(ref mq_client_instance) = self.client_instance {
2341 let mq_client_api = mq_client_instance.get_mq_client_api_impl()?;
2342 let timeout_millis = self.remoting_timeout_millis()?;
2343 mq_client_api
2344 .update_user(broker_addr, &user_info, timeout_millis)
2345 .await?;
2346 Ok(())
2347 } else {
2348 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2349 }
2350 }
2351
2352 async fn delete_user(
2353 &self,
2354 broker_addr: CheetahString,
2355 username: CheetahString,
2356 ) -> rocketmq_error::RocketMQResult<()> {
2357 if let Some(ref mq_client_instance) = self.client_instance {
2358 let mq_client_api = mq_client_instance.get_mq_client_api_impl()?;
2359 let timeout_millis = self.remoting_timeout_millis()?;
2360 mq_client_api.delete_user(broker_addr, username, timeout_millis).await?;
2361 Ok(())
2362 } else {
2363 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2364 }
2365 }
2366
2367 async fn create_acl(
2368 &self,
2369 broker_addr: CheetahString,
2370 subject: CheetahString,
2371 resources: Vec<CheetahString>,
2372 actions: Vec<CheetahString>,
2373 source_ips: Vec<CheetahString>,
2374 decision: CheetahString,
2375 ) -> rocketmq_error::RocketMQResult<()> {
2376 let acl_info = build_acl_info(subject, resources, actions, source_ips, decision);
2377 if let Some(ref mq_client_instance) = self.client_instance {
2378 mq_client_instance
2379 .get_mq_client_api_impl()?
2380 .create_acl(broker_addr, &acl_info, self.remoting_timeout_millis()?)
2381 .await
2382 } else {
2383 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2384 }
2385 }
2386
2387 async fn update_acl(
2388 &self,
2389 broker_addr: CheetahString,
2390 subject: CheetahString,
2391 resources: Vec<CheetahString>,
2392 actions: Vec<CheetahString>,
2393 source_ips: Vec<CheetahString>,
2394 decision: CheetahString,
2395 ) -> rocketmq_error::RocketMQResult<()> {
2396 let acl_info = build_acl_info(subject, resources, actions, source_ips, decision);
2397 if let Some(ref mq_client_instance) = self.client_instance {
2398 mq_client_instance
2399 .get_mq_client_api_impl()?
2400 .update_acl(broker_addr, &acl_info, self.remoting_timeout_millis()?)
2401 .await
2402 } else {
2403 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2404 }
2405 }
2406
2407 async fn delete_acl(
2408 &self,
2409 broker_addr: CheetahString,
2410 subject: CheetahString,
2411 resource: CheetahString,
2412 ) -> rocketmq_error::RocketMQResult<()> {
2413 if let Some(ref client_instance) = self.client_instance {
2414 let mq_client_api = client_instance.get_mq_client_api_impl()?;
2415 mq_client_api
2416 .delete_acl(broker_addr, subject, resource, self.remoting_timeout_millis()?)
2417 .await
2418 } else {
2419 Err(rocketmq_error::RocketMQError::ClientNotStarted)
2420 }
2421 }
2422
2423 async fn create_lite_pull_topic(
2424 &self,
2425 addr: CheetahString,
2426 topic: CheetahString,
2427 queue_num: i32,
2428 topic_sys_flag: i32,
2429 read_queue_nums: i32,
2430 write_queue_nums: i32,
2431 ) -> rocketmq_error::RocketMQResult<()> {
2432 let config = lite_pull_topic_config(
2433 topic,
2434 queue_num,
2435 topic_sys_flag,
2436 read_queue_nums,
2437 write_queue_nums,
2438 false,
2439 )?;
2440 self.create_and_update_topic_config(addr, config).await
2441 }
2442
2443 async fn update_lite_pull_topic(
2444 &self,
2445 addr: CheetahString,
2446 topic: CheetahString,
2447 read_queue_nums: i32,
2448 write_queue_nums: i32,
2449 ) -> rocketmq_error::RocketMQResult<()> {
2450 let config = lite_pull_topic_config(topic, 0, 0, read_queue_nums, write_queue_nums, true)?;
2451 self.create_and_update_topic_config(addr, config).await
2452 }
2453
2454 async fn get_lite_pull_topic(
2455 &self,
2456 addr: CheetahString,
2457 topic: CheetahString,
2458 ) -> rocketmq_error::RocketMQResult<TopicConfig> {
2459 let lite_info = self
2460 .mq_client_api()?
2461 .get_broker_lite_info(&addr, self.remoting_timeout_millis()?)
2462 .await?;
2463 if !lite_info.get_topic_meta().contains_key(&topic) {
2464 return Err(mq_client_err!(format!("Lite pull topic not found: {topic}")));
2465 }
2466 self.examine_topic_config(addr, topic).await
2467 }
2468
2469 async fn delete_lite_pull_topic(
2470 &self,
2471 addr: CheetahString,
2472 cluster_name: CheetahString,
2473 topic: CheetahString,
2474 ) -> rocketmq_error::RocketMQResult<()> {
2475 self.delete_topic_in_broker(HashSet::from([addr]), topic.clone())
2476 .await?;
2477 if cluster_name.is_empty() {
2478 return Ok(());
2479 }
2480 let namesrv_addrs = self.get_name_server_address_list().await.into_iter().collect();
2481 self.delete_topic_in_name_server(namesrv_addrs, Some(cluster_name), topic)
2482 .await
2483 }
2484
2485 async fn query_lite_pull_topic_list(&self, addr: CheetahString) -> rocketmq_error::RocketMQResult<TopicList> {
2486 let lite_info = self
2487 .mq_client_api()?
2488 .get_broker_lite_info(&addr, self.remoting_timeout_millis()?)
2489 .await?;
2490 Ok(lite_topic_list_from_broker_lite_info(Some(addr), &lite_info))
2491 }
2492
2493 async fn query_lite_pull_topic_by_cluster(
2494 &self,
2495 cluster_name: CheetahString,
2496 ) -> rocketmq_error::RocketMQResult<TopicList> {
2497 let timeout_millis = self.remoting_timeout_millis()?;
2498 let api = self.mq_client_api()?;
2499 let cluster_info = api.get_broker_cluster_info(timeout_millis).await?;
2500 let mut topic_names = HashSet::new();
2501
2502 for broker_addr in broker_addrs_for_cluster(&cluster_info, &cluster_name) {
2503 let lite_info = api.get_broker_lite_info(&broker_addr, timeout_millis).await?;
2504 topic_names.extend(lite_info.get_topic_meta().keys().cloned());
2505 }
2506
2507 Ok(topic_list_from_lite_topic_names(None, topic_names))
2508 }
2509
2510 async fn query_lite_pull_subscription_list(
2511 &self,
2512 addr: CheetahString,
2513 topic: CheetahString,
2514 ) -> rocketmq_error::RocketMQResult<GroupList> {
2515 let lite_info = self
2516 .mq_client_api()?
2517 .get_broker_lite_info(&addr, self.remoting_timeout_millis()?)
2518 .await?;
2519 Ok(lite_subscription_group_list_from_broker_lite_info(&topic, &lite_info))
2520 }
2521
2522 async fn update_lite_pull_consumer_offset(
2523 &self,
2524 addr: CheetahString,
2525 topic: CheetahString,
2526 group: CheetahString,
2527 queue_id: i32,
2528 offset: u64,
2529 ) -> rocketmq_error::RocketMQResult<()> {
2530 let request_header = lite_pull_update_consumer_offset_request_header(topic, group, queue_id, offset)?;
2531 self.mq_client_api()?
2532 .update_consumer_offset(&addr, request_header, self.remoting_timeout_millis()?)
2533 .await
2534 }
2535
2536 async fn examine_consume_stats_with_queue(
2537 &self,
2538 consumer_group: CheetahString,
2539 topic: Option<CheetahString>,
2540 queue_id: Option<i32>,
2541 ) -> rocketmq_error::RocketMQResult<ConsumeStats> {
2542 let mut stats = self
2543 .examine_consume_stats(consumer_group, topic.clone(), None, None, None)
2544 .await?;
2545 filter_consume_stats(&mut stats, topic.as_ref(), queue_id);
2546 Ok(stats)
2547 }
2548
2549 async fn examine_consume_stats_concurrent(
2550 &self,
2551 consumer_group: CheetahString,
2552 topic: Option<CheetahString>,
2553 ) -> AdminToolResult<ConsumeStats> {
2554 match self
2555 .examine_consume_stats(consumer_group, topic, None, None, None)
2556 .await
2557 {
2558 Ok(stats) => AdminToolResult::success(stats),
2559 Err(error) => AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string()),
2560 }
2561 }
2562
2563 async fn examine_consume_stats_concurrent_with_cluster(
2564 &self,
2565 consumer_group: CheetahString,
2566 topic: Option<CheetahString>,
2567 cluster_name: Option<CheetahString>,
2568 ) -> AdminToolResult<ConsumeStats> {
2569 match self
2570 .examine_consume_stats(consumer_group, topic, cluster_name, None, None)
2571 .await
2572 {
2573 Ok(stats) => AdminToolResult::success(stats),
2574 Err(error) => AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string()),
2575 }
2576 }
2577
2578 async fn export_rocksdb_consumer_offset_to_json(
2579 &self,
2580 broker_addr: CheetahString,
2581 file_path: CheetahString,
2582 ) -> rocketmq_error::RocketMQResult<()> {
2583 if !file_path.is_empty() {
2584 return Err(RocketMQError::illegal_argument(
2585 "exportRocksDB consumerOffsets filePath is local-mode only and cannot be sent over RPC",
2586 ));
2587 }
2588
2589 self.mq_client_api()?
2590 .export_rocksdb_config_to_json(
2591 broker_addr,
2592 vec![CheetahString::from_static_str(ROCKSDB_CONFIG_TYPE_CONSUMER_OFFSETS)],
2593 self.remoting_timeout_millis()?,
2594 )
2595 .await
2596 }
2597
2598 async fn export_rocksdb_consumer_offset_from_memory(
2599 &self,
2600 broker_addr: CheetahString,
2601 ) -> rocketmq_error::RocketMQResult<CheetahString> {
2602 self.mq_client_api()?
2603 .get_all_consumer_offset_json(broker_addr, self.remoting_timeout_millis()?)
2604 .await
2605 }
2606
2607 async fn sync_broker_member_group(
2608 &self,
2609 controller_addr: CheetahString,
2610 cluster_name: CheetahString,
2611 broker_name: CheetahString,
2612 ) -> rocketmq_error::RocketMQResult<()> {
2613 if !controller_addr.is_empty() {
2614 return Err(RocketMQError::illegal_argument(
2615 "syncBrokerMemberGroup uses NameServer; controllerAddr is not supported by this facade",
2616 ));
2617 }
2618
2619 self.mq_client_api()?
2620 .sync_broker_member_group(&cluster_name, &broker_name, false)
2621 .await?;
2622 Ok(())
2623 }
2624
2625 async fn get_topic_config_by_topic_name(
2626 &self,
2627 broker_addr: CheetahString,
2628 topic_name: CheetahString,
2629 ) -> rocketmq_error::RocketMQResult<TopicConfig> {
2630 self.client_instance
2631 .as_ref()
2632 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?
2633 .get_topic_config(&broker_addr, topic_name, self.remoting_timeout_millis()?)
2634 .await
2635 }
2636
2637 async fn notify_min_broker_id_changed(
2638 &self,
2639 cluster_name: CheetahString,
2640 broker_name: CheetahString,
2641 min_broker_id: u64,
2642 min_broker_addr: CheetahString,
2643 offline_broker_addr: Option<CheetahString>,
2644 ha_broker_addr: Option<CheetahString>,
2645 ) -> rocketmq_error::RocketMQResult<()> {
2646 let request_header = notify_min_broker_id_change_request_header(
2647 min_broker_id,
2648 min_broker_addr,
2649 offline_broker_addr,
2650 ha_broker_addr,
2651 )?;
2652 let mq_client_api = self.mq_client_api()?;
2653 let broker_member_group = mq_client_api
2654 .sync_broker_member_group(&cluster_name, &broker_name, false)
2655 .await?
2656 .unwrap_or_else(|| BrokerMemberGroup::new(cluster_name.clone(), broker_name.clone()));
2657 let broker_addrs = choose_min_broker_notify_addrs(
2658 &broker_member_group.broker_addrs,
2659 min_broker_id,
2660 request_header.offline_broker_addr.as_ref(),
2661 );
2662
2663 if broker_addrs.is_empty() {
2664 return Err(RocketMQError::illegal_argument(format!(
2665 "notifyMinBrokerIdChanged cannot resolve broker addresses for cluster `{}` broker `{}`",
2666 cluster_name, broker_name
2667 )));
2668 }
2669
2670 for broker_addr in broker_addrs {
2671 mq_client_api
2672 .notify_min_broker_id_changed(&broker_addr, request_header.clone(), 300)
2673 .await?;
2674 }
2675 Ok(())
2676 }
2677
2678 async fn get_topic_stats_info(
2679 &self,
2680 broker_addr: CheetahString,
2681 topic: CheetahString,
2682 ) -> rocketmq_error::RocketMQResult<TopicStatsTable> {
2683 self.mq_client_api()?
2684 .get_topic_stats_info(
2685 &broker_addr,
2686 GetTopicStatsInfoRequestHeader {
2687 topic,
2688 topic_request_header: None,
2689 },
2690 self.remoting_timeout_millis()?,
2691 )
2692 .await
2693 }
2694
2695 async fn query_broker_has_topic(
2696 &self,
2697 broker_addr: CheetahString,
2698 topic: CheetahString,
2699 ) -> rocketmq_error::RocketMQResult<bool> {
2700 map_topic_config_lookup_result(self.get_topic_config_by_topic_name(broker_addr, topic).await)
2701 }
2702
2703 async fn get_system_topic_list_from_broker(
2704 &self,
2705 broker_addr: CheetahString,
2706 ) -> rocketmq_error::RocketMQResult<TopicList> {
2707 self.mq_client_api()?
2708 .get_system_topic_list_from_broker(&broker_addr, self.remoting_timeout_millis()?)
2709 .await
2710 }
2711
2712 async fn examine_topic_route_info_with_timeout(
2713 &self,
2714 topic: CheetahString,
2715 timeout_millis: u64,
2716 ) -> rocketmq_error::RocketMQResult<Option<TopicRouteData>> {
2717 self.mq_client_api()?
2718 .get_topic_route_info_from_name_server(&topic, timeout_millis)
2719 .await
2720 }
2721
2722 async fn export_pop_records(&self, broker_addr: CheetahString, timeout: u64) -> rocketmq_error::RocketMQResult<()> {
2723 self.mq_client_api()?.export_pop_record(broker_addr, timeout).await
2724 }
2725
2726 async fn switch_timer_engine(
2727 &self,
2728 broker_addr: CheetahString,
2729 des_timer_engine: CheetahString,
2730 ) -> rocketmq_error::RocketMQResult<()> {
2731 self.mq_client_api()?
2732 .switch_timer_engine(&broker_addr, des_timer_engine, self.remoting_timeout_millis()?)
2733 .await
2734 }
2735
2736 async fn trigger_lite_dispatch(
2737 &self,
2738 broker_addr: CheetahString,
2739 group: CheetahString,
2740 client_id: CheetahString,
2741 ) -> rocketmq_error::RocketMQResult<()> {
2742 self.mq_client_api()?
2743 .trigger_lite_dispatch(&broker_addr, group, client_id, self.remoting_timeout_millis()?)
2744 .await
2745 }
2746 #[allow(deprecated)]
2747 async fn delete_topic_in_broker_concurrent(
2748 &self,
2749 addrs: HashSet<CheetahString>,
2750 topic: CheetahString,
2751 ) -> AdminToolResult<BrokerOperatorResult> {
2752 let api = match self.mq_client_api() {
2753 Ok(api) => api,
2754 Err(error) => {
2755 return AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string());
2756 }
2757 };
2758 let timeout = match self.remoting_timeout_millis() {
2759 Ok(timeout) => timeout,
2760 Err(error) => return AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string()),
2761 };
2762 let mut success_list = Vec::new();
2763 let mut failure_list = Vec::new();
2764
2765 for addr in addrs {
2766 let request_header = DeleteTopicRequestHeader {
2767 topic: topic.clone(),
2768 topic_request_header: None,
2769 };
2770 match api.delete_topic_in_broker(&addr, request_header, timeout).await {
2771 Ok(()) => success_list.push(addr),
2772 Err(error) => {
2773 warn!("deleteTopicInBroker error. topic={}, broker={}, {}", topic, addr, error);
2774 failure_list.push(addr);
2775 }
2776 }
2777 }
2778
2779 AdminToolResult::success(broker_operator_result(success_list, failure_list))
2780 }
2781
2782 async fn reset_offset_by_timestamp_old(
2783 &self,
2784 cluster_name: Option<CheetahString>,
2785 consumer_group: CheetahString,
2786 topic: CheetahString,
2787 timestamp: u64,
2788 force: bool,
2789 ) -> rocketmq_error::RocketMQResult<Vec<RollbackStats>> {
2790 let timestamp = timestamp_to_java_long("resetOffsetByTimestampOld", timestamp)?;
2791 let mut route_topic = topic.clone();
2792 if !topic.is_empty()
2793 && (mix_all::is_lmq(Some(topic.as_str()))
2794 || topic.as_str() == format!("{}wheel_timer", TopicValidator::SYSTEM_TOPIC_PREFIX))
2795 && cluster_name.as_ref().is_some_and(|name| !name.is_empty())
2796 {
2797 if let Some(cluster_name) = cluster_name {
2798 route_topic = cluster_name;
2799 }
2800 }
2801 let topic_route_data = self.examine_topic_route_info(route_topic).await?;
2802 let mut rollback_stats_list = Vec::new();
2803
2804 if let Some(route_data) = topic_route_data {
2805 let mut topic_route_map = HashMap::new();
2806 for queue_data in &route_data.queue_datas {
2807 topic_route_map.insert(queue_data.broker_name().to_string(), queue_data.clone());
2808 }
2809
2810 for broker_data in &route_data.broker_datas {
2811 if let Some(addr) = broker_data.select_broker_addr() {
2812 if let Some(queue_data) = topic_route_map.get(broker_data.broker_name().as_str()) {
2813 let mut rollback_stats = self
2814 .reset_offset_by_timestamp_old_on_broker(
2815 addr,
2816 queue_data,
2817 consumer_group.clone(),
2818 topic.clone(),
2819 timestamp,
2820 force,
2821 )
2822 .await?;
2823 rollback_stats_list.append(&mut rollback_stats);
2824 }
2825 }
2826 }
2827 }
2828
2829 Ok(rollback_stats_list)
2830 }
2831 #[allow(deprecated)]
2832 async fn reset_offset_new_concurrent(
2833 &self,
2834 group: CheetahString,
2835 topic: CheetahString,
2836 timestamp: u64,
2837 ) -> AdminToolResult<BrokerOperatorResult> {
2838 let timestamp = match timestamp_to_java_long("resetOffsetNewConcurrent", timestamp) {
2839 Ok(timestamp) => timestamp,
2840 Err(error) => return AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string()),
2841 };
2842 let api = match self.mq_client_api() {
2843 Ok(api) => api,
2844 Err(error) => return AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string()),
2845 };
2846 let timeout = match self.remoting_timeout_millis() {
2847 Ok(timeout) => timeout,
2848 Err(error) => return AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string()),
2849 };
2850 let route_data = match api.get_topic_route_info_from_name_server(&topic, timeout).await {
2851 Ok(Some(route_data)) if !route_data.broker_datas.is_empty() => route_data,
2852 Ok(_) => {
2853 return AdminToolResult::failure(
2854 AdminToolsResultCodeEnum::TopicRouteInfoNotExist,
2855 "topic router info not found".to_string(),
2856 );
2857 }
2858 Err(error) => return AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string()),
2859 };
2860
2861 let mut topic_route_map = HashMap::new();
2862 for queue_data in &route_data.queue_datas {
2863 topic_route_map.insert(queue_data.broker_name().clone(), queue_data.clone());
2864 }
2865
2866 let mut success_list = Vec::new();
2867 let mut failure_list = Vec::new();
2868 for broker_data in &route_data.broker_datas {
2869 let Some(addr) = broker_data.select_broker_addr() else {
2870 continue;
2871 };
2872
2873 let reset_header = ResetOffsetRequestHeader {
2874 topic: topic.clone(),
2875 group: group.clone(),
2876 queue_id: -1,
2877 offset: Some(-1),
2878 timestamp,
2879 is_force: true,
2880 topic_request_header: None,
2881 };
2882
2883 match api.invoke_broker_to_reset_offset(&addr, reset_header, timeout).await {
2884 Ok(_) => success_list.push(addr),
2885 Err(error) if is_consumer_not_online_error(&error) => {
2886 match topic_route_map.get(broker_data.broker_name()) {
2887 Some(queue_data) => {
2888 match self
2889 .reset_offset_by_timestamp_old_on_broker(
2890 addr.clone(),
2891 queue_data,
2892 group.clone(),
2893 topic.clone(),
2894 timestamp,
2895 true,
2896 )
2897 .await
2898 {
2899 Ok(_) => success_list.push(addr),
2900 Err(error) => {
2901 warn!(
2902 "resetOffsetByTimestampOld error. addr={}, topic={}, group={}, timestamp={}, \
2903 {}",
2904 addr, topic, group, timestamp, error
2905 );
2906 failure_list.push(addr);
2907 }
2908 }
2909 }
2910 None => {
2911 warn!(
2912 "resetOffsetByTimestampOld error. addr={}, topic={}, group={}, timestamp={}, missing \
2913 queue data for broker {}",
2914 addr,
2915 topic,
2916 group,
2917 timestamp,
2918 broker_data.broker_name()
2919 );
2920 failure_list.push(addr);
2921 }
2922 }
2923 }
2924 Err(error) if response_code_from_error(&error) == Some(ResponseCode::SystemError) => {
2925 success_list.push(addr);
2926 }
2927 Err(error) => {
2928 warn!(
2929 "resetOffsetNewConcurrent error. addr={}, topic={}, group={}, timestamp={}, {}",
2930 addr, topic, group, timestamp, error
2931 );
2932 failure_list.push(addr);
2933 }
2934 }
2935 }
2936
2937 let success = success_list.len() == route_data.broker_datas.len();
2938 let result = broker_operator_result(success_list, failure_list);
2939 if success {
2940 AdminToolResult::success(result)
2941 } else {
2942 AdminToolResult::failure_with_data(
2943 AdminToolsResultCodeEnum::MQBrokerError,
2944 "operator failure".into(),
2945 result,
2946 )
2947 }
2948 }
2949
2950 async fn query_consume_time_span(
2951 &self,
2952 topic: CheetahString,
2953 group: CheetahString,
2954 ) -> rocketmq_error::RocketMQResult<Vec<QueueTimeSpan>> {
2955 let timeout = self.remoting_timeout_millis()?;
2956 let mut result = Vec::new();
2957 if let Some(route_data) = self.examine_topic_route_info(topic.clone()).await? {
2958 for broker_data in &route_data.broker_datas {
2959 if let Some(master_addr) = broker_data.broker_addrs().get(&mix_all::MASTER_ID) {
2960 let spans = self
2961 .mq_client_api()?
2962 .query_consume_time_span(
2963 master_addr,
2964 QueryConsumeTimeSpanRequestHeader {
2965 topic: topic.clone(),
2966 group: group.clone(),
2967 topic_request_header: None,
2968 },
2969 timeout,
2970 )
2971 .await?;
2972 result.extend(spans);
2973 }
2974 }
2975 }
2976 Ok(result)
2977 }
2978
2979 async fn query_consume_time_span_concurrent(
2980 &self,
2981 topic: CheetahString,
2982 group: CheetahString,
2983 ) -> AdminToolResult<Vec<QueueTimeSpan>> {
2984 match self.query_consume_time_span(topic, group).await {
2985 Ok(spans) => AdminToolResult::success(spans),
2986 Err(error) => AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string()),
2987 }
2988 }
2989 #[allow(deprecated)]
2990 async fn message_track_detail(&self, msg: MessageExt) -> rocketmq_error::RocketMQResult<Vec<MessageTrack>> {
2991 let group_list = self.query_topic_consume_by_who(msg.topic().clone()).await?;
2992 let mut result = Vec::with_capacity(group_list.get_group_list().len());
2993
2994 for group in group_list.get_group_list() {
2995 let mut track = build_message_track(group.as_str());
2996 let consumer_connection = match self.examine_consumer_connection_info(group.clone(), None).await {
2997 Ok(connection) => connection,
2998 Err(error) => {
2999 apply_track_error(&mut track, &error);
3000 result.push(track);
3001 continue;
3002 }
3003 };
3004
3005 match consumer_connection.get_consume_type() {
3006 Some(ConsumeType::ConsumeActively) => {
3007 track.set_track_type(TrackType::Pull);
3008 }
3009 Some(ConsumeType::ConsumePassively) => {
3010 if consumer_connection.get_message_model() == Some(MessageModel::Broadcasting) {
3011 track.set_track_type(TrackType::ConsumeBroadcasting);
3012 result.push(track);
3013 continue;
3014 }
3015
3016 let consumed = match self.message_consumed_by_group(&msg, group).await {
3017 Ok(consumed) => consumed,
3018 Err(error) => {
3019 apply_track_error(&mut track, &error);
3020 result.push(track);
3021 continue;
3022 }
3023 };
3024
3025 if consumed {
3026 track.set_track_type(resolve_consumed_track_type(&msg, &consumer_connection));
3027 } else {
3028 track.set_track_type(TrackType::NotConsumedYet);
3029 }
3030 }
3031 _ => {}
3032 }
3033
3034 result.push(track);
3035 }
3036
3037 result.sort_by(|left, right| left.consumer_group.cmp(&right.consumer_group));
3038 Ok(result)
3039 }
3040 #[allow(deprecated)]
3041 async fn message_track_detail_concurrent(&self, msg: MessageExt) -> AdminToolResult<Vec<MessageTrack>> {
3042 match self.message_track_detail(msg).await {
3043 Ok(data) => AdminToolResult::success(data),
3044 Err(error) => AdminToolResult::failure(admin_result_code_for_error(&error), error.to_string()),
3045 }
3046 }
3047
3048 async fn view_broker_stats_data(
3049 &self,
3050 broker_addr: CheetahString,
3051 stats_name: CheetahString,
3052 stats_key: CheetahString,
3053 ) -> rocketmq_error::RocketMQResult<BrokerStatsData> {
3054 let request_header = ViewBrokerStatsDataRequestHeader { stats_name, stats_key };
3055 self.mq_client_api()?
3056 .view_broker_stats_data(&broker_addr, request_header, self.remoting_timeout_millis()?)
3057 .await
3058 }
3059
3060 async fn fetch_consume_stats_in_broker(
3061 &self,
3062 broker_addr: CheetahString,
3063 is_order: bool,
3064 timeout_millis: u64,
3065 ) -> rocketmq_error::RocketMQResult<ConsumeStatsList> {
3066 self.mq_client_api()?
3067 .fetch_consume_stats_in_broker(&broker_addr, GetConsumeStatsInBrokerHeader { is_order }, timeout_millis)
3068 .await
3069 }
3070
3071 async fn get_all_subscription_group(
3072 &self,
3073 broker_addr: CheetahString,
3074 timeout_millis: u64,
3075 ) -> rocketmq_error::RocketMQResult<SubscriptionGroupWrapper> {
3076 self.mq_client_api()?
3077 .get_all_subscription_group_config(&broker_addr, timeout_millis)
3078 .await
3079 }
3080
3081 async fn get_user_subscription_group(
3082 &self,
3083 broker_addr: CheetahString,
3084 timeout_millis: u64,
3085 ) -> rocketmq_error::RocketMQResult<SubscriptionGroupWrapper> {
3086 let subscription_group_wrapper = self.get_all_subscription_group(broker_addr, timeout_millis).await?;
3087
3088 let system_group_set = get_system_group_set();
3089 let table = subscription_group_wrapper.get_subscription_group_table();
3090 table.retain(|key, _| !mix_all::is_sys_consumer_group(key.as_str()) && !system_group_set.contains(key));
3092
3093 Ok(subscription_group_wrapper)
3094 }
3095
3096 async fn query_consume_queue(
3097 &self,
3098 broker_addr: CheetahString,
3099 topic: CheetahString,
3100 queue_id: i32,
3101 index: u64,
3102 count: i32,
3103 consumer_group: CheetahString,
3104 ) -> rocketmq_error::RocketMQResult<QueryConsumeQueueResponseBody> {
3105 let index = query_consume_queue_index_to_java_long(index)?;
3106 self.mq_client_api()?
3107 .query_consume_queue(
3108 &broker_addr,
3109 topic,
3110 queue_id,
3111 index,
3112 count,
3113 consumer_group,
3114 self.remoting_timeout_millis()?,
3115 )
3116 .await
3117 }
3118
3119 async fn update_and_get_group_read_forbidden(
3120 &self,
3121 broker_addr: CheetahString,
3122 group_name: CheetahString,
3123 topic_name: CheetahString,
3124 readable: Option<bool>,
3125 ) -> rocketmq_error::RocketMQResult<GroupForbidden> {
3126 let client_instance = self
3127 .client_instance
3128 .as_ref()
3129 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?;
3130 let request_header = update_group_forbidden_request_header(group_name, topic_name, readable);
3131
3132 client_instance
3133 .get_mq_client_api_impl()?
3134 .update_and_get_group_forbidden(&broker_addr, request_header, self.remoting_timeout_millis()?)
3135 .await
3136 }
3137
3138 async fn query_message(
3139 &self,
3140 _cluster_name: CheetahString,
3141 topic: CheetahString,
3142 msg_id: CheetahString,
3143 ) -> rocketmq_error::RocketMQResult<MessageExt> {
3144 let client_instance = self
3145 .client_instance
3146 .as_ref()
3147 .ok_or(rocketmq_error::RocketMQError::ClientNotStarted)?;
3148
3149 let msg_id_str = msg_id.as_str();
3150
3151 if let Err(e) = message_decoder::validate_message_id(msg_id_str) {
3152 return Err(rocketmq_error::RocketMQError::IllegalArgument(format!(
3153 "Invalid message ID: {}",
3154 e
3155 )));
3156 }
3157
3158 let message_id = message_decoder::decode_message_id(msg_id_str).map_err(|e| {
3159 rocketmq_error::RocketMQError::IllegalArgument(format!("Failed to decode message ID: {}", e))
3160 })?;
3161 let broker_addr =
3162 CheetahString::from_string(format!("{}:{}", message_id.address.ip(), message_id.address.port()));
3163
3164 let request_header = ViewMessageRequestHeader {
3165 topic: Some(topic),
3166 offset: message_id.offset,
3167 };
3168
3169 client_instance
3170 .get_mq_client_api_impl()?
3171 .view_message(&broker_addr, request_header, self.remoting_timeout_millis()?)
3172 .await
3173 }
3174
3175 async fn get_broker_ha_status(&self, broker_addr: CheetahString) -> rocketmq_error::RocketMQResult<HARuntimeInfo> {
3176 if let Some(ref mq_client_instance) = self.client_instance {
3177 Ok(mq_client_instance
3178 .get_mq_client_api_impl()?
3179 .get_broker_ha_status(broker_addr, self.remoting_timeout_millis()?)
3180 .await?)
3181 } else {
3182 Err(rocketmq_error::RocketMQError::ClientNotStarted)
3183 }
3184 }
3185
3186 async fn get_in_sync_state_data(
3187 &self,
3188 controller_address: CheetahString,
3189 brokers: Vec<CheetahString>,
3190 ) -> rocketmq_error::RocketMQResult<BrokerReplicasInfo> {
3191 if let Some(ref mq_client_instance) = self.client_instance {
3192 Ok(mq_client_instance
3193 .get_mq_client_api_impl()?
3194 .get_in_sync_state_data(controller_address, brokers, self.remoting_timeout_millis()?)
3195 .await?)
3196 } else {
3197 Err(rocketmq_error::RocketMQError::ClientNotStarted)
3198 }
3199 }
3200
3201 async fn get_broker_epoch_cache(
3202 &self,
3203 broker_addr: CheetahString,
3204 ) -> rocketmq_error::RocketMQResult<EpochEntryCache> {
3205 if let Some(ref mq_client_instance) = self.client_instance {
3206 Ok(mq_client_instance
3207 .get_mq_client_api_impl()?
3208 .get_broker_epoch_cache(broker_addr, self.remoting_timeout_millis()?)
3209 .await?)
3210 } else {
3211 Err(rocketmq_error::RocketMQError::ClientNotStarted)
3212 }
3213 }
3214
3215 async fn elect_master(
3216 &self,
3217 controller_addr: CheetahString,
3218 cluster_name: CheetahString,
3219 broker_name: CheetahString,
3220 broker_id: Option<u64>,
3221 ) -> rocketmq_error::RocketMQResult<(ElectMasterResponseHeader, BrokerMemberGroup)> {
3222 if let Some(ref mq_client_instance) = self.client_instance {
3223 mq_client_instance
3224 .get_mq_client_api_impl()?
3225 .elect_master(
3226 controller_addr,
3227 cluster_name,
3228 broker_name,
3229 broker_id,
3230 self.remoting_timeout_millis()?,
3231 )
3232 .await
3233 } else {
3234 Err(rocketmq_error::RocketMQError::ClientNotStarted)
3235 }
3236 }
3237
3238 async fn create_user_with_info(
3239 &self,
3240 broker_addr: CheetahString,
3241 username: CheetahString,
3242 password: CheetahString,
3243 ) -> rocketmq_error::RocketMQResult<()> {
3244 let user_info = UserInfo {
3245 username: Some(username),
3246 password: Some(password),
3247 user_type: None,
3248 user_status: None,
3249 };
3250
3251 self.mq_client_api()?
3252 .create_user(broker_addr, &user_info, self.remoting_timeout_millis()?)
3253 .await
3254 }
3255
3256 async fn update_user_with_info(
3257 &self,
3258 broker_addr: CheetahString,
3259 username: CheetahString,
3260 password: CheetahString,
3261 ) -> rocketmq_error::RocketMQResult<()> {
3262 let user_info = UserInfo {
3263 username: Some(username),
3264 password: Some(password),
3265 user_type: None,
3266 user_status: None,
3267 };
3268
3269 self.mq_client_api()?
3270 .update_user(broker_addr, &user_info, self.remoting_timeout_millis()?)
3271 .await
3272 }
3273
3274 async fn get_user(
3275 &self,
3276 broker_addr: CheetahString,
3277 username: CheetahString,
3278 ) -> rocketmq_error::RocketMQResult<Option<UserInfo>> {
3279 if let Some(ref mq_client_instance) = self.client_instance {
3280 let mq_client_api = mq_client_instance.get_mq_client_api_impl()?;
3281 let timeout_millis = self.remoting_timeout_millis()?;
3282 let result = mq_client_api.get_user(broker_addr, username, timeout_millis).await?;
3283 Ok(result)
3284 } else {
3285 Err(rocketmq_error::RocketMQError::ClientNotStarted)
3286 }
3287 }
3288
3289 async fn list_users(
3290 &self,
3291 broker_addr: CheetahString,
3292 filter: CheetahString,
3293 ) -> rocketmq_error::RocketMQResult<Vec<UserInfo>> {
3294 if let Some(ref mq_client_instance) = self.client_instance {
3295 let mq_client_api = mq_client_instance.get_mq_client_api_impl()?;
3296 let timeout_millis = self.remoting_timeout_millis()?;
3297 let result = mq_client_api.list_users(broker_addr, filter, timeout_millis).await?;
3298 Ok(result)
3299 } else {
3300 Err(rocketmq_error::RocketMQError::ClientNotStarted)
3301 }
3302 }
3303
3304 async fn create_acl_with_info(
3305 &self,
3306 broker_addr: CheetahString,
3307 subject: CheetahString,
3308 ) -> rocketmq_error::RocketMQResult<()> {
3309 self.create_acl_with_acl_info(
3310 broker_addr,
3311 AclInfo {
3312 subject: Some(subject),
3313 policies: None,
3314 },
3315 )
3316 .await
3317 }
3318
3319 async fn update_acl_with_info(
3320 &self,
3321 broker_addr: CheetahString,
3322 subject: CheetahString,
3323 ) -> rocketmq_error::RocketMQResult<()> {
3324 self.update_acl_with_acl_info(
3325 broker_addr,
3326 AclInfo {
3327 subject: Some(subject),
3328 policies: None,
3329 },
3330 )
3331 .await
3332 }
3333
3334 async fn get_acl(
3335 &self,
3336 broker_addr: CheetahString,
3337 subject: CheetahString,
3338 ) -> rocketmq_error::RocketMQResult<AclInfo> {
3339 let acl_infos = self
3340 .list_acl(broker_addr.clone(), subject.clone(), CheetahString::default())
3341 .await?;
3342 acl_infos
3343 .into_iter()
3344 .find(|acl_info| acl_info.subject.as_ref() == Some(&subject))
3345 .ok_or_else(|| {
3346 RocketMQError::illegal_argument(format!(
3347 "ACL with subject {} was not found on broker {}",
3348 subject, broker_addr
3349 ))
3350 })
3351 }
3352
3353 async fn list_acl(
3354 &self,
3355 broker_addr: CheetahString,
3356 subject_filter: CheetahString,
3357 resource_filter: CheetahString,
3358 ) -> rocketmq_error::RocketMQResult<Vec<AclInfo>> {
3359 if let Some(ref mq_client_instance) = self.client_instance {
3360 let mq_client_api = mq_client_instance.get_mq_client_api_impl()?;
3361 let timeout_millis = self.remoting_timeout_millis()?;
3362 let result = mq_client_api
3363 .list_acl(broker_addr, subject_filter, resource_filter, timeout_millis)
3364 .await?;
3365 Ok(result)
3366 } else {
3367 Err(rocketmq_error::RocketMQError::ClientNotStarted)
3368 }
3369 }
3370
3371 async fn get_broker_lite_info(
3372 &self,
3373 broker_addr: CheetahString,
3374 ) -> rocketmq_error::RocketMQResult<GetBrokerLiteInfoResponseBody> {
3375 if let Some(ref mq_client_instance) = self.client_instance {
3376 mq_client_instance
3377 .get_mq_client_api_impl()?
3378 .get_broker_lite_info(&broker_addr, self.remoting_timeout_millis()?)
3379 .await
3380 } else {
3381 Err(rocketmq_error::RocketMQError::ClientNotStarted)
3382 }
3383 }
3384
3385 async fn get_parent_topic_info(
3386 &self,
3387 broker_addr: CheetahString,
3388 topic: CheetahString,
3389 ) -> rocketmq_error::RocketMQResult<GetParentTopicInfoResponseBody> {
3390 self.mq_client_api()?
3391 .get_parent_topic_info(&broker_addr, topic, self.remoting_timeout_millis()?)
3392 .await
3393 }
3394
3395 async fn get_lite_topic_info(
3396 &self,
3397 broker_addr: CheetahString,
3398 parent_topic: CheetahString,
3399 lite_topic: CheetahString,
3400 ) -> rocketmq_error::RocketMQResult<GetLiteTopicInfoResponseBody> {
3401 if let Some(ref mq_client_instance) = self.client_instance {
3402 mq_client_instance
3403 .get_mq_client_api_impl()?
3404 .get_lite_topic_info(
3405 &broker_addr,
3406 &parent_topic,
3407 &lite_topic,
3408 self.remoting_timeout_millis()?,
3409 )
3410 .await
3411 } else {
3412 Err(rocketmq_error::RocketMQError::ClientNotStarted)
3413 }
3414 }
3415
3416 async fn get_lite_client_info(
3417 &self,
3418 broker_addr: CheetahString,
3419 parent_topic: CheetahString,
3420 group: CheetahString,
3421 client_id: CheetahString,
3422 ) -> rocketmq_error::RocketMQResult<GetLiteClientInfoResponseBody> {
3423 self.mq_client_api()?
3424 .get_lite_client_info(
3425 &broker_addr,
3426 parent_topic,
3427 group,
3428 client_id,
3429 self.remoting_timeout_millis()?,
3430 )
3431 .await
3432 }
3433
3434 async fn get_lite_group_info(
3435 &self,
3436 broker_addr: CheetahString,
3437 group: CheetahString,
3438 lite_topic: CheetahString,
3439 top_k: i32,
3440 ) -> rocketmq_error::RocketMQResult<GetLiteGroupInfoResponseBody> {
3441 self.mq_client_api()?
3442 .get_lite_group_info(&broker_addr, group, lite_topic, top_k, self.remoting_timeout_millis()?)
3443 .await
3444 }
3445
3446 async fn export_rocksdb_config_to_json(
3447 &self,
3448 broker_addr: CheetahString,
3449 config_types: Vec<CheetahString>,
3450 ) -> rocketmq_error::RocketMQResult<()> {
3451 if let Some(ref mq_client_instance) = self.client_instance {
3452 mq_client_instance
3453 .get_mq_client_api_impl()?
3454 .export_rocksdb_config_to_json(broker_addr, config_types, self.remoting_timeout_millis()?)
3455 .await
3456 } else {
3457 Err(rocketmq_error::RocketMQError::ClientNotStarted)
3458 }
3459 }
3460
3461 async fn search_offset(
3462 &self,
3463 broker_addr: CheetahString,
3464 topic_name: CheetahString,
3465 queue_id: i32,
3466 timestamp: u64,
3467 timeout_millis: u64,
3468 ) -> rocketmq_error::RocketMQResult<u64> {
3469 let timestamp = search_offset_timestamp_to_java_long(timestamp)?;
3470 let mq = MessageQueue::from_parts(&topic_name, "", queue_id);
3471 let offset = self
3472 .mq_client_api()?
3473 .search_offset_by_timestamp(
3474 broker_addr.as_str(),
3475 &mq,
3476 timestamp,
3477 rocketmq_common::common::boundary_type::BoundaryType::Lower,
3478 timeout_millis,
3479 )
3480 .await?;
3481 java_long_to_u64("searchOffset", "offset", offset)
3482 }
3483
3484 async fn min_offset(
3485 &self,
3486 broker_addr: CheetahString,
3487 message_queue: MessageQueue,
3488 timeout_millis: u64,
3489 ) -> rocketmq_error::RocketMQResult<i64> {
3490 self.mq_client_api()?
3491 .get_min_offset(broker_addr.as_str(), &message_queue, timeout_millis)
3492 .await
3493 }
3494
3495 async fn max_offset(
3496 &self,
3497 broker_addr: CheetahString,
3498 message_queue: MessageQueue,
3499 timeout_millis: u64,
3500 ) -> rocketmq_error::RocketMQResult<i64> {
3501 self.mq_client_api()?
3502 .get_max_offset(broker_addr.as_str(), &message_queue, timeout_millis)
3503 .await
3504 }
3505}
3506
3507impl DefaultMQAdminExtImpl {
3508 async fn reset_offset_by_timestamp_old_on_broker(
3509 &self,
3510 broker_addr: CheetahString,
3511 queue_data: &QueueData,
3512 consumer_group: CheetahString,
3513 topic: CheetahString,
3514 timestamp: i64,
3515 force: bool,
3516 ) -> rocketmq_error::RocketMQResult<Vec<RollbackStats>> {
3517 let consume_stats = self
3518 .mq_client_api()?
3519 .get_consume_stats(
3520 &broker_addr,
3521 GetConsumeStatsRequestHeader {
3522 consumer_group: consumer_group.clone(),
3523 topic: CheetahString::empty(),
3524 topic_request_header: None,
3525 },
3526 self.remoting_timeout_millis()?,
3527 )
3528 .await?;
3529
3530 let mut rollback_stats_list = Vec::new();
3531 let mut has_consumed = false;
3532
3533 for (queue, offset_wrapper) in &consume_stats.offset_table {
3534 if queue.topic() == &topic {
3535 has_consumed = true;
3536 rollback_stats_list.push(
3537 self.reset_offset_consume_offset(
3538 broker_addr.clone(),
3539 consumer_group.clone(),
3540 queue.clone(),
3541 offset_wrapper,
3542 timestamp,
3543 force,
3544 )
3545 .await?,
3546 );
3547 }
3548 }
3549
3550 if !has_consumed {
3551 let topic_status = self
3552 .mq_client_api()?
3553 .get_topic_stats_info(
3554 &broker_addr,
3555 GetTopicStatsInfoRequestHeader {
3556 topic: topic.clone(),
3557 topic_request_header: None,
3558 },
3559 self.remoting_timeout_millis()?,
3560 )
3561 .await?;
3562
3563 for queue_id in 0..queue_data.read_queue_nums() {
3564 let queue = MessageQueue::from_parts(topic.clone(), queue_data.broker_name().clone(), queue_id as i32);
3565 let mut offset_wrapper = OffsetWrapper::new();
3566 let topic_offset = topic_status
3567 .get_offset_table()
3568 .get(&queue)
3569 .cloned()
3570 .unwrap_or_else(TopicOffset::new);
3571 offset_wrapper.set_broker_offset(topic_offset.get_max_offset());
3572 offset_wrapper.set_consumer_offset(topic_offset.get_min_offset());
3573 rollback_stats_list.push(
3574 self.reset_offset_consume_offset(
3575 broker_addr.clone(),
3576 consumer_group.clone(),
3577 queue,
3578 &offset_wrapper,
3579 timestamp,
3580 force,
3581 )
3582 .await?,
3583 );
3584 }
3585 }
3586
3587 Ok(rollback_stats_list)
3588 }
3589
3590 async fn reset_offset_consume_offset(
3591 &self,
3592 broker_addr: CheetahString,
3593 consumer_group: CheetahString,
3594 queue: MessageQueue,
3595 offset_wrapper: &OffsetWrapper,
3596 timestamp: i64,
3597 force: bool,
3598 ) -> rocketmq_error::RocketMQResult<RollbackStats> {
3599 let reset_offset = if timestamp == -1 {
3600 self.mq_client_api()?
3601 .get_max_offset(broker_addr.as_str(), &queue, self.remoting_timeout_millis()?)
3602 .await?
3603 } else {
3604 self.mq_client_api()?
3605 .search_offset_by_timestamp(
3606 broker_addr.as_str(),
3607 &queue,
3608 timestamp,
3609 rocketmq_common::common::boundary_type::BoundaryType::Lower,
3610 self.remoting_timeout_millis()?,
3611 )
3612 .await?
3613 };
3614
3615 let mut rollback_stats = RollbackStats {
3616 broker_name: queue.broker_name().clone(),
3617 queue_id: queue.queue_id() as i64,
3618 broker_offset: offset_wrapper.get_broker_offset(),
3619 consumer_offset: offset_wrapper.get_consumer_offset(),
3620 timestamp_offset: reset_offset,
3621 rollback_offset: offset_wrapper.get_consumer_offset(),
3622 };
3623
3624 if force || reset_offset <= offset_wrapper.get_consumer_offset() {
3625 rollback_stats.rollback_offset = reset_offset;
3626 self.mq_client_api()?
3627 .update_consumer_offset(
3628 &broker_addr,
3629 UpdateConsumerOffsetRequestHeader {
3630 consumer_group,
3631 topic: queue.topic().clone(),
3632 queue_id: queue.queue_id(),
3633 commit_offset: reset_offset,
3634 topic_request_header: None,
3635 },
3636 self.remoting_timeout_millis()?,
3637 )
3638 .await?;
3639 }
3640
3641 Ok(rollback_stats)
3642 }
3643
3644 async fn message_consumed_by_group(
3645 &self,
3646 msg: &MessageExt,
3647 group: &CheetahString,
3648 ) -> rocketmq_error::RocketMQResult<bool> {
3649 let consume_stats = self
3650 .examine_consume_stats(group.clone(), None, None, None, None)
3651 .await?;
3652 let cluster_info = self.examine_broker_cluster_info().await?;
3653
3654 Ok(is_message_consumed(msg, &consume_stats, &cluster_info))
3655 }
3656}
3657
3658fn merge_order_conf_entries(existing: &str, value: &str) -> String {
3659 let mut entries = HashMap::new();
3660 for item in existing.split(';').filter(|item| !item.trim().is_empty()) {
3661 if let Some((broker_name, _)) = item.split_once(':') {
3662 entries.insert(broker_name.to_string(), item.to_string());
3663 }
3664 }
3665 if let Some((broker_name, _)) = value.split_once(':') {
3666 entries.insert(broker_name.to_string(), value.to_string());
3667 } else if !value.trim().is_empty() {
3668 entries.insert(value.to_string(), value.to_string());
3669 }
3670
3671 let mut broker_names: Vec<String> = entries.keys().cloned().collect();
3672 broker_names.sort();
3673 broker_names
3674 .into_iter()
3675 .filter_map(|broker_name| entries.remove(&broker_name))
3676 .collect::<Vec<_>>()
3677 .join(";")
3678}
3679
3680fn select_consumer_direct_connection(
3681 consumer_group: &CheetahString,
3682 consumer_connection: &ConsumerConnection,
3683 requested_client_id: Option<&CheetahString>,
3684) -> rocketmq_error::RocketMQResult<(CheetahString, CheetahString)> {
3685 let requested = requested_client_id.filter(|client_id| !client_id.is_empty());
3686 let connection = consumer_connection
3687 .get_connection_set()
3688 .iter()
3689 .find(|connection| {
3690 requested
3691 .map(|client_id| connection.get_client_id() == *client_id)
3692 .unwrap_or_else(|| !connection.get_client_id().is_empty())
3693 })
3694 .ok_or_else(|| {
3695 let message = requested
3696 .map(|client_id| {
3697 format!(
3698 "Client `{}` was not found in consumer group `{}`",
3699 client_id, consumer_group
3700 )
3701 })
3702 .unwrap_or_else(|| format!("NO CONSUMER for consumer group `{}`", consumer_group));
3703 rocketmq_error::RocketMQError::IllegalArgument(message)
3704 })?;
3705
3706 Ok((connection.get_client_id(), connection.get_client_addr()))
3707}
3708
3709fn build_acl_info(
3710 subject: CheetahString,
3711 resources: Vec<CheetahString>,
3712 actions: Vec<CheetahString>,
3713 source_ips: Vec<CheetahString>,
3714 decision: CheetahString,
3715) -> AclInfo {
3716 let entries = if resources.is_empty() {
3717 vec![PolicyEntryInfo {
3718 resource: None,
3719 actions: Some(actions),
3720 source_ips: Some(source_ips),
3721 decision: if decision.is_empty() { None } else { Some(decision) },
3722 }]
3723 } else {
3724 resources
3725 .into_iter()
3726 .map(|resource| PolicyEntryInfo {
3727 resource: Some(resource),
3728 actions: Some(actions.clone()),
3729 source_ips: Some(source_ips.clone()),
3730 decision: if decision.is_empty() {
3731 None
3732 } else {
3733 Some(decision.clone())
3734 },
3735 })
3736 .collect()
3737 };
3738
3739 AclInfo {
3740 subject: Some(subject),
3741 policies: Some(vec![PolicyInfo {
3742 policy_type: Some(CheetahString::from_static_str("Custom")),
3743 entries: Some(entries),
3744 }]),
3745 }
3746}
3747
3748#[allow(deprecated)]
3749fn build_message_track(consumer_group: &str) -> MessageTrack {
3750 MessageTrack {
3751 consumer_group: consumer_group.to_string(),
3752 track_type: Some(TrackType::Unknown),
3753 exception_desc: String::new(),
3754 }
3755}
3756
3757#[allow(deprecated)]
3758fn resolve_consumed_track_type(msg: &MessageExt, consumer_connection: &ConsumerConnection) -> TrackType {
3759 let Some(subscription_data) = consumer_connection.get_subscription_table().get(msg.topic()) else {
3760 return TrackType::Consumed;
3761 };
3762
3763 let Some(message_tag) = msg.get_tags() else {
3764 return TrackType::Consumed;
3765 };
3766
3767 if subscription_data.tags_set.is_empty()
3768 || subscription_data
3769 .tags_set
3770 .contains(&CheetahString::from_static_str(SubscriptionData::SUB_ALL))
3771 || subscription_data.tags_set.contains(&message_tag)
3772 {
3773 TrackType::Consumed
3774 } else {
3775 TrackType::ConsumedButFiltered
3776 }
3777}
3778
3779fn is_message_consumed(msg: &MessageExt, consume_stats: &ConsumeStats, cluster_info: &ClusterInfo) -> bool {
3780 consume_stats.get_offset_table().iter().any(|(queue, offset_wrapper)| {
3781 queue.topic() == msg.topic()
3782 && queue.queue_id() == msg.queue_id()
3783 && resolve_master_broker_addr(cluster_info, queue)
3784 .map(|broker_addr| {
3785 broker_addr_matches_store_host(broker_addr, msg.store_host())
3786 && offset_wrapper.get_consumer_offset() > msg.queue_offset()
3787 })
3788 .unwrap_or(false)
3789 })
3790}
3791
3792fn resolve_master_broker_addr<'a>(cluster_info: &'a ClusterInfo, queue: &MessageQueue) -> Option<&'a CheetahString> {
3793 cluster_info
3794 .broker_addr_table
3795 .as_ref()?
3796 .get(queue.broker_name())?
3797 .broker_addrs()
3798 .get(&mix_all::MASTER_ID)
3799}
3800
3801fn broker_addr_matches_store_host(broker_addr: &CheetahString, store_host: std::net::SocketAddr) -> bool {
3802 broker_addr
3803 .parse::<std::net::SocketAddr>()
3804 .map(|parsed| parsed == store_host)
3805 .unwrap_or_else(|_| broker_addr.as_str() == store_host.to_string())
3806}
3807
3808#[allow(deprecated)]
3809fn apply_track_error(track: &mut MessageTrack, error: &RocketMQError) {
3810 if let Some(code) = response_code_from_error(error) {
3811 match code {
3812 ResponseCode::ConsumerNotOnline => track.set_track_type(TrackType::NotOnline),
3813 ResponseCode::BroadcastConsumption => track.set_track_type(TrackType::ConsumeBroadcasting),
3814 _ => {}
3815 }
3816 }
3817
3818 track.set_exception_desc(track_exception_desc(error));
3819}
3820
3821fn response_code_from_error(error: &RocketMQError) -> Option<ResponseCode> {
3822 match error {
3823 RocketMQError::BrokerOperationFailed { code, .. } => Some(ResponseCode::from(*code)),
3824 RocketMQError::IllegalArgument(message) => parse_response_code_from_message(message),
3825 _ => None,
3826 }
3827}
3828
3829fn is_consumer_not_online_error(error: &RocketMQError) -> bool {
3830 response_code_from_error(error) == Some(ResponseCode::ConsumerNotOnline)
3831}
3832
3833fn map_topic_config_lookup_result<T>(
3834 result: rocketmq_error::RocketMQResult<T>,
3835) -> rocketmq_error::RocketMQResult<bool> {
3836 match result {
3837 Ok(_) => Ok(true),
3838 Err(error) if response_code_from_error(&error) == Some(ResponseCode::TopicNotExist) => Ok(false),
3839 Err(error) => Err(error),
3840 }
3841}
3842
3843fn parse_response_code_from_message(message: &str) -> Option<ResponseCode> {
3844 let code_start = message.find("CODE:")?;
3845 let digits = message[code_start + "CODE:".len()..]
3846 .trim_start()
3847 .chars()
3848 .take_while(|ch| ch.is_ascii_digit() || *ch == '-')
3849 .collect::<String>();
3850
3851 if digits.is_empty() {
3852 return None;
3853 }
3854
3855 digits.parse::<i32>().ok().map(ResponseCode::from)
3856}
3857
3858fn track_exception_desc(error: &RocketMQError) -> String {
3859 match error {
3860 RocketMQError::BrokerOperationFailed { code, message, .. } => format!("CODE:{code} DESC:{message}"),
3861 _ => error.to_string(),
3862 }
3863}
3864
3865fn admin_result_code_for_error(error: &RocketMQError) -> AdminToolsResultCodeEnum {
3866 match response_code_from_error(error) {
3867 Some(ResponseCode::ConsumerNotOnline) => AdminToolsResultCodeEnum::ConsumerNotOnline,
3868 Some(ResponseCode::BroadcastConsumption) => AdminToolsResultCodeEnum::BroadcastConsumption,
3869 Some(_) => AdminToolsResultCodeEnum::MQBrokerError,
3870 None => AdminToolsResultCodeEnum::MQClientError,
3871 }
3872}
3873
3874fn filter_consume_stats(stats: &mut ConsumeStats, topic: Option<&CheetahString>, queue_id: Option<i32>) {
3875 if topic.is_none() && queue_id.is_none() {
3876 return;
3877 }
3878
3879 stats.offset_table.retain(|queue, _| {
3880 let topic_matches = topic.is_none_or(|topic| queue.topic() == topic);
3881 let queue_matches = queue_id.is_none_or(|queue_id| queue.queue_id() == queue_id);
3882 topic_matches && queue_matches
3883 });
3884}
3885
3886fn retain_java_user_topic_config(
3887 topic_table: &mut HashMap<CheetahString, TopicConfig>,
3888 broker_system_topics: &[CheetahString],
3889 special_topic: bool,
3890) {
3891 topic_table.retain(|topic_name, topic_config| {
3892 let topic = topic_config
3893 .topic_name
3894 .as_ref()
3895 .map(CheetahString::as_str)
3896 .unwrap_or_else(|| topic_name.as_str());
3897 if broker_system_topics
3898 .iter()
3899 .any(|system_topic| system_topic.as_str() == topic)
3900 || TopicValidator::is_system_topic(topic)
3901 {
3902 return false;
3903 }
3904 if !special_topic && (topic.starts_with(RETRY_GROUP_TOPIC_PREFIX) || topic.starts_with(DLQ_GROUP_TOPIC_PREFIX))
3905 {
3906 return false;
3907 }
3908 PermName::is_valid(topic_config.perm)
3909 });
3910}
3911
3912#[cfg(test)]
3913mod tests {
3914 use std::collections::BTreeSet;
3915 use std::collections::HashMap;
3916 use std::collections::HashSet;
3917 use std::time::Duration;
3918
3919 use crate::admin::mq_admin_ext_async::MQAdminExt;
3920 use crate::base::client_config::ClientConfig;
3921 use crate::common::admin_tools_result_code_enum::AdminToolsResultCodeEnum;
3922 use cheetah_string::CheetahString;
3923 use rocketmq_common::common::base::plain_access_config::PlainAccessConfig;
3924 use rocketmq_common::common::config::TopicConfig;
3925 use rocketmq_common::common::constant::PermName;
3926 use rocketmq_common::common::message::message_builder::MessageBuilder;
3927 use rocketmq_common::common::message::message_enum::MessageRequestMode;
3928 use rocketmq_common::common::message::message_ext::MessageExt;
3929 use rocketmq_common::common::message::message_queue::MessageQueue;
3930 use rocketmq_common::common::message::MessageConst;
3931 use rocketmq_common::common::mix_all;
3932 use rocketmq_common::common::mix_all::DLQ_GROUP_TOPIC_PREFIX;
3933 use rocketmq_common::common::mix_all::RETRY_GROUP_TOPIC_PREFIX;
3934 #[allow(deprecated)]
3935 use rocketmq_common::common::tools::track_type::TrackType;
3936 use rocketmq_common::common::topic::TopicValidator;
3937 use rocketmq_remoting::code::response_code::ResponseCode;
3938 use rocketmq_remoting::protocol::admin::consume_stats::ConsumeStats;
3939 use rocketmq_remoting::protocol::admin::offset_wrapper::OffsetWrapper;
3940 use rocketmq_remoting::protocol::body::acl_info::AclInfo;
3941 use rocketmq_remoting::protocol::body::broker_body::cluster_info::ClusterInfo;
3942 use rocketmq_remoting::protocol::body::connection::Connection;
3943 use rocketmq_remoting::protocol::body::consumer_connection::ConsumerConnection;
3944 use rocketmq_remoting::protocol::body::get_broker_lite_info_response_body::GetBrokerLiteInfoResponseBody;
3945 use rocketmq_remoting::protocol::body::producer_connection::ProducerConnection;
3946 use rocketmq_remoting::protocol::heartbeat::consume_type::ConsumeType;
3947 use rocketmq_remoting::protocol::heartbeat::subscription_data::SubscriptionData;
3948 use rocketmq_remoting::protocol::route::route_data_view::BrokerData;
3949 use rocketmq_remoting::protocol::static_topic::topic_queue_mapping_detail::TopicQueueMappingDetail;
3950 use rocketmq_rust::ArcMut;
3951
3952 use super::broker_addrs_for_cluster;
3953 use super::broker_operator_result;
3954 use super::choose_min_broker_notify_addrs;
3955 use super::cluster_names_for_admin_operation;
3956 use super::controller_servers_or_namesrv;
3957 use super::encode_topic_attributes;
3958 use super::filter_consume_stats;
3959 use super::is_consumer_not_online_error;
3960 use super::is_message_consumed;
3961 use super::java_long_to_u64;
3962 use super::lite_pull_topic_config;
3963 use super::lite_pull_update_consumer_offset_request_header;
3964 use super::lite_subscription_group_list_from_broker_lite_info;
3965 use super::lite_topic_list_from_broker_lite_info;
3966 use super::map_topic_config_lookup_result;
3967 use super::master_flush_offset_to_java_long;
3968 use super::merge_consume_status_result;
3969 use super::merge_order_conf_entries;
3970 use super::notify_min_broker_id_change_request_header;
3971 use super::parse_response_code_from_message;
3972 use super::query_consume_queue_index_to_java_long;
3973 use super::reset_offset_by_queue_id_request_headers;
3974 use super::resolve_consumed_track_type;
3975 use super::retain_java_user_topic_config;
3976 use super::search_offset_timestamp_to_java_long;
3977 use super::select_consumer_direct_connection;
3978 use super::timeout_millis_to_u64;
3979 use super::timestamp_to_java_long;
3980 use super::topic_list_from_lite_topic_names;
3981 use super::update_consume_offset_request_header;
3982 use super::update_group_forbidden_request_header;
3983 use super::validate_acl_file_path_for_global_white_addr_config;
3984 use super::DefaultMQAdminExtImpl;
3985 use rocketmq_error::RocketMQError;
3986
3987 fn new_unstarted_admin() -> DefaultMQAdminExtImpl {
3988 DefaultMQAdminExtImpl::new(
3989 None,
3990 Duration::from_secs(3),
3991 ArcMut::new(ClientConfig::default()),
3992 CheetahString::from("admin-group"),
3993 )
3994 }
3995
3996 #[test]
3997 fn retain_java_user_topic_config_filters_java_internal_topics() {
3998 let mut topic_table = HashMap::from([
3999 (
4000 CheetahString::from_static_str("UserTopic"),
4001 TopicConfig::new("UserTopic"),
4002 ),
4003 (
4004 CheetahString::from_static_str("BrokerInternalTopic"),
4005 TopicConfig::new("BrokerInternalTopic"),
4006 ),
4007 (
4008 CheetahString::from_static_str(TopicValidator::RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC),
4009 TopicConfig::new(TopicValidator::RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC),
4010 ),
4011 (
4012 CheetahString::from_string(format!("{RETRY_GROUP_TOPIC_PREFIX}group-a")),
4013 TopicConfig::new(format!("{RETRY_GROUP_TOPIC_PREFIX}group-a")),
4014 ),
4015 (
4016 CheetahString::from_string(format!("{DLQ_GROUP_TOPIC_PREFIX}group-a")),
4017 TopicConfig::new(format!("{DLQ_GROUP_TOPIC_PREFIX}group-a")),
4018 ),
4019 (
4020 CheetahString::from_static_str("InvalidPermTopic"),
4021 TopicConfig {
4022 perm: PermName::PERM_PRIORITY,
4023 ..TopicConfig::new("InvalidPermTopic")
4024 },
4025 ),
4026 ]);
4027 let broker_system_topics = vec![CheetahString::from_static_str("BrokerInternalTopic")];
4028
4029 retain_java_user_topic_config(&mut topic_table, &broker_system_topics, false);
4030
4031 assert_eq!(topic_table.len(), 1);
4032 assert!(topic_table.contains_key("UserTopic"));
4033 }
4034
4035 #[test]
4036 fn retain_java_user_topic_config_keeps_retry_and_dlq_when_special_topic_enabled() {
4037 let retry_topic = CheetahString::from_string(format!("{RETRY_GROUP_TOPIC_PREFIX}group-a"));
4038 let dlq_topic = CheetahString::from_string(format!("{DLQ_GROUP_TOPIC_PREFIX}group-a"));
4039 let mut topic_table = HashMap::from([
4040 (retry_topic.clone(), TopicConfig::new(retry_topic.clone())),
4041 (dlq_topic.clone(), TopicConfig::new(dlq_topic.clone())),
4042 (
4043 CheetahString::from_static_str("InvalidPermTopic"),
4044 TopicConfig {
4045 perm: PermName::PERM_PRIORITY,
4046 ..TopicConfig::new("InvalidPermTopic")
4047 },
4048 ),
4049 ]);
4050
4051 retain_java_user_topic_config(&mut topic_table, &[], true);
4052
4053 assert_eq!(topic_table.len(), 2);
4054 assert!(topic_table.contains_key(&retry_topic));
4055 assert!(topic_table.contains_key(&dlq_topic));
4056 }
4057
4058 #[test]
4059 fn use_tls_updates_admin_impl_client_config_before_start() {
4060 let mut admin = new_unstarted_admin();
4061
4062 assert!(!admin.is_use_tls());
4063
4064 admin.set_use_tls(true);
4065
4066 assert!(admin.is_use_tls());
4067 assert!(admin.client_config.is_use_tls());
4068 }
4069
4070 #[test]
4071 fn controller_servers_or_namesrv_matches_java_controller_config_target_selection() {
4072 let namesrv_addrs = vec![
4073 CheetahString::from("127.0.0.1:9876"),
4074 CheetahString::from("127.0.0.2:9876"),
4075 ];
4076
4077 assert_eq!(
4078 controller_servers_or_namesrv(Vec::new(), &namesrv_addrs),
4079 namesrv_addrs.clone()
4080 );
4081
4082 let explicit_controllers = vec![CheetahString::from("127.0.0.3:9878")];
4083 assert_eq!(
4084 controller_servers_or_namesrv(explicit_controllers.clone(), &namesrv_addrs),
4085 explicit_controllers
4086 );
4087
4088 assert!(controller_servers_or_namesrv(Vec::new(), &[]).is_empty());
4089 }
4090
4091 #[test]
4092 fn merge_order_conf_entries_replaces_existing_broker_value() {
4093 let merged = merge_order_conf_entries("broker-a:4;broker-b:4", "broker-a:8");
4094 assert_eq!(merged, "broker-a:8;broker-b:4");
4095 }
4096
4097 #[test]
4098 fn merge_order_conf_entries_adds_new_broker_value() {
4099 let merged = merge_order_conf_entries("broker-a:4", "broker-b:8");
4100 assert_eq!(merged, "broker-a:4;broker-b:8");
4101 }
4102
4103 #[test]
4104 fn encode_topic_attributes_matches_java_attribute_parser_format() {
4105 let mut attributes = HashMap::<CheetahString, CheetahString>::new();
4106 attributes.insert("+message.type".into(), "NORMAL".into());
4107
4108 let encoded = encode_topic_attributes(&attributes);
4109
4110 assert_eq!(encoded, Some(CheetahString::from("+message.type=NORMAL")));
4111 }
4112
4113 #[test]
4114 fn merge_consume_status_result_combines_offsets_by_client() {
4115 let mut target = HashMap::new();
4116 let client_id = CheetahString::from_static_str("client-a");
4117 let mut first_offsets = HashMap::new();
4118 first_offsets.insert(MessageQueue::from_parts("TopicA", "broker-a", 0), 12);
4119 merge_consume_status_result(&mut target, HashMap::from([(client_id.clone(), first_offsets)]))
4120 .expect("first broker status should merge");
4121
4122 let mut second_offsets = HashMap::new();
4123 second_offsets.insert(MessageQueue::from_parts("TopicA", "broker-b", 1), 34);
4124 merge_consume_status_result(&mut target, HashMap::from([(client_id.clone(), second_offsets)]))
4125 .expect("second broker status should merge");
4126
4127 let offsets = target.get(&client_id).expect("client offsets should be present");
4128 assert_eq!(offsets.len(), 2);
4129 assert_eq!(
4130 offsets.get(&MessageQueue::from_parts("TopicA", "broker-a", 0)),
4131 Some(&12)
4132 );
4133 assert_eq!(
4134 offsets.get(&MessageQueue::from_parts("TopicA", "broker-b", 1)),
4135 Some(&34)
4136 );
4137 }
4138
4139 #[test]
4140 fn lite_pull_topic_config_marks_lite_and_uses_queue_num_fallback() {
4141 let config = lite_pull_topic_config(CheetahString::from("LiteTopic"), 8, 3, 0, 0, false)
4142 .expect("create lite topic should use queueNum fallback");
4143
4144 assert_eq!(config.topic_name.as_ref().map(CheetahString::as_str), Some("LiteTopic"));
4145 assert_eq!(config.read_queue_nums, 8);
4146 assert_eq!(config.write_queue_nums, 8);
4147 assert_eq!(config.topic_sys_flag, 3);
4148 assert_eq!(
4149 config.attributes.get(&CheetahString::from_static_str("message.type")),
4150 Some(&CheetahString::from_static_str("LITE"))
4151 );
4152 }
4153
4154 #[test]
4155 fn lite_pull_topic_config_update_requires_explicit_positive_queue_nums() {
4156 let error = lite_pull_topic_config(CheetahString::from("LiteTopic"), 0, 0, 0, 8, true)
4157 .expect_err("update lite topic should not use queueNum fallback");
4158
4159 assert!(error.to_string().contains("readQueueNums must be positive"));
4160 }
4161
4162 #[test]
4163 fn lite_pull_topic_config_rejects_negative_topic_sys_flag() {
4164 let error = lite_pull_topic_config(CheetahString::from("LiteTopic"), 8, -1, 0, 0, false)
4165 .expect_err("negative topicSysFlag should be rejected");
4166
4167 assert!(error.to_string().contains("topicSysFlag must be non-negative"));
4168 }
4169
4170 #[test]
4171 fn timestamp_to_java_long_rejects_values_outside_java_range() {
4172 assert_eq!(
4173 timestamp_to_java_long("resetOffsetNewConcurrent", i64::MAX as u64).expect("max Java long is valid"),
4174 i64::MAX
4175 );
4176
4177 let error = timestamp_to_java_long("resetOffsetNewConcurrent", i64::MAX as u64 + 1)
4178 .expect_err("value larger than Java long should be rejected");
4179
4180 assert!(error
4181 .to_string()
4182 .contains("resetOffsetNewConcurrent timestamp exceeds Java long range"));
4183 }
4184
4185 #[test]
4186 fn timeout_millis_to_u64_rejects_values_outside_rust_range() {
4187 assert_eq!(
4188 timeout_millis_to_u64(Duration::from_millis(u64::MAX)).expect("max u64 millis is valid"),
4189 u64::MAX
4190 );
4191
4192 let error = timeout_millis_to_u64(Duration::from_secs(u64::MAX))
4193 .expect_err("duration larger than u64 milliseconds should be rejected");
4194
4195 assert!(error
4196 .to_string()
4197 .contains("DefaultMQAdminExt timeoutMillis exceeds Rust u64 millisecond range"));
4198 }
4199
4200 #[test]
4201 fn master_flush_offset_to_java_long_rejects_values_outside_java_range() {
4202 assert_eq!(
4203 master_flush_offset_to_java_long(i64::MAX as u64).expect("max Java long is valid"),
4204 i64::MAX
4205 );
4206
4207 let error = master_flush_offset_to_java_long(i64::MAX as u64 + 1)
4208 .expect_err("value larger than Java long should be rejected");
4209
4210 assert!(error
4211 .to_string()
4212 .contains("resetMasterFlushOffset offset exceeds Java long range"));
4213 }
4214
4215 #[test]
4216 fn query_consume_queue_index_to_java_long_rejects_values_outside_java_range() {
4217 assert_eq!(
4218 query_consume_queue_index_to_java_long(i64::MAX as u64).expect("max Java long is valid"),
4219 i64::MAX
4220 );
4221
4222 let error = query_consume_queue_index_to_java_long(i64::MAX as u64 + 1)
4223 .expect_err("value larger than Java long should be rejected");
4224
4225 assert!(error
4226 .to_string()
4227 .contains("queryConsumeQueue offset exceeds Java long range"));
4228 }
4229
4230 #[test]
4231 fn search_offset_timestamp_to_java_long_rejects_values_outside_java_range() {
4232 assert_eq!(
4233 search_offset_timestamp_to_java_long(i64::MAX as u64).expect("max Java long is valid"),
4234 i64::MAX
4235 );
4236
4237 let error = search_offset_timestamp_to_java_long(i64::MAX as u64 + 1)
4238 .expect_err("value larger than Java long should be rejected");
4239
4240 assert!(error
4241 .to_string()
4242 .contains("searchOffset timestamp exceeds Java long range"));
4243 }
4244
4245 #[test]
4246 fn java_long_to_u64_rejects_negative_values_from_broker() {
4247 assert_eq!(
4248 java_long_to_u64("searchOffset", "offset", 42).expect("positive Java long should convert"),
4249 42
4250 );
4251
4252 let error = java_long_to_u64("searchOffset", "offset", -1).expect_err("negative broker offset must not wrap");
4253
4254 assert!(error
4255 .to_string()
4256 .contains("searchOffset offset is negative and cannot be represented as Rust u64"));
4257 }
4258
4259 #[test]
4260 fn global_white_addr_config_rejects_legacy_acl_file_path() {
4261 validate_acl_file_path_for_global_white_addr_config(Some(&CheetahString::from(
4262 "/opt/rocketmq/conf/plain_acl.yml",
4263 )))
4264 .expect_err("modern ACL 2.0 global white address RPC has no aclFileFullPath field");
4265
4266 validate_acl_file_path_for_global_white_addr_config(None).expect("missing path is valid");
4267 validate_acl_file_path_for_global_white_addr_config(Some(&CheetahString::new()))
4268 .expect("empty path follows Java optional parameter behavior");
4269 }
4270
4271 #[test]
4272 #[allow(deprecated)]
4273 fn broker_operator_result_sets_success_and_failure_lists() {
4274 let result = broker_operator_result(
4275 vec![CheetahString::from("broker-a")],
4276 vec![CheetahString::from("broker-b")],
4277 );
4278
4279 assert_eq!(result.get_success_list(), &vec![CheetahString::from("broker-a")]);
4280 assert_eq!(result.get_failure_list(), &vec![CheetahString::from("broker-b")]);
4281 }
4282
4283 #[test]
4284 fn lite_topic_list_from_broker_info_sorts_topics_and_preserves_addr() {
4285 let mut lite_info = GetBrokerLiteInfoResponseBody::new();
4286 lite_info.get_topic_meta_mut().insert(CheetahString::from("topic-b"), 8);
4287 lite_info.get_topic_meta_mut().insert(CheetahString::from("topic-a"), 4);
4288
4289 let topic_list =
4290 lite_topic_list_from_broker_lite_info(Some(CheetahString::from("127.0.0.1:10911")), &lite_info);
4291
4292 assert_eq!(
4293 topic_list.topic_list,
4294 vec![CheetahString::from("topic-a"), CheetahString::from("topic-b")]
4295 );
4296 assert_eq!(topic_list.broker_addr, Some(CheetahString::from("127.0.0.1:10911")));
4297 }
4298
4299 #[test]
4300 fn lite_topic_list_from_names_deduplicates_cluster_results() {
4301 let topic_list = topic_list_from_lite_topic_names(
4302 None,
4303 [
4304 CheetahString::from("topic-b"),
4305 CheetahString::from("topic-a"),
4306 CheetahString::from("topic-b"),
4307 ],
4308 );
4309
4310 assert_eq!(
4311 topic_list.topic_list,
4312 vec![CheetahString::from("topic-a"), CheetahString::from("topic-b")]
4313 );
4314 assert_eq!(topic_list.broker_addr, None);
4315 }
4316
4317 #[test]
4318 fn lite_subscription_group_list_filters_by_topic() {
4319 let mut lite_info = GetBrokerLiteInfoResponseBody::new();
4320 let topic = CheetahString::from("topic-a");
4321 let groups = HashSet::from([CheetahString::from("group-a"), CheetahString::from("group-b")]);
4322 let mut group_meta = HashMap::new();
4323 group_meta.insert(topic.clone(), groups.clone());
4324 lite_info.set_group_meta(group_meta);
4325
4326 let group_list = lite_subscription_group_list_from_broker_lite_info(&topic, &lite_info);
4327 let missing_group_list =
4328 lite_subscription_group_list_from_broker_lite_info(&CheetahString::from("topic-missing"), &lite_info);
4329
4330 assert_eq!(group_list.group_list, groups);
4331 assert!(missing_group_list.group_list.is_empty());
4332 }
4333
4334 #[tokio::test]
4335 async fn acl_info_facades_without_started_client_return_typed_errors() {
4336 let admin = new_unstarted_admin();
4337 let acl_info = AclInfo {
4338 subject: Some(CheetahString::from("User:alice")),
4339 policies: None,
4340 };
4341
4342 let error = admin
4343 .create_acl_with_acl_info(CheetahString::from("127.0.0.1:10911"), acl_info.clone())
4344 .await
4345 .expect_err("create_acl_with_acl_info should require a started client");
4346 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4347
4348 let error = admin
4349 .update_acl_with_acl_info(CheetahString::from("127.0.0.1:10911"), acl_info)
4350 .await
4351 .expect_err("update_acl_with_acl_info should require a started client");
4352 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4353 }
4354
4355 #[tokio::test]
4356 async fn acl_subject_facades_without_started_client_return_typed_errors() {
4357 let admin = new_unstarted_admin();
4358
4359 let error = MQAdminExt::create_acl_with_info(
4360 &admin,
4361 CheetahString::from("127.0.0.1:10911"),
4362 CheetahString::from("User:alice"),
4363 )
4364 .await
4365 .expect_err("create_acl_with_info should require a started client");
4366 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4367
4368 let error = MQAdminExt::update_acl_with_info(
4369 &admin,
4370 CheetahString::from("127.0.0.1:10911"),
4371 CheetahString::from("User:alice"),
4372 )
4373 .await
4374 .expect_err("update_acl_with_info should require a started client");
4375 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4376 }
4377
4378 #[tokio::test]
4379 async fn examine_broker_cluster_acl_version_info_without_started_client_returns_typed_error() {
4380 let admin = new_unstarted_admin();
4381
4382 let error = MQAdminExt::examine_broker_cluster_acl_version_info(&admin, CheetahString::from("127.0.0.1:10911"))
4383 .await
4384 .expect_err("examine_broker_cluster_acl_version_info should require a started client");
4385
4386 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4387 }
4388
4389 #[tokio::test]
4390 async fn acl_info_facades_reject_blank_subject_before_remoting() {
4391 let admin = new_unstarted_admin();
4392 let acl_info = AclInfo {
4393 subject: Some(CheetahString::default()),
4394 policies: None,
4395 };
4396
4397 let error = admin
4398 .create_acl_with_acl_info(CheetahString::from("127.0.0.1:10911"), acl_info.clone())
4399 .await
4400 .expect_err("create_acl_with_acl_info should reject blank subject locally");
4401 assert!(matches!(error, rocketmq_error::RocketMQError::IllegalArgument(_)));
4402
4403 let error = admin
4404 .update_acl_with_acl_info(CheetahString::from("127.0.0.1:10911"), acl_info)
4405 .await
4406 .expect_err("update_acl_with_acl_info should reject blank subject locally");
4407 assert!(matches!(error, rocketmq_error::RocketMQError::IllegalArgument(_)));
4408 }
4409
4410 #[tokio::test]
4411 async fn acl_subject_facades_reject_blank_subject_before_remoting() {
4412 let admin = new_unstarted_admin();
4413
4414 let error =
4415 MQAdminExt::create_acl_with_info(&admin, CheetahString::from("127.0.0.1:10911"), CheetahString::default())
4416 .await
4417 .expect_err("create_acl_with_info should reject blank subject locally");
4418 assert!(matches!(error, rocketmq_error::RocketMQError::IllegalArgument(_)));
4419
4420 let error =
4421 MQAdminExt::update_acl_with_info(&admin, CheetahString::from("127.0.0.1:10911"), CheetahString::default())
4422 .await
4423 .expect_err("update_acl_with_info should reject blank subject locally");
4424 assert!(matches!(error, rocketmq_error::RocketMQError::IllegalArgument(_)));
4425 }
4426
4427 #[test]
4428 fn producer_connection_empty_set_represents_offline_group() {
4429 let connection = ProducerConnection::new();
4430 assert!(connection.connection_set().is_empty());
4431 }
4432
4433 #[test]
4434 fn producer_connection_with_entries_represents_online_group() {
4435 let mut connection = ProducerConnection::new();
4436 let mut entry = Connection::new();
4437 entry.set_client_id("client-a".into());
4438 connection.connection_set_mut().insert(entry);
4439
4440 assert_eq!(connection.connection_set().len(), 1);
4441 }
4442
4443 #[test]
4444 fn select_consumer_direct_connection_uses_requested_client_when_present() {
4445 let consumer_group = CheetahString::from("group-a");
4446 let requested_client_id = CheetahString::from("client-b");
4447 let mut consumer_connection = ConsumerConnection::new();
4448 let mut first = Connection::new();
4449 first.set_client_id("client-a".into());
4450 first.set_client_addr("127.0.0.1:1001".into());
4451 let mut second = Connection::new();
4452 second.set_client_id(requested_client_id.clone());
4453 second.set_client_addr("127.0.0.1:1002".into());
4454 consumer_connection.insert_connection(first);
4455 consumer_connection.insert_connection(second);
4456
4457 let (client_id, client_addr) =
4458 select_consumer_direct_connection(&consumer_group, &consumer_connection, Some(&requested_client_id))
4459 .expect("requested client should be selected");
4460
4461 assert_eq!(client_id, requested_client_id);
4462 assert_eq!(client_addr, CheetahString::from("127.0.0.1:1002"));
4463 }
4464
4465 #[test]
4466 fn select_consumer_direct_connection_returns_first_available_client_when_unspecified() {
4467 let consumer_group = CheetahString::from("group-a");
4468 let mut consumer_connection = ConsumerConnection::new();
4469 let mut only = Connection::new();
4470 only.set_client_id("client-a".into());
4471 only.set_client_addr("127.0.0.1:1001".into());
4472 consumer_connection.insert_connection(only);
4473
4474 let (client_id, client_addr) =
4475 select_consumer_direct_connection(&consumer_group, &consumer_connection, Some(&CheetahString::default()))
4476 .expect("single consumer should be selected");
4477
4478 assert_eq!(client_id, CheetahString::from("client-a"));
4479 assert_eq!(client_addr, CheetahString::from("127.0.0.1:1001"));
4480 }
4481
4482 #[test]
4483 fn select_consumer_direct_connection_errors_when_group_is_offline() {
4484 let consumer_group = CheetahString::from("group-a");
4485 let consumer_connection = ConsumerConnection::new();
4486
4487 let error = select_consumer_direct_connection(&consumer_group, &consumer_connection, None)
4488 .expect_err("offline group should not resolve a client");
4489
4490 assert!(error.to_string().contains("NO CONSUMER"));
4491 }
4492
4493 #[test]
4494 #[allow(deprecated)]
4495 fn resolve_consumed_track_type_marks_filtered_subscription() {
4496 let message = MessageBuilder::new()
4497 .topic("TopicTest")
4498 .body_slice(b"payload")
4499 .tags("TagA")
4500 .build_unchecked();
4501 let mut message_ext = MessageExt::default();
4502 message_ext.set_message_inner(message);
4503
4504 let mut subscription = SubscriptionData {
4505 topic: CheetahString::from("TopicTest"),
4506 ..Default::default()
4507 };
4508 subscription.tags_set = BTreeSet::from([CheetahString::from("TagB")]);
4509
4510 let mut connection = ConsumerConnection::new();
4511 connection.set_consume_type(ConsumeType::ConsumePassively);
4512 connection
4513 .get_subscription_table_mut()
4514 .insert(CheetahString::from("TopicTest"), subscription);
4515
4516 let track_type = resolve_consumed_track_type(&message_ext, &connection);
4517
4518 assert_eq!(track_type, TrackType::ConsumedButFiltered);
4519 }
4520
4521 #[test]
4522 fn is_message_consumed_returns_true_when_offset_has_advanced_on_master() {
4523 let message = MessageBuilder::new()
4524 .topic("TopicTest")
4525 .body_slice(b"payload")
4526 .build_unchecked();
4527 let mut message_ext = MessageExt::default();
4528 message_ext.set_message_inner(message);
4529 message_ext.set_queue_id(1);
4530 message_ext.set_queue_offset(10);
4531 message_ext.set_store_host("127.0.0.1:10911".parse().expect("store host"));
4532
4533 let mut consume_stats = ConsumeStats::new();
4534 let mut offset_wrapper = OffsetWrapper::default();
4535 offset_wrapper.set_consumer_offset(11);
4536 consume_stats
4537 .get_offset_table_mut()
4538 .insert(MessageQueue::from_parts("TopicTest", "broker-a", 1), offset_wrapper);
4539
4540 let mut broker_addrs = HashMap::new();
4541 broker_addrs.insert(mix_all::MASTER_ID, CheetahString::from("127.0.0.1:10911"));
4542 let broker_data = BrokerData::new(
4543 CheetahString::from("cluster-a"),
4544 CheetahString::from("broker-a"),
4545 broker_addrs,
4546 None,
4547 );
4548 let cluster_info = ClusterInfo::new(
4549 Some(HashMap::from([(CheetahString::from("broker-a"), broker_data)])),
4550 None,
4551 );
4552
4553 assert!(is_message_consumed(&message_ext, &consume_stats, &cluster_info));
4554 }
4555
4556 #[test]
4557 fn filter_consume_stats_keeps_only_matching_topic_and_queue() {
4558 let mut consume_stats = ConsumeStats::new();
4559 consume_stats.set_consume_tps(12.5);
4560
4561 for (topic, queue_id, consumer_offset) in [("TopicTest", 0, 10), ("TopicTest", 1, 11), ("OtherTopic", 0, 12)] {
4562 let mut offset_wrapper = OffsetWrapper::default();
4563 offset_wrapper.set_consumer_offset(consumer_offset);
4564 consume_stats
4565 .get_offset_table_mut()
4566 .insert(MessageQueue::from_parts(topic, "broker-a", queue_id), offset_wrapper);
4567 }
4568
4569 filter_consume_stats(&mut consume_stats, Some(&CheetahString::from("TopicTest")), Some(0));
4570
4571 assert_eq!(consume_stats.get_consume_tps(), 12.5);
4572 assert_eq!(consume_stats.get_offset_table().len(), 1);
4573 let (queue, offset) = consume_stats
4574 .get_offset_table()
4575 .iter()
4576 .next()
4577 .expect("one queue should remain");
4578 assert_eq!(queue.topic(), &CheetahString::from("TopicTest"));
4579 assert_eq!(queue.queue_id(), 0);
4580 assert_eq!(offset.get_consumer_offset(), 10);
4581 }
4582
4583 #[test]
4584 fn broker_cleanup_cluster_helpers_match_java_cluster_expansion() {
4585 let broker_a = BrokerData::new(
4586 CheetahString::from("cluster-a"),
4587 CheetahString::from("broker-a"),
4588 HashMap::from([
4589 (mix_all::MASTER_ID, CheetahString::from("127.0.0.1:10911")),
4590 (1, CheetahString::from("127.0.0.1:10912")),
4591 ]),
4592 None,
4593 );
4594 let broker_b = BrokerData::new(
4595 CheetahString::from("cluster-b"),
4596 CheetahString::from("broker-b"),
4597 HashMap::from([(mix_all::MASTER_ID, CheetahString::from("127.0.0.2:10911"))]),
4598 None,
4599 );
4600 let cluster_info = ClusterInfo::new(
4601 Some(HashMap::from([
4602 (CheetahString::from("broker-a"), broker_a),
4603 (CheetahString::from("broker-b"), broker_b),
4604 ])),
4605 Some(HashMap::from([
4606 (
4607 CheetahString::from("cluster-a"),
4608 HashSet::from([CheetahString::from("broker-a")]),
4609 ),
4610 (
4611 CheetahString::from("cluster-b"),
4612 HashSet::from([CheetahString::from("broker-b")]),
4613 ),
4614 ])),
4615 );
4616
4617 let mut cluster_names = cluster_names_for_admin_operation(&cluster_info, None);
4618 cluster_names.sort();
4619 assert_eq!(
4620 cluster_names,
4621 vec![CheetahString::from("cluster-a"), CheetahString::from("cluster-b")]
4622 );
4623
4624 let mut addrs = broker_addrs_for_cluster(&cluster_info, &CheetahString::from("cluster-a"));
4625 addrs.sort();
4626 assert_eq!(
4627 addrs,
4628 vec![
4629 CheetahString::from("127.0.0.1:10911"),
4630 CheetahString::from("127.0.0.1:10912")
4631 ]
4632 );
4633 }
4634
4635 #[test]
4636 fn parse_response_code_from_message_reads_consumer_not_online_code() {
4637 let code = parse_response_code_from_message("CODE: 206 DESC: Not found the consumer group connection");
4638
4639 assert_eq!(code, Some(ResponseCode::ConsumerNotOnline));
4640 }
4641
4642 #[test]
4643 fn topic_config_lookup_maps_topic_not_exist_to_false_only() {
4644 let missing = mq_client_err!(ResponseCode::TopicNotExist as i32, "topic not exist");
4645 assert!(!map_topic_config_lookup_result::<()>(Err(missing)).expect("TopicNotExist should map to false"));
4646
4647 let client_not_started = RocketMQError::ClientNotStarted;
4648 let error = map_topic_config_lookup_result::<()>(Err(client_not_started))
4649 .expect_err("non broker topic-not-exist errors should stay typed errors");
4650 assert!(matches!(error, RocketMQError::ClientNotStarted));
4651 }
4652
4653 #[test]
4654 fn reset_offset_new_fallback_classifier_matches_java_consumer_not_online_branch() {
4655 let broker_error = RocketMQError::broker_operation_failed(
4656 "BROKER_OPERATION",
4657 ResponseCode::ConsumerNotOnline as i32,
4658 "offline",
4659 )
4660 .with_broker_addr("127.0.0.1:10911");
4661 assert!(is_consumer_not_online_error(&broker_error));
4662
4663 let legacy_client_error = mq_client_err!(
4664 ResponseCode::ConsumerNotOnline as i32,
4665 "Not found the consumer group connection"
4666 );
4667 assert!(is_consumer_not_online_error(&legacy_client_error));
4668
4669 let other_error = RocketMQError::broker_operation_failed(
4670 "BROKER_OPERATION",
4671 ResponseCode::SystemError as i32,
4672 "system error",
4673 );
4674 assert!(!is_consumer_not_online_error(&other_error));
4675 }
4676
4677 #[test]
4678 fn update_consume_offset_request_header_matches_java_fields() {
4679 let mq = MessageQueue::from_parts("TopicTest", "broker-a", 3);
4680
4681 let header = update_consume_offset_request_header(CheetahString::from("group-a"), &mq, 42)
4682 .expect("valid offset should build update offset header");
4683
4684 assert_eq!(header.consumer_group, "group-a");
4685 assert_eq!(header.topic, "TopicTest");
4686 assert_eq!(header.queue_id, 3);
4687 assert_eq!(header.commit_offset, 42);
4688 assert_eq!(
4689 header
4690 .topic_request_header
4691 .as_ref()
4692 .and_then(|topic_header| topic_header.rpc.as_ref())
4693 .and_then(|rpc_header| rpc_header.broker_name.as_ref()),
4694 Some(&CheetahString::from("broker-a"))
4695 );
4696 }
4697
4698 #[test]
4699 fn update_consume_offset_request_header_rejects_offsets_outside_java_long_range() {
4700 let mq = MessageQueue::from_parts("TopicTest", "broker-a", 0);
4701
4702 let error = update_consume_offset_request_header(CheetahString::from("group-a"), &mq, i64::MAX as u64 + 1)
4703 .expect_err("offset larger than Java long should be rejected");
4704
4705 assert!(error.to_string().contains("offset exceeds Java long range"));
4706 }
4707
4708 #[test]
4709 fn lite_pull_update_consumer_offset_request_header_matches_java_fields() {
4710 let header = lite_pull_update_consumer_offset_request_header(
4711 CheetahString::from("TopicTest"),
4712 CheetahString::from("group-a"),
4713 2,
4714 42,
4715 )
4716 .expect("valid lite pull offset should build update offset header");
4717
4718 assert_eq!(header.consumer_group, "group-a");
4719 assert_eq!(header.topic, "TopicTest");
4720 assert_eq!(header.queue_id, 2);
4721 assert_eq!(header.commit_offset, 42);
4722 assert!(header.topic_request_header.is_none());
4723 }
4724
4725 #[test]
4726 fn lite_pull_update_consumer_offset_rejects_offsets_outside_java_long_range() {
4727 let error = lite_pull_update_consumer_offset_request_header(
4728 CheetahString::from("TopicTest"),
4729 CheetahString::from("group-a"),
4730 0,
4731 i64::MAX as u64 + 1,
4732 )
4733 .expect_err("offset larger than Java long should be rejected");
4734
4735 assert!(error.to_string().contains("offset exceeds Java long range"));
4736 }
4737
4738 #[test]
4739 fn notify_min_broker_id_change_request_header_matches_java_fields() {
4740 let header = notify_min_broker_id_change_request_header(
4741 1,
4742 CheetahString::from("127.0.0.1:10912"),
4743 Some(CheetahString::from("127.0.0.1:10911")),
4744 Some(CheetahString::from("127.0.0.1:10913")),
4745 )
4746 .expect("valid notify-min-broker header should build");
4747
4748 assert_eq!(header.min_broker_id, Some(1));
4749 assert!(header.broker_name.is_none());
4750 assert_eq!(header.min_broker_addr.as_deref(), Some("127.0.0.1:10912"));
4751 assert_eq!(header.offline_broker_addr.as_deref(), Some("127.0.0.1:10911"));
4752 assert_eq!(header.ha_broker_addr.as_deref(), Some("127.0.0.1:10913"));
4753 }
4754
4755 #[test]
4756 fn notify_min_broker_id_change_request_header_rejects_blank_min_broker_addr() {
4757 let error = notify_min_broker_id_change_request_header(1, CheetahString::new(), None, None)
4758 .expect_err("blank min broker address should be rejected before remoting");
4759
4760 assert!(error.to_string().contains("requires minBrokerAddr"));
4761 }
4762
4763 #[test]
4764 fn choose_min_broker_notify_addrs_matches_java_new_broker_rule() {
4765 let broker_addrs = HashMap::from([
4766 (0, CheetahString::from("127.0.0.1:10911")),
4767 (1, CheetahString::from("127.0.0.1:10912")),
4768 ]);
4769
4770 let notify_addrs = choose_min_broker_notify_addrs(&broker_addrs, 0, None);
4771
4772 assert_eq!(notify_addrs, vec![CheetahString::from("127.0.0.1:10912")]);
4773 }
4774
4775 #[test]
4776 fn choose_min_broker_notify_addrs_matches_java_offline_and_single_broker_rules() {
4777 let broker_addrs = HashMap::from([
4778 (0, CheetahString::from("127.0.0.1:10911")),
4779 (1, CheetahString::from("127.0.0.1:10912")),
4780 ]);
4781
4782 let notify_addrs =
4783 choose_min_broker_notify_addrs(&broker_addrs, 1, Some(&CheetahString::from("127.0.0.1:10911")));
4784 assert_eq!(
4785 notify_addrs,
4786 vec![
4787 CheetahString::from("127.0.0.1:10911"),
4788 CheetahString::from("127.0.0.1:10912")
4789 ]
4790 );
4791
4792 let single_broker = HashMap::from([(2, CheetahString::from("127.0.0.1:10913"))]);
4793 let notify_addrs = choose_min_broker_notify_addrs(&single_broker, 2, None);
4794 assert_eq!(notify_addrs, vec![CheetahString::from("127.0.0.1:10913")]);
4795 }
4796
4797 #[tokio::test]
4798 async fn update_consume_offset_without_started_client_returns_typed_error() {
4799 let admin = new_unstarted_admin();
4800 let mq = MessageQueue::from_parts("TopicTest", "broker-a", 0);
4801
4802 let error = admin
4803 .update_consume_offset(
4804 CheetahString::from("127.0.0.1:10911"),
4805 CheetahString::from("group-a"),
4806 mq,
4807 1,
4808 )
4809 .await
4810 .expect_err("unstarted admin should not try to send update offset");
4811
4812 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4813 }
4814
4815 #[tokio::test]
4816 async fn update_lite_pull_consumer_offset_without_started_client_returns_typed_error() {
4817 let admin = new_unstarted_admin();
4818
4819 let error = admin
4820 .update_lite_pull_consumer_offset(
4821 CheetahString::from("127.0.0.1:10911"),
4822 CheetahString::from("TopicTest"),
4823 CheetahString::from("group-a"),
4824 0,
4825 1,
4826 )
4827 .await
4828 .expect_err("unstarted admin should not try to send lite pull update offset");
4829
4830 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4831 }
4832
4833 #[tokio::test]
4834 async fn sync_broker_member_group_without_started_client_returns_typed_error() {
4835 let admin = new_unstarted_admin();
4836
4837 let error = admin
4838 .sync_broker_member_group(
4839 CheetahString::new(),
4840 CheetahString::from("cluster-a"),
4841 CheetahString::from("broker-a"),
4842 )
4843 .await
4844 .expect_err("sync_broker_member_group should require a started client");
4845
4846 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4847 }
4848
4849 #[tokio::test]
4850 async fn sync_broker_member_group_rejects_controller_addr() {
4851 let admin = new_unstarted_admin();
4852
4853 let error = admin
4854 .sync_broker_member_group(
4855 CheetahString::from("127.0.0.1:9878"),
4856 CheetahString::from("cluster-a"),
4857 CheetahString::from("broker-a"),
4858 )
4859 .await
4860 .expect_err("sync_broker_member_group should not silently ignore controller_addr");
4861
4862 assert!(matches!(
4863 error,
4864 rocketmq_error::RocketMQError::IllegalArgument(message)
4865 if message.contains("controllerAddr is not supported")
4866 ));
4867 }
4868
4869 #[tokio::test]
4870 async fn notify_min_broker_id_changed_without_started_client_returns_typed_error() {
4871 let admin = new_unstarted_admin();
4872
4873 let error = admin
4874 .notify_min_broker_id_changed(
4875 CheetahString::from("cluster-a"),
4876 CheetahString::from("broker-a"),
4877 1,
4878 CheetahString::from("127.0.0.1:10912"),
4879 None,
4880 None,
4881 )
4882 .await
4883 .expect_err("notify_min_broker_id_changed should require a started client");
4884
4885 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4886 }
4887
4888 #[tokio::test]
4889 async fn export_rocksdb_consumer_offset_to_json_rejects_local_file_path() {
4890 let admin = new_unstarted_admin();
4891
4892 let error = admin
4893 .export_rocksdb_consumer_offset_to_json(
4894 CheetahString::from("127.0.0.1:10911"),
4895 CheetahString::from("D:/tmp/consumerOffsets.json"),
4896 )
4897 .await
4898 .expect_err("RPC export cannot accept local export file path");
4899
4900 assert!(matches!(
4901 error,
4902 rocketmq_error::RocketMQError::IllegalArgument(message)
4903 if message.contains("filePath is local-mode only")
4904 ));
4905 }
4906
4907 #[tokio::test]
4908 async fn export_rocksdb_consumer_offset_to_json_without_started_client_returns_typed_error() {
4909 let admin = new_unstarted_admin();
4910
4911 let error = admin
4912 .export_rocksdb_consumer_offset_to_json(CheetahString::from("127.0.0.1:10911"), CheetahString::new())
4913 .await
4914 .expect_err("RPC export should require a started client");
4915
4916 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4917 }
4918
4919 #[tokio::test]
4920 async fn export_rocksdb_consumer_offset_from_memory_without_started_client_returns_typed_error() {
4921 let admin = new_unstarted_admin();
4922
4923 let error = admin
4924 .export_rocksdb_consumer_offset_from_memory(CheetahString::from("127.0.0.1:10911"))
4925 .await
4926 .expect_err("memory export should require a started client");
4927
4928 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4929 }
4930
4931 #[test]
4932 fn reset_offset_by_queue_id_request_headers_match_java_fields() {
4933 let (update_header, reset_header) = reset_offset_by_queue_id_request_headers(
4934 CheetahString::from("group-a"),
4935 CheetahString::from("TopicTest"),
4936 2,
4937 100,
4938 )
4939 .expect("valid reset offset should build request headers");
4940
4941 assert_eq!(update_header.consumer_group, "group-a");
4942 assert_eq!(update_header.topic, "TopicTest");
4943 assert_eq!(update_header.queue_id, 2);
4944 assert_eq!(update_header.commit_offset, 100);
4945 assert!(update_header.topic_request_header.is_none());
4946
4947 assert_eq!(reset_header.group, "group-a");
4948 assert_eq!(reset_header.topic, "TopicTest");
4949 assert_eq!(reset_header.queue_id, 2);
4950 assert_eq!(reset_header.offset, Some(100));
4951 assert_eq!(reset_header.timestamp, 0);
4952 assert!(!reset_header.is_force);
4953 assert!(reset_header.topic_request_header.is_none());
4954 }
4955
4956 #[test]
4957 fn reset_offset_by_queue_id_rejects_offsets_outside_java_long_range() {
4958 let error = reset_offset_by_queue_id_request_headers(
4959 CheetahString::from("group-a"),
4960 CheetahString::from("TopicTest"),
4961 0,
4962 i64::MAX as u64 + 1,
4963 )
4964 .expect_err("offset larger than Java long should be rejected");
4965
4966 assert!(error
4967 .to_string()
4968 .contains("resetOffsetByQueueId offset exceeds Java long range"));
4969 }
4970
4971 #[tokio::test]
4972 async fn reset_offset_by_queue_id_without_started_client_returns_typed_error() {
4973 let admin = new_unstarted_admin();
4974
4975 let error = admin
4976 .reset_offset_by_queue_id(
4977 CheetahString::from("127.0.0.1:10911"),
4978 CheetahString::from("group-a"),
4979 CheetahString::from("TopicTest"),
4980 0,
4981 100,
4982 )
4983 .await
4984 .expect_err("unstarted admin should not try to reset offset");
4985
4986 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
4987 }
4988
4989 #[test]
4990 fn update_group_forbidden_request_header_matches_java_fields() {
4991 let header = update_group_forbidden_request_header(
4992 CheetahString::from("group-a"),
4993 CheetahString::from("TopicTest"),
4994 Some(false),
4995 );
4996
4997 assert_eq!(header.group, "group-a");
4998 assert_eq!(header.topic, "TopicTest");
4999 assert_eq!(header.readable, Some(false));
5000 assert!(header.topic_request_header.is_none());
5001 }
5002
5003 #[test]
5004 fn update_group_forbidden_request_header_preserves_unspecified_readable() {
5005 let header = update_group_forbidden_request_header(
5006 CheetahString::from("group-a"),
5007 CheetahString::from("TopicTest"),
5008 None,
5009 );
5010
5011 assert_eq!(header.readable, None);
5012 }
5013
5014 #[tokio::test]
5015 async fn update_and_get_group_read_forbidden_without_started_client_returns_typed_error() {
5016 let admin = new_unstarted_admin();
5017
5018 let error = admin
5019 .update_and_get_group_read_forbidden(
5020 CheetahString::from("127.0.0.1:10911"),
5021 CheetahString::from("group-a"),
5022 CheetahString::from("TopicTest"),
5023 Some(false),
5024 )
5025 .await
5026 .expect_err("unstarted admin should not try to update group forbidden state");
5027
5028 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5029 }
5030
5031 #[tokio::test]
5032 async fn start_without_inner_returns_configuration_error_instead_of_panicking() {
5033 let mut admin = new_unstarted_admin();
5034
5035 let error = admin
5036 .start()
5037 .await
5038 .expect_err("direct impl start without inner should return a typed error");
5039
5040 assert!(matches!(
5041 error,
5042 rocketmq_error::RocketMQError::IllegalArgument(message)
5043 if message.contains("inner is required before start")
5044 ));
5045 }
5046
5047 #[tokio::test]
5048 async fn admin_api_facades_without_started_client_return_typed_errors() {
5049 let admin = new_unstarted_admin();
5050 let mq = MessageQueue::from_parts("TopicTest", "broker-a", 0);
5051
5052 let result = admin
5053 .pull_message_from_queue("127.0.0.1:10911", &mq, "*", 0, 32, 3000)
5054 .await;
5055 assert!(matches!(result, Err(rocketmq_error::RocketMQError::ClientNotStarted)));
5056
5057 let error = admin
5058 .delete_topic_in_broker(
5059 HashSet::from([CheetahString::from("127.0.0.1:10911")]),
5060 CheetahString::from("TopicTest"),
5061 )
5062 .await
5063 .expect_err("delete_topic_in_broker should require a started client");
5064 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5065
5066 let error = admin
5067 .set_message_request_mode(
5068 CheetahString::from("127.0.0.1:10911"),
5069 CheetahString::from("TopicTest"),
5070 CheetahString::from("group-a"),
5071 MessageRequestMode::Pull,
5072 0,
5073 3000,
5074 )
5075 .await
5076 .expect_err("set_message_request_mode should require a started client");
5077 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5078
5079 let error = admin
5080 .get_parent_topic_info(CheetahString::from("127.0.0.1:10911"), CheetahString::from("TopicTest"))
5081 .await
5082 .expect_err("get_parent_topic_info should require a started client");
5083 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5084
5085 let error = admin
5086 .create_lite_pull_topic(
5087 CheetahString::from("127.0.0.1:10911"),
5088 CheetahString::from("LiteTopic"),
5089 4,
5090 0,
5091 0,
5092 0,
5093 )
5094 .await
5095 .expect_err("create_lite_pull_topic should require a started client");
5096 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5097
5098 let error = admin
5099 .update_lite_pull_topic(
5100 CheetahString::from("127.0.0.1:10911"),
5101 CheetahString::from("LiteTopic"),
5102 4,
5103 4,
5104 )
5105 .await
5106 .expect_err("update_lite_pull_topic should require a started client");
5107 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5108
5109 let error = admin
5110 .get_lite_pull_topic(CheetahString::from("127.0.0.1:10911"), CheetahString::from("LiteTopic"))
5111 .await
5112 .expect_err("get_lite_pull_topic should require a started client");
5113 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5114
5115 let error = admin
5116 .delete_lite_pull_topic(
5117 CheetahString::from("127.0.0.1:10911"),
5118 CheetahString::from("cluster-a"),
5119 CheetahString::from("LiteTopic"),
5120 )
5121 .await
5122 .expect_err("delete_lite_pull_topic should require a started client");
5123 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5124
5125 let error = admin
5126 .query_lite_pull_topic_list(CheetahString::from("127.0.0.1:10911"))
5127 .await
5128 .expect_err("query_lite_pull_topic_list should require a started client");
5129 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5130
5131 let error = admin
5132 .query_lite_pull_topic_by_cluster(CheetahString::from("cluster-a"))
5133 .await
5134 .expect_err("query_lite_pull_topic_by_cluster should require a started client");
5135 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5136
5137 let error = admin
5138 .query_lite_pull_subscription_list(CheetahString::from("127.0.0.1:10911"), CheetahString::from("TopicTest"))
5139 .await
5140 .expect_err("query_lite_pull_subscription_list should require a started client");
5141 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5142
5143 let error = admin
5144 .examine_topic_config(CheetahString::from("127.0.0.1:10911"), CheetahString::from("TopicTest"))
5145 .await
5146 .expect_err("examine_topic_config should require a started client");
5147 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5148
5149 let error = admin
5150 .get_topic_config_by_topic_name(CheetahString::from("127.0.0.1:10911"), CheetahString::from("TopicTest"))
5151 .await
5152 .expect_err("get_topic_config_by_topic_name should require a started client");
5153 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5154
5155 let error = admin
5156 .get_topic_stats_info(CheetahString::from("127.0.0.1:10911"), CheetahString::from("TopicTest"))
5157 .await
5158 .expect_err("get_topic_stats_info should require a started client");
5159 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5160
5161 let error = admin
5162 .query_broker_has_topic(CheetahString::from("127.0.0.1:10911"), CheetahString::from("TopicTest"))
5163 .await
5164 .expect_err("query_broker_has_topic should require a started client");
5165 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5166
5167 let error = admin
5168 .fetch_topics_by_cluster(CheetahString::from("cluster-a"))
5169 .await
5170 .expect_err("fetch_topics_by_cluster should require a started client");
5171 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5172
5173 let error = admin
5174 .create_and_update_plain_access_config(
5175 CheetahString::from("127.0.0.1:10911"),
5176 PlainAccessConfig {
5177 access_key: Some(CheetahString::from("AK")),
5178 secret_key: Some(CheetahString::from("SK")),
5179 admin: true,
5180 ..Default::default()
5181 },
5182 )
5183 .await
5184 .expect_err("create_and_update_plain_access_config should require a started client");
5185 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5186
5187 let error = admin
5188 .delete_plain_access_config(CheetahString::from("127.0.0.1:10911"), CheetahString::from("AK"))
5189 .await
5190 .expect_err("delete_plain_access_config should require a started client");
5191 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5192
5193 let error = admin
5194 .get_system_topic_list_from_broker(CheetahString::from("127.0.0.1:10911"))
5195 .await
5196 .expect_err("get_system_topic_list_from_broker should require a started client");
5197 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5198
5199 let error = admin
5200 .get_kv_list_by_namespace(CheetahString::from("namespace-a"))
5201 .await
5202 .expect_err("get_kv_list_by_namespace should require a started client");
5203 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5204
5205 let error = admin
5206 .examine_topic_route_info_with_timeout(CheetahString::from("TopicTest"), 3000)
5207 .await
5208 .expect_err("examine_topic_route_info_with_timeout should require a started client");
5209 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5210
5211 let error = admin
5212 .query_consume_time_span(CheetahString::from("TopicTest"), CheetahString::from("group-a"))
5213 .await
5214 .expect_err("query_consume_time_span should require a started client");
5215 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5216
5217 let error = admin
5218 .reset_offset_new(
5219 CheetahString::from("group-a"),
5220 CheetahString::from("TopicTest"),
5221 1_700_000_000_000,
5222 )
5223 .await
5224 .expect_err("reset_offset_new should require a started client");
5225 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5226
5227 let result = admin
5228 .reset_offset_new_concurrent(
5229 CheetahString::from("group-a"),
5230 CheetahString::from("TopicTest"),
5231 1_700_000_000_000,
5232 )
5233 .await;
5234 assert!(!result.is_success());
5235 assert_eq!(result.get_code(), AdminToolsResultCodeEnum::MQClientError.get_code());
5236
5237 let error = admin
5238 .query_topics_by_consumer(CheetahString::from("group-a"))
5239 .await
5240 .expect_err("query_topics_by_consumer should require a started client");
5241 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5242
5243 let result = admin
5244 .query_topics_by_consumer_concurrent(CheetahString::from("group-a"))
5245 .await;
5246 assert!(!result.is_success());
5247 assert_eq!(result.get_code(), AdminToolsResultCodeEnum::MQClientError.get_code());
5248
5249 let error = admin
5250 .examine_consume_stats_with_queue(
5251 CheetahString::from("group-a"),
5252 Some(CheetahString::from("TopicTest")),
5253 Some(0),
5254 )
5255 .await
5256 .expect_err("examine_consume_stats_with_queue should require a started client");
5257 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5258
5259 let result = admin
5260 .examine_consume_stats_concurrent(CheetahString::from("group-a"), Some(CheetahString::from("TopicTest")))
5261 .await;
5262 assert!(!result.is_success());
5263 assert_eq!(result.get_code(), AdminToolsResultCodeEnum::MQClientError.get_code());
5264
5265 let result = admin
5266 .examine_consume_stats_concurrent_with_cluster(
5267 CheetahString::from("group-a"),
5268 Some(CheetahString::from("TopicTest")),
5269 Some(CheetahString::from("cluster-a")),
5270 )
5271 .await;
5272 assert!(!result.is_success());
5273 assert_eq!(result.get_code(), AdminToolsResultCodeEnum::MQClientError.get_code());
5274
5275 let error = admin
5276 .query_subscription(CheetahString::from("group-a"), CheetahString::from("TopicTest"))
5277 .await
5278 .expect_err("query_subscription should require a started client");
5279 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5280
5281 let error = admin
5282 .clone_group_offset(
5283 CheetahString::from("source-group"),
5284 CheetahString::from("target-group"),
5285 CheetahString::from("TopicTest"),
5286 false,
5287 )
5288 .await
5289 .expect_err("clone_group_offset should require a started client");
5290 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5291
5292 let error = admin
5293 .get_cluster_list(String::from("TopicTest"))
5294 .await
5295 .expect_err("get_cluster_list should require a started client");
5296 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5297
5298 let result = admin
5299 .fetch_consume_stats_in_broker(CheetahString::from("127.0.0.1:10911"), false, 3000)
5300 .await;
5301 assert!(matches!(result, Err(rocketmq_error::RocketMQError::ClientNotStarted)));
5302
5303 let error = admin
5304 .update_global_white_addr_config(
5305 CheetahString::from("127.0.0.1:10911"),
5306 CheetahString::from("10.10.*.*"),
5307 None,
5308 )
5309 .await
5310 .expect_err("update_global_white_addr_config should require a started client");
5311 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5312
5313 let error = admin
5314 .update_global_white_addr_config(
5315 CheetahString::from("127.0.0.1:10911"),
5316 CheetahString::from("10.10.*.*"),
5317 Some(CheetahString::from("/opt/rocketmq/conf/plain_acl.yml")),
5318 )
5319 .await
5320 .expect_err("update_global_white_addr_config should require a started client before validating RPC fields");
5321 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5322
5323 let error = admin
5324 .create_static_topic(
5325 CheetahString::from("127.0.0.1:10911"),
5326 CheetahString::from("TBW102"),
5327 TopicConfig::new("TopicTest"),
5328 TopicQueueMappingDetail::default(),
5329 true,
5330 )
5331 .await
5332 .expect_err("create_static_topic should require a started client");
5333 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5334
5335 let error = admin
5336 .resume_check_half_message(
5337 CheetahString::from("TopicTest"),
5338 CheetahString::from("AC11000100002A9F0000000000000001"),
5339 )
5340 .await
5341 .expect_err("resume_check_half_message should require a started client");
5342 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5343
5344 let error = admin
5345 .switch_timer_engine(
5346 CheetahString::from("127.0.0.1:10911"),
5347 CheetahString::from(MessageConst::TIMER_ENGINE_ROCKSDB_TIMELINE),
5348 )
5349 .await
5350 .expect_err("switch_timer_engine should require a started client");
5351 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5352
5353 let error = admin
5354 .remove_cold_data_flow_ctr_group_config(
5355 CheetahString::from("127.0.0.1:10911"),
5356 CheetahString::from("group-a"),
5357 )
5358 .await
5359 .expect_err("remove_cold_data_flow_ctr_group_config should require a started client");
5360 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5361
5362 let error = admin
5363 .get_cold_data_flow_ctr_info(CheetahString::from("127.0.0.1:10911"))
5364 .await
5365 .expect_err("get_cold_data_flow_ctr_info should require a started client");
5366 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5367
5368 let error = admin
5369 .set_commit_log_read_ahead_mode(CheetahString::from("127.0.0.1:10911"), CheetahString::from("1"))
5370 .await
5371 .expect_err("set_commit_log_read_ahead_mode should require a started client");
5372 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5373
5374 let error = admin
5375 .create_user_with_info(
5376 CheetahString::from("127.0.0.1:10911"),
5377 CheetahString::from("alice"),
5378 CheetahString::from("secret"),
5379 )
5380 .await
5381 .expect_err("create_user_with_info should require a started client");
5382 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5383
5384 let error = admin
5385 .update_user_with_info(
5386 CheetahString::from("127.0.0.1:10911"),
5387 CheetahString::from("alice"),
5388 CheetahString::from("new-secret"),
5389 )
5390 .await
5391 .expect_err("update_user_with_info should require a started client");
5392 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5393
5394 let error = admin
5395 .export_pop_records(CheetahString::from("127.0.0.1:10911"), 3000)
5396 .await
5397 .expect_err("export_pop_records should require a started client");
5398 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5399
5400 let error = admin
5401 .clean_expired_consumer_queue(None, Some(CheetahString::from("127.0.0.1:10911")))
5402 .await
5403 .expect_err("clean_expired_consumer_queue should require a started client");
5404 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5405
5406 let error = admin
5407 .clean_expired_consumer_queue_by_addr(CheetahString::from("127.0.0.1:10911"))
5408 .await
5409 .expect_err("clean_expired_consumer_queue_by_addr should require a started client");
5410 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5411
5412 let error = admin
5413 .delete_expired_commit_log(None, Some(CheetahString::from("127.0.0.1:10911")))
5414 .await
5415 .expect_err("delete_expired_commit_log should require a started client");
5416 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5417
5418 let error = admin
5419 .delete_expired_commit_log_by_addr(CheetahString::from("127.0.0.1:10911"))
5420 .await
5421 .expect_err("delete_expired_commit_log_by_addr should require a started client");
5422 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5423
5424 let error = admin
5425 .clean_unused_topic(None, Some(CheetahString::from("127.0.0.1:10911")))
5426 .await
5427 .expect_err("clean_unused_topic should require a started client");
5428 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5429
5430 let error = admin
5431 .clean_unused_topic_by_addr(CheetahString::from("127.0.0.1:10911"))
5432 .await
5433 .expect_err("clean_unused_topic_by_addr should require a started client");
5434 assert!(matches!(error, rocketmq_error::RocketMQError::ClientNotStarted));
5435
5436 let result = admin
5437 .delete_topic_in_broker_concurrent(
5438 HashSet::from([CheetahString::from("127.0.0.1:10911")]),
5439 CheetahString::from("TopicTest"),
5440 )
5441 .await;
5442 assert!(!result.is_success());
5443 assert_eq!(result.get_code(), AdminToolsResultCodeEnum::MQClientError.get_code());
5444
5445 let namesrv_list = admin.get_name_server_address_list().await;
5446 assert!(namesrv_list.is_empty());
5447 }
5448}