Skip to main content

rocketmq_client_rust/admin/
default_mq_admin_ext_impl.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![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                    // No messages found on this broker, continue
872                }
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                // do nothing
946            }
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        // Remove system consumer groups
3091        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}