1use super::request_builder::*;
16use super::response_decoder::*;
17use super::*;
18use rocketmq_error::fields;
19use rocketmq_error::Error;
20use rocketmq_error::ErrorContext;
21use rocketmq_model::topic::TopicConfig;
22use rocketmq_protocol::protocol::subscription::subscription_group_config::SubscriptionGroupConfig;
23use rocketmq_transport::api::OutboundRequestContractReason;
24use rocketmq_transport::api::OutboundRequestRejectionReason;
25use rocketmq_transport::api::OutboundRequestStage;
26use serde::de::DeserializeOwned;
27
28#[cfg(feature = "admin-full")]
29mod compatibility;
30#[cfg(feature = "admin-read")]
31mod controller_rollout;
32mod metadata;
33#[cfg(feature = "admin-mutation")]
34mod supervised_mutation;
35#[cfg(feature = "admin-mutation")]
36mod supervised_mutation_decode;
37mod versioned_config;
38
39fn admin_request_error(operation: &'static str, input: RetryInput) -> ClientError {
40 match input {
41 RetryInput::Transport(error) => ClientError::from_shared(error.into_shared_error()),
42 RetryInput::Rejected(rejection) => match rejection.reason() {
43 OutboundRequestRejectionReason::DeadlineExpired => {
44 let mut context = ErrorContext::new().with_text(fields::OPERATION_DIAGNOSTIC, operation);
45 if let Some(timeout_millis) = rejection.timeout_millis() {
46 context = context.with_u64(fields::TIMEOUT_MS, timeout_millis);
47 }
48 ClientError::from_shared(Arc::new(
49 Error::new(&rocketmq_error::CORE_OPERATION_TIMED_OUT).with_context(context),
50 ))
51 }
52 OutboundRequestRejectionReason::ClientStopping => ClientError::not_started(),
53 OutboundRequestRejectionReason::QueueSaturated => ClientError::from_shared(Arc::new(Error::new(
54 &rocketmq_error::TRANSPORT_ADMISSION_QUEUE_SATURATED,
55 ))),
56 OutboundRequestRejectionReason::Cancelled
57 | OutboundRequestRejectionReason::SessionClosed
58 | OutboundRequestRejectionReason::EndpointUnavailable => {
59 let phase = match rejection.reason() {
60 OutboundRequestRejectionReason::SessionClosed => "closed",
61 _ => admin_request_stage_phase(rejection.stage()),
62 };
63 let mut context = ErrorContext::new().with_text(fields::PHASE, phase);
64 if rejection.remote_addr_present() {
65 context = context.with_secret_presence(fields::REMOTE_ADDR_PRESENT);
66 }
67 ClientError::from_shared(Arc::new(
68 Error::new(&rocketmq_error::TRANSPORT_CONNECTION_FAILED).with_context(context),
69 ))
70 }
71 },
72 RetryInput::Contract(contract) => match contract.reason() {
73 OutboundRequestContractReason::NameServerEndpointMissing => {
74 let mut context = ErrorContext::new().with_text(fields::PHASE, "connect");
75 if contract.remote_addr_present() {
76 context = context.with_secret_presence(fields::REMOTE_ADDR_PRESENT);
77 }
78 ClientError::from_shared(Arc::new(
79 Error::new(&rocketmq_error::TRANSPORT_CONNECTION_FAILED).with_context(context),
80 ))
81 }
82 },
83 RetryInput::Response { terminal_error, .. } | RetryInput::BusinessError(terminal_error) => terminal_error,
84 RetryInput::SendStatus(_) | RetryInput::RouteUnavailable => {
86 unreachable!("admin request returned an unrelated retry input")
87 }
88 }
89}
90
91const fn admin_request_stage_phase(stage: OutboundRequestStage) -> &'static str {
92 match stage {
93 OutboundRequestStage::BeforeWrite => "before_write",
94 OutboundRequestStage::Writing => "writing",
95 OutboundRequestStage::AwaitingResponse => "awaiting_response",
96 OutboundRequestStage::ResponseReceived => "response_received",
97 }
98}
99
100fn decode_admin_json<T: DeserializeOwned>(bytes: &[u8]) -> ClientResult<T> {
101 serde_json::from_slice(bytes).map_err(|source| {
102 let context = ErrorContext::new()
103 .with_text(fields::OPERATION_DIAGNOSTIC, "deserialize")
104 .with_text(fields::FORMAT, "JSON")
105 .with_secret_presence(fields::SOURCE_PRESENT);
106 ClientError::from_error(
107 Error::caused_by(&rocketmq_error::CORE_SERIALIZATION_FAILED, source).with_context(context),
108 )
109 })
110}
111
112pub struct AdminClient<'a> {
113 api: &'a MQClientAPIImpl,
114}
115
116impl AdminClient<'_> {
117 pub async fn broker_cluster_info(&self, timeout_millis: u64) -> ClientResult<ClusterInfo> {
118 self.api.get_broker_cluster_info(timeout_millis).await
119 }
120}
121
122impl MQClientAPIImpl {
123 #[must_use]
124 pub fn admin_client(&self) -> AdminClient<'_> {
125 AdminClient { api: self }
126 }
127}
128
129impl MQClientAPIImpl {
130 pub(crate) async fn get_kvconfig_value(
131 &self,
132 namespace: CheetahString,
133 key: CheetahString,
134 timeout_millis: u64,
135 ) -> ClientResult<Option<CheetahString>> {
136 let request_header = GetKVConfigRequestHeader::new(namespace, key);
137 let request = self.create_request_command(RequestCode::GetKvConfig, request_header);
138
139 let name_server_address_list = self.remoting_client.get_name_server_address_list();
140 let max_attempts = u32::try_from(name_server_address_list.len()).unwrap_or(u32::MAX);
141 let deadline = RequestDeadline::from_timeout_millis(timeout_millis);
142 let mut last_error = None;
143 for (index, name_srv_addr) in name_server_address_list.into_iter().enumerate() {
144 if deadline.is_expired() {
145 if let Some(error) = last_error.take() {
146 return Err(error);
147 }
148 }
149 let attempt = u32::try_from(index).unwrap_or(u32::MAX).saturating_add(1);
150 let outcome = self
151 .remoting_client
152 .invoke_request_with_deadline(Some(&name_srv_addr), request.clone(), deadline)
153 .await;
154 let input = match outcome {
155 Ok(OutboundRequestOutcome::Response(response)) => match ResponseCode::from(response.code()) {
156 ResponseCode::Success => {
157 let response_header = response
158 .decode_command_custom_header::<GetKVConfigResponseHeader>()
159 .map_err(|error| {
160 ClientError::response_process_source("decode GetKVConfigResponseHeader", error)
161 })?;
162 return Ok(response_header.value);
163 }
164 ResponseCode::QueryNotFound => return Ok(None),
165 _ => {
166 let code = response.code();
167 RetryInput::Response {
168 code,
169 retry_after: None,
170 terminal_error: mq_client_err!(
171 code,
172 response.remark().map_or("".to_string(), |s| s.to_string())
173 ),
174 }
175 }
176 },
177 Ok(OutboundRequestOutcome::Rejected(rejection)) => RetryInput::Rejected(rejection),
178 Ok(OutboundRequestOutcome::Contract(contract)) => RetryInput::Contract(contract),
179 Err(error) => RetryInput::Transport(error),
180 };
181 let action = RetryPolicy::decide(
182 RetryContext {
183 operation: RetryOperation::NameServerKv,
184 idempotency: RetryIdempotency::Idempotent,
185 attempt,
186 max_attempts,
187 remaining: deadline.remaining(),
188 producer_retry_response_codes: None,
189 retry_not_store_ok: false,
190 },
191 &input,
192 );
193 let terminal_error = admin_request_error("get_kvconfig_value", input);
194 match action {
195 RetryAction::Stop => return Err(terminal_error),
196 RetryAction::RetryAfter(delay) => {
197 if tokio::time::timeout_at(deadline.instant(), tokio::time::sleep(delay))
198 .await
199 .is_err()
200 {
201 return Err(terminal_error);
202 }
203 last_error = Some(terminal_error);
204 }
205 RetryAction::RetryNow | RetryAction::SwitchBroker => {
206 last_error = Some(terminal_error);
207 }
208 RetryAction::RefreshRoute | RetryAction::RefreshLeader => {
209 return Err(terminal_error);
210 }
211 }
212 if deadline.is_expired() {
213 if let Some(error) = last_error.take() {
214 return Err(error);
215 }
216 }
217 }
218
219 match last_error {
220 Some(error) => Err(error),
221 None => Ok(None),
222 }
223 }
224
225 pub(crate) async fn get_kv_config_value(
226 &self,
227 namespace: CheetahString,
228 key: CheetahString,
229 timeout_millis: u64,
230 ) -> ClientResult<Option<CheetahString>> {
231 self.get_kvconfig_value(namespace, key, timeout_millis).await
232 }
233
234 #[cfg(feature = "admin-mutation")]
235 pub(crate) async fn delete_kvconfig_value(
236 &self,
237 namespace: CheetahString,
238 key: CheetahString,
239 timeout_millis: u64,
240 ) -> ClientResult<()> {
241 let request_header = DeleteKVConfigRequestHeader::new(namespace, key);
242 let request = self.create_request_command(RequestCode::DeleteKvConfig, request_header);
243
244 let name_server_address_list = self.remoting_client.get_name_server_address_list();
245 let mut err_response = None;
246 for name_srv_addr in name_server_address_list {
247 let outcome = self
248 .remoting_client
249 .invoke_request(Some(&name_srv_addr), request.clone(), timeout_millis)
250 .await;
251 let response = match outcome {
252 Ok(OutboundRequestOutcome::Response(response)) => response,
253 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
254 return Err(admin_request_error(
255 "delete_kvconfig_value",
256 RetryInput::Rejected(rejection),
257 ));
258 }
259 Ok(OutboundRequestOutcome::Contract(contract)) => {
260 return Err(admin_request_error(
261 "delete_kvconfig_value",
262 RetryInput::Contract(contract),
263 ));
264 }
265 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
266 };
267 match ResponseCode::from(response.code()) {
268 ResponseCode::Success => {}
269 _ => err_response = Some(response),
270 }
271 }
272
273 if let Some(err_response) = err_response {
274 return Err(mq_client_err!(
275 err_response.code(),
276 err_response.remark().map_or("".to_string(), |s| s.to_string())
277 ));
278 }
279 Ok(())
280 }
281
282 #[cfg(feature = "admin-mutation")]
283 pub(crate) async fn delete_kv_config_value(
284 &self,
285 namespace: CheetahString,
286 key: CheetahString,
287 timeout_millis: u64,
288 ) -> ClientResult<()> {
289 self.delete_kvconfig_value(namespace, key, timeout_millis).await
290 }
291
292 pub(crate) async fn get_kvlist_by_namespace(
293 &self,
294 namespace: CheetahString,
295 timeout_millis: u64,
296 ) -> ClientResult<rocketmq_protocol::protocol::body::kv_table::KVTable> {
297 let request_header = GetKVListByNamespaceRequestHeader::new(namespace);
298 let request = self.create_request_command(RequestCode::GetKvlistByNamespace, request_header);
299
300 let outcome = self.remoting_client.invoke_request(None, request, timeout_millis).await;
301 let response = match outcome {
302 Ok(OutboundRequestOutcome::Response(response)) => response,
303 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
304 return Err(admin_request_error(
305 "get_kvlist_by_namespace",
306 RetryInput::Rejected(rejection),
307 ));
308 }
309 Ok(OutboundRequestOutcome::Contract(contract)) => {
310 return Err(admin_request_error(
311 "get_kvlist_by_namespace",
312 RetryInput::Contract(contract),
313 ));
314 }
315 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
316 };
317 if ResponseCode::from(response.code()) == ResponseCode::Success {
318 if let Some(body) = response.get_body() {
319 return rocketmq_protocol::protocol::body::kv_table::KVTable::decode(body.as_ref())
320 .map_err(ClientError::from);
321 }
322 }
323
324 Err(mq_client_err!(
325 response.code(),
326 response.remark().map_or("".to_string(), |s| s.to_string())
327 ))
328 }
329
330 pub(crate) async fn get_kv_list_by_namespace(
331 &self,
332 namespace: CheetahString,
333 timeout_millis: u64,
334 ) -> ClientResult<rocketmq_protocol::protocol::body::kv_table::KVTable> {
335 self.get_kvlist_by_namespace(namespace, timeout_millis).await
336 }
337
338 #[cfg(feature = "admin-mutation")]
339 pub(crate) async fn put_kvconfig_value(
340 &self,
341 namespace: CheetahString,
342 key: CheetahString,
343 value: CheetahString,
344 timeout_millis: u64,
345 ) -> ClientResult<()> {
346 let request_header = PutKVConfigRequestHeader::new(namespace, key, value);
347 let request = self.create_request_command(RequestCode::PutKvConfig, request_header);
348
349 let name_server_address_list = self.remoting_client.get_name_server_address_list();
350 let mut err_response = None;
351 for name_srv_addr in name_server_address_list {
352 let outcome = self
353 .remoting_client
354 .invoke_request(Some(&name_srv_addr), request.clone(), timeout_millis)
355 .await;
356 let response = match outcome {
357 Ok(OutboundRequestOutcome::Response(response)) => response,
358 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
359 return Err(admin_request_error(
360 "put_kvconfig_value",
361 RetryInput::Rejected(rejection),
362 ));
363 }
364 Ok(OutboundRequestOutcome::Contract(contract)) => {
365 return Err(admin_request_error(
366 "put_kvconfig_value",
367 RetryInput::Contract(contract),
368 ));
369 }
370 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
371 };
372 match ResponseCode::from(response.code()) {
373 ResponseCode::Success => {}
374 _ => err_response = Some(response),
375 }
376 }
377
378 if let Some(err_response) = err_response {
379 return Err(mq_client_err!(
380 err_response.code(),
381 err_response.remark().map_or("".to_string(), |s| s.to_string())
382 ));
383 }
384 Ok(())
385 }
386
387 #[cfg(feature = "admin-mutation")]
388 pub(crate) async fn put_kv_config_value(
389 &self,
390 namespace: CheetahString,
391 key: CheetahString,
392 value: CheetahString,
393 timeout_millis: u64,
394 ) -> ClientResult<()> {
395 self.put_kvconfig_value(namespace, key, value, timeout_millis).await
396 }
397
398 #[cfg(feature = "admin-mutation")]
399 pub(crate) async fn create_user(
400 &self,
401 broker_address: CheetahString,
402 user_info: &UserInfo,
403 timeout_millis: u64,
404 ) -> ClientResult<()> {
405 let mut request_header = CreateUserRequestHeader::default();
406 let username = user_info
407 .username
408 .clone()
409 .ok_or_else(|| mq_client_err!(-1, "username is required".to_string()))?;
410 request_header.set_username(username);
411 let mut request = self.create_request_command(RequestCode::AuthCreateUser, request_header);
412 request = request.set_body(user_info.encode()?);
413
414 let outcome = self
415 .remoting_client
416 .invoke_request(Some(&broker_address), request.clone(), timeout_millis)
417 .await;
418 let response = match outcome {
419 Ok(OutboundRequestOutcome::Response(response)) => response,
420 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
421 return Err(admin_request_error("create_user", RetryInput::Rejected(rejection)));
422 }
423 Ok(OutboundRequestOutcome::Contract(contract)) => {
424 return Err(admin_request_error("create_user", RetryInput::Contract(contract)));
425 }
426 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
427 };
428
429 let mut err_response = None;
430 match ResponseCode::from(response.code()) {
431 ResponseCode::Success => {}
432 _ => err_response = Some(response),
433 }
434
435 if let Some(err_response) = err_response {
436 return Err(mq_client_err!(
437 err_response.code(),
438 err_response.remark().map_or("".to_string(), |s| s.to_string())
439 ));
440 }
441 Ok(())
442 }
443
444 #[cfg(feature = "admin-mutation")]
445 pub(crate) async fn update_user(
446 &self,
447 broker_address: CheetahString,
448 user_info: &UserInfo,
449 timeout_millis: u64,
450 ) -> ClientResult<()> {
451 let mut request_header = UpdateUserRequestHeader::default();
452 let username = user_info
453 .username
454 .clone()
455 .ok_or_else(|| mq_client_err!(-1, "username is required".to_string()))?;
456 request_header.set_username(username);
457 let mut request = self.create_request_command(RequestCode::AuthUpdateUser, request_header);
458 request = request.set_body(user_info.encode()?);
459
460 let outcome = self
461 .remoting_client
462 .invoke_request(Some(&broker_address), request.clone(), timeout_millis)
463 .await;
464 let response = match outcome {
465 Ok(OutboundRequestOutcome::Response(response)) => response,
466 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
467 return Err(admin_request_error("update_user", RetryInput::Rejected(rejection)));
468 }
469 Ok(OutboundRequestOutcome::Contract(contract)) => {
470 return Err(admin_request_error("update_user", RetryInput::Contract(contract)));
471 }
472 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
473 };
474
475 let mut err_response = None;
476 match ResponseCode::from(response.code()) {
477 ResponseCode::Success => {}
478 _ => err_response = Some(response),
479 }
480
481 if let Some(err_response) = err_response {
482 return Err(mq_client_err!(
483 err_response.code(),
484 err_response.remark().map_or("".to_string(), |s| s.to_string())
485 ));
486 }
487 Ok(())
488 }
489
490 #[cfg(feature = "admin-mutation")]
491 pub(crate) async fn create_acl(
492 &self,
493 broker_address: CheetahString,
494 acl_info: &AclInfo,
495 timeout_millis: u64,
496 ) -> ClientResult<()> {
497 let subject = acl_info
498 .subject
499 .clone()
500 .ok_or_else(|| mq_client_err!(-1, "ACL subject is required".to_string()))?;
501 let request_header = CreateAclRequestHeader { subject };
502 let request = self
503 .create_request_command(RequestCode::AuthCreateAcl, request_header)
504 .set_body(acl_info.encode()?);
505
506 let outcome = self
507 .remoting_client
508 .invoke_request(Some(&broker_address), request, timeout_millis)
509 .await;
510 let response = match outcome {
511 Ok(OutboundRequestOutcome::Response(response)) => response,
512 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
513 return Err(admin_request_error("create_acl", RetryInput::Rejected(rejection)));
514 }
515 Ok(OutboundRequestOutcome::Contract(contract)) => {
516 return Err(admin_request_error("create_acl", RetryInput::Contract(contract)));
517 }
518 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
519 };
520
521 match ResponseCode::from(response.code()) {
522 ResponseCode::Success => Ok(()),
523 _ => Err(mq_client_err!(
524 response.code(),
525 response.remark().map_or("".to_string(), |s| s.to_string())
526 )),
527 }
528 }
529
530 #[cfg(feature = "admin-mutation")]
531 pub(crate) async fn update_acl(
532 &self,
533 broker_address: CheetahString,
534 acl_info: &AclInfo,
535 timeout_millis: u64,
536 ) -> ClientResult<()> {
537 let subject = acl_info
538 .subject
539 .clone()
540 .ok_or_else(|| mq_client_err!(-1, "ACL subject is required".to_string()))?;
541 let request_header = UpdateAclRequestHeader { subject };
542 let request = self
543 .create_request_command(RequestCode::AuthUpdateAcl, request_header)
544 .set_body(acl_info.encode()?);
545
546 let outcome = self
547 .remoting_client
548 .invoke_request(Some(&broker_address), request, timeout_millis)
549 .await;
550 let response = match outcome {
551 Ok(OutboundRequestOutcome::Response(response)) => response,
552 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
553 return Err(admin_request_error("update_acl", RetryInput::Rejected(rejection)));
554 }
555 Ok(OutboundRequestOutcome::Contract(contract)) => {
556 return Err(admin_request_error("update_acl", RetryInput::Contract(contract)));
557 }
558 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
559 };
560
561 match ResponseCode::from(response.code()) {
562 ResponseCode::Success => Ok(()),
563 _ => Err(mq_client_err!(
564 response.code(),
565 response.remark().map_or("".to_string(), |s| s.to_string())
566 )),
567 }
568 }
569
570 #[cfg(feature = "admin-mutation")]
571 pub(crate) async fn create_and_update_plain_access_config(
572 &self,
573 broker_address: CheetahString,
574 plain_access_config: &PlainAccessConfig,
575 timeout_millis: u64,
576 ) -> ClientResult<()> {
577 let request = create_and_update_plain_access_config_request(&self.command_factory, plain_access_config)?;
578
579 let outcome = self
580 .remoting_client
581 .invoke_request(Some(&broker_address), request, timeout_millis)
582 .await;
583 let response = match outcome {
584 Ok(OutboundRequestOutcome::Response(response)) => response,
585 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
586 return Err(admin_request_error(
587 "create_and_update_plain_access_config",
588 RetryInput::Rejected(rejection),
589 ));
590 }
591 Ok(OutboundRequestOutcome::Contract(contract)) => {
592 return Err(admin_request_error(
593 "create_and_update_plain_access_config",
594 RetryInput::Contract(contract),
595 ));
596 }
597 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
598 };
599
600 match ResponseCode::from(response.code()) {
601 ResponseCode::Success => Ok(()),
602 _ => Err(mq_client_err!(
603 response.code(),
604 response.remark().map_or_else(String::new, ToString::to_string)
605 )),
606 }
607 }
608
609 #[cfg(feature = "admin-mutation")]
610 pub(crate) async fn delete_plain_access_config(
611 &self,
612 broker_address: CheetahString,
613 access_key: CheetahString,
614 timeout_millis: u64,
615 ) -> ClientResult<()> {
616 let request = delete_plain_access_config_request(&self.command_factory, &access_key);
617
618 let outcome = self
619 .remoting_client
620 .invoke_request(Some(&broker_address), request, timeout_millis)
621 .await;
622 let response = match outcome {
623 Ok(OutboundRequestOutcome::Response(response)) => response,
624 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
625 return Err(admin_request_error(
626 "delete_plain_access_config",
627 RetryInput::Rejected(rejection),
628 ));
629 }
630 Ok(OutboundRequestOutcome::Contract(contract)) => {
631 return Err(admin_request_error(
632 "delete_plain_access_config",
633 RetryInput::Contract(contract),
634 ));
635 }
636 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
637 };
638
639 match ResponseCode::from(response.code()) {
640 ResponseCode::Success => Ok(()),
641 _ => Err(mq_client_err!(
642 response.code(),
643 response.remark().map_or_else(String::new, ToString::to_string)
644 )),
645 }
646 }
647
648 #[cfg(feature = "admin-mutation")]
649 pub(crate) async fn update_global_white_addrs_config(
650 &self,
651 broker_address: CheetahString,
652 global_white_addrs: CheetahString,
653 timeout_millis: u64,
654 ) -> ClientResult<()> {
655 let request_header = UpdateGlobalWhiteAddrsConfigRequestHeader { global_white_addrs };
656 let request = self.create_request_command(RequestCode::UpdateGlobalWhiteAddrsConfig, request_header);
657
658 let outcome = self
659 .remoting_client
660 .invoke_request(Some(&broker_address), request, timeout_millis)
661 .await;
662 let response = match outcome {
663 Ok(OutboundRequestOutcome::Response(response)) => response,
664 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
665 return Err(admin_request_error(
666 "update_global_white_addrs_config",
667 RetryInput::Rejected(rejection),
668 ));
669 }
670 Ok(OutboundRequestOutcome::Contract(contract)) => {
671 return Err(admin_request_error(
672 "update_global_white_addrs_config",
673 RetryInput::Contract(contract),
674 ));
675 }
676 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
677 };
678
679 match ResponseCode::from(response.code()) {
680 ResponseCode::Success => Ok(()),
681 _ => Err(mq_client_err!(
682 response.code(),
683 response.remark().map_or("".to_string(), |s| s.to_string())
684 )),
685 }
686 }
687
688 pub(crate) async fn get_broker_cluster_acl_version_info(
689 &self,
690 broker_address: CheetahString,
691 timeout_millis: u64,
692 ) -> ClientResult<ClusterAclVersionInfo> {
693 let request = self.create_remoting_command(RequestCode::GetBrokerClusterAclInfo);
694
695 let outcome = self
696 .remoting_client
697 .invoke_request(Some(&broker_address), request, timeout_millis)
698 .await;
699 let response = match outcome {
700 Ok(OutboundRequestOutcome::Response(response)) => response,
701 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
702 return Err(admin_request_error(
703 "get_broker_cluster_acl_version_info",
704 RetryInput::Rejected(rejection),
705 ));
706 }
707 Ok(OutboundRequestOutcome::Contract(contract)) => {
708 return Err(admin_request_error(
709 "get_broker_cluster_acl_version_info",
710 RetryInput::Contract(contract),
711 ));
712 }
713 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
714 };
715
716 match ResponseCode::from(response.code()) {
717 ResponseCode::Success => decode_cluster_acl_version_info_response_body(response.body()),
718 _ => Err(mq_client_err!(
719 response.code(),
720 response.remark().map_or_else(String::new, |remark| remark.to_string())
721 )),
722 }
723 }
724
725 #[cfg(feature = "admin-mutation")]
726 pub(crate) async fn delete_acl(
727 &self,
728 broker_address: CheetahString,
729 subject: CheetahString,
730 resource: CheetahString,
731 timeout_millis: u64,
732 ) -> ClientResult<()> {
733 let resource_option = if resource.is_empty() { None } else { Some(resource) };
734 let request_header = DeleteAclRequestHeader::new(subject, resource_option);
735 self.delete_acl_request(broker_address, request_header, timeout_millis)
736 .await
737 }
738
739 #[cfg(feature = "admin-mutation")]
740 pub(crate) async fn delete_acl_entry(
741 &self,
742 broker_address: CheetahString,
743 subject: CheetahString,
744 policy_type: CheetahString,
745 resource: CheetahString,
746 timeout_millis: u64,
747 ) -> ClientResult<()> {
748 let header = acl_entry_delete_header(subject, policy_type, resource)?;
749 self.delete_acl_request(broker_address, header, timeout_millis).await
750 }
751
752 #[cfg(feature = "admin-mutation")]
753 async fn delete_acl_request(
754 &self,
755 broker_address: CheetahString,
756 request_header: DeleteAclRequestHeader,
757 timeout_millis: u64,
758 ) -> ClientResult<()> {
759 let request = self.create_request_command(RequestCode::AuthDeleteAcl, request_header);
760
761 let outcome = self
762 .remoting_client
763 .invoke_request(Some(&broker_address), request, timeout_millis)
764 .await;
765 let response = match outcome {
766 Ok(OutboundRequestOutcome::Response(response)) => response,
767 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
768 return Err(admin_request_error("delete_acl", RetryInput::Rejected(rejection)));
769 }
770 Ok(OutboundRequestOutcome::Contract(contract)) => {
771 return Err(admin_request_error("delete_acl", RetryInput::Contract(contract)));
772 }
773 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
774 };
775
776 match ResponseCode::from(response.code()) {
777 ResponseCode::Success => Ok(()),
778 _ => Err(mq_client_err!(
779 response.code(),
780 response.remark().map_or("".to_string(), |s| s.to_string())
781 )),
782 }
783 }
784
785 pub(crate) async fn list_acl(
786 &self,
787 broker_address: CheetahString,
788 subject_filter: CheetahString,
789 resource_filter: CheetahString,
790 timeout_millis: u64,
791 ) -> ClientResult<Vec<AclInfo>> {
792 let request_header = ListAclRequestHeader {
793 subject_filter,
794 resource_filter,
795 };
796 let request = self.create_request_command(RequestCode::AuthListAcl, request_header);
797
798 let outcome = self
799 .remoting_client
800 .invoke_request(Some(&broker_address), request, timeout_millis)
801 .await;
802 let response = match outcome {
803 Ok(OutboundRequestOutcome::Response(response)) => response,
804 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
805 return Err(admin_request_error("list_acl", RetryInput::Rejected(rejection)));
806 }
807 Ok(OutboundRequestOutcome::Contract(contract)) => {
808 return Err(admin_request_error("list_acl", RetryInput::Contract(contract)));
809 }
810 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
811 };
812
813 match ResponseCode::from(response.code()) {
814 ResponseCode::Success => {
815 if let Some(body) = response.get_body() {
816 Vec::<AclInfo>::decode(body.as_ref()).map_err(ClientError::from)
817 } else {
818 Ok(Vec::new())
819 }
820 }
821 _ => Err(mq_client_err!(
822 response.code(),
823 response.remark().map_or("".to_string(), |s| s.to_string())
824 )),
825 }
826 }
827
828 pub(crate) async fn get_acl(
829 &self,
830 broker_address: CheetahString,
831 subject: CheetahString,
832 timeout_millis: u64,
833 ) -> ClientResult<AclInfo> {
834 let request = get_acl_request(&self.command_factory, subject);
835 let outcome = self
836 .remoting_client
837 .invoke_request(Some(&broker_address), request, timeout_millis)
838 .await;
839 let response = match outcome {
840 Ok(OutboundRequestOutcome::Response(response)) => response,
841 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
842 return Err(admin_request_error("get_acl", RetryInput::Rejected(rejection)));
843 }
844 Ok(OutboundRequestOutcome::Contract(contract)) => {
845 return Err(admin_request_error("get_acl", RetryInput::Contract(contract)));
846 }
847 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
848 };
849
850 match ResponseCode::from(response.code()) {
851 ResponseCode::Success => {
852 let body = response
853 .get_body()
854 .ok_or_else(|| mq_client_err!("get_acl response body is empty".to_string()))?;
855 AclInfo::decode(body.as_ref()).map_err(ClientError::from)
856 }
857 _ => Err(mq_client_err!(
858 response.code(),
859 response.remark().map_or("".to_string(), |s| s.to_string())
860 )),
861 }
862 }
863
864 pub(crate) async fn get_user(
865 &self,
866 broker_address: CheetahString,
867 username: CheetahString,
868 timeout_millis: u64,
869 ) -> ClientResult<Option<UserInfo>> {
870 let request_header = GetUserRequestHeader {
871 username: username.clone(),
872 };
873 let request = self.create_request_command(RequestCode::AuthGetUser, request_header);
874
875 let outcome = self
876 .remoting_client
877 .invoke_request(Some(&broker_address), request, timeout_millis)
878 .await;
879 let response = match outcome {
880 Ok(OutboundRequestOutcome::Response(response)) => response,
881 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
882 return Err(admin_request_error("get_user", RetryInput::Rejected(rejection)));
883 }
884 Ok(OutboundRequestOutcome::Contract(contract)) => {
885 return Err(admin_request_error("get_user", RetryInput::Contract(contract)));
886 }
887 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
888 };
889
890 match ResponseCode::from(response.code()) {
891 ResponseCode::Success => {
892 let _body = response.get_body();
893 if let Some(body) = response.get_body() {
894 let user_info = UserInfo::decode(body)?;
895 Ok(Some(user_info))
896 } else {
897 Ok(None)
898 }
899 }
900 _ => Err(mq_client_err!(
901 response.code(),
902 response.remark().map(|s| s.to_string()).unwrap_or_default()
903 )),
904 }
905 }
906
907 pub(crate) async fn list_users(
908 &self,
909 broker_address: CheetahString,
910 filter: CheetahString,
911 timeout_millis: u64,
912 ) -> ClientResult<Vec<UserInfo>> {
913 let request_header = ListUsersRequestHeader { filter };
914 let request = self.create_request_command(RequestCode::AuthListUsers, request_header);
915
916 let outcome = self
917 .remoting_client
918 .invoke_request(Some(&broker_address), request, timeout_millis)
919 .await;
920 let response = match outcome {
921 Ok(OutboundRequestOutcome::Response(response)) => response,
922 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
923 return Err(admin_request_error("list_users", RetryInput::Rejected(rejection)));
924 }
925 Ok(OutboundRequestOutcome::Contract(contract)) => {
926 return Err(admin_request_error("list_users", RetryInput::Contract(contract)));
927 }
928 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
929 };
930
931 match ResponseCode::from(response.code()) {
932 ResponseCode::Success => {
933 if let Some(body) = response.get_body() {
934 Vec::<UserInfo>::decode(body.as_ref()).map_err(ClientError::from)
935 } else {
936 Ok(Vec::new())
937 }
938 }
939 _ => Err(mq_client_err!(
940 response.code(),
941 response.remark().map_or("".to_string(), |s| s.to_string())
942 )),
943 }
944 }
945
946 pub(crate) async fn list_user(
947 &self,
948 broker_address: CheetahString,
949 filter: CheetahString,
950 timeout_millis: u64,
951 ) -> ClientResult<Vec<UserInfo>> {
952 self.list_users(broker_address, filter, timeout_millis).await
953 }
954
955 #[cfg(feature = "admin-mutation")]
956 pub(crate) async fn delete_user(
957 &self,
958 broker_address: CheetahString,
959 username: CheetahString,
960 timeout_millis: u64,
961 ) -> ClientResult<()> {
962 let mut request_header = DeleteUserRequestHeader::default();
963 request_header.set_username(username);
964 let request = self.create_request_command(RequestCode::AuthDeleteUser, request_header);
965
966 let outcome = self
967 .remoting_client
968 .invoke_request(Some(&broker_address), request.clone(), timeout_millis)
969 .await;
970 let response = match outcome {
971 Ok(OutboundRequestOutcome::Response(response)) => response,
972 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
973 return Err(admin_request_error("delete_user", RetryInput::Rejected(rejection)));
974 }
975 Ok(OutboundRequestOutcome::Contract(contract)) => {
976 return Err(admin_request_error("delete_user", RetryInput::Contract(contract)));
977 }
978 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
979 };
980
981 let mut err_response = None;
982 match ResponseCode::from(response.code()) {
983 ResponseCode::Success => {}
984 _ => err_response = Some(response),
985 }
986
987 if let Some(err_response) = err_response {
988 return Err(mq_client_err!(
989 err_response.code(),
990 err_response.remark().map_or("".to_string(), |s| s.to_string())
991 ));
992 }
993 Ok(())
994 }
995
996 #[cfg(feature = "admin-mutation")]
997 pub(crate) async fn update_name_server_config(
998 &self,
999 properties: HashMap<CheetahString, CheetahString>,
1000 special_name_servers: Option<Vec<CheetahString>>,
1001 timeout_millis: u64,
1002 ) -> ClientResult<()> {
1003 let body = mix_all::properties_to_string(&properties);
1004 if body.is_empty() {
1005 return Ok(());
1006 }
1007 let invoke_name_servers = if let Some(name_servers) = special_name_servers {
1008 if !name_servers.is_empty() {
1009 name_servers
1010 } else {
1011 self.get_name_server_address_list()
1012 }
1013 } else {
1014 self.get_name_server_address_list()
1015 };
1016 if invoke_name_servers.is_empty() {
1017 return Ok(());
1018 }
1019 let empty_header = EmptyHeader {};
1020 let mut request = self.create_request_command(RequestCode::UpdateNamesrvConfig, empty_header);
1021
1022 request = request.set_body(body.to_string());
1023 let mut err_response = None;
1024 for name_srv_addr in invoke_name_servers {
1025 let outcome = self
1026 .remoting_client
1027 .invoke_request(Some(&name_srv_addr), request.clone(), timeout_millis)
1028 .await;
1029 let response = match outcome {
1030 Ok(OutboundRequestOutcome::Response(response)) => response,
1031 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1032 return Err(admin_request_error(
1033 "update_name_server_config",
1034 RetryInput::Rejected(rejection),
1035 ));
1036 }
1037 Ok(OutboundRequestOutcome::Contract(contract)) => {
1038 return Err(admin_request_error(
1039 "update_name_server_config",
1040 RetryInput::Contract(contract),
1041 ));
1042 }
1043 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1044 };
1045 match ResponseCode::from(response.code()) {
1046 ResponseCode::Success => {}
1047 _ => err_response = Some(response),
1048 }
1049 }
1050
1051 if let Some(err_response) = err_response {
1052 return Err(mq_client_err!(
1053 err_response.code(),
1054 err_response.remark().map_or("".to_string(), |s| s.to_string())
1055 ));
1056 }
1057 Ok(())
1058 }
1059
1060 #[cfg(feature = "admin-mutation")]
1061 pub(crate) async fn add_write_perm_of_broker(
1062 &self,
1063 namesrv_addr: CheetahString,
1064 broker_name: CheetahString,
1065 timeout_millis: u64,
1066 ) -> ClientResult<i32> {
1067 let request_header = AddWritePermOfBrokerRequestHeader::new(broker_name);
1068 let request = self.create_request_command(RequestCode::AddWritePermOfBroker, request_header);
1069
1070 let outcome = self
1071 .remoting_client
1072 .invoke_request(Some(&namesrv_addr), request, timeout_millis)
1073 .await;
1074 let response = match outcome {
1075 Ok(OutboundRequestOutcome::Response(response)) => response,
1076 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1077 return Err(admin_request_error(
1078 "add_write_perm_of_broker",
1079 RetryInput::Rejected(rejection),
1080 ));
1081 }
1082 Ok(OutboundRequestOutcome::Contract(contract)) => {
1083 return Err(admin_request_error(
1084 "add_write_perm_of_broker",
1085 RetryInput::Contract(contract),
1086 ));
1087 }
1088 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1089 };
1090 if ResponseCode::from(response.code()) == ResponseCode::Success {
1091 let request_header = response.decode_command_custom_header_fast::<AddWritePermOfBrokerResponseHeader>()?;
1092 return Ok(request_header.get_add_topic_count());
1093 }
1094 Err(mq_client_err!(
1095 response.code(),
1096 response.remark().map_or("".to_string(), |s| s.to_string())
1097 ))
1098 }
1099
1100 #[cfg(feature = "admin-mutation")]
1101 pub(crate) async fn wipe_write_perm_of_broker(
1102 &self,
1103 namesrv_addr: CheetahString,
1104 broker_name: CheetahString,
1105 timeout_millis: u64,
1106 ) -> ClientResult<i32> {
1107 let request_header = WipeWritePermOfBrokerRequestHeader::new(broker_name);
1108 let request = self.create_request_command(RequestCode::WipeWritePermOfBroker, request_header);
1109
1110 let outcome = self
1111 .remoting_client
1112 .invoke_request(Some(&namesrv_addr), request, timeout_millis)
1113 .await;
1114 let response = match outcome {
1115 Ok(OutboundRequestOutcome::Response(response)) => response,
1116 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1117 return Err(admin_request_error(
1118 "wipe_write_perm_of_broker",
1119 RetryInput::Rejected(rejection),
1120 ));
1121 }
1122 Ok(OutboundRequestOutcome::Contract(contract)) => {
1123 return Err(admin_request_error(
1124 "wipe_write_perm_of_broker",
1125 RetryInput::Contract(contract),
1126 ));
1127 }
1128 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1129 };
1130 if ResponseCode::from(response.code()) == ResponseCode::Success {
1131 let request_header = response.decode_command_custom_header_fast::<WipeWritePermOfBrokerResponseHeader>()?;
1132 return Ok(request_header.get_wipe_topic_count());
1133 }
1134 Err(mq_client_err!(
1135 response.code(),
1136 response.remark().map_or("".to_string(), |s| s.to_string())
1137 ))
1138 }
1139
1140 pub(crate) async fn get_broker_cluster_info(&self, timeout_millis: u64) -> ClientResult<ClusterInfo> {
1141 let request = self.create_request_command(RequestCode::GetBrokerClusterInfo, EmptyHeader {});
1142 let outcome = self.remoting_client.invoke_request(None, request, timeout_millis).await;
1143 let response = match outcome {
1144 Ok(OutboundRequestOutcome::Response(response)) => response,
1145 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1146 return Err(admin_request_error(
1147 "get_broker_cluster_info",
1148 RetryInput::Rejected(rejection),
1149 ));
1150 }
1151 Ok(OutboundRequestOutcome::Contract(contract)) => {
1152 return Err(admin_request_error(
1153 "get_broker_cluster_info",
1154 RetryInput::Contract(contract),
1155 ));
1156 }
1157 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1158 };
1159 if ResponseCode::from(response.code()) == ResponseCode::Success {
1160 if let Some(body) = response.get_body() {
1161 return ClusterInfo::decode(body.as_ref()).map_err(ClientError::from);
1162 }
1163 }
1164 Err(mq_client_err!(
1165 response.code(),
1166 response.remark().map_or("".to_string(), |s| s.to_string())
1167 ))
1168 }
1169
1170 pub(crate) async fn get_broker_runtime_info(
1171 &self,
1172 addr: &CheetahString,
1173 timeout_millis: u64,
1174 ) -> ClientResult<rocketmq_protocol::protocol::body::kv_table::KVTable> {
1175 let request = self.create_request_command(RequestCode::GetBrokerRuntimeInfo, EmptyHeader {});
1176 let outcome = self
1177 .remoting_client
1178 .invoke_request(Some(addr), request, timeout_millis)
1179 .await;
1180 let response = match outcome {
1181 Ok(OutboundRequestOutcome::Response(response)) => response,
1182 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1183 return Err(admin_request_error(
1184 "get_broker_runtime_info",
1185 RetryInput::Rejected(rejection),
1186 ));
1187 }
1188 Ok(OutboundRequestOutcome::Contract(contract)) => {
1189 return Err(admin_request_error(
1190 "get_broker_runtime_info",
1191 RetryInput::Contract(contract),
1192 ));
1193 }
1194 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1195 };
1196 if ResponseCode::from(response.code()) == ResponseCode::Success {
1197 if let Some(body) = response.get_body() {
1198 return rocketmq_protocol::protocol::body::kv_table::KVTable::decode(body.as_ref())
1199 .map_err(ClientError::from);
1200 }
1201 }
1202 Err(mq_client_err!(
1203 response.code(),
1204 response.remark().map_or("".to_string(), |s| s.to_string())
1205 ))
1206 }
1207
1208 #[cfg(feature = "admin-mutation")]
1209 pub(crate) async fn sync_broker_member_group(
1210 &self,
1211 cluster_name: &CheetahString,
1212 broker_name: &CheetahString,
1213 is_compatible_with_old_name_srv: bool,
1214 ) -> ClientResult<Option<BrokerMemberGroup>> {
1215 if is_compatible_with_old_name_srv {
1216 self.get_broker_member_group_compatible(cluster_name, broker_name).await
1217 } else {
1218 self.get_broker_member_group(cluster_name, broker_name).await
1219 }
1220 }
1221
1222 pub(super) async fn get_broker_member_group(
1223 &self,
1224 cluster_name: &CheetahString,
1225 broker_name: &CheetahString,
1226 ) -> ClientResult<Option<BrokerMemberGroup>> {
1227 let request_header = GetBrokerMemberGroupRequestHeader::new(cluster_name.clone(), broker_name.clone());
1228 let request = self.create_request_command(RequestCode::GetBrokerMemberGroup, request_header);
1229 let outcome = self.remoting_client.invoke_request(None, request, 3000).await;
1230 let mut response = match outcome {
1231 Ok(OutboundRequestOutcome::Response(response)) => response,
1232 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1233 return Err(admin_request_error(
1234 "get_broker_member_group",
1235 RetryInput::Rejected(rejection),
1236 ));
1237 }
1238 Ok(OutboundRequestOutcome::Contract(contract)) => {
1239 return Err(admin_request_error(
1240 "get_broker_member_group",
1241 RetryInput::Contract(contract),
1242 ));
1243 }
1244 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1245 };
1246 if ResponseCode::from(response.code()) == ResponseCode::Success {
1247 if let Some(body) = response.take_body() {
1248 let response_body = GetBrokerMemberGroupResponseBody::decode(body.as_ref())?;
1249 return Ok(Some(
1250 response_body
1251 .broker_member_group
1252 .unwrap_or_else(|| empty_broker_member_group(cluster_name, broker_name)),
1253 ));
1254 }
1255 }
1256 Ok(Some(empty_broker_member_group(cluster_name, broker_name)))
1257 }
1258
1259 pub(super) async fn get_broker_member_group_compatible(
1260 &self,
1261 cluster_name: &CheetahString,
1262 broker_name: &CheetahString,
1263 ) -> ClientResult<Option<BrokerMemberGroup>> {
1264 let request_header = GetRouteInfoRequestHeader {
1265 topic: CheetahString::from_string(format!(
1266 "{}{}",
1267 TopicValidator::SYNC_BROKER_MEMBER_GROUP_PREFIX,
1268 broker_name
1269 )),
1270 ..Default::default()
1271 };
1272 let request = self.create_request_command(RequestCode::GetRouteinfoByTopic, request_header);
1273 let outcome = self.remoting_client.invoke_request(None, request, 3000).await;
1274 let mut response = match outcome {
1275 Ok(OutboundRequestOutcome::Response(response)) => response,
1276 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1277 return Err(admin_request_error(
1278 "get_broker_member_group_compatible",
1279 RetryInput::Rejected(rejection),
1280 ));
1281 }
1282 Ok(OutboundRequestOutcome::Contract(contract)) => {
1283 return Err(admin_request_error(
1284 "get_broker_member_group_compatible",
1285 RetryInput::Contract(contract),
1286 ));
1287 }
1288 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1289 };
1290 if ResponseCode::from(response.code()) == ResponseCode::Success {
1291 if let Some(body) = response.take_body() {
1292 let topic_route_data = TopicRouteData::decode(body.as_ref())?;
1293 return Ok(Some(broker_member_group_from_route_data(
1294 cluster_name,
1295 broker_name,
1296 &topic_route_data,
1297 )));
1298 }
1299 }
1300 Ok(Some(empty_broker_member_group(cluster_name, broker_name)))
1301 }
1302
1303 pub(crate) async fn get_broker_lite_info(
1304 &self,
1305 addr: &CheetahString,
1306 timeout_millis: u64,
1307 ) -> ClientResult<GetBrokerLiteInfoResponseBody> {
1308 let request = self.create_request_command(RequestCode::GetBrokerLiteInfo, EmptyHeader {});
1309 let outcome = self
1310 .remoting_client
1311 .invoke_request(Some(addr), request, timeout_millis)
1312 .await;
1313 let response = match outcome {
1314 Ok(OutboundRequestOutcome::Response(response)) => response,
1315 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1316 return Err(admin_request_error(
1317 "get_broker_lite_info",
1318 RetryInput::Rejected(rejection),
1319 ));
1320 }
1321 Ok(OutboundRequestOutcome::Contract(contract)) => {
1322 return Err(admin_request_error(
1323 "get_broker_lite_info",
1324 RetryInput::Contract(contract),
1325 ));
1326 }
1327 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1328 };
1329 if ResponseCode::from(response.code()) == ResponseCode::Success {
1330 if let Some(body) = response.get_body() {
1331 return GetBrokerLiteInfoResponseBody::decode(body.as_ref()).map_err(ClientError::from);
1332 }
1333 }
1334 Err(mq_client_err!(
1335 response.code(),
1336 response.remark().map_or("".to_string(), |s| s.to_string())
1337 ))
1338 }
1339
1340 pub(crate) async fn check_rocksdb_cq_write_progress(
1341 &self,
1342 addr: &CheetahString,
1343 topic: CheetahString,
1344 check_store_time: i64,
1345 timeout_millis: u64,
1346 ) -> ClientResult<CheckRocksdbCqWriteResult> {
1347 let request_header = CheckRocksdbCqWriteProgressRequestHeader {
1348 topic,
1349 check_store_time,
1350 rpc: None,
1351 };
1352 let request = self.create_request_command(RequestCode::CheckRocksdbCqWriteProgress, request_header);
1353 let outcome = self
1354 .remoting_client
1355 .invoke_request(Some(addr), request, timeout_millis)
1356 .await;
1357 let response = match outcome {
1358 Ok(OutboundRequestOutcome::Response(response)) => response,
1359 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1360 return Err(admin_request_error(
1361 "check_rocksdb_cq_write_progress",
1362 RetryInput::Rejected(rejection),
1363 ));
1364 }
1365 Ok(OutboundRequestOutcome::Contract(contract)) => {
1366 return Err(admin_request_error(
1367 "check_rocksdb_cq_write_progress",
1368 RetryInput::Contract(contract),
1369 ));
1370 }
1371 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1372 };
1373 if ResponseCode::from(response.code()) == ResponseCode::Success {
1374 if let Some(body) = response.get_body() {
1375 let result: CheckRocksdbCqWriteResult = serde_json::from_slice(body.as_ref()).map_err(|e| {
1376 mq_client_err!(-1, format!("Failed to deserialize CheckRocksdbCqWriteResult: {}", e))
1377 })?;
1378 return Ok(result);
1379 }
1380 }
1381 Err(mq_client_err!(
1382 response.code(),
1383 response.remark().map_or("".to_string(), |s| s.to_string())
1384 ))
1385 }
1386
1387 pub(crate) async fn query_consume_queue(
1388 &self,
1389 addr: &CheetahString,
1390 topic: CheetahString,
1391 queue_id: i32,
1392 index: i64,
1393 count: i32,
1394 consumer_group: CheetahString,
1395 timeout_millis: u64,
1396 ) -> ClientResult<QueryConsumeQueueResponseBody> {
1397 let request_header = QueryConsumeQueueRequestHeader {
1398 topic,
1399 queue_id,
1400 index,
1401 count,
1402 consumer_group: if consumer_group.is_empty() {
1403 None
1404 } else {
1405 Some(consumer_group)
1406 },
1407 topic_request_header: None,
1408 };
1409 let request = self.create_request_command(RequestCode::QueryConsumeQueue, request_header);
1410 let outcome = self
1411 .remoting_client
1412 .invoke_request(Some(addr), request, timeout_millis)
1413 .await;
1414 let response = match outcome {
1415 Ok(OutboundRequestOutcome::Response(response)) => response,
1416 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1417 return Err(admin_request_error(
1418 "query_consume_queue",
1419 RetryInput::Rejected(rejection),
1420 ));
1421 }
1422 Ok(OutboundRequestOutcome::Contract(contract)) => {
1423 return Err(admin_request_error(
1424 "query_consume_queue",
1425 RetryInput::Contract(contract),
1426 ));
1427 }
1428 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1429 };
1430 if ResponseCode::from(response.code()) == ResponseCode::Success {
1431 if let Some(body) = response.get_body() {
1432 let result: QueryConsumeQueueResponseBody = serde_json::from_slice(body.as_ref()).map_err(|e| {
1433 mq_client_err!(
1434 -1,
1435 format!("Failed to deserialize QueryConsumeQueueResponseBody: {}", e)
1436 )
1437 })?;
1438 return Ok(result);
1439 }
1440 }
1441 Err(mq_client_err!(
1442 response.code(),
1443 response.remark().map_or("".to_string(), |s| s.to_string())
1444 ))
1445 }
1446
1447 pub(crate) async fn get_lite_group_info(
1448 &self,
1449 addr: &CheetahString,
1450 group: CheetahString,
1451 lite_topic: CheetahString,
1452 top_k: i32,
1453 timeout_millis: u64,
1454 ) -> ClientResult<GetLiteGroupInfoResponseBody> {
1455 let request_header = GetLiteGroupInfoRequestHeader {
1456 group,
1457 lite_topic,
1458 top_k,
1459 rpc: None,
1460 };
1461 let request = self.create_request_command(RequestCode::GetLiteGroupInfo, request_header);
1462 let outcome = self
1463 .remoting_client
1464 .invoke_request(Some(addr), request, timeout_millis)
1465 .await;
1466 let response = match outcome {
1467 Ok(OutboundRequestOutcome::Response(response)) => response,
1468 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1469 return Err(admin_request_error(
1470 "get_lite_group_info",
1471 RetryInput::Rejected(rejection),
1472 ));
1473 }
1474 Ok(OutboundRequestOutcome::Contract(contract)) => {
1475 return Err(admin_request_error(
1476 "get_lite_group_info",
1477 RetryInput::Contract(contract),
1478 ));
1479 }
1480 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1481 };
1482 if ResponseCode::from(response.code()) == ResponseCode::Success {
1483 if let Some(body) = response.get_body() {
1484 return GetLiteGroupInfoResponseBody::decode(body.as_ref()).map_err(ClientError::from);
1485 }
1486 }
1487 Err(mq_client_err!(
1488 response.code(),
1489 response.remark().map_or("".to_string(), |s| s.to_string())
1490 ))
1491 }
1492
1493 pub(crate) async fn get_lite_client_info(
1494 &self,
1495 addr: &CheetahString,
1496 parent_topic: CheetahString,
1497 group: CheetahString,
1498 client_id: CheetahString,
1499 timeout_millis: u64,
1500 ) -> ClientResult<GetLiteClientInfoResponseBody> {
1501 let request_header = GetLiteClientInfoRequestHeader {
1502 parent_topic: Some(parent_topic),
1503 group: Some(group),
1504 client_id: Some(client_id),
1505 max_count: 1000,
1506 };
1507 let request = self.create_request_command(RequestCode::GetLiteClientInfo, request_header);
1508 let outcome = self
1509 .remoting_client
1510 .invoke_request(Some(addr), request, timeout_millis)
1511 .await;
1512 let response = match outcome {
1513 Ok(OutboundRequestOutcome::Response(response)) => response,
1514 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1515 return Err(admin_request_error(
1516 "get_lite_client_info",
1517 RetryInput::Rejected(rejection),
1518 ));
1519 }
1520 Ok(OutboundRequestOutcome::Contract(contract)) => {
1521 return Err(admin_request_error(
1522 "get_lite_client_info",
1523 RetryInput::Contract(contract),
1524 ));
1525 }
1526 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1527 };
1528 if ResponseCode::from(response.code()) == ResponseCode::Success {
1529 if let Some(body) = response.get_body() {
1530 return GetLiteClientInfoResponseBody::decode(body.as_ref()).map_err(ClientError::from);
1531 }
1532 }
1533 Err(mq_client_err!(
1534 response.code(),
1535 response.remark().map_or("".to_string(), |s| s.to_string())
1536 ))
1537 }
1538
1539 #[cfg(feature = "admin-mutation")]
1540 pub(crate) async fn trigger_lite_dispatch(
1541 &self,
1542 addr: &CheetahString,
1543 group: CheetahString,
1544 client_id: CheetahString,
1545 timeout_millis: u64,
1546 ) -> ClientResult<()> {
1547 let request_header = TriggerLiteDispatchRequestHeader {
1548 group,
1549 client_id: if client_id.is_empty() { None } else { Some(client_id) },
1550 };
1551 let request = self.create_request_command(RequestCode::TriggerLiteDispatch, request_header);
1552 let outcome = self
1553 .remoting_client
1554 .invoke_request(Some(addr), request, timeout_millis)
1555 .await;
1556 let response = match outcome {
1557 Ok(OutboundRequestOutcome::Response(response)) => response,
1558 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1559 return Err(admin_request_error(
1560 "trigger_lite_dispatch",
1561 RetryInput::Rejected(rejection),
1562 ));
1563 }
1564 Ok(OutboundRequestOutcome::Contract(contract)) => {
1565 return Err(admin_request_error(
1566 "trigger_lite_dispatch",
1567 RetryInput::Contract(contract),
1568 ));
1569 }
1570 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1571 };
1572 match ResponseCode::from(response.code()) {
1573 ResponseCode::Success => Ok(()),
1574 _ => Err(mq_client_err!(
1575 response.code(),
1576 response.remark().map_or("".to_string(), |s| s.to_string())
1577 )),
1578 }
1579 }
1580
1581 #[cfg(feature = "admin-mutation")]
1582 pub(crate) async fn sync_lite_subscription(
1583 &self,
1584 broker_addr: &CheetahString,
1585 lite_subscription_dto: LiteSubscriptionDTO,
1586 timeout_millis: u64,
1587 ) -> ClientResult<()> {
1588 let request = lite_subscription_ctl_request(&self.command_factory, lite_subscription_dto)?;
1589 let outcome = self
1590 .remoting_client
1591 .invoke_request(Some(broker_addr), request, timeout_millis)
1592 .await;
1593 let response = match outcome {
1594 Ok(OutboundRequestOutcome::Response(response)) => response,
1595 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1596 return Err(admin_request_error(
1597 "sync_lite_subscription",
1598 RetryInput::Rejected(rejection),
1599 ));
1600 }
1601 Ok(OutboundRequestOutcome::Contract(contract)) => {
1602 return Err(admin_request_error(
1603 "sync_lite_subscription",
1604 RetryInput::Contract(contract),
1605 ));
1606 }
1607 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1608 };
1609
1610 match ResponseCode::from(response.code()) {
1611 ResponseCode::Success => Ok(()),
1612 _ => Err(client_broker_err!(
1613 response.code(),
1614 response.remark().map_or_else(String::new, |remark| remark.to_string()),
1615 broker_addr
1616 )),
1617 }
1618 }
1619
1620 #[cfg(feature = "admin-mutation")]
1621 pub async fn sync_lite_subscription_async(
1622 &self,
1623 broker_addr: &CheetahString,
1624 lite_subscription_dto: LiteSubscriptionDTO,
1625 timeout_millis: u64,
1626 ) -> ClientResult<()> {
1627 self.sync_lite_subscription(broker_addr, lite_subscription_dto, timeout_millis)
1628 .await
1629 }
1630
1631 pub(crate) async fn get_lite_topic_info(
1632 &self,
1633 addr: &CheetahString,
1634 parent_topic: &CheetahString,
1635 lite_topic: &CheetahString,
1636 timeout_millis: u64,
1637 ) -> ClientResult<GetLiteTopicInfoResponseBody> {
1638 let request_header = GetLiteTopicInfoRequestHeader {
1639 parent_topic: parent_topic.clone(),
1640 lite_topic: lite_topic.clone(),
1641 };
1642 let request = self.create_request_command(RequestCode::GetLiteTopicInfo, request_header);
1643 let outcome = self
1644 .remoting_client
1645 .invoke_request(Some(addr), request, timeout_millis)
1646 .await;
1647 let response = match outcome {
1648 Ok(OutboundRequestOutcome::Response(response)) => response,
1649 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1650 return Err(admin_request_error(
1651 "get_lite_topic_info",
1652 RetryInput::Rejected(rejection),
1653 ));
1654 }
1655 Ok(OutboundRequestOutcome::Contract(contract)) => {
1656 return Err(admin_request_error(
1657 "get_lite_topic_info",
1658 RetryInput::Contract(contract),
1659 ));
1660 }
1661 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1662 };
1663 if ResponseCode::from(response.code()) == ResponseCode::Success {
1664 if let Some(body) = response.get_body() {
1665 return GetLiteTopicInfoResponseBody::decode(body.as_ref()).map_err(ClientError::from);
1666 }
1667 }
1668 Err(mq_client_err!(
1669 response.code(),
1670 response.remark().map_or("".to_string(), |s| s.to_string())
1671 ))
1672 }
1673 pub(crate) async fn get_parent_topic_info(
1674 &self,
1675 addr: &CheetahString,
1676 topic: CheetahString,
1677 timeout_millis: u64,
1678 ) -> ClientResult<GetParentTopicInfoResponseBody> {
1679 let request_header = GetParentTopicInfoRequestHeader { topic, rpc: None };
1680 let request = self.create_request_command(RequestCode::GetParentTopicInfo, request_header);
1681 let outcome = self
1682 .remoting_client
1683 .invoke_request(Some(addr), request, timeout_millis)
1684 .await;
1685 let response = match outcome {
1686 Ok(OutboundRequestOutcome::Response(response)) => response,
1687 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1688 return Err(admin_request_error(
1689 "get_parent_topic_info",
1690 RetryInput::Rejected(rejection),
1691 ));
1692 }
1693 Ok(OutboundRequestOutcome::Contract(contract)) => {
1694 return Err(admin_request_error(
1695 "get_parent_topic_info",
1696 RetryInput::Contract(contract),
1697 ));
1698 }
1699 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1700 };
1701 if ResponseCode::from(response.code()) == ResponseCode::Success {
1702 if let Some(body) = response.get_body() {
1703 return GetParentTopicInfoResponseBody::decode(body.as_ref()).map_err(ClientError::from);
1704 }
1705 }
1706 Err(mq_client_err!(
1707 response.code(),
1708 response.remark().map_or("".to_string(), |s| s.to_string())
1709 ))
1710 }
1711
1712 #[cfg(feature = "admin-mutation")]
1713 pub async fn delete_subscription_group(
1714 &self,
1715 addr: &CheetahString,
1716 group_name: CheetahString,
1717 clean_offset: bool,
1718 timeout_millis: u64,
1719 ) -> ClientResult<()> {
1720 let request_header = DeleteSubscriptionGroupRequestHeader {
1721 group_name,
1722 clean_offset,
1723 rpc_request_header: None,
1724 };
1725
1726 let request = self.create_request_command(RequestCode::DeleteSubscriptionGroup, request_header);
1727
1728 let outcome = self
1729 .remoting_client
1730 .invoke_request(Some(addr), request, timeout_millis)
1731 .await;
1732 let response = match outcome {
1733 Ok(OutboundRequestOutcome::Response(response)) => response,
1734 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1735 return Err(admin_request_error(
1736 "delete_subscription_group",
1737 RetryInput::Rejected(rejection),
1738 ));
1739 }
1740 Ok(OutboundRequestOutcome::Contract(contract)) => {
1741 return Err(admin_request_error(
1742 "delete_subscription_group",
1743 RetryInput::Contract(contract),
1744 ));
1745 }
1746 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1747 };
1748
1749 if ResponseCode::from(response.code()) == ResponseCode::Success {
1750 return Ok(());
1751 }
1752
1753 Err(mq_client_err!(
1754 response.code(),
1755 response.remark().map_or("".to_string(), |s| s.to_string())
1756 ))
1757 }
1758
1759 #[cfg(feature = "admin-mutation")]
1761 pub async fn delete_subscription_group_list(
1762 &self,
1763 addr: &CheetahString,
1764 group_name_list: Vec<CheetahString>,
1765 clean_offset: bool,
1766 timeout_millis: u64,
1767 ) -> ClientResult<()> {
1768 let request = delete_subscription_group_list_request(&self.command_factory, group_name_list, clean_offset)?;
1769 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
1770 let outcome = self
1771 .remoting_client
1772 .invoke_request(Some(&broker_addr), request, timeout_millis)
1773 .await;
1774 let response = match outcome {
1775 Ok(OutboundRequestOutcome::Response(response)) => response,
1776 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1777 return Err(admin_request_error(
1778 "delete_subscription_group_list",
1779 RetryInput::Rejected(rejection),
1780 ));
1781 }
1782 Ok(OutboundRequestOutcome::Contract(contract)) => {
1783 return Err(admin_request_error(
1784 "delete_subscription_group_list",
1785 RetryInput::Contract(contract),
1786 ));
1787 }
1788 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1789 };
1790
1791 match ResponseCode::from(response.code()) {
1792 ResponseCode::Success => Ok(()),
1793 _ => Err(mq_client_err!(
1794 response.code(),
1795 response.remark().map_or_else(String::new, |remark| remark.to_string())
1796 )),
1797 }
1798 }
1799
1800 #[cfg(feature = "admin-mutation")]
1801 pub async fn reset_master_flush_offset(
1802 &self,
1803 broker_addr: &CheetahString,
1804 master_flush_offset: i64,
1805 ) -> ClientResult<()> {
1806 let request_header = ResetMasterFlushOffsetHeader {
1807 master_flush_offset: Some(master_flush_offset),
1808 };
1809
1810 let request = self.create_request_command(RequestCode::ResetMasterFlushOffset, request_header);
1811
1812 let outcome = self
1813 .remoting_client
1814 .invoke_request(Some(broker_addr), request, 3000)
1815 .await;
1816 let response = match outcome {
1817 Ok(OutboundRequestOutcome::Response(response)) => response,
1818 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1819 return Err(admin_request_error(
1820 "reset_master_flush_offset",
1821 RetryInput::Rejected(rejection),
1822 ));
1823 }
1824 Ok(OutboundRequestOutcome::Contract(contract)) => {
1825 return Err(admin_request_error(
1826 "reset_master_flush_offset",
1827 RetryInput::Contract(contract),
1828 ));
1829 }
1830 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1831 };
1832
1833 if ResponseCode::from(response.code()) == ResponseCode::Success {
1834 return Ok(());
1835 }
1836
1837 Err(mq_client_err!(
1838 response.code(),
1839 response.remark().map_or("".to_string(), |s| s.to_string())
1840 ))
1841 }
1842
1843 pub(super) async fn get_topic_list_from_name_server_by_code(
1844 &self,
1845 request_code: RequestCode,
1846 timeout_millis: u64,
1847 ) -> ClientResult<TopicList> {
1848 let request = self.create_request_command(request_code, EmptyHeader {});
1849 let outcome = self.remoting_client.invoke_request(None, request, timeout_millis).await;
1850 let response = match outcome {
1851 Ok(OutboundRequestOutcome::Response(response)) => response,
1852 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1853 return Err(admin_request_error(
1854 "get_topic_list_from_name_server_by_code",
1855 RetryInput::Rejected(rejection),
1856 ));
1857 }
1858 Ok(OutboundRequestOutcome::Contract(contract)) => {
1859 return Err(admin_request_error(
1860 "get_topic_list_from_name_server_by_code",
1861 RetryInput::Contract(contract),
1862 ));
1863 }
1864 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1865 };
1866 if ResponseCode::from(response.code()) == ResponseCode::Success {
1867 if let Some(body) = response.get_body() {
1868 return TopicList::decode(body.as_ref()).map_err(ClientError::from);
1869 }
1870 }
1871 Err(mq_client_err!(
1872 response.code(),
1873 response.remark().map_or("".to_string(), |s| s.to_string())
1874 ))
1875 }
1876
1877 pub(crate) async fn get_topic_list_from_name_server(&self, timeout_millis: u64) -> ClientResult<TopicList> {
1878 self.get_topic_list_from_name_server_by_code(RequestCode::GetAllTopicListFromNameserver, timeout_millis)
1879 .await
1880 }
1881
1882 pub(crate) async fn get_all_topic_list_from_name_server(&self, timeout_millis: u64) -> ClientResult<TopicList> {
1883 self.get_topic_list_from_name_server(timeout_millis).await
1884 }
1885
1886 pub(crate) async fn get_system_topic_list(&self, timeout_millis: u64) -> ClientResult<TopicList> {
1887 let mut topic_list = self
1888 .get_topic_list_from_name_server_by_code(RequestCode::GetSystemTopicListFromNs, timeout_millis)
1889 .await?;
1890 merge_system_topic_list_from_broker(self, &mut topic_list, timeout_millis).await?;
1891 Ok(topic_list)
1892 }
1893
1894 pub(crate) async fn get_unit_topic_list(
1895 &self,
1896 contain_retry: bool,
1897 timeout_millis: u64,
1898 ) -> ClientResult<TopicList> {
1899 let mut topic_list = self
1900 .get_topic_list_from_name_server_by_code(RequestCode::GetUnitTopicList, timeout_millis)
1901 .await?;
1902 filter_retry_topics_like_java(&mut topic_list, contain_retry);
1903 Ok(topic_list)
1904 }
1905
1906 pub(crate) async fn get_has_unit_sub_topic_list(
1907 &self,
1908 contain_retry: bool,
1909 timeout_millis: u64,
1910 ) -> ClientResult<TopicList> {
1911 let mut topic_list = self
1912 .get_topic_list_from_name_server_by_code(RequestCode::GetHasUnitSubTopicList, timeout_millis)
1913 .await?;
1914 filter_retry_topics_like_java(&mut topic_list, contain_retry);
1915 Ok(topic_list)
1916 }
1917
1918 pub(crate) async fn get_has_unit_sub_un_unit_topic_list(
1919 &self,
1920 contain_retry: bool,
1921 timeout_millis: u64,
1922 ) -> ClientResult<TopicList> {
1923 let mut topic_list = self
1924 .get_topic_list_from_name_server_by_code(RequestCode::GetHasUnitSubUnunitTopicList, timeout_millis)
1925 .await?;
1926 filter_retry_topics_like_java(&mut topic_list, contain_retry);
1927 Ok(topic_list)
1928 }
1929
1930 pub(crate) async fn get_topics_by_cluster(
1931 &self,
1932 cluster: CheetahString,
1933 timeout_millis: u64,
1934 ) -> ClientResult<rocketmq_protocol::protocol::body::topic::topic_list::TopicList> {
1935 let request_header = GetTopicsByClusterRequestHeader::new(cluster);
1936 let request = self.create_request_command(RequestCode::GetTopicsByCluster, request_header);
1937 let outcome = self.remoting_client.invoke_request(None, request, timeout_millis).await;
1938 let response = match outcome {
1939 Ok(OutboundRequestOutcome::Response(response)) => response,
1940 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1941 return Err(admin_request_error(
1942 "get_topics_by_cluster",
1943 RetryInput::Rejected(rejection),
1944 ));
1945 }
1946 Ok(OutboundRequestOutcome::Contract(contract)) => {
1947 return Err(admin_request_error(
1948 "get_topics_by_cluster",
1949 RetryInput::Contract(contract),
1950 ));
1951 }
1952 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
1953 };
1954 if ResponseCode::from(response.code()) == ResponseCode::Success {
1955 if let Some(body) = response.get_body() {
1956 return rocketmq_protocol::protocol::body::topic::topic_list::TopicList::decode(body.as_ref())
1957 .map_err(ClientError::from);
1958 }
1959 }
1960 Err(mq_client_err!(
1961 response.code(),
1962 response.remark().map_or("".to_string(), |s| s.to_string())
1963 ))
1964 }
1965
1966 pub(crate) async fn get_cluster_list(
1967 &self,
1968 topic: CheetahString,
1969 timeout_millis: u64,
1970 ) -> ClientResult<HashSet<CheetahString>> {
1971 let topic_route_data = self
1972 .get_topic_route_info_from_name_server(&topic, timeout_millis)
1973 .await?
1974 .ok_or_else(|| mq_client_err!(format!("Topic route not found for: {topic}")))?;
1975 let cluster_info = self.get_broker_cluster_info(timeout_millis).await?;
1976 Ok(cluster_names_for_topic_route(&cluster_info, &topic_route_data))
1977 }
1978
1979 pub(crate) async fn get_system_topic_list_from_broker(
1980 &self,
1981 addr: &CheetahString,
1982 timeout_millis: u64,
1983 ) -> ClientResult<rocketmq_protocol::protocol::body::topic::topic_list::TopicList> {
1984 let request = self.create_request_command(RequestCode::GetSystemTopicListFromBroker, EmptyHeader {});
1985 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
1986 let outcome = self
1987 .remoting_client
1988 .invoke_request(Some(&broker_addr), request, timeout_millis)
1989 .await;
1990 let response = match outcome {
1991 Ok(OutboundRequestOutcome::Response(response)) => response,
1992 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
1993 return Err(admin_request_error(
1994 "get_system_topic_list_from_broker",
1995 RetryInput::Rejected(rejection),
1996 ));
1997 }
1998 Ok(OutboundRequestOutcome::Contract(contract)) => {
1999 return Err(admin_request_error(
2000 "get_system_topic_list_from_broker",
2001 RetryInput::Contract(contract),
2002 ));
2003 }
2004 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2005 };
2006 if ResponseCode::from(response.code()) == ResponseCode::Success {
2007 if let Some(body) = response.get_body() {
2008 return rocketmq_protocol::protocol::body::topic::topic_list::TopicList::decode(body.as_ref())
2009 .map_err(ClientError::from);
2010 }
2011 }
2012 Err(mq_client_err!(
2013 response.code(),
2014 response.remark().map_or("".to_string(), |s| s.to_string())
2015 ))
2016 }
2017
2018 pub(crate) async fn get_consume_stats(
2019 &self,
2020 addr: &CheetahString,
2021 request_header: rocketmq_protocol::protocol::header::get_consume_stats_request_header::GetConsumeStatsRequestHeader,
2022 timeout_millis: u64,
2023 ) -> ClientResult<rocketmq_protocol::protocol::admin::consume_stats::ConsumeStats> {
2024 let request = self.create_request_command(RequestCode::GetConsumeStats, request_header);
2025 let outcome = self
2026 .remoting_client
2027 .invoke_request(Some(addr), request, timeout_millis)
2028 .await;
2029 let response = match outcome {
2030 Ok(OutboundRequestOutcome::Response(response)) => response,
2031 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2032 return Err(admin_request_error(
2033 "get_consume_stats",
2034 RetryInput::Rejected(rejection),
2035 ));
2036 }
2037 Ok(OutboundRequestOutcome::Contract(contract)) => {
2038 return Err(admin_request_error("get_consume_stats", RetryInput::Contract(contract)));
2039 }
2040 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2041 };
2042 if ResponseCode::from(response.code()) == ResponseCode::Success {
2043 if let Some(body) = response.get_body() {
2044 return rocketmq_protocol::protocol::admin::consume_stats::ConsumeStats::decode(body.as_ref())
2045 .map_err(ClientError::from);
2046 }
2047 }
2048 Err(client_broker_err!(
2049 response.code(),
2050 response.remark().map_or("".to_string(), |s| s.to_string()),
2051 addr
2052 ))
2053 }
2054
2055 pub(crate) async fn query_consume_time_span(
2056 &self,
2057 addr: &CheetahString,
2058 request_header: QueryConsumeTimeSpanRequestHeader,
2059 timeout_millis: u64,
2060 ) -> ClientResult<Vec<rocketmq_protocol::protocol::body::queue_time_span::QueueTimeSpan>> {
2061 let request = self.create_request_command(RequestCode::QueryConsumeTimeSpan, request_header);
2062 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2063 let outcome = self
2064 .remoting_client
2065 .invoke_request(Some(&broker_addr), request, timeout_millis)
2066 .await;
2067 let response = match outcome {
2068 Ok(OutboundRequestOutcome::Response(response)) => response,
2069 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2070 return Err(admin_request_error(
2071 "query_consume_time_span",
2072 RetryInput::Rejected(rejection),
2073 ));
2074 }
2075 Ok(OutboundRequestOutcome::Contract(contract)) => {
2076 return Err(admin_request_error(
2077 "query_consume_time_span",
2078 RetryInput::Contract(contract),
2079 ));
2080 }
2081 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2082 };
2083 if ResponseCode::from(response.code()) == ResponseCode::Success {
2084 if let Some(body) = response.get_body() {
2085 let body: QueryConsumeTimeSpanBody = decode_admin_json(body.as_ref())?;
2086 return Ok(body.consume_time_span_set);
2087 }
2088 }
2089 Err(mq_client_err!(
2090 response.code(),
2091 response.remark().map_or("".to_string(), |s| s.to_string())
2092 ))
2093 }
2094
2095 pub(crate) async fn get_topic_stats_info(
2096 &self,
2097 addr: &CheetahString,
2098 request_header: GetTopicStatsInfoRequestHeader,
2099 timeout_millis: u64,
2100 ) -> ClientResult<rocketmq_protocol::protocol::admin::topic_stats_table::TopicStatsTable> {
2101 let request = self.create_request_command(RequestCode::GetTopicStatsInfo, request_header);
2102 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2103 let outcome = self
2104 .remoting_client
2105 .invoke_request(Some(&broker_addr), request, timeout_millis)
2106 .await;
2107 let response = match outcome {
2108 Ok(OutboundRequestOutcome::Response(response)) => response,
2109 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2110 return Err(admin_request_error(
2111 "get_topic_stats_info",
2112 RetryInput::Rejected(rejection),
2113 ));
2114 }
2115 Ok(OutboundRequestOutcome::Contract(contract)) => {
2116 return Err(admin_request_error(
2117 "get_topic_stats_info",
2118 RetryInput::Contract(contract),
2119 ));
2120 }
2121 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2122 };
2123 if ResponseCode::from(response.code()) == ResponseCode::Success {
2124 if let Some(body) = response.get_body() {
2125 return rocketmq_protocol::protocol::admin::topic_stats_table::TopicStatsTable::decode(body.as_ref())
2126 .map_err(ClientError::from);
2127 }
2128 }
2129 Err(mq_client_err!(
2130 response.code(),
2131 response.remark().map_or("".to_string(), |s| s.to_string())
2132 ))
2133 }
2134
2135 pub(crate) async fn get_topic_config(
2136 &self,
2137 addr: &CheetahString,
2138 request_header: GetTopicConfigRequestHeader,
2139 timeout_millis: u64,
2140 ) -> ClientResult<TopicConfigAndQueueMapping> {
2141 let request = self.create_request_command(RequestCode::GetTopicConfig, request_header);
2142 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2143 let outcome = self
2144 .remoting_client
2145 .invoke_request(Some(&broker_addr), request, timeout_millis)
2146 .await;
2147 let response = match outcome {
2148 Ok(OutboundRequestOutcome::Response(response)) => response,
2149 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2150 return Err(admin_request_error("get_topic_config", RetryInput::Rejected(rejection)));
2151 }
2152 Ok(OutboundRequestOutcome::Contract(contract)) => {
2153 return Err(admin_request_error("get_topic_config", RetryInput::Contract(contract)));
2154 }
2155 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2156 };
2157 if ResponseCode::from(response.code()) == ResponseCode::Success {
2158 if let Some(body) = response.get_body() {
2159 return decode_admin_json(body.as_ref());
2160 }
2161 }
2162 Err(mq_client_err!(
2163 response.code(),
2164 response.remark().map_or("".to_string(), |s| s.to_string())
2165 ))
2166 }
2167
2168 #[cfg(feature = "admin-mutation")]
2169 pub(crate) async fn create_static_topic(
2170 &self,
2171 addr: &CheetahString,
2172 default_topic: CheetahString,
2173 topic_config: TopicConfig,
2174 mapping_detail: TopicQueueMappingDetail,
2175 force: bool,
2176 timeout_millis: u64,
2177 ) -> ClientResult<()> {
2178 let topic = topic_config
2179 .topic_name
2180 .clone()
2181 .filter(|topic| !topic.is_empty())
2182 .ok_or_else(|| crate::ClientError::illegal_argument("Topic name is required"))?;
2183 let read_queue_nums = i32::try_from(topic_config.read_queue_nums)
2184 .map_err(|_| crate::ClientError::illegal_argument("readQueueNums exceeds Java int range"))?;
2185 let write_queue_nums = i32::try_from(topic_config.write_queue_nums)
2186 .map_err(|_| crate::ClientError::illegal_argument("writeQueueNums exceeds Java int range"))?;
2187 let perm = i32::try_from(topic_config.perm)
2188 .map_err(|_| crate::ClientError::illegal_argument("perm exceeds Java int range"))?;
2189 let topic_sys_flag = i32::try_from(topic_config.topic_sys_flag)
2190 .map_err(|_| crate::ClientError::illegal_argument("topicSysFlag exceeds Java int range"))?;
2191
2192 let request_header = CreateTopicRequestHeader {
2193 topic,
2194 default_topic,
2195 read_queue_nums,
2196 write_queue_nums,
2197 perm,
2198 topic_filter_type: CheetahString::from_static_str(topic_config.topic_filter_type.as_str()),
2199 topic_sys_flag: Some(topic_sys_flag),
2200 order: topic_config.order,
2201 attributes: None,
2202 force: Some(force),
2203 topic_request_header: None,
2204 };
2205 let request = self
2206 .create_request_command(RequestCode::UpdateAndCreateStaticTopic, request_header)
2207 .set_body(mapping_detail.encode()?);
2208 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2209 let outcome = self
2210 .remoting_client
2211 .invoke_request(Some(&broker_addr), request, timeout_millis)
2212 .await;
2213 let response = match outcome {
2214 Ok(OutboundRequestOutcome::Response(response)) => response,
2215 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2216 return Err(admin_request_error(
2217 "create_static_topic",
2218 RetryInput::Rejected(rejection),
2219 ));
2220 }
2221 Ok(OutboundRequestOutcome::Contract(contract)) => {
2222 return Err(admin_request_error(
2223 "create_static_topic",
2224 RetryInput::Contract(contract),
2225 ));
2226 }
2227 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2228 };
2229
2230 if ResponseCode::from(response.code()) == ResponseCode::Success {
2231 return Ok(());
2232 }
2233
2234 Err(client_broker_err!(
2235 response.code(),
2236 response.remark().map_or("".to_string(), |s| s.to_string()),
2237 addr
2238 ))
2239 }
2240
2241 pub(crate) async fn query_topic_consume_by_who(
2242 &self,
2243 addr: &CheetahString,
2244 request_header: rocketmq_protocol::protocol::header::query_topic_consume_by_who_request_header::QueryTopicConsumeByWhoRequestHeader,
2245 timeout_millis: u64,
2246 ) -> ClientResult<rocketmq_protocol::protocol::body::group_list::GroupList> {
2247 let request = self.create_request_command(RequestCode::QueryTopicConsumeByWho, request_header);
2248 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2249 let outcome = self
2250 .remoting_client
2251 .invoke_request(Some(&broker_addr), request, timeout_millis)
2252 .await;
2253 let response = match outcome {
2254 Ok(OutboundRequestOutcome::Response(response)) => response,
2255 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2256 return Err(admin_request_error(
2257 "query_topic_consume_by_who",
2258 RetryInput::Rejected(rejection),
2259 ));
2260 }
2261 Ok(OutboundRequestOutcome::Contract(contract)) => {
2262 return Err(admin_request_error(
2263 "query_topic_consume_by_who",
2264 RetryInput::Contract(contract),
2265 ));
2266 }
2267 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2268 };
2269 if ResponseCode::from(response.code()) == ResponseCode::Success {
2270 if let Some(body) = response.get_body() {
2271 return rocketmq_protocol::protocol::body::group_list::GroupList::decode(body.as_ref())
2272 .map_err(ClientError::from);
2273 }
2274 }
2275 Err(mq_client_err!(
2276 response.code(),
2277 response.remark().map_or("".to_string(), |s| s.to_string())
2278 ))
2279 }
2280
2281 pub(crate) async fn query_topics_by_consumer(
2282 &self,
2283 addr: &CheetahString,
2284 request_header: QueryTopicsByConsumerRequestHeader,
2285 timeout_millis: u64,
2286 ) -> ClientResult<rocketmq_protocol::protocol::body::topic::topic_list::TopicList> {
2287 let request = self.create_request_command(RequestCode::QueryTopicsByConsumer, request_header);
2288 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2289 let outcome = self
2290 .remoting_client
2291 .invoke_request(Some(&broker_addr), request, timeout_millis)
2292 .await;
2293 let response = match outcome {
2294 Ok(OutboundRequestOutcome::Response(response)) => response,
2295 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2296 return Err(admin_request_error(
2297 "query_topics_by_consumer",
2298 RetryInput::Rejected(rejection),
2299 ));
2300 }
2301 Ok(OutboundRequestOutcome::Contract(contract)) => {
2302 return Err(admin_request_error(
2303 "query_topics_by_consumer",
2304 RetryInput::Contract(contract),
2305 ));
2306 }
2307 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2308 };
2309 if ResponseCode::from(response.code()) == ResponseCode::Success {
2310 if let Some(body) = response.get_body() {
2311 return rocketmq_protocol::protocol::body::topic::topic_list::TopicList::decode(body.as_ref())
2312 .map_err(ClientError::from);
2313 }
2314 }
2315 Err(mq_client_err!(
2316 response.code(),
2317 response.remark().map_or("".to_string(), |s| s.to_string())
2318 ))
2319 }
2320
2321 pub(crate) async fn query_subscription_by_consumer(
2322 &self,
2323 addr: &CheetahString,
2324 request_header: QuerySubscriptionByConsumerRequestHeader,
2325 timeout_millis: u64,
2326 ) -> ClientResult<SubscriptionData> {
2327 let request = self.create_request_command(RequestCode::QuerySubscriptionByConsumer, request_header);
2328 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2329 let outcome = self
2330 .remoting_client
2331 .invoke_request(Some(&broker_addr), request, timeout_millis)
2332 .await;
2333 let response = match outcome {
2334 Ok(OutboundRequestOutcome::Response(response)) => response,
2335 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2336 return Err(admin_request_error(
2337 "query_subscription_by_consumer",
2338 RetryInput::Rejected(rejection),
2339 ));
2340 }
2341 Ok(OutboundRequestOutcome::Contract(contract)) => {
2342 return Err(admin_request_error(
2343 "query_subscription_by_consumer",
2344 RetryInput::Contract(contract),
2345 ));
2346 }
2347 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2348 };
2349 if ResponseCode::from(response.code()) == ResponseCode::Success {
2350 let body = response
2351 .get_body()
2352 .ok_or_else(|| mq_client_err!("query_subscription_by_consumer response body is empty".to_string()))?;
2353 let response_body: QuerySubscriptionResponseBody = decode_admin_json(body.as_ref())?;
2354 return response_body.subscription_data.ok_or_else(|| {
2355 mq_client_err!("query_subscription_by_consumer response subscriptionData is empty".to_string())
2356 });
2357 }
2358 Err(client_broker_err!(
2359 response.code(),
2360 response.remark().map_or("".to_string(), |s| s.to_string()),
2361 addr
2362 ))
2363 }
2364
2365 #[cfg(feature = "admin-mutation")]
2366 pub(super) async fn invoke_broker_success_request(
2367 &self,
2368 addr: &CheetahString,
2369 request_code: RequestCode,
2370 timeout_millis: u64,
2371 ) -> ClientResult<bool> {
2372 let request = self.create_request_command(request_code, EmptyHeader {});
2373 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2374 let outcome = self
2375 .remoting_client
2376 .invoke_request(Some(&broker_addr), request, timeout_millis)
2377 .await;
2378 let response = match outcome {
2379 Ok(OutboundRequestOutcome::Response(response)) => response,
2380 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2381 return Err(admin_request_error(
2382 "invoke_broker_success_request",
2383 RetryInput::Rejected(rejection),
2384 ));
2385 }
2386 Ok(OutboundRequestOutcome::Contract(contract)) => {
2387 return Err(admin_request_error(
2388 "invoke_broker_success_request",
2389 RetryInput::Contract(contract),
2390 ));
2391 }
2392 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2393 };
2394 if ResponseCode::from(response.code()) == ResponseCode::Success {
2395 return Ok(true);
2396 }
2397 Err(mq_client_err!(
2398 response.code(),
2399 response.remark().map_or("".to_string(), |s| s.to_string())
2400 ))
2401 }
2402
2403 #[cfg(feature = "admin-mutation")]
2404 pub(crate) async fn clean_expired_consume_queue(
2405 &self,
2406 addr: &CheetahString,
2407 timeout_millis: u64,
2408 ) -> ClientResult<bool> {
2409 self.invoke_broker_success_request(addr, RequestCode::CleanExpiredConsumequeue, timeout_millis)
2410 .await
2411 }
2412
2413 #[cfg(feature = "admin-mutation")]
2414 pub(crate) async fn delete_expired_commit_log(
2415 &self,
2416 addr: &CheetahString,
2417 timeout_millis: u64,
2418 ) -> ClientResult<bool> {
2419 self.invoke_broker_success_request(addr, RequestCode::DeleteExpiredCommitlog, timeout_millis)
2420 .await
2421 }
2422
2423 #[cfg(feature = "admin-mutation")]
2424 pub(crate) async fn clean_unused_topic(&self, addr: &CheetahString, timeout_millis: u64) -> ClientResult<bool> {
2425 self.invoke_broker_success_request(addr, RequestCode::CleanUnusedTopic, timeout_millis)
2426 .await
2427 }
2428
2429 #[cfg(feature = "admin-mutation")]
2430 pub(crate) async fn clean_unused_topic_by_addr(
2431 &self,
2432 addr: &CheetahString,
2433 timeout_millis: u64,
2434 ) -> ClientResult<bool> {
2435 self.clean_unused_topic(addr, timeout_millis).await
2436 }
2437
2438 #[cfg(feature = "admin-mutation")]
2439 pub(crate) async fn create_topic(
2440 &self,
2441 addr: &CheetahString,
2442 default_topic: CheetahString,
2443 topic_config: &TopicConfig,
2444 timeout_millis: u64,
2445 ) -> ClientResult<()> {
2446 let request_header = create_topic_request_header_like_java(default_topic, topic_config)?;
2447 self.update_or_create_topic(addr, request_header, timeout_millis).await
2448 }
2449
2450 #[cfg(feature = "admin-mutation")]
2451 pub(crate) async fn create_topic_list(
2452 &self,
2453 address: &CheetahString,
2454 topic_config_list: Vec<TopicConfig>,
2455 timeout_millis: u64,
2456 ) -> ClientResult<()> {
2457 let request = create_topic_list_request(&self.command_factory, topic_config_list)?;
2458 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, address.as_str());
2459 let outcome = self
2460 .remoting_client
2461 .invoke_request(Some(&broker_addr), request, timeout_millis)
2462 .await;
2463 let response = match outcome {
2464 Ok(OutboundRequestOutcome::Response(response)) => response,
2465 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2466 return Err(admin_request_error(
2467 "create_topic_list",
2468 RetryInput::Rejected(rejection),
2469 ));
2470 }
2471 Ok(OutboundRequestOutcome::Contract(contract)) => {
2472 return Err(admin_request_error("create_topic_list", RetryInput::Contract(contract)));
2473 }
2474 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2475 };
2476 match ResponseCode::from(response.code()) {
2477 ResponseCode::Success => Ok(()),
2478 _ => Err(mq_client_err!(
2479 response.code(),
2480 response.remark().map_or_else(String::new, |remark| remark.to_string())
2481 )),
2482 }
2483 }
2484
2485 pub(crate) async fn update_or_create_topic(
2486 &self,
2487 addr: &CheetahString,
2488 request_header: CreateTopicRequestHeader,
2489 timeout_millis: u64,
2490 ) -> ClientResult<()> {
2491 self.update_or_create_topic_once(
2492 addr,
2493 request_header,
2494 RequestDeadline::from_timeout_millis(timeout_millis),
2495 )
2496 .await
2497 .map_err(|input| admin_request_error("update_or_create_topic", input))
2498 }
2499
2500 pub(crate) async fn update_or_create_topic_once(
2501 &self,
2502 addr: &CheetahString,
2503 request_header: CreateTopicRequestHeader,
2504 deadline: RequestDeadline,
2505 ) -> Result<(), RetryInput> {
2506 let request = self.create_request_command(RequestCode::UpdateAndCreateTopic, request_header);
2507 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2508 let outcome = self
2509 .remoting_client
2510 .invoke_request_with_deadline(Some(&broker_addr), request, deadline)
2511 .await;
2512 let response = match outcome {
2513 Ok(OutboundRequestOutcome::Response(response)) => response,
2514 Ok(OutboundRequestOutcome::Rejected(rejection)) => return Err(RetryInput::Rejected(rejection)),
2515 Ok(OutboundRequestOutcome::Contract(contract)) => return Err(RetryInput::Contract(contract)),
2516 Err(error) => return Err(RetryInput::Transport(error)),
2517 };
2518 if ResponseCode::from(response.code()) == ResponseCode::Success {
2519 Ok(())
2520 } else {
2521 let code = response.code();
2522 Err(RetryInput::Response {
2523 code,
2524 retry_after: None,
2525 terminal_error: mq_client_err!(code, response.remark().map_or("".to_string(), |s| s.to_string())),
2526 })
2527 }
2528 }
2529
2530 #[cfg(feature = "admin-mutation")]
2531 pub(crate) async fn create_subscription_group_list(
2532 &self,
2533 address: &CheetahString,
2534 configs: Vec<SubscriptionGroupConfig>,
2535 timeout_millis: u64,
2536 ) -> ClientResult<()> {
2537 let request = create_subscription_group_list_request(&self.command_factory, configs)?;
2538 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, address.as_str());
2539 let outcome = self
2540 .remoting_client
2541 .invoke_request(Some(&broker_addr), request, timeout_millis)
2542 .await;
2543 let response = match outcome {
2544 Ok(OutboundRequestOutcome::Response(response)) => response,
2545 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2546 return Err(admin_request_error(
2547 "create_subscription_group_list",
2548 RetryInput::Rejected(rejection),
2549 ));
2550 }
2551 Ok(OutboundRequestOutcome::Contract(contract)) => {
2552 return Err(admin_request_error(
2553 "create_subscription_group_list",
2554 RetryInput::Contract(contract),
2555 ));
2556 }
2557 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2558 };
2559
2560 match ResponseCode::from(response.code()) {
2561 ResponseCode::Success => Ok(()),
2562 _ => Err(mq_client_err!(
2563 response.code(),
2564 response.remark().map_or_else(String::new, |remark| remark.to_string())
2565 )),
2566 }
2567 }
2568
2569 #[cfg(feature = "admin-mutation")]
2570 pub(crate) async fn delete_topic_in_broker(
2571 &self,
2572 addr: &CheetahString,
2573 request_header: DeleteTopicRequestHeader,
2574 timeout_millis: u64,
2575 ) -> ClientResult<()> {
2576 let request = self.create_request_command(RequestCode::DeleteTopicInBroker, request_header);
2577 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2578 let outcome = self
2579 .remoting_client
2580 .invoke_request(Some(&broker_addr), request, timeout_millis)
2581 .await;
2582 let response = match outcome {
2583 Ok(OutboundRequestOutcome::Response(response)) => response,
2584 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2585 return Err(admin_request_error(
2586 "delete_topic_in_broker",
2587 RetryInput::Rejected(rejection),
2588 ));
2589 }
2590 Ok(OutboundRequestOutcome::Contract(contract)) => {
2591 return Err(admin_request_error(
2592 "delete_topic_in_broker",
2593 RetryInput::Contract(contract),
2594 ));
2595 }
2596 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2597 };
2598 if ResponseCode::from(response.code()) == ResponseCode::Success {
2599 return Ok(());
2600 }
2601 Err(mq_client_err!(
2602 response.code(),
2603 response.remark().map_or("".to_string(), |s| s.to_string())
2604 ))
2605 }
2606
2607 #[cfg(feature = "admin-mutation")]
2609 pub async fn delete_topic_in_broker_list(
2610 &self,
2611 addr: &CheetahString,
2612 topic_list: Vec<CheetahString>,
2613 timeout_millis: u64,
2614 ) -> ClientResult<()> {
2615 let request = delete_topic_list_request(&self.command_factory, topic_list)?;
2616 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2617 let outcome = self
2618 .remoting_client
2619 .invoke_request(Some(&broker_addr), request, timeout_millis)
2620 .await;
2621 let response = match outcome {
2622 Ok(OutboundRequestOutcome::Response(response)) => response,
2623 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2624 return Err(admin_request_error(
2625 "delete_topic_in_broker_list",
2626 RetryInput::Rejected(rejection),
2627 ));
2628 }
2629 Ok(OutboundRequestOutcome::Contract(contract)) => {
2630 return Err(admin_request_error(
2631 "delete_topic_in_broker_list",
2632 RetryInput::Contract(contract),
2633 ));
2634 }
2635 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2636 };
2637
2638 match ResponseCode::from(response.code()) {
2639 ResponseCode::Success => Ok(()),
2640 _ => Err(mq_client_err!(
2641 response.code(),
2642 response.remark().map_or_else(String::new, |remark| remark.to_string())
2643 )),
2644 }
2645 }
2646
2647 #[cfg(feature = "admin-mutation")]
2648 pub(crate) async fn delete_topic_in_name_server(
2649 &self,
2650 addr: &CheetahString,
2651 cluster_name: Option<CheetahString>,
2652 topic: CheetahString,
2653 timeout_millis: u64,
2654 ) -> ClientResult<()> {
2655 self.delete_topic_in_nameserver(
2656 addr,
2657 DeleteTopicFromNamesrvRequestHeader::new(topic, cluster_name),
2658 timeout_millis,
2659 )
2660 .await
2661 }
2662
2663 #[cfg(feature = "admin-mutation")]
2664 pub(crate) async fn delete_topic_in_nameserver(
2665 &self,
2666 addr: &CheetahString,
2667 request_header: DeleteTopicFromNamesrvRequestHeader,
2668 timeout_millis: u64,
2669 ) -> ClientResult<()> {
2670 let request = self.create_request_command(RequestCode::DeleteTopicInNamesrv, request_header);
2671 let outcome = self
2672 .remoting_client
2673 .invoke_request(Some(addr), request, timeout_millis)
2674 .await;
2675 let response = match outcome {
2676 Ok(OutboundRequestOutcome::Response(response)) => response,
2677 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2678 return Err(admin_request_error(
2679 "delete_topic_in_nameserver",
2680 RetryInput::Rejected(rejection),
2681 ));
2682 }
2683 Ok(OutboundRequestOutcome::Contract(contract)) => {
2684 return Err(admin_request_error(
2685 "delete_topic_in_nameserver",
2686 RetryInput::Contract(contract),
2687 ));
2688 }
2689 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2690 };
2691 if ResponseCode::from(response.code()) == ResponseCode::Success {
2692 return Ok(());
2693 }
2694 Err(mq_client_err!(
2695 response.code(),
2696 response.remark().map_or("".to_string(), |s| s.to_string())
2697 ))
2698 }
2699
2700 #[cfg(feature = "admin-mutation")]
2701 pub(crate) async fn invoke_broker_to_reset_offset(
2702 &self,
2703 addr: &CheetahString,
2704 request_header: ResetOffsetRequestHeader,
2705 timeout_millis: u64,
2706 ) -> ClientResult<HashMap<MessageQueue, i64>> {
2707 let request = self.create_request_command(RequestCode::InvokeBrokerToResetOffset, request_header);
2708 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2709 let outcome = self
2710 .remoting_client
2711 .invoke_request(Some(&broker_addr), request, timeout_millis)
2712 .await;
2713 let response = match outcome {
2714 Ok(OutboundRequestOutcome::Response(response)) => response,
2715 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2716 return Err(admin_request_error(
2717 "invoke_broker_to_reset_offset",
2718 RetryInput::Rejected(rejection),
2719 ));
2720 }
2721 Ok(OutboundRequestOutcome::Contract(contract)) => {
2722 return Err(admin_request_error(
2723 "invoke_broker_to_reset_offset",
2724 RetryInput::Contract(contract),
2725 ));
2726 }
2727 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2728 };
2729 if ResponseCode::from(response.code()) == ResponseCode::Success {
2730 return reset_offset_table_from_response(&response);
2731 }
2732 Err(mq_client_err!(
2733 response.code(),
2734 response.remark().map_or("".to_string(), |s| s.to_string())
2735 ))
2736 }
2737
2738 pub(crate) async fn query_correction_offset(
2739 &self,
2740 addr: &CheetahString,
2741 topic: CheetahString,
2742 group: CheetahString,
2743 filter_groups: Option<Vec<CheetahString>>,
2744 timeout_millis: u64,
2745 ) -> ClientResult<HashMap<i32, i64>> {
2746 let request = query_correction_offset_request(&self.command_factory, topic, group, filter_groups);
2747 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2748 let outcome = self
2749 .remoting_client
2750 .invoke_request(Some(&broker_addr), request, timeout_millis)
2751 .await;
2752 let response = match outcome {
2753 Ok(OutboundRequestOutcome::Response(response)) => response,
2754 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2755 return Err(admin_request_error(
2756 "query_correction_offset",
2757 RetryInput::Rejected(rejection),
2758 ));
2759 }
2760 Ok(OutboundRequestOutcome::Contract(contract)) => {
2761 return Err(admin_request_error(
2762 "query_correction_offset",
2763 RetryInput::Contract(contract),
2764 ));
2765 }
2766 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2767 };
2768 if ResponseCode::from(response.code()) == ResponseCode::Success {
2769 if let Some(body) = response.get_body() {
2770 return QueryCorrectionOffsetBody::decode(body.as_ref())
2771 .map(|body| body.correction_offsets)
2772 .map_err(ClientError::from);
2773 }
2774 }
2775 Err(mq_client_err!(
2776 response.code(),
2777 response.remark().map_or("".to_string(), |s| s.to_string())
2778 ))
2779 }
2780
2781 pub(crate) async fn view_broker_stats_data(
2782 &self,
2783 addr: &CheetahString,
2784 request_header: rocketmq_protocol::protocol::header::view_broker_stats_data_request_header::ViewBrokerStatsDataRequestHeader,
2785 timeout_millis: u64,
2786 ) -> ClientResult<rocketmq_protocol::protocol::subscription::broker_stats_data::BrokerStatsData> {
2787 let request = self.create_request_command(RequestCode::ViewBrokerStatsData, request_header);
2788 let outcome = self
2789 .remoting_client
2790 .invoke_request(Some(addr), request, timeout_millis)
2791 .await;
2792 let response = match outcome {
2793 Ok(OutboundRequestOutcome::Response(response)) => response,
2794 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2795 return Err(admin_request_error(
2796 "view_broker_stats_data",
2797 RetryInput::Rejected(rejection),
2798 ));
2799 }
2800 Ok(OutboundRequestOutcome::Contract(contract)) => {
2801 return Err(admin_request_error(
2802 "view_broker_stats_data",
2803 RetryInput::Contract(contract),
2804 ));
2805 }
2806 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2807 };
2808 if ResponseCode::from(response.code()) == ResponseCode::Success {
2809 if let Some(body) = response.get_body() {
2810 return rocketmq_protocol::protocol::subscription::broker_stats_data::BrokerStatsData::decode(
2811 body.as_ref(),
2812 )
2813 .map_err(ClientError::from);
2814 }
2815 }
2816 Err(mq_client_err!(
2817 response.code(),
2818 response.remark().map_or("".to_string(), |s| s.to_string())
2819 ))
2820 }
2821
2822 pub(crate) async fn fetch_consume_stats_in_broker(
2823 &self,
2824 addr: &CheetahString,
2825 request_header: rocketmq_protocol::protocol::header::get_consume_stats_in_broker_header::GetConsumeStatsInBrokerHeader,
2826 timeout_millis: u64,
2827 ) -> ClientResult<rocketmq_protocol::protocol::admin::consume_stats_list::ConsumeStatsList> {
2828 let request = self.create_request_command(RequestCode::GetBrokerConsumeStats, request_header);
2829 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2830 let outcome = self
2831 .remoting_client
2832 .invoke_request(Some(&broker_addr), request, timeout_millis)
2833 .await;
2834 let response = match outcome {
2835 Ok(OutboundRequestOutcome::Response(response)) => response,
2836 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2837 return Err(admin_request_error(
2838 "fetch_consume_stats_in_broker",
2839 RetryInput::Rejected(rejection),
2840 ));
2841 }
2842 Ok(OutboundRequestOutcome::Contract(contract)) => {
2843 return Err(admin_request_error(
2844 "fetch_consume_stats_in_broker",
2845 RetryInput::Contract(contract),
2846 ));
2847 }
2848 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2849 };
2850
2851 if ResponseCode::from(response.code()) == ResponseCode::Success {
2852 if let Some(body) = response.get_body() {
2853 return rocketmq_protocol::protocol::admin::consume_stats_list::ConsumeStatsList::decode(body.as_ref())
2854 .map_err(ClientError::from);
2855 }
2856 }
2857
2858 Err(client_broker_err!(
2859 response.code(),
2860 response.remark().map_or("".to_string(), |s| s.to_string()),
2861 addr
2862 ))
2863 }
2864
2865 #[cfg(feature = "admin-mutation")]
2866 pub(crate) async fn clone_group_offset(
2867 &self,
2868 addr: &CheetahString,
2869 src_group: CheetahString,
2870 dest_group: CheetahString,
2871 topic: CheetahString,
2872 is_offline: bool,
2873 timeout_millis: u64,
2874 ) -> ClientResult<()> {
2875 let request_header = CloneGroupOffsetRequestHeader {
2876 src_group,
2877 dest_group,
2878 topic: Some(topic),
2879 offline: is_offline,
2880 rpc_request_header: None,
2881 };
2882 let request = self.create_request_command(RequestCode::CloneGroupOffset, request_header);
2883 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
2884 let outcome = self
2885 .remoting_client
2886 .invoke_request(Some(&broker_addr), request, timeout_millis)
2887 .await;
2888 let response = match outcome {
2889 Ok(OutboundRequestOutcome::Response(response)) => response,
2890 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2891 return Err(admin_request_error(
2892 "clone_group_offset",
2893 RetryInput::Rejected(rejection),
2894 ));
2895 }
2896 Ok(OutboundRequestOutcome::Contract(contract)) => {
2897 return Err(admin_request_error(
2898 "clone_group_offset",
2899 RetryInput::Contract(contract),
2900 ));
2901 }
2902 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2903 };
2904
2905 if ResponseCode::from(response.code()) == ResponseCode::Success {
2906 return Ok(());
2907 }
2908
2909 Err(client_broker_err!(
2910 response.code(),
2911 response.remark().map_or("".to_string(), |s| s.to_string()),
2912 addr
2913 ))
2914 }
2915}
2916
2917impl MQClientAPIImpl {
2918 #[cfg(feature = "admin-read")]
2919 pub(crate) async fn get_proxy_drain_state(
2920 &self,
2921 addr: &CheetahString,
2922 timeout_millis: u64,
2923 ) -> ClientResult<ProxyDrainStateResponseBody> {
2924 let request = self.create_remoting_command(RequestCode::GetProxyDrainState);
2925 let outcome = self
2926 .remoting_client
2927 .invoke_request(Some(addr), request, timeout_millis)
2928 .await;
2929 let response = match outcome {
2930 Ok(OutboundRequestOutcome::Response(response)) => response,
2931 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2932 return Err(admin_request_error(
2933 "get_proxy_drain_state",
2934 RetryInput::Rejected(rejection),
2935 ));
2936 }
2937 Ok(OutboundRequestOutcome::Contract(contract)) => {
2938 return Err(admin_request_error(
2939 "get_proxy_drain_state",
2940 RetryInput::Contract(contract),
2941 ));
2942 }
2943 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
2944 };
2945 decode_proxy_drain_response(response, "get_proxy_drain_state")
2946 }
2947
2948 #[cfg(feature = "admin-mutation")]
2949 pub(crate) async fn begin_proxy_drain(
2950 &self,
2951 addr: &CheetahString,
2952 operation_id: CheetahString,
2953 timeout_millis: u64,
2954 ) -> ClientResult<ProxyDrainStateResponseBody> {
2955 self.proxy_drain_operation(addr, RequestCode::BeginProxyDrain, operation_id, timeout_millis)
2956 .await
2957 }
2958
2959 #[cfg(feature = "admin-mutation")]
2960 pub(crate) async fn cancel_proxy_drain(
2961 &self,
2962 addr: &CheetahString,
2963 operation_id: CheetahString,
2964 timeout_millis: u64,
2965 ) -> ClientResult<ProxyDrainStateResponseBody> {
2966 self.proxy_drain_operation(addr, RequestCode::CancelProxyDrain, operation_id, timeout_millis)
2967 .await
2968 }
2969
2970 #[cfg(feature = "admin-mutation")]
2971 async fn proxy_drain_operation(
2972 &self,
2973 addr: &CheetahString,
2974 request_code: RequestCode,
2975 operation_id: CheetahString,
2976 timeout_millis: u64,
2977 ) -> ClientResult<ProxyDrainStateResponseBody> {
2978 if operation_id.trim().is_empty() {
2979 return Err(ClientError::illegal_argument(
2980 "Proxy drain operation id must not be empty",
2981 ));
2982 }
2983 let body = ProxyDrainOperationRequestBody {
2984 schema_version: PROXY_DRAIN_SCHEMA_VERSION.to_owned(),
2985 operation_id: operation_id.to_string(),
2986 }
2987 .encode()?;
2988 let request = self.create_request(request_code, body);
2989 let outcome = self
2990 .remoting_client
2991 .invoke_request(Some(addr), request, timeout_millis)
2992 .await;
2993 let response = match outcome {
2994 Ok(OutboundRequestOutcome::Response(response)) => response,
2995 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
2996 return Err(admin_request_error(
2997 "proxy_drain_operation",
2998 RetryInput::Rejected(rejection),
2999 ));
3000 }
3001 Ok(OutboundRequestOutcome::Contract(contract)) => {
3002 return Err(admin_request_error(
3003 "proxy_drain_operation",
3004 RetryInput::Contract(contract),
3005 ));
3006 }
3007 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3008 };
3009 decode_proxy_drain_response(response, "proxy_drain_operation")
3010 }
3011
3012 #[cfg(feature = "admin-mutation")]
3013 pub(crate) async fn update_broker_config(
3014 &self,
3015 addr: &CheetahString,
3016 properties: HashMap<CheetahString, CheetahString>,
3017 timeout_millis: u64,
3018 ) -> ClientResult<()> {
3019 let validator_input = properties
3020 .iter()
3021 .map(|(key, value)| (key.to_string(), value.to_string()))
3022 .collect::<HashMap<String, String>>();
3023 crate::base::validators::Validators::check_broker_config(&validator_input)?;
3024
3025 let body = mix_all::properties_to_string(&properties);
3026 if body.is_empty() {
3027 return Ok(());
3028 }
3029
3030 let request = self
3031 .create_remoting_command(RequestCode::UpdateBrokerConfig)
3032 .set_body(body.to_string());
3033 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
3034 let outcome = self
3035 .remoting_client
3036 .invoke_request(Some(&broker_addr), request, timeout_millis)
3037 .await;
3038 let response = match outcome {
3039 Ok(OutboundRequestOutcome::Response(response)) => response,
3040 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3041 return Err(admin_request_error(
3042 "update_broker_config",
3043 RetryInput::Rejected(rejection),
3044 ));
3045 }
3046 Ok(OutboundRequestOutcome::Contract(contract)) => {
3047 return Err(admin_request_error(
3048 "update_broker_config",
3049 RetryInput::Contract(contract),
3050 ));
3051 }
3052 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3053 };
3054
3055 match ResponseCode::from(response.code()) {
3056 ResponseCode::Success => Ok(()),
3057 _ => Err(mq_client_err!(
3058 response.code(),
3059 response.remark().map_or_else(String::new, |remark| remark.to_string())
3060 )),
3061 }
3062 }
3063
3064 #[cfg(feature = "admin-mutation")]
3065 pub async fn add_broker(
3066 &self,
3067 addr: &CheetahString,
3068 broker_config_path: CheetahString,
3069 timeout_millis: u64,
3070 ) -> ClientResult<()> {
3071 let request_header = AddBrokerRequestHeader {
3072 config_path: Some(broker_config_path),
3073 };
3074 let request = self.create_request_command(RequestCode::AddBroker, request_header);
3075 let outcome = self
3076 .remoting_client
3077 .invoke_request(Some(addr), request, timeout_millis)
3078 .await;
3079 let response = match outcome {
3080 Ok(OutboundRequestOutcome::Response(response)) => response,
3081 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3082 return Err(admin_request_error("add_broker", RetryInput::Rejected(rejection)));
3083 }
3084 Ok(OutboundRequestOutcome::Contract(contract)) => {
3085 return Err(admin_request_error("add_broker", RetryInput::Contract(contract)));
3086 }
3087 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3088 };
3089
3090 match ResponseCode::from(response.code()) {
3091 ResponseCode::Success => Ok(()),
3092 _ => Err(mq_client_err!(
3093 response.code(),
3094 response.remark().map_or_else(String::new, |remark| remark.to_string())
3095 )),
3096 }
3097 }
3098
3099 #[cfg(feature = "admin-mutation")]
3100 pub async fn remove_broker(
3101 &self,
3102 addr: &CheetahString,
3103 cluster_name: CheetahString,
3104 broker_name: CheetahString,
3105 broker_id: u64,
3106 timeout_millis: u64,
3107 ) -> ClientResult<()> {
3108 let request_header = RemoveBrokerRequestHeader {
3109 broker_name,
3110 broker_cluster_name: cluster_name,
3111 broker_id,
3112 };
3113 let request = self.create_request_command(RequestCode::RemoveBroker, request_header);
3114 let outcome = self
3115 .remoting_client
3116 .invoke_request(Some(addr), request, timeout_millis)
3117 .await;
3118 let response = match outcome {
3119 Ok(OutboundRequestOutcome::Response(response)) => response,
3120 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3121 return Err(admin_request_error("remove_broker", RetryInput::Rejected(rejection)));
3122 }
3123 Ok(OutboundRequestOutcome::Contract(contract)) => {
3124 return Err(admin_request_error("remove_broker", RetryInput::Contract(contract)));
3125 }
3126 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3127 };
3128
3129 match ResponseCode::from(response.code()) {
3130 ResponseCode::Success => Ok(()),
3131 _ => Err(mq_client_err!(
3132 response.code(),
3133 response.remark().map_or_else(String::new, |remark| remark.to_string())
3134 )),
3135 }
3136 }
3137
3138 #[cfg(feature = "admin-mutation")]
3139 pub(crate) async fn notify_min_broker_id_changed(
3140 &self,
3141 broker_addr: &CheetahString,
3142 request_header: NotifyMinBrokerIdChangeRequestHeader,
3143 timeout_millis: u64,
3144 ) -> ClientResult<()> {
3145 let request = self.create_request_command(RequestCode::NotifyMinBrokerIdChange, request_header);
3146 self.remoting_client
3147 .invoke_request_oneway(broker_addr, request, timeout_millis)
3148 .await
3149 .map_err(ClientError::from)
3150 }
3151
3152 #[cfg(feature = "admin-mutation")]
3153 pub async fn export_rocksdb_config_to_json(
3154 &self,
3155 broker_addr: CheetahString,
3156 config_types: Vec<CheetahString>,
3157 timeout_millis: u64,
3158 ) -> ClientResult<()> {
3159 let mut config_type = config_types
3160 .into_iter()
3161 .map(|config_type| config_type.as_str().trim().to_string())
3162 .filter(|config_type| !config_type.is_empty())
3163 .collect::<Vec<_>>()
3164 .join(";");
3165 if !config_type.is_empty() {
3166 config_type.push(';');
3167 }
3168
3169 let request_header = ExportRocksdbConfigToJsonRequestHeader {
3170 config_type: CheetahString::from(config_type),
3171 };
3172 let request = self.create_request_command(RequestCode::ExportRocksdbConfigToJson, request_header);
3173 let outcome = self
3174 .remoting_client
3175 .invoke_request(Some(&broker_addr), request, timeout_millis)
3176 .await;
3177 let response = match outcome {
3178 Ok(OutboundRequestOutcome::Response(response)) => response,
3179 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3180 return Err(admin_request_error(
3181 "export_rocksdb_config_to_json",
3182 RetryInput::Rejected(rejection),
3183 ));
3184 }
3185 Ok(OutboundRequestOutcome::Contract(contract)) => {
3186 return Err(admin_request_error(
3187 "export_rocksdb_config_to_json",
3188 RetryInput::Contract(contract),
3189 ));
3190 }
3191 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3192 };
3193
3194 match ResponseCode::from(response.code()) {
3195 ResponseCode::Success => Ok(()),
3196 _ => Err(mq_client_err!(
3197 response.code(),
3198 response.remark().map_or_else(String::new, |remark| remark.to_string())
3199 )),
3200 }
3201 }
3202
3203 #[cfg(feature = "admin-mutation")]
3204 pub async fn export_rocks_db_config_to_json(
3205 &self,
3206 broker_addr: CheetahString,
3207 config_types: Vec<CheetahString>,
3208 timeout_millis: u64,
3209 ) -> ClientResult<()> {
3210 self.export_rocksdb_config_to_json(broker_addr, config_types, timeout_millis)
3211 .await
3212 }
3213
3214 pub(crate) async fn get_all_consumer_offset_json(
3215 &self,
3216 broker_addr: CheetahString,
3217 timeout_millis: u64,
3218 ) -> ClientResult<CheetahString> {
3219 let request = get_all_consumer_offset_request(&self.command_factory);
3220 let outcome = self
3221 .remoting_client
3222 .invoke_request(Some(&broker_addr), request, timeout_millis)
3223 .await;
3224 let response = match outcome {
3225 Ok(OutboundRequestOutcome::Response(response)) => response,
3226 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3227 return Err(admin_request_error(
3228 "get_all_consumer_offset_json",
3229 RetryInput::Rejected(rejection),
3230 ));
3231 }
3232 Ok(OutboundRequestOutcome::Contract(contract)) => {
3233 return Err(admin_request_error(
3234 "get_all_consumer_offset_json",
3235 RetryInput::Contract(contract),
3236 ));
3237 }
3238 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3239 };
3240
3241 consumer_offset_json_from_response(&response)
3242 }
3243
3244 pub(crate) async fn get_broker_config(
3245 &self,
3246 addr: &CheetahString,
3247 timeout_millis: u64,
3248 ) -> ClientResult<HashMap<CheetahString, CheetahString>> {
3249 self.get_broker_config_snapshot(addr, timeout_millis)
3250 .await
3251 .map(|snapshot| snapshot.properties)
3252 }
3253
3254 pub async fn get_name_server_config(
3255 &self,
3256 name_servers: Option<Vec<CheetahString>>,
3257 timeout_millis: Duration,
3258 ) -> ClientResult<Option<HashMap<CheetahString, HashMap<CheetahString, CheetahString>>>> {
3259 let invoke_name_servers = match name_servers {
3261 Some(servers) if !servers.is_empty() => servers,
3262 _ => self.remoting_client.get_name_server_address_list().to_vec(),
3263 };
3264
3265 if invoke_name_servers.is_empty() {
3266 return Ok(None);
3267 }
3268
3269 let request = self.create_remoting_command(RequestCode::GetNamesrvConfig);
3271 let timeout_millis = duration_millis_to_u64("getNameServerConfig", timeout_millis)?;
3272 let mut config_map = HashMap::with_capacity(4);
3273 for name_server in invoke_name_servers {
3275 let outcome = self
3277 .remoting_client
3278 .invoke_request(Some(&name_server), request.clone(), timeout_millis)
3279 .await;
3280 let response = match outcome {
3281 Ok(OutboundRequestOutcome::Response(response)) => response,
3282 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3283 return Err(admin_request_error(
3284 "get_name_server_config",
3285 RetryInput::Rejected(rejection),
3286 ));
3287 }
3288 Ok(OutboundRequestOutcome::Contract(contract)) => {
3289 return Err(admin_request_error(
3290 "get_name_server_config",
3291 RetryInput::Contract(contract),
3292 ));
3293 }
3294 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3295 };
3296 match ResponseCode::from(response.code()) {
3298 ResponseCode::Success => {
3299 match response.get_body() {
3301 Some(body) => {
3302 let body_str = String::from_utf8_lossy(body.as_ref()).to_string();
3303 let properties = if body_str.contains('=') {
3305 mix_all::string_to_properties(&body_str).ok_or_else(|| {
3306 mq_client_err!("Failed to parse namesrv config properties".to_string())
3307 })?
3308 } else {
3309 SerdeJsonUtils::from_json_str::<HashMap<CheetahString, CheetahString>>(&body_str)
3310 .map_err(|source| {
3311 ClientError::response_process_source("parse namesrv config JSON", source)
3312 })?
3313 };
3314
3315 config_map.insert(name_server.clone(), properties);
3316 }
3317 None => return Err(mq_client_err!("Body is empty".to_string())),
3318 }
3319 }
3320 _code => {
3321 return Err(mq_client_err!(
3322 response.code(),
3323 response.remark().map_or("".to_string(), |s| s.to_string())
3324 ));
3325 }
3326 }
3327 }
3328 Ok(Some(config_map))
3329 }
3330
3331 pub async fn probe_name_server(&self, name_server: &CheetahString, timeout_millis: Duration) -> ClientResult<()> {
3332 let request = self.create_request_command(
3333 RequestCode::GetNamesrvConfig,
3334 GetNamesrvConfigRequestHeader::for_probe(),
3335 );
3336 let timeout_millis = duration_millis_to_u64("probeNameServer", timeout_millis)?;
3337 let outcome = self
3338 .remoting_client
3339 .invoke_request(Some(name_server), request, timeout_millis)
3340 .await;
3341 let response = match outcome {
3342 Ok(OutboundRequestOutcome::Response(response)) => response,
3343 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3344 return Err(admin_request_error(
3345 "probe_name_server",
3346 RetryInput::Rejected(rejection),
3347 ));
3348 }
3349 Ok(OutboundRequestOutcome::Contract(contract)) => {
3350 return Err(admin_request_error("probe_name_server", RetryInput::Contract(contract)));
3351 }
3352 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3353 };
3354
3355 match ResponseCode::from(response.code()) {
3356 ResponseCode::Success => Ok(()),
3357 _ => Err(mq_client_err!(
3358 response.code(),
3359 response.remark().map_or("".to_string(), |s| s.to_string())
3360 )),
3361 }
3362 }
3363
3364 pub async fn get_controller_metadata(
3365 &self,
3366 controller_address: CheetahString,
3367 timeout_millis: u64,
3368 ) -> ClientResult<GetMetaDataResponseHeader> {
3369 let request = self.create_remoting_command(RequestCode::ControllerGetMetadataInfo);
3370 let outcome = self
3371 .remoting_client
3372 .invoke_request(Some(&controller_address), request, timeout_millis)
3373 .await;
3374 let response = match outcome {
3375 Ok(OutboundRequestOutcome::Response(response)) => response,
3376 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3377 return Err(admin_request_error(
3378 "get_controller_metadata",
3379 RetryInput::Rejected(rejection),
3380 ));
3381 }
3382 Ok(OutboundRequestOutcome::Contract(contract)) => {
3383 return Err(admin_request_error(
3384 "get_controller_metadata",
3385 RetryInput::Contract(contract),
3386 ));
3387 }
3388 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3389 };
3390 match ResponseCode::from(response.code()) {
3391 ResponseCode::Success => response
3392 .decode_command_custom_header_fast::<GetMetaDataResponseHeader>()
3393 .map_err(|source| ClientError::response_process_source("decode GetMetaDataResponseHeader", source)),
3394 _ => Err(mq_client_err!(
3395 response.code(),
3396 response.remark().map_or("".to_string(), |s| s.to_string())
3397 )),
3398 }
3399 }
3400
3401 pub async fn get_controller_meta_data(
3402 &self,
3403 controller_address: CheetahString,
3404 timeout_millis: u64,
3405 ) -> ClientResult<GetMetaDataResponseHeader> {
3406 self.get_controller_metadata(controller_address, timeout_millis).await
3407 }
3408
3409 pub async fn get_in_sync_state_data(
3410 &self,
3411 controller_address: CheetahString,
3412 brokers: Vec<CheetahString>,
3413 timeout_millis: u64,
3414 ) -> ClientResult<BrokerReplicasInfo> {
3415 let controller_meta_data = self.get_controller_metadata(controller_address, timeout_millis).await?;
3416 let leader_address = controller_leader_address(controller_meta_data)?;
3417
3418 self.get_in_sync_state_data_at(leader_address, brokers, timeout_millis)
3419 .await
3420 }
3421
3422 pub async fn get_in_sync_state_data_at(
3427 &self,
3428 controller_address: CheetahString,
3429 brokers: Vec<CheetahString>,
3430 timeout_millis: u64,
3431 ) -> ClientResult<BrokerReplicasInfo> {
3432 let request = self.create_remoting_command(RequestCode::ControllerGetSyncStateData);
3433 let body =
3434 serde_json::to_vec(&brokers).map_err(|source| ClientError::internal("serialize broker names", source))?;
3435 let request = request.set_body(body);
3436 let outcome = self
3437 .remoting_client
3438 .invoke_request(Some(&controller_address), request, timeout_millis)
3439 .await;
3440 let response = match outcome {
3441 Ok(OutboundRequestOutcome::Response(response)) => response,
3442 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3443 return Err(admin_request_error(
3444 "get_in_sync_state_data_at",
3445 RetryInput::Rejected(rejection),
3446 ));
3447 }
3448 Ok(OutboundRequestOutcome::Contract(contract)) => {
3449 return Err(admin_request_error(
3450 "get_in_sync_state_data_at",
3451 RetryInput::Contract(contract),
3452 ));
3453 }
3454 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3455 };
3456 match ResponseCode::from(response.code()) {
3457 ResponseCode::Success => {
3458 if let Some(body) = response.body() {
3459 serde_json::from_slice(body)
3460 .map_err(|source| ClientError::response_process_source("decode BrokerReplicasInfo", source))
3461 } else {
3462 Err(mq_client_err!(
3463 "get_in_sync_state_data response body is empty".to_string()
3464 ))
3465 }
3466 }
3467 _ => Err(mq_client_err!(
3468 response.code(),
3469 response.remark().map_or("".to_string(), |s| s.to_string())
3470 )),
3471 }
3472 }
3473
3474 pub async fn get_broker_epoch_cache(
3475 &self,
3476 broker_addr: CheetahString,
3477 timeout_millis: u64,
3478 ) -> ClientResult<EpochEntryCache> {
3479 let request = self.create_remoting_command(RequestCode::GetBrokerEpochCache);
3480 let outcome = self
3481 .remoting_client
3482 .invoke_request(Some(&broker_addr), request, timeout_millis)
3483 .await;
3484 let response = match outcome {
3485 Ok(OutboundRequestOutcome::Response(response)) => response,
3486 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3487 return Err(admin_request_error(
3488 "get_broker_epoch_cache",
3489 RetryInput::Rejected(rejection),
3490 ));
3491 }
3492 Ok(OutboundRequestOutcome::Contract(contract)) => {
3493 return Err(admin_request_error(
3494 "get_broker_epoch_cache",
3495 RetryInput::Contract(contract),
3496 ));
3497 }
3498 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3499 };
3500 match ResponseCode::from(response.code()) {
3501 ResponseCode::Success => {
3502 if let Some(body) = response.body() {
3503 match EpochEntryCache::decode(body) {
3504 Ok(value) => Ok(value),
3505 Err(e) => Err(mq_client_err!(format!("decode EpochEntryCache failed: {}", e))),
3506 }
3507 } else {
3508 Err(mq_client_err!(
3509 "get_broker_epoch_cache response body is empty".to_string()
3510 ))
3511 }
3512 }
3513 _ => Err(mq_client_err!(
3514 response.code(),
3515 response.remark().map_or("".to_string(), |s| s.to_string())
3516 )),
3517 }
3518 }
3519
3520 pub async fn get_broker_ha_status(
3521 &self,
3522 broker_addr: CheetahString,
3523 timeout_millis: u64,
3524 ) -> ClientResult<HARuntimeInfo> {
3525 let request = self.create_remoting_command(RequestCode::GetBrokerHaStatus);
3526 let outcome = self
3527 .remoting_client
3528 .invoke_request(Some(&broker_addr), request, timeout_millis)
3529 .await;
3530 let response = match outcome {
3531 Ok(OutboundRequestOutcome::Response(response)) => response,
3532 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3533 return Err(admin_request_error(
3534 "get_broker_ha_status",
3535 RetryInput::Rejected(rejection),
3536 ));
3537 }
3538 Ok(OutboundRequestOutcome::Contract(contract)) => {
3539 return Err(admin_request_error(
3540 "get_broker_ha_status",
3541 RetryInput::Contract(contract),
3542 ));
3543 }
3544 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3545 };
3546 match ResponseCode::from(response.code()) {
3547 ResponseCode::Success => {
3548 if let Some(body) = response.body() {
3549 serde_json::from_slice(body)
3550 .map_err(|source| ClientError::response_process_source("decode HARuntimeInfo", source))
3551 } else {
3552 Err(mq_client_err!("get_broker_ha_status response body is empty".to_string()))
3553 }
3554 }
3555 _ => Err(mq_client_err!(
3556 response.code(),
3557 response.remark().map_or("".to_string(), |s| s.to_string())
3558 )),
3559 }
3560 }
3561
3562 #[cfg(feature = "admin-mutation")]
3563 pub async fn update_and_get_group_forbidden(
3564 &self,
3565 broker_addr: &CheetahString,
3566 request_header: UpdateGroupForbiddenRequestHeader,
3567 timeout_millis: u64,
3568 ) -> ClientResult<GroupForbidden> {
3569 let request = self.create_request_command(RequestCode::UpdateAndGetGroupForbidden, request_header);
3570 let broker_addr_vip = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, broker_addr.as_str());
3571 let outcome = self
3572 .remoting_client
3573 .invoke_request(Some(&broker_addr_vip), request, timeout_millis)
3574 .await;
3575 let response = match outcome {
3576 Ok(OutboundRequestOutcome::Response(response)) => response,
3577 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3578 return Err(admin_request_error(
3579 "update_and_get_group_forbidden",
3580 RetryInput::Rejected(rejection),
3581 ));
3582 }
3583 Ok(OutboundRequestOutcome::Contract(contract)) => {
3584 return Err(admin_request_error(
3585 "update_and_get_group_forbidden",
3586 RetryInput::Contract(contract),
3587 ));
3588 }
3589 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3590 };
3591
3592 match ResponseCode::from(response.code()) {
3593 ResponseCode::Success => {
3594 let Some(body) = response.body() else {
3595 return Err(mq_client_err!(
3596 "update_and_get_group_forbidden response body is empty".to_string()
3597 ));
3598 };
3599 SerdeJsonUtils::from_json_slice(body.as_ref())
3600 .map_err(|source| ClientError::response_process_source("decode GroupForbidden", source))
3601 }
3602 _ => Err(client_broker_err!(
3603 response.code(),
3604 response.remark().map_or_else(String::new, |remark| remark.to_string()),
3605 broker_addr
3606 )),
3607 }
3608 }
3609
3610 pub async fn get_controller_config(
3611 &self,
3612 controller_address: CheetahString,
3613 timeout_millis: u64,
3614 ) -> ClientResult<HashMap<CheetahString, CheetahString>> {
3615 let request = self.create_remoting_command(RequestCode::GetControllerConfig);
3616 let outcome = self
3617 .remoting_client
3618 .invoke_request(Some(&controller_address), request, timeout_millis)
3619 .await;
3620 let response = match outcome {
3621 Ok(OutboundRequestOutcome::Response(response)) => response,
3622 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3623 return Err(admin_request_error(
3624 "get_controller_config",
3625 RetryInput::Rejected(rejection),
3626 ));
3627 }
3628 Ok(OutboundRequestOutcome::Contract(contract)) => {
3629 return Err(admin_request_error(
3630 "get_controller_config",
3631 RetryInput::Contract(contract),
3632 ));
3633 }
3634 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3635 };
3636
3637 match ResponseCode::from(response.code()) {
3638 ResponseCode::Success => {
3639 let body = response
3640 .body()
3641 .ok_or_else(|| mq_client_err!("Controller config response body is empty".to_string()))?;
3642 controller_config_from_response_body(body)
3643 }
3644 _ => Err(mq_client_err!(
3645 response.code(),
3646 response.remark().map_or("".to_string(), |s| s.to_string())
3647 )),
3648 }
3649 }
3650
3651 #[cfg(feature = "admin-mutation")]
3652 pub async fn update_controller_config(
3653 &self,
3654 properties: HashMap<CheetahString, CheetahString>,
3655 controllers: Vec<CheetahString>,
3656 timeout_millis: u64,
3657 ) -> ClientResult<()> {
3658 let body = mix_all::properties_to_string(&properties);
3659 if body.is_empty() || controllers.is_empty() {
3660 return Ok(());
3661 }
3662
3663 let request = self
3664 .create_remoting_command(RequestCode::UpdateControllerConfig)
3665 .set_body(body.to_string());
3666 let mut err_response = None;
3667 for controller_addr in controllers {
3668 let outcome = self
3669 .remoting_client
3670 .invoke_request(Some(&controller_addr), request.clone(), timeout_millis)
3671 .await;
3672 let response = match outcome {
3673 Ok(OutboundRequestOutcome::Response(response)) => response,
3674 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3675 return Err(admin_request_error(
3676 "update_controller_config",
3677 RetryInput::Rejected(rejection),
3678 ));
3679 }
3680 Ok(OutboundRequestOutcome::Contract(contract)) => {
3681 return Err(admin_request_error(
3682 "update_controller_config",
3683 RetryInput::Contract(contract),
3684 ));
3685 }
3686 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3687 };
3688 match ResponseCode::from(response.code()) {
3689 ResponseCode::Success => {}
3690 _ => err_response = Some(response),
3691 }
3692 }
3693
3694 if let Some(err_response) = err_response {
3695 return Err(mq_client_err!(
3696 err_response.code(),
3697 err_response
3698 .remark()
3699 .map_or_else(String::new, |remark| remark.to_string())
3700 ));
3701 }
3702 Ok(())
3703 }
3704
3705 #[cfg(feature = "admin-mutation")]
3706 pub async fn elect_master(
3707 &self,
3708 controller_addr: CheetahString,
3709 cluster_name: CheetahString,
3710 broker_name: CheetahString,
3711 broker_id: Option<u64>,
3712 timeout_millis: u64,
3713 ) -> ClientResult<(ElectMasterResponseHeader, BrokerMemberGroup)> {
3714 let controller_meta_data = self.get_controller_metadata(controller_addr, timeout_millis).await?;
3715 let leader_address = controller_leader_address(controller_meta_data)?;
3716 let designate_elect = broker_id.is_some();
3717 let broker_id = match broker_id {
3718 Some(broker_id) => i64::try_from(broker_id).map_err(ClientError::illegal_argument_source)?,
3719 None => -1,
3720 };
3721 let request_header = ElectMasterRequestHeader::new(
3722 cluster_name,
3723 broker_name,
3724 broker_id,
3725 designate_elect,
3726 rocketmq_runtime::common::time_utils::current_millis(),
3727 );
3728 let request = self.create_request_command(RequestCode::ControllerElectMaster, request_header);
3729 let outcome = self
3730 .remoting_client
3731 .invoke_request(Some(&leader_address), request, timeout_millis)
3732 .await;
3733 let response = match outcome {
3734 Ok(OutboundRequestOutcome::Response(response)) => response,
3735 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3736 return Err(admin_request_error("elect_master", RetryInput::Rejected(rejection)));
3737 }
3738 Ok(OutboundRequestOutcome::Contract(contract)) => {
3739 return Err(admin_request_error("elect_master", RetryInput::Contract(contract)));
3740 }
3741 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3742 };
3743
3744 match ResponseCode::from(response.code()) {
3745 ResponseCode::Success => {
3746 let response_header = response
3747 .decode_command_custom_header_fast::<ElectMasterResponseHeader>()
3748 .map_err(|source| {
3749 ClientError::response_process_source("decode ElectMasterResponseHeader", source)
3750 })?;
3751 let broker_member_group = response
3752 .body()
3753 .ok_or_else(|| mq_client_err!("elect_master response body is empty".to_string()))
3754 .and_then(|body| {
3755 serde_json::from_slice::<BrokerMemberGroup>(body)
3756 .map_err(|source| ClientError::response_process_source("decode BrokerMemberGroup", source))
3757 })?;
3758 Ok((response_header, broker_member_group))
3759 }
3760 _ => Err(mq_client_err!(
3761 response.code(),
3762 response.remark().map_or_else(String::new, |remark| remark.to_string())
3763 )),
3764 }
3765 }
3766
3767 #[cfg(feature = "admin-mutation")]
3768 pub async fn clean_controller_broker_data(
3769 &self,
3770 controller_addr: CheetahString,
3771 cluster_name: CheetahString,
3772 broker_name: CheetahString,
3773 broker_controller_ids_to_clean: Option<CheetahString>,
3774 clean_living_broker: bool,
3775 timeout_millis: u64,
3776 ) -> ClientResult<()> {
3777 let controller_meta_data = self.get_controller_metadata(controller_addr, timeout_millis).await?;
3778 let leader_address = controller_leader_address(controller_meta_data)?;
3779 let request_header = CleanBrokerDataRequestHeader {
3780 cluster_name: if cluster_name.is_empty() {
3781 None
3782 } else {
3783 Some(cluster_name)
3784 },
3785 broker_name,
3786 broker_controller_ids_to_clean,
3787 clean_living_broker,
3788 ..Default::default()
3789 };
3790 let request = self.create_request_command(RequestCode::CleanBrokerData, request_header);
3791 let outcome = self
3792 .remoting_client
3793 .invoke_request(Some(&leader_address), request, timeout_millis)
3794 .await;
3795 let response = match outcome {
3796 Ok(OutboundRequestOutcome::Response(response)) => response,
3797 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3798 return Err(admin_request_error(
3799 "clean_controller_broker_data",
3800 RetryInput::Rejected(rejection),
3801 ));
3802 }
3803 Ok(OutboundRequestOutcome::Contract(contract)) => {
3804 return Err(admin_request_error(
3805 "clean_controller_broker_data",
3806 RetryInput::Contract(contract),
3807 ));
3808 }
3809 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3810 };
3811
3812 match ResponseCode::from(response.code()) {
3813 ResponseCode::Success => Ok(()),
3814 _ => Err(mq_client_err!(
3815 response.code(),
3816 response.remark().map_or_else(String::new, |remark| remark.to_string())
3817 )),
3818 }
3819 }
3820
3821 #[cfg(feature = "admin-mutation")]
3822 pub async fn update_cold_data_flow_ctr_group_config(
3823 &self,
3824 broker_addr: CheetahString,
3825 properties: HashMap<CheetahString, CheetahString>,
3826 timeout_millis: u64,
3827 ) -> ClientResult<()> {
3828 let body = mix_all::properties_to_string(&properties);
3829 if body.is_empty() {
3830 return Ok(());
3831 }
3832
3833 let request = self.create_remoting_command(RequestCode::UpdateColdDataFlowCtrConfig);
3834 let request = request.set_body(body.to_string());
3835 let broker_addr =
3836 mix_all::broker_vip_channel(self.client_config.is_vip_channel_enabled(), broker_addr.as_str());
3837 let outcome = self
3838 .remoting_client
3839 .invoke_request(Some(broker_addr).as_ref(), request, timeout_millis)
3840 .await;
3841 let response = match outcome {
3842 Ok(OutboundRequestOutcome::Response(response)) => response,
3843 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3844 return Err(admin_request_error(
3845 "update_cold_data_flow_ctr_group_config",
3846 RetryInput::Rejected(rejection),
3847 ));
3848 }
3849 Ok(OutboundRequestOutcome::Contract(contract)) => {
3850 return Err(admin_request_error(
3851 "update_cold_data_flow_ctr_group_config",
3852 RetryInput::Contract(contract),
3853 ));
3854 }
3855 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3856 };
3857
3858 match ResponseCode::from(response.code()) {
3859 ResponseCode::Success => Ok(()),
3860 _ => Err(mq_client_err!(
3861 response.code(),
3862 response.remark().map(|s| s.to_string()).unwrap_or_default()
3863 )),
3864 }
3865 }
3866
3867 #[cfg(feature = "admin-mutation")]
3868 pub async fn remove_cold_data_flow_ctr_group_config(
3869 &self,
3870 broker_addr: CheetahString,
3871 consumer_group: CheetahString,
3872 timeout_millis: u64,
3873 ) -> ClientResult<()> {
3874 if consumer_group.is_empty() {
3875 return Ok(());
3876 }
3877
3878 let request = self
3879 .create_request_command(RequestCode::RemoveColdDataFlowCtrConfig, EmptyHeader {})
3880 .set_body(consumer_group.to_string());
3881 let broker_addr_vip =
3882 mix_all::broker_vip_channel(self.client_config.is_vip_channel_enabled(), broker_addr.as_str());
3883 let outcome = self
3884 .remoting_client
3885 .invoke_request(Some(&broker_addr_vip), request, timeout_millis)
3886 .await;
3887 let response = match outcome {
3888 Ok(OutboundRequestOutcome::Response(response)) => response,
3889 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3890 return Err(admin_request_error(
3891 "remove_cold_data_flow_ctr_group_config",
3892 RetryInput::Rejected(rejection),
3893 ));
3894 }
3895 Ok(OutboundRequestOutcome::Contract(contract)) => {
3896 return Err(admin_request_error(
3897 "remove_cold_data_flow_ctr_group_config",
3898 RetryInput::Contract(contract),
3899 ));
3900 }
3901 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3902 };
3903
3904 match ResponseCode::from(response.code()) {
3905 ResponseCode::Success => Ok(()),
3906 _ => Err(client_broker_err!(
3907 response.code(),
3908 response.remark().map_or_else(String::new, |remark| remark.to_string()),
3909 &broker_addr
3910 )),
3911 }
3912 }
3913
3914 pub async fn get_cold_data_flow_ctr_info(
3915 &self,
3916 broker_addr: CheetahString,
3917 timeout_millis: u64,
3918 ) -> ClientResult<CheetahString> {
3919 let request = self.create_request_command(RequestCode::GetColdDataFlowCtrInfo, EmptyHeader {});
3920 let outcome = self
3921 .remoting_client
3922 .invoke_request(Some(&broker_addr), request, timeout_millis)
3923 .await;
3924 let response = match outcome {
3925 Ok(OutboundRequestOutcome::Response(response)) => response,
3926 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3927 return Err(admin_request_error(
3928 "get_cold_data_flow_ctr_info",
3929 RetryInput::Rejected(rejection),
3930 ));
3931 }
3932 Ok(OutboundRequestOutcome::Contract(contract)) => {
3933 return Err(admin_request_error(
3934 "get_cold_data_flow_ctr_info",
3935 RetryInput::Contract(contract),
3936 ));
3937 }
3938 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3939 };
3940
3941 match ResponseCode::from(response.code()) {
3942 ResponseCode::Success => Ok(response
3943 .body()
3944 .filter(|body| !body.is_empty())
3945 .map(|body| CheetahString::from_string(String::from_utf8_lossy(body.as_ref()).into_owned()))
3946 .unwrap_or_default()),
3947 _ => Err(client_broker_err!(
3948 response.code(),
3949 response.remark().map_or_else(String::new, |remark| remark.to_string()),
3950 &broker_addr
3951 )),
3952 }
3953 }
3954
3955 #[cfg(feature = "admin-mutation")]
3956 pub async fn set_commit_log_read_ahead_mode(
3957 &self,
3958 broker_addr: CheetahString,
3959 mode: CheetahString,
3960 timeout_millis: u64,
3961 ) -> ClientResult<CheetahString> {
3962 let mut request = self.create_request_command(RequestCode::SetCommitlogReadMode, EmptyHeader {});
3963 request.ensure_ext_fields_initialized();
3964 request.add_ext_field(file_readahead_mode::READ_AHEAD_MODE, mode);
3965 let outcome = self
3966 .remoting_client
3967 .invoke_request(Some(&broker_addr), request, timeout_millis)
3968 .await;
3969 let response = match outcome {
3970 Ok(OutboundRequestOutcome::Response(response)) => response,
3971 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
3972 return Err(admin_request_error(
3973 "set_commit_log_read_ahead_mode",
3974 RetryInput::Rejected(rejection),
3975 ));
3976 }
3977 Ok(OutboundRequestOutcome::Contract(contract)) => {
3978 return Err(admin_request_error(
3979 "set_commit_log_read_ahead_mode",
3980 RetryInput::Contract(contract),
3981 ));
3982 }
3983 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
3984 };
3985
3986 match ResponseCode::from(response.code()) {
3987 ResponseCode::Success => Ok(response.remark().cloned().unwrap_or_default()),
3988 _ => Err(client_broker_err!(
3989 response.code(),
3990 response.remark().map_or_else(String::new, |remark| remark.to_string()),
3991 &broker_addr
3992 )),
3993 }
3994 }
3995
3996 #[cfg(feature = "admin-mutation")]
3997 pub async fn export_pop_record(&self, broker_addr: CheetahString, timeout_millis: u64) -> ClientResult<()> {
3998 let request = self.create_request_command(RequestCode::PopRollback, EmptyHeader {});
3999 let outcome = self
4000 .remoting_client
4001 .invoke_request(Some(&broker_addr), request, timeout_millis)
4002 .await;
4003 let response = match outcome {
4004 Ok(OutboundRequestOutcome::Response(response)) => response,
4005 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
4006 return Err(admin_request_error(
4007 "export_pop_record",
4008 RetryInput::Rejected(rejection),
4009 ));
4010 }
4011 Ok(OutboundRequestOutcome::Contract(contract)) => {
4012 return Err(admin_request_error("export_pop_record", RetryInput::Contract(contract)));
4013 }
4014 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
4015 };
4016
4017 if ResponseCode::from(response.code()) == ResponseCode::Success {
4018 return Ok(());
4019 }
4020
4021 Err(client_broker_err!(
4022 response.code(),
4023 response.remark().map_or_else(String::new, |remark| remark.to_string()),
4024 &broker_addr
4025 ))
4026 }
4027
4028 pub(crate) async fn get_all_subscription_group(
4029 &self,
4030 addr: &CheetahString,
4031 timeout_millis: u64,
4032 ) -> ClientResult<rocketmq_protocol::protocol::body::subscription_group_wrapper::SubscriptionGroupWrapper> {
4033 self.get_all_subscription_group_config(addr, timeout_millis).await
4034 }
4035
4036 pub(crate) async fn get_subscription_group_config(
4037 &self,
4038 addr: &CheetahString,
4039 group: CheetahString,
4040 timeout_millis: u64,
4041 ) -> ClientResult<rocketmq_protocol::protocol::subscription::subscription_group_config::SubscriptionGroupConfig>
4042 {
4043 let request = self.create_request_command(
4044 RequestCode::GetSubscriptionGroupConfig,
4045 rocketmq_protocol::protocol::header::get_subscription_group_config_request_header::GetSubscriptionGroupConfigRequestHeader {
4046 group,
4047 rpc_request_header: None,
4048 },
4049 );
4050 let outcome = self
4051 .remoting_client
4052 .invoke_request(Some(addr), request, timeout_millis)
4053 .await;
4054 let mut response = match outcome {
4055 Ok(OutboundRequestOutcome::Response(response)) => response,
4056 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
4057 return Err(admin_request_error(
4058 "get_subscription_group_config",
4059 RetryInput::Rejected(rejection),
4060 ));
4061 }
4062 Ok(OutboundRequestOutcome::Contract(contract)) => {
4063 return Err(admin_request_error(
4064 "get_subscription_group_config",
4065 RetryInput::Contract(contract),
4066 ));
4067 }
4068 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
4069 };
4070 if ResponseCode::from(response.code()) == ResponseCode::Success {
4071 if let Some(body) = response.take_body() {
4072 return rocketmq_protocol::protocol::subscription::subscription_group_config::SubscriptionGroupConfig::decode(
4073 body.as_ref(),
4074 )
4075 .map_err(ClientError::from);
4076 }
4077 }
4078 Err(mq_client_err!(
4079 response.code(),
4080 response.remark().map_or("".to_string(), |s| s.to_string())
4081 ))
4082 }
4083
4084 #[cfg(feature = "admin-mutation")]
4085 pub(crate) async fn create_subscription_group(
4086 &self,
4087 addr: &CheetahString,
4088 config: &rocketmq_protocol::protocol::subscription::subscription_group_config::SubscriptionGroupConfig,
4089 timeout_millis: u64,
4090 ) -> ClientResult<()> {
4091 let request = self
4092 .create_request_command(RequestCode::UpdateAndCreateSubscriptionGroup, EmptyHeader {})
4093 .set_body(config.encode()?);
4094 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
4095 let outcome = self
4096 .remoting_client
4097 .invoke_request(Some(&broker_addr), request, timeout_millis)
4098 .await;
4099 let response = match outcome {
4100 Ok(OutboundRequestOutcome::Response(response)) => response,
4101 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
4102 return Err(admin_request_error(
4103 "create_subscription_group",
4104 RetryInput::Rejected(rejection),
4105 ));
4106 }
4107 Ok(OutboundRequestOutcome::Contract(contract)) => {
4108 return Err(admin_request_error(
4109 "create_subscription_group",
4110 RetryInput::Contract(contract),
4111 ));
4112 }
4113 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
4114 };
4115
4116 match ResponseCode::from(response.code()) {
4117 ResponseCode::Success => Ok(()),
4118 _ => Err(mq_client_err!(
4119 response.code(),
4120 response.remark().map_or_else(String::new, |remark| remark.to_string())
4121 )),
4122 }
4123 }
4124
4125 pub(crate) async fn get_consumer_running_info(
4126 &self,
4127 addr: &CheetahString,
4128 consumer_group: CheetahString,
4129 client_id: CheetahString,
4130 jstack: bool,
4131 timeout_millis: u64,
4132 ) -> ClientResult<rocketmq_protocol::protocol::body::consumer_running_info::ConsumerRunningInfo> {
4133 let request_header = GetConsumerRunningInfoRequestHeader {
4134 consumer_group,
4135 client_id,
4136 jstack_enable: jstack,
4137 rpc_request_header: None,
4138 };
4139 let request = self.create_request_command(RequestCode::GetConsumerRunningInfo, request_header);
4140 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
4141 let outcome = self
4142 .remoting_client
4143 .invoke_request(Some(&broker_addr), request, timeout_millis)
4144 .await;
4145 let mut response = match outcome {
4146 Ok(OutboundRequestOutcome::Response(response)) => response,
4147 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
4148 return Err(admin_request_error(
4149 "get_consumer_running_info",
4150 RetryInput::Rejected(rejection),
4151 ));
4152 }
4153 Ok(OutboundRequestOutcome::Contract(contract)) => {
4154 return Err(admin_request_error(
4155 "get_consumer_running_info",
4156 RetryInput::Contract(contract),
4157 ));
4158 }
4159 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
4160 };
4161
4162 match ResponseCode::from(response.code()) {
4163 ResponseCode::Success => {
4164 let Some(body) = response.take_body() else {
4165 return Err(mq_client_err!(
4166 "get_consumer_running_info response body is empty".to_string()
4167 ));
4168 };
4169 rocketmq_protocol::protocol::body::consumer_running_info::ConsumerRunningInfo::decode(&body)
4170 .map_err(|source| ClientError::response_process_source("decode get_consumer_running_info", source))
4171 }
4172 _ => Err(mq_client_err!(
4173 response.code(),
4174 response.remark().map_or_else(String::new, |remark| remark.to_string())
4175 )),
4176 }
4177 }
4178
4179 #[cfg(feature = "admin-mutation")]
4180 pub(crate) async fn consume_message_directly(
4181 &self,
4182 client_addr: &CheetahString,
4183 request_header: ConsumeMessageDirectlyResultRequestHeader,
4184 message: &MessageExt,
4185 timeout_millis: u64,
4186 ) -> ClientResult<rocketmq_protocol::protocol::body::consume_message_directly_result::ConsumeMessageDirectlyResult>
4187 {
4188 let body = MessageDecoder::encode(message, false)?;
4189 let request = self
4190 .create_request_command(RequestCode::ConsumeMessageDirectly, request_header)
4191 .set_body(body.to_vec());
4192 let outcome = self
4193 .remoting_client
4194 .invoke_request(Some(client_addr), request, timeout_millis)
4195 .await;
4196 let mut response = match outcome {
4197 Ok(OutboundRequestOutcome::Response(response)) => response,
4198 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
4199 return Err(admin_request_error(
4200 "consume_message_directly",
4201 RetryInput::Rejected(rejection),
4202 ));
4203 }
4204 Ok(OutboundRequestOutcome::Contract(contract)) => {
4205 return Err(admin_request_error(
4206 "consume_message_directly",
4207 RetryInput::Contract(contract),
4208 ));
4209 }
4210 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
4211 };
4212
4213 match ResponseCode::from(response.code()) {
4214 ResponseCode::Success => {
4215 if let Some(body) = response.take_body() {
4216 rocketmq_protocol::protocol::body::consume_message_directly_result::ConsumeMessageDirectlyResult::decode(
4217 body.as_ref(),
4218 )
4219 .map_err(ClientError::from)
4220 } else {
4221 Err(mq_client_err!(
4222 "consume_message_directly response body is empty".to_string()
4223 ))
4224 }
4225 }
4226 _ => Err(mq_client_err!(
4227 response.code(),
4228 response.remark().map_or_else(String::new, |remark| remark.to_string())
4229 )),
4230 }
4231 }
4232
4233 pub async fn view_message(
4234 &self,
4235 addr: &CheetahString,
4236 request_header: ViewMessageRequestHeader,
4237 timeout_millis: u64,
4238 ) -> ClientResult<MessageExt> {
4239 let request = self.create_request_command(RequestCode::ViewMessageById, request_header);
4240 let outcome = self
4241 .remoting_client
4242 .invoke_request(Some(addr), request, timeout_millis)
4243 .await;
4244 let response = match outcome {
4245 Ok(OutboundRequestOutcome::Response(response)) => response,
4246 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
4247 return Err(admin_request_error("view_message", RetryInput::Rejected(rejection)));
4248 }
4249 Ok(OutboundRequestOutcome::Contract(contract)) => {
4250 return Err(admin_request_error("view_message", RetryInput::Contract(contract)));
4251 }
4252 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
4253 };
4254
4255 match ResponseCode::from(response.code()) {
4256 ResponseCode::Success => {
4257 if let Some(body) = response.get_body() {
4258 let mut bytes = body.clone();
4259 let body_len = bytes.len();
4260 MessageDecoder::decode(&mut bytes, true, true, false, false, false).ok_or_else(|| {
4261 mq_client_err!(format!(
4262 "Failed to decode message from view_message response body: body_len={}, possible causes: \
4263 CRC check failed or malformed message data",
4264 body_len
4265 ))
4266 })
4267 } else {
4268 Err(mq_client_err!("view_message response body is empty".to_string()))
4269 }
4270 }
4271 _ => Err(mq_client_err!(
4272 response.code(),
4273 response.remark().map_or("".to_string(), |s| s.to_string())
4274 )),
4275 }
4276 }
4277
4278 #[cfg(feature = "admin-mutation")]
4279 pub(crate) async fn resume_check_half_message(
4280 &self,
4281 addr: &CheetahString,
4282 topic: CheetahString,
4283 msg_id: CheetahString,
4284 timeout_millis: u64,
4285 ) -> ClientResult<bool> {
4286 let request_header = ResumeCheckHalfMessageRequestHeader {
4287 topic,
4288 msg_id: Some(msg_id),
4289 };
4290 let request = self.create_request_command(RequestCode::ResumeCheckHalfMessage, request_header);
4291 let broker_addr = mix_all::broker_vip_channel(self.client_config.vip_channel_enabled, addr.as_str());
4292 let outcome = self
4293 .remoting_client
4294 .invoke_request(Some(&broker_addr), request, timeout_millis)
4295 .await;
4296 let response = match outcome {
4297 Ok(OutboundRequestOutcome::Response(response)) => response,
4298 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
4299 return Err(admin_request_error(
4300 "resume_check_half_message",
4301 RetryInput::Rejected(rejection),
4302 ));
4303 }
4304 Ok(OutboundRequestOutcome::Contract(contract)) => {
4305 return Err(admin_request_error(
4306 "resume_check_half_message",
4307 RetryInput::Contract(contract),
4308 ));
4309 }
4310 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
4311 };
4312
4313 match ResponseCode::from(response.code()) {
4314 ResponseCode::Success => Ok(true),
4315 _ => {
4316 error!(
4317 "Failed to resume half message check logic. Remark={}",
4318 response.remark().map_or("", |remark| remark)
4319 );
4320 Ok(false)
4321 }
4322 }
4323 }
4324
4325 #[cfg(feature = "admin-mutation")]
4326 pub(crate) async fn switch_timer_engine(
4327 &self,
4328 broker_addr: &CheetahString,
4329 engine_type: CheetahString,
4330 timeout_millis: u64,
4331 ) -> ClientResult<()> {
4332 let mut request = self.create_request_command(RequestCode::SwitchTimerEngine, EmptyHeader {});
4333 request.ensure_ext_fields_initialized();
4334 request.add_ext_field(MessageConst::TIMER_ENGINE_TYPE, engine_type);
4335 let outcome = self
4336 .remoting_client
4337 .invoke_request(Some(broker_addr), request, timeout_millis)
4338 .await;
4339 let response = match outcome {
4340 Ok(OutboundRequestOutcome::Response(response)) => response,
4341 Ok(OutboundRequestOutcome::Rejected(rejection)) => {
4342 return Err(admin_request_error(
4343 "switch_timer_engine",
4344 RetryInput::Rejected(rejection),
4345 ));
4346 }
4347 Ok(OutboundRequestOutcome::Contract(contract)) => {
4348 return Err(admin_request_error(
4349 "switch_timer_engine",
4350 RetryInput::Contract(contract),
4351 ));
4352 }
4353 Err(error) => return Err(ClientError::from_shared(error.into_shared_error())),
4354 };
4355
4356 if ResponseCode::from(response.code()) == ResponseCode::Success {
4357 return Ok(());
4358 }
4359
4360 Err(client_broker_err!(
4361 response.code(),
4362 response.remark().map_or("".to_string(), |s| s.to_string()),
4363 broker_addr
4364 ))
4365 }
4366
4367 pub(super) fn build_queue_offset_sorted_map(
4368 topic: &str,
4369 msg_found_list: &[MessageExt],
4370 ) -> ClientResult<HashMap<String, Vec<i64>>> {
4371 let mut sort_map: HashMap<String, Vec<i64>> = HashMap::with_capacity(16);
4372 for message_ext in msg_found_list {
4373 let key: String;
4374 let dispatch = message_ext
4375 .property(&CheetahString::from_static_str(
4376 MessageConst::PROPERTY_INNER_MULTI_DISPATCH,
4377 ))
4378 .unwrap_or_default();
4379 if mix_all::is_lmq(Some(topic)) && message_ext.reconsume_times() == 0 && !dispatch.is_empty() {
4380 let queues: Vec<&str> = dispatch.split_str(mix_all::MULTI_DISPATCH_QUEUE_SPLITTER).collect();
4382 let data = message_ext
4383 .property(&CheetahString::from_static_str(
4384 MessageConst::PROPERTY_INNER_MULTI_QUEUE_OFFSET,
4385 ))
4386 .unwrap_or_default();
4387 let queue_offsets: Vec<&str> = data.split_str(mix_all::MULTI_DISPATCH_QUEUE_SPLITTER).collect();
4388 key = ExtraInfoUtil::get_start_offset_info_map_key(topic, mix_all::LMQ_QUEUE_ID as i64);
4390 let Some(position) = queues.iter().position(|&q| q == topic) else {
4391 warn!(
4392 "LMQ dispatch queue does not contain topic={}, dispatch={}",
4393 topic, dispatch
4394 );
4395 continue;
4396 };
4397 let Some(offset_value) = queue_offsets.get(position) else {
4398 warn!(
4399 "LMQ queue offset is missing for topic={}, dispatch={}, offsets={}",
4400 topic, dispatch, data
4401 );
4402 continue;
4403 };
4404 let Ok(offset) = offset_value.parse::<i64>() else {
4405 warn!(
4406 "LMQ queue offset is invalid for topic={}, offset={}",
4407 topic, offset_value
4408 );
4409 continue;
4410 };
4411 sort_map
4412 .entry(key)
4413 .or_insert_with(|| Vec::with_capacity(4))
4414 .push(offset);
4415 continue;
4416 }
4417 key = ExtraInfoUtil::get_start_offset_info_map_key_with_pop_ck(
4420 message_ext.topic(),
4421 message_ext
4422 .property(&CheetahString::from_static_str(MessageConst::PROPERTY_POP_CK))
4423 .clone()
4424 .as_ref()
4425 .map(|item| item.as_str()),
4426 message_ext.queue_id() as i64,
4427 )?;
4428 sort_map
4429 .entry(key)
4430 .or_insert_with(|| Vec::with_capacity(4))
4431 .push(message_ext.queue_offset());
4432 }
4433 Ok(sort_map)
4434 }
4435}
4436
4437#[cfg(any(feature = "admin-read", feature = "admin-mutation"))]
4438fn decode_proxy_drain_response(
4439 response: RemotingCommand,
4440 operation: &'static str,
4441) -> ClientResult<ProxyDrainStateResponseBody> {
4442 if ResponseCode::from(response.code()) != ResponseCode::Success {
4443 return Err(mq_client_err!(
4444 response.code(),
4445 response.remark().map_or_else(String::new, |remark| remark.to_string())
4446 ));
4447 }
4448 let body = response
4449 .body()
4450 .ok_or_else(|| ClientError::response_process_failed(operation, "Proxy drain response body is missing"))?;
4451 let state = ProxyDrainStateResponseBody::decode(body.as_ref())
4452 .map_err(|error| ClientError::response_process_source(operation, error))?;
4453 if state.schema_version != PROXY_DRAIN_SCHEMA_VERSION {
4454 return Err(ClientError::response_process_failed(
4455 operation,
4456 format!(
4457 "unsupported Proxy drain schema '{}'; expected '{PROXY_DRAIN_SCHEMA_VERSION}'",
4458 state.schema_version
4459 ),
4460 ));
4461 }
4462 Ok(state)
4463}
4464
4465pub(super) fn pop_msg_queue_offset_for_index(
4466 queue_id_key: &str,
4467 queue_offset: i64,
4468 sort_map: &HashMap<String, Vec<i64>>,
4469 msg_offset_info: &HashMap<String, Vec<i64>>,
4470) -> Option<i64> {
4471 let index = sort_map
4472 .get(queue_id_key)?
4473 .iter()
4474 .position(|&offset| offset == queue_offset)?;
4475 msg_offset_info
4476 .get(queue_id_key)
4477 .and_then(|offsets| offsets.get(index))
4478 .copied()
4479}
4480
4481pub(super) fn empty_broker_member_group(
4482 cluster_name: &CheetahString,
4483 broker_name: &CheetahString,
4484) -> BrokerMemberGroup {
4485 BrokerMemberGroup::new(cluster_name.clone(), broker_name.clone())
4486}
4487
4488pub(super) fn broker_member_group_from_route_data(
4489 cluster_name: &CheetahString,
4490 broker_name: &CheetahString,
4491 topic_route_data: &TopicRouteData,
4492) -> BrokerMemberGroup {
4493 let mut broker_member_group = empty_broker_member_group(cluster_name, broker_name);
4494 if let Some(broker_data) = topic_route_data
4495 .broker_datas
4496 .iter()
4497 .find(|broker_data| broker_data.cluster() == cluster_name.as_str() && broker_data.broker_name() == broker_name)
4498 {
4499 broker_member_group
4500 .broker_addrs
4501 .extend(broker_data.broker_addrs().clone());
4502 }
4503 broker_member_group
4504}
4505
4506pub(super) fn cluster_names_for_topic_route(
4507 cluster_info: &ClusterInfo,
4508 topic_route_data: &TopicRouteData,
4509) -> HashSet<CheetahString> {
4510 let mut cluster_names = HashSet::new();
4511 let Some(cluster_addr_table) = cluster_info.cluster_addr_table.as_ref() else {
4512 return cluster_names;
4513 };
4514
4515 for broker_data in &topic_route_data.broker_datas {
4516 cluster_names.extend(
4517 cluster_addr_table
4518 .iter()
4519 .filter(|(_, broker_names)| broker_names.contains(broker_data.broker_name()))
4520 .map(|(cluster_name, _)| cluster_name.clone()),
4521 );
4522 }
4523
4524 cluster_names
4525}
4526
4527pub(super) fn encode_topic_attributes_like_java(
4528 attributes: &HashMap<CheetahString, CheetahString>,
4529) -> Option<CheetahString> {
4530 if attributes.is_empty() {
4531 return None;
4532 }
4533
4534 let encoded = AttributeParser::parse_to_string(
4535 &attributes
4536 .iter()
4537 .map(|(key, value)| (key.to_string(), value.to_string()))
4538 .collect::<HashMap<String, String>>(),
4539 );
4540 if encoded.is_empty() {
4541 None
4542 } else {
4543 Some(CheetahString::from_string(encoded))
4544 }
4545}
4546
4547pub(super) fn topic_config_u32_to_java_i32(_field_name: &'static str, value: u32) -> ClientResult<i32> {
4548 i32::try_from(value).map_err(ClientError::illegal_argument_source)
4549}
4550
4551pub(super) fn create_topic_request_header_like_java(
4552 default_topic: CheetahString,
4553 topic_config: &TopicConfig,
4554) -> ClientResult<CreateTopicRequestHeader> {
4555 Validators::check_topic_config(topic_config)?;
4556 let topic = topic_config
4557 .topic_name
4558 .clone()
4559 .ok_or_else(|| mq_client_err!("topicConfig.topicName is required".to_string()))?;
4560
4561 Ok(CreateTopicRequestHeader {
4562 topic,
4563 default_topic,
4564 read_queue_nums: topic_config_u32_to_java_i32("readQueueNums", topic_config.read_queue_nums)?,
4565 write_queue_nums: topic_config_u32_to_java_i32("writeQueueNums", topic_config.write_queue_nums)?,
4566 perm: topic_config_u32_to_java_i32("perm", topic_config.perm)?,
4567 topic_filter_type: CheetahString::from_static_str(topic_config.topic_filter_type.as_str()),
4568 topic_sys_flag: Some(topic_config_u32_to_java_i32(
4569 "topicSysFlag",
4570 topic_config.topic_sys_flag,
4571 )?),
4572 order: topic_config.order,
4573 attributes: encode_topic_attributes_like_java(&topic_config.attributes),
4574 force: None,
4575 topic_request_header: None,
4576 })
4577}
4578
4579pub(super) fn query_correction_offset_request(
4580 command_factory: &RemotingCommandFactory,
4581 topic: CheetahString,
4582 group: CheetahString,
4583 filter_groups: Option<Vec<CheetahString>>,
4584) -> RemotingCommand {
4585 let filter_groups = filter_groups.map(|groups| {
4586 CheetahString::from_string(
4587 groups
4588 .into_iter()
4589 .map(|group| group.to_string())
4590 .collect::<Vec<_>>()
4591 .join(","),
4592 )
4593 });
4594 let request_header = QueryCorrectionOffsetHeader {
4595 filter_groups,
4596 compare_group: group,
4597 topic,
4598 topic_request_header: None,
4599 };
4600 command_factory.create_request_command(RequestCode::QueryCorrectionOffset, request_header)
4601}
4602
4603pub(super) fn split_lite_dispatch_value(value: &str) -> Vec<&str> {
4604 value
4605 .split(mix_all::MULTI_DISPATCH_QUEUE_SPLITTER)
4606 .filter(|segment| !segment.is_empty())
4607 .collect()
4608}
4609
4610pub(super) fn parse_lite_order_count_info_like_java(
4611 order_count_info: Option<&CheetahString>,
4612 msg_count: usize,
4613) -> Option<Vec<i32>> {
4614 let order_count_info = order_count_info?.as_str();
4615 if order_count_info.trim().is_empty() {
4616 return None;
4617 }
4618 let infos = order_count_info.split(';').collect::<Vec<_>>();
4619 if infos.len() != msg_count {
4620 return None;
4621 }
4622 Some(infos.into_iter().map(parse_lite_order_count_like_java).collect())
4623}
4624
4625pub(super) fn parse_lite_order_count_like_java(info: &str) -> i32 {
4626 if info.trim().is_empty() {
4627 return 0;
4628 }
4629 if !info.contains(MessageConst::KEY_SEPARATOR) {
4630 return info.parse::<i32>().unwrap_or_default();
4631 }
4632 let split = info.split(MessageConst::KEY_SEPARATOR).collect::<Vec<_>>();
4633 if split.len() != 3 {
4634 return 0;
4635 }
4636 split[2].parse::<i32>().unwrap_or_default()
4637}
4638
4639pub(super) fn filter_retry_topics_like_java(topic_list: &mut TopicList, contain_retry: bool) {
4640 if contain_retry {
4641 return;
4642 }
4643 topic_list
4644 .topic_list
4645 .retain(|topic| !topic.as_str().starts_with(mix_all::RETRY_GROUP_TOPIC_PREFIX));
4646}
4647
4648pub(super) fn controller_leader_address(header: GetMetaDataResponseHeader) -> ClientResult<CheetahString> {
4649 header
4650 .controller_leader_address
4651 .ok_or_else(|| mq_client_err!("Controller leader address is not available".to_string()))
4652}
4653
4654pub(super) fn controller_config_from_response_body(body: &[u8]) -> ClientResult<HashMap<CheetahString, CheetahString>> {
4655 let body_str = String::from_utf8_lossy(body);
4656 mix_all::string_to_properties(body_str.as_ref())
4657 .ok_or_else(|| mq_client_err!("Failed to parse controller config response body".to_string()))
4658}
4659
4660pub(super) fn should_fetch_system_topic_list_from_broker(topic_list: &TopicList) -> bool {
4661 !topic_list.topic_list.is_empty()
4662 && topic_list
4663 .broker_addr
4664 .as_ref()
4665 .is_some_and(|broker_addr| !broker_addr.as_str().trim().is_empty())
4666}
4667
4668pub(super) fn append_system_topic_list_from_broker_like_java(topic_list: &mut TopicList, broker_topic_list: TopicList) {
4669 if !broker_topic_list.topic_list.is_empty() {
4670 topic_list.topic_list.extend(broker_topic_list.topic_list);
4671 }
4672}
4673
4674pub(super) async fn merge_system_topic_list_from_broker(
4675 api: &MQClientAPIImpl,
4676 topic_list: &mut TopicList,
4677 timeout_millis: u64,
4678) -> ClientResult<()> {
4679 if !should_fetch_system_topic_list_from_broker(topic_list) {
4680 return Ok(());
4681 }
4682 let Some(broker_addr) = topic_list.broker_addr.clone() else {
4683 return Ok(());
4684 };
4685 let broker_topic_list = api
4686 .get_system_topic_list_from_broker(&broker_addr, timeout_millis)
4687 .await?;
4688 append_system_topic_list_from_broker_like_java(topic_list, broker_topic_list);
4689 Ok(())
4690}
4691
4692pub(super) fn decode_cluster_acl_version_info_response_body(
4693 body: Option<&bytes::Bytes>,
4694) -> ClientResult<ClusterAclVersionInfo> {
4695 let body = body.ok_or_else(|| mq_client_err!("get_broker_cluster_acl_version_info response body is empty"))?;
4696 SerdeJsonUtils::from_json_slice(body.as_ref())
4697 .map_err(|source| ClientError::response_process_source("decode ClusterAclVersionInfo", source))
4698}
4699
4700pub(super) fn admin_message_matches_query(
4701 topic: &CheetahString,
4702 key: &CheetahString,
4703 msg: &MessageExt,
4704 unique_key_flag: bool,
4705) -> bool {
4706 if msg.topic().as_str() != topic.as_str() {
4707 return false;
4708 }
4709
4710 if unique_key_flag {
4711 if let Some(uniq_id) = MessageClientIDSetter::get_uniq_id(msg) {
4712 if uniq_id.as_str() == key.as_str() {
4713 return true;
4714 }
4715 }
4716 return msg.msg_id.as_str() == key.as_str();
4717 }
4718
4719 let Some(keys) = MessageTrait::get_keys(msg) else {
4720 return false;
4721 };
4722
4723 keys.split_str(MessageConst::KEY_SEPARATOR)
4724 .any(|candidate| candidate == key.as_str())
4725}
4726
4727impl MQClientAPIImpl {
4728 pub(super) async fn invoke_admin_request(
4729 &self,
4730 address: &str,
4731 request: RemotingCommand,
4732 timeout_millis: u64,
4733 ) -> Result<OutboundRequestOutcome, rocketmq_transport::api::TransportError> {
4734 let address = CheetahString::from_slice(address);
4735 self.remoting_client
4736 .invoke_request(Some(&address), request, timeout_millis)
4737 .await
4738 }
4739}
4740
4741#[cfg(test)]
4742mod retry_executor_tests {
4743 use std::collections::VecDeque;
4744 use std::future::Future;
4745 use std::pin::Pin;
4746 use std::sync::atomic::AtomicUsize;
4747 use std::sync::atomic::Ordering;
4748 use std::sync::Arc;
4749 use std::sync::Mutex;
4750 use std::time::Duration;
4751
4752 use rocketmq_protocol::protocol::header::namesrv::kv_config_header::GetKVConfigResponseHeader;
4753 use rocketmq_protocol::RemotingCommand;
4754 use rocketmq_runtime::RuntimeContext;
4755 use rocketmq_runtime::ShutdownDeadline;
4756 use rocketmq_transport::api::AdmissionController;
4757 use rocketmq_transport::api::AdmissionLimits;
4758 use rocketmq_transport::test_support::SessionProcessor;
4759 use rocketmq_transport::test_support::SessionTransportServer;
4760 use rocketmq_transport::test_support::SessionTransportServerConfig;
4761
4762 use super::*;
4763 use crate::base::client_config::ClientConfig;
4764 use crate::factory::mq_client_instance::MQClientInstance;
4765
4766 #[derive(Default)]
4767 struct KvProcessor {
4768 responses: Mutex<VecDeque<RemotingCommand>>,
4769 requests: AtomicUsize,
4770 }
4771
4772 impl KvProcessor {
4773 fn push(&self, response: RemotingCommand) {
4774 self.responses.lock().expect("KV response queue").push_back(response);
4775 }
4776
4777 fn request_count(&self) -> usize {
4778 self.requests.load(Ordering::SeqCst)
4779 }
4780 }
4781
4782 impl SessionProcessor for KvProcessor {
4783 fn process(
4784 &self,
4785 request: RemotingCommand,
4786 ) -> Pin<Box<dyn Future<Output = Result<RemotingCommand, rocketmq_error::SharedError>> + Send + '_>> {
4787 Box::pin(async move {
4788 if request.code() != RequestCode::GetKvConfig.to_i32() {
4789 return Err(
4790 ClientError::illegal_argument(format!("unexpected request code {}", request.code())).into(),
4791 );
4792 }
4793 self.requests.fetch_add(1, Ordering::SeqCst);
4794 let response = self
4795 .responses
4796 .lock()
4797 .expect("KV response queue")
4798 .pop_front()
4799 .ok_or_else(|| ClientError::illegal_argument("unexpected KV request"))?;
4800 Ok(response.set_opaque(request.opaque()))
4801 })
4802 }
4803 }
4804
4805 async fn bind_kv_server(
4806 runtime: &RuntimeContext,
4807 scope: &'static str,
4808 processor: Arc<KvProcessor>,
4809 ) -> Arc<SessionTransportServer> {
4810 let admission = Arc::new(AdmissionController::new(AdmissionLimits::default()));
4811 let server = SessionTransportServer::bind(
4812 runtime.service_context(scope),
4813 SessionTransportServerConfig::loopback(),
4814 processor as Arc<dyn SessionProcessor>,
4815 admission,
4816 )
4817 .await
4818 .expect("bind KV server");
4819 server.start().expect("start KV server");
4820 server
4821 }
4822
4823 #[tokio::test]
4824 async fn kv_executor_switches_name_server_and_keeps_query_not_found_terminal() {
4825 let server_runtime = RuntimeContext::from_current("kv-retry-executor-test");
4826 let first = Arc::new(KvProcessor::default());
4827 let second = Arc::new(KvProcessor::default());
4828 let first_server = bind_kv_server(&server_runtime, "kv-first", Arc::clone(&first)).await;
4829 let second_server = bind_kv_server(&server_runtime, "kv-second", Arc::clone(&second)).await;
4830 let client_runtime = crate::runtime::test_client_runtime("kv-retry-executor-test");
4831 let client_instance = MQClientInstance::new_arc(
4832 ClientConfig::default(),
4833 0,
4834 "kv-retry-client",
4835 None,
4836 client_runtime.component("instance"),
4837 client_runtime.telemetry_handle().clone(),
4838 client_runtime.pool().request_future_holder(),
4839 );
4840 let api = client_instance.get_mq_client_api_impl().expect("client API");
4841 let first_addr = CheetahString::from_string(first_server.local_addr().to_string());
4842 let second_addr = CheetahString::from_string(second_server.local_addr().to_string());
4843 api.update_name_server_targets_sync(vec![first_addr.clone(), second_addr.clone()]);
4844 let selected_targets = api.remoting_client.get_name_server_address_list();
4845 assert_eq!(selected_targets.len(), 2, "both NameServers must be selected");
4846 let (initial, fallback) = if selected_targets[0] == first_addr {
4847 (&first, &second)
4848 } else {
4849 assert_eq!(selected_targets[0], second_addr);
4850 (&second, &first)
4851 };
4852 initial.push(RemotingCommand::create_response_command_with_code_remark(
4853 ResponseCode::SystemError,
4854 "initial-name-server-failed",
4855 ));
4856 let mut found = RemotingCommand::create_response_command_with_code_and_header(
4857 ResponseCode::Success,
4858 GetKVConfigResponseHeader::new(Some(CheetahString::from_static_str("expected-value"))),
4859 );
4860 found.make_custom_header_to_net();
4861 fallback.push(found);
4862 api.start().await.expect("start client transport");
4863
4864 let value = api
4865 .get_kvconfig_value("namespace".into(), "key".into(), 2_000)
4866 .await
4867 .expect("second NameServer should satisfy the query");
4868 assert_eq!(value.as_deref(), Some("expected-value"));
4869 assert_eq!((initial.request_count(), fallback.request_count()), (1, 1));
4870
4871 first.push(RemotingCommand::create_response_command_with_code(
4872 ResponseCode::QueryNotFound,
4873 ));
4874 api.update_name_server_targets_sync(vec![first_addr]);
4875 let first_before_terminal = first.request_count();
4876 let second_before_terminal = second.request_count();
4877 let missing = api
4878 .get_kvconfig_value("namespace".into(), "missing".into(), 2_000)
4879 .await
4880 .expect("QueryNotFound is a normal terminal outcome");
4881 assert_eq!(missing, None);
4882 assert_eq!(first.request_count(), first_before_terminal + 1);
4883 assert_eq!(
4884 second.request_count(),
4885 second_before_terminal,
4886 "QueryNotFound must not switch endpoint"
4887 );
4888
4889 client_instance.shutdown().await;
4890 client_runtime
4891 .shutdown()
4892 .await
4893 .assert_no_task_leak()
4894 .expect("client runtime tasks drained");
4895 for server in [first_server, second_server] {
4896 server
4897 .shutdown_until(ShutdownDeadline::after(Duration::from_secs(5)))
4898 .await
4899 .assert_no_task_leak()
4900 .expect("KV server tasks drained");
4901 }
4902 server_runtime
4903 .shutdown_tasks(Duration::from_secs(5))
4904 .await
4905 .assert_no_task_leak()
4906 .expect("KV runtime tasks drained");
4907 }
4908}
4909
4910#[cfg(test)]
4911mod json_error_boundary_tests {
4912 use std::error::Error as StdError;
4913
4914 #[test]
4915 fn admin_json_decode_preserves_source_and_public_boundary() {
4916 let error = super::decode_admin_json::<serde_json::Value>(b"invalid").unwrap_err();
4917
4918 assert_eq!(error.to_string(), "core.serialization.failed: Serialization failed");
4919 assert_eq!(error.descriptor().public_message(), "Serialization failed");
4920
4921 let canonical = StdError::source(&error).expect("canonical error source");
4922 assert!(canonical.downcast_ref::<rocketmq_error::Error>().is_some());
4923 assert!(error.source_ref::<serde_json::Error>().is_some());
4924
4925 assert!(error.is(&rocketmq_error::CORE_SERIALIZATION_FAILED));
4926 }
4927
4928 #[cfg(any(feature = "admin-read", feature = "admin-mutation"))]
4929 #[test]
4930 fn proxy_drain_decode_preserves_source() {
4931 let response = rocketmq_protocol::RemotingCommand::create_success_response_command().set_body("invalid");
4932
4933 let error =
4934 super::decode_proxy_drain_response(response, "proxy_drain_test").expect_err("invalid body must fail");
4935
4936 assert!(error.is(&rocketmq_error::PROTOCOL_RESPONSE_FAILED));
4937 assert!(error.source_ref::<rocketmq_error::Error>().is_some());
4938 }
4939}
4940
4941#[cfg(feature = "admin-mutation")]
4942fn acl_entry_delete_header(
4943 subject: CheetahString,
4944 policy_type: CheetahString,
4945 resource: CheetahString,
4946) -> ClientResult<DeleteAclRequestHeader> {
4947 if subject.trim().is_empty()
4948 || resource.trim().is_empty()
4949 || !(policy_type.eq_ignore_ascii_case("Custom") || policy_type.eq_ignore_ascii_case("Default"))
4950 {
4951 return Err(ClientError::illegal_argument(
4952 "ACL entry deletion requires subject, resource and Custom or Default policy type",
4953 ));
4954 }
4955 Ok(DeleteAclRequestHeader::with_policy_type(
4956 subject,
4957 Some(policy_type),
4958 Some(resource),
4959 ))
4960}
4961#[cfg(all(test, feature = "admin-mutation"))]
4962mod acl_policy_entry_tests {
4963 use super::*;
4964 #[test]
4965 fn acl_policy_entry_delete_preserves_type_and_never_expands_empty_resource() {
4966 let header = acl_entry_delete_header("User:alice".into(), "Default".into(), "Topic:orders".into()).unwrap();
4967 assert_eq!(header.policy_type.as_deref(), Some("Default"));
4968 assert_eq!(header.resource.as_deref(), Some("Topic:orders"));
4969 assert!(acl_entry_delete_header("User:alice".into(), "Default".into(), "".into()).is_err());
4970 assert!(acl_entry_delete_header("User:alice".into(), "invalid".into(), "Topic:orders".into()).is_err());
4971 let legacy = DeleteAclRequestHeader::new("User:alice".into(), None);
4972 assert!(legacy.policy_type.is_none());
4973 assert!(legacy.resource.is_none());
4974 }
4975}