1use std::cmp::Ordering;
8use std::collections::{HashMap, VecDeque};
9use std::fmt;
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12
13use bytes::{Bytes, BytesMut};
14
15use crate::cluster::Cluster;
16use crate::error::{self, Error, Result};
17use crate::net::{BrokerConn, TlsConfig};
18use crate::protocol::acl::{
19 decode_create_acls_response, decode_delete_acls_filter_results, decode_describe_acls_response,
20 encode_create_acls_request, encode_delete_acls_request, encode_describe_acls_request,
21};
22use crate::protocol::admin::{
23 decode_allocate_producer_ids_response, decode_alter_client_quotas_response,
24 decode_alter_configs_resource_results, decode_alter_partition_reassignments_response,
25 decode_alter_replica_log_dirs_response, decode_alter_share_group_offsets_response,
26 decode_alter_user_scram_credentials_response, decode_assign_replicas_to_dirs_response,
27 decode_consumer_group_describe_response, decode_create_delegation_token_response,
28 decode_create_partitions_response, decode_create_topics_response,
29 decode_delete_groups_response, decode_delete_records_topics_response,
30 decode_delete_share_group_offsets_response, decode_delete_topics_response,
31 decode_describe_client_quotas_response, decode_describe_cluster_response,
32 decode_describe_configs_response, decode_describe_delegation_token_response,
33 decode_describe_groups_response, decode_describe_log_dirs_response,
34 decode_describe_producers_response, decode_describe_share_group_offsets_response,
35 decode_describe_topic_partitions_response, decode_describe_transactions_response,
36 decode_describe_user_scram_credentials_response, decode_expire_delegation_token_response,
37 decode_get_telemetry_subscriptions_response, decode_incremental_alter_configs_resource_results,
38 decode_list_config_resources_response, decode_list_groups_response,
39 decode_list_partition_reassignments_response, decode_list_transactions_response,
40 decode_push_telemetry_response, decode_renew_delegation_token_response,
41 decode_share_group_describe_response, decode_unregister_broker_response,
42 decode_update_features_response, encode_allocate_producer_ids_request,
43 encode_alter_client_quotas_request, encode_alter_configs_resources_request,
44 encode_alter_partition_reassignments_request, encode_alter_replica_log_dirs_request,
45 encode_alter_share_group_offsets_request, encode_alter_user_scram_credentials_request,
46 encode_assign_replicas_to_dirs_request, encode_consumer_group_describe_request,
47 encode_create_delegation_token_request, encode_create_partitions_request,
48 encode_create_topics_request, encode_delete_groups_request,
49 encode_delete_records_topics_request, encode_delete_share_group_offsets_request,
50 encode_delete_topics_states_request, encode_describe_client_quotas_request,
51 encode_describe_cluster_request, encode_describe_configs_request,
52 encode_describe_delegation_token_request, encode_describe_groups_request,
53 encode_describe_log_dirs_request, encode_describe_producers_topics_request,
54 encode_describe_share_group_offsets_request, encode_describe_topic_partitions_request,
55 encode_describe_transactions_request, encode_describe_user_scram_credentials_request,
56 encode_expire_delegation_token_request, encode_get_telemetry_subscriptions_request,
57 encode_incremental_alter_configs_resources_request, encode_list_config_resources_request,
58 encode_list_groups_request, encode_list_partition_reassignments_request,
59 encode_list_transactions_request, encode_push_telemetry_request,
60 encode_renew_delegation_token_request, encode_share_group_describe_request,
61 encode_unregister_broker_request, encode_update_features_request, AlterConfigsResource,
62 AlterableResource, CreatableTopic, CreatePartitionsTopic, CreateTopicsRequest,
63 DeleteRecordsPartition, DeleteRecordsTopic, DeleteTopicState, DescribeConfigsResource,
64 DescribeConfigsResult, DescribeProducersTopicRequest, FeatureUpdateKey, ListReassignmentTopic,
65 ReassignablePartition, ReassignableTopic, ReplicaAssignment, ScramCredentialDeletion,
66 ScramCredentialUpsertion, TopicConfig, TopicResult, RESOURCE_BROKER, RESOURCE_BROKER_LOGGER,
67 RESOURCE_CLIENT_METRICS, RESOURCE_GROUP, RESOURCE_TOPIC,
68};
69use crate::protocol::api::{
70 decode_api_versions_response, decode_metadata_response, encode_api_versions_request,
71 encode_metadata_request_topics, ApiVersion, MetadataRequestTopic, MetadataResponse,
72};
73use crate::protocol::api_keys::{
74 pick_version, ALLOCATE_PRODUCER_IDS, ALTER_CLIENT_QUOTAS, ALTER_CONFIGS,
75 ALTER_PARTITION_REASSIGNMENTS, ALTER_REPLICA_LOG_DIRS, ALTER_SHARE_GROUP_OFFSETS,
76 ALTER_USER_SCRAM_CREDENTIALS, API_VERSIONS, ASSIGN_REPLICAS_TO_DIRS, CONSUMER_GROUP_DESCRIBE,
77 CREATE_ACLS, CREATE_DELEGATION_TOKEN, CREATE_PARTITIONS, CREATE_TOPICS, DELETE_ACLS,
78 DELETE_GROUPS, DELETE_RECORDS, DELETE_SHARE_GROUP_OFFSETS, DELETE_TOPICS, DESCRIBE_ACLS,
79 DESCRIBE_CLIENT_QUOTAS, DESCRIBE_CLUSTER, DESCRIBE_CONFIGS, DESCRIBE_DELEGATION_TOKEN,
80 DESCRIBE_GROUPS, DESCRIBE_LOG_DIRS, DESCRIBE_PRODUCERS, DESCRIBE_SHARE_GROUP_OFFSETS,
81 DESCRIBE_TOPIC_PARTITIONS, DESCRIBE_TRANSACTIONS, DESCRIBE_USER_SCRAM_CREDENTIALS,
82 EXPIRE_DELEGATION_TOKEN, FIND_COORDINATOR, GET_TELEMETRY_SUBSCRIPTIONS,
83 INCREMENTAL_ALTER_CONFIGS, INIT_PRODUCER_ID, LEAVE_GROUP, LIST_CONFIG_RESOURCES, LIST_GROUPS,
84 LIST_OFFSETS, LIST_PARTITION_REASSIGNMENTS, LIST_TRANSACTIONS, METADATA, OFFSET_COMMIT,
85 OFFSET_DELETE, OFFSET_FETCH, PUSH_TELEMETRY, RENEW_DELEGATION_TOKEN, SHARE_GROUP_DESCRIBE,
86 UNREGISTER_BROKER, UPDATE_FEATURES, WRITE_TXN_MARKERS,
87};
88use crate::protocol::group::{
89 decode_find_coordinator_response, decode_find_coordinator_response_coordinators,
90 decode_leave_group_response_version, decode_offset_commit_response,
91 decode_offset_delete_response, decode_offset_fetch_groups_response,
92 decode_offset_fetch_response, encode_find_coordinator_request_keys,
93 encode_find_coordinator_request_typed, encode_leave_group_request_members,
94 encode_offset_commit_request, encode_offset_delete_request, encode_offset_fetch_groups_request,
95 encode_offset_fetch_request, LeaveGroupMember, OffsetDeleteTopic, OffsetFetchGroup,
96 COORDINATOR_GROUP, COORDINATOR_TRANSACTION, DEFAULT_RETENTION_TIME,
97};
98use crate::protocol::idem::{decode_init_producer_id_response, encode_init_producer_id_request};
99use crate::protocol::offsets::{
100 decode_list_offsets_topics_response, encode_list_offsets_topics_request,
101 ListOffsetsPartitionRequest, ListOffsetsResponsePartition, ListOffsetsTopicRequest,
102};
103use crate::protocol::sasl;
104use crate::protocol::txn::{
105 decode_write_txn_markers_response, encode_write_txn_markers_request, TransactionResult,
106 WritableTxnMarker, WritableTxnMarkerTopic,
107};
108
109pub use crate::protocol::acl::{
110 AccessControlEntry, AccessControlEntryFilter, AclBinding, AclBindingFilter, AclCreationResult,
111 AclOperation, AclPatternType, AclPermission, AclResourceType, DeleteAclsMatchingAcl,
112 DeletedAclsFilterResult, ResourcePattern, ResourcePatternFilter,
113};
114pub use crate::protocol::admin::{
115 ActiveProducer, AlterConfig, AlterConfigOp, AlterConfigOpType, AlterConfigsResourceResult,
116 AlterReplicaLogDirsDirectory, AlterReplicaLogDirsRequest, AlterReplicaLogDirsResponse,
117 AlterReplicaLogDirsResponsePartition, AlterReplicaLogDirsResponseTopic,
118 AlterReplicaLogDirsTopic, AlterShareGroupOffsetsPartition, AlterShareGroupOffsetsTopic,
119 AlteredShareGroupOffsets, AlteredShareGroupOffsetsPartition, AlteredShareGroupOffsetsTopic,
120 AssignReplicasToDirsDirectory, AssignReplicasToDirsPartition, AssignReplicasToDirsRequest,
121 AssignReplicasToDirsResponse, AssignReplicasToDirsResponseDirectory,
122 AssignReplicasToDirsResponsePartition, AssignReplicasToDirsResponseTopic,
123 AssignReplicasToDirsTopic, ClientQuotaAlteration, ClientQuotaAlterationResult,
124 ClientQuotaEntity, ClientQuotaEntry, ClientQuotaFilter, ClientQuotaFilterComponent,
125 ClientQuotaOp, ClientQuotaValue, ClusterDescription, ClusterResource, Config, ConfigEntry,
126 ConfigSource, ConfigSynonym, ConfigType, ConsumerGroupAssignment, ConsumerGroupMember,
127 ConsumerGroupTopicPartitions, CreatableRenewer, CreateDelegationTokenRequest,
128 CreateDelegationTokenResponse, DeletableGroupResult, DeleteShareGroupOffsetsTopic,
129 DeletedShareGroupOffsets, DeletedShareGroupOffsetsTopic, DescribableLogDirTopic,
130 DescribeClusterBroker, DescribeDelegationTokenOwner, DescribeDelegationTokenRequest,
131 DescribeDelegationTokenResponse, DescribeLogDirsPartition, DescribeLogDirsRequest,
132 DescribeLogDirsResponse, DescribeLogDirsResult, DescribeLogDirsTopic,
133 DescribeProducersPartition, DescribeProducersTopic, DescribeShareGroupOffsetsGroup,
134 DescribeShareGroupOffsetsTopic, DescribeTopicPartitionsResponse,
135 DescribeUserScramCredentialsResult, DescribedConsumerGroup, DescribedDelegationToken,
136 DescribedDelegationTokenRenewer, DescribedGroup, DescribedGroupMember, DescribedShareGroup,
137 DescribedShareGroupOffsets, DescribedShareGroupOffsetsPartition,
138 DescribedShareGroupOffsetsTopic, DescribedTopicPartition, DescribedTopicPartitions,
139 EndpointType, ExpireDelegationTokenRequest, ExpireDelegationTokenResponse,
140 GetTelemetrySubscriptionsResponse, GroupState, GroupType, ListedConfigResource, ListedGroup,
141 Node, PushTelemetryRequest, PushTelemetryResponse, RenewDelegationTokenRequest,
142 RenewDelegationTokenResponse, ScramCredentialInfo, ScramMechanism, ShareGroupAssignment,
143 ShareGroupMember, ShareGroupTopicPartitions, TopicPartitionCursor, TopicPartitionInfo,
144 TransactionListing, TransactionState, TransactionTopic, UnregisterBrokerResponse, UpgradeType,
145 ALTER_CONFIG_APPEND, ALTER_CONFIG_DELETE, ALTER_CONFIG_SET, ALTER_CONFIG_SUBTRACT,
146 AUTHORIZED_OPERATIONS_OMITTED, CONFIG_SOURCE_DEFAULT, CONFIG_SOURCE_DYNAMIC_BROKER,
147 CONFIG_SOURCE_DYNAMIC_BROKER_LOGGER, CONFIG_SOURCE_DYNAMIC_CLIENT_METRICS,
148 CONFIG_SOURCE_DYNAMIC_DEFAULT_BROKER, CONFIG_SOURCE_DYNAMIC_GROUP, CONFIG_SOURCE_DYNAMIC_TOPIC,
149 CONFIG_SOURCE_STATIC_BROKER, CONFIG_SOURCE_UNKNOWN, CONFIG_TYPE_BOOLEAN, CONFIG_TYPE_CLASS,
150 CONFIG_TYPE_DOUBLE, CONFIG_TYPE_INT, CONFIG_TYPE_LIST, CONFIG_TYPE_LONG, CONFIG_TYPE_PASSWORD,
151 CONFIG_TYPE_SHORT, CONFIG_TYPE_STRING, CONFIG_TYPE_UNKNOWN, ENDPOINT_TYPE_BROKERS,
152 ENDPOINT_TYPE_CONTROLLERS, INVALID_OFFSET_LAG, QUOTA_MATCH_ANY, QUOTA_MATCH_DEFAULT,
153 QUOTA_MATCH_EXACT, RESOURCE_BROKER as CONFIG_RESOURCE_BROKER,
154 RESOURCE_BROKER_LOGGER as CONFIG_RESOURCE_BROKER_LOGGER,
155 RESOURCE_CLIENT_METRICS as CONFIG_RESOURCE_CLIENT_METRICS,
156 RESOURCE_GROUP as CONFIG_RESOURCE_GROUP, RESOURCE_TOPIC as CONFIG_RESOURCE_TOPIC,
157 SCRAM_SHA_256, SCRAM_SHA_512, SCRAM_UNKNOWN, UNKNOWN_VOLUME_BYTES, UPGRADE_TYPE_SAFE_DOWNGRADE,
158 UPGRADE_TYPE_UNSAFE_DOWNGRADE, UPGRADE_TYPE_UPGRADE,
159};
160pub use crate::protocol::group::OffsetDeleteResult;
161
162#[derive(Debug, Clone)]
164pub struct AdminConfig {
165 pub bootstrap: Vec<String>,
167 pub client_id: String,
169 pub request_timeout: Duration,
171 pub connect_timeout: Duration,
173 pub reconnect_backoff: Duration,
178 pub reconnect_backoff_max: Duration,
181 pub connections_max_idle: Duration,
185 pub retry_backoff: Duration,
191 pub retry_backoff_max: Duration,
194 pub sasl_plain: Option<(String, String)>,
196 pub sasl_scram: Option<(String, String)>,
198 pub sasl_scram_sha512: Option<(String, String)>,
200 pub sasl_oauthbearer: Option<String>,
202 pub sasl_oauthbearer_oidc: Option<crate::OidcConfig>,
204 pub tls: Option<TlsConfig>,
206}
207
208impl Default for AdminConfig {
209 fn default() -> Self {
210 Self {
211 bootstrap: vec!["127.0.0.1:9092".into()],
212 client_id: "partitionline".into(),
213 request_timeout: Duration::from_secs(30),
214 connect_timeout: Duration::from_secs(10),
215 reconnect_backoff: crate::config::DEFAULT_RECONNECT_BACKOFF,
216 reconnect_backoff_max: crate::config::DEFAULT_RECONNECT_BACKOFF_MAX,
217 connections_max_idle: crate::config::DEFAULT_CONNECTIONS_MAX_IDLE,
218 retry_backoff: crate::config::DEFAULT_RETRY_BACKOFF,
219 retry_backoff_max: crate::config::DEFAULT_RETRY_BACKOFF_MAX,
220 sasl_plain: None,
221 sasl_scram: None,
222 sasl_scram_sha512: None,
223 sasl_oauthbearer: None,
224 sasl_oauthbearer_oidc: None,
225 tls: None,
226 }
227 }
228}
229
230impl AdminConfig {
231 pub fn bootstrap<S: Into<String>>(servers: impl IntoIterator<Item = S>) -> Self {
233 Self {
234 bootstrap: servers.into_iter().map(Into::into).collect(),
235 ..Self::default()
236 }
237 }
238
239 #[must_use]
241 pub fn client_id(mut self, id: impl Into<String>) -> Self {
242 self.client_id = id.into();
243 self
244 }
245
246 #[must_use]
248 pub fn sasl(mut self, sasl: crate::Sasl) -> Self {
249 sasl.apply_to(
250 &mut self.sasl_plain,
251 &mut self.sasl_scram,
252 &mut self.sasl_scram_sha512,
253 &mut self.sasl_oauthbearer,
254 &mut self.sasl_oauthbearer_oidc,
255 );
256 self
257 }
258
259 #[must_use]
261 pub fn tls(mut self, tls: TlsConfig) -> Self {
262 crate::config::apply_tls(&mut self.tls, tls);
263 self
264 }
265
266 #[must_use]
268 pub fn request_timeout(mut self, timeout: Duration) -> Self {
269 self.request_timeout = timeout;
270 self
271 }
272
273 #[must_use]
275 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
276 self.connect_timeout = timeout;
277 self
278 }
279
280 #[must_use]
286 pub fn reconnect_backoff(mut self, backoff: Duration) -> Self {
287 self.reconnect_backoff = backoff;
288 self
289 }
290
291 #[must_use]
295 pub fn reconnect_backoff_max(mut self, backoff: Duration) -> Self {
296 self.reconnect_backoff_max = backoff;
297 self
298 }
299
300 #[must_use]
305 pub fn connections_max_idle(mut self, idle: Duration) -> Self {
306 self.connections_max_idle = idle;
307 self
308 }
309
310 #[must_use]
316 pub fn retry_backoff(mut self, backoff: Duration) -> Self {
317 self.retry_backoff = backoff;
318 self
319 }
320
321 #[must_use]
325 pub fn retry_backoff_max(mut self, backoff: Duration) -> Self {
326 self.retry_backoff_max = backoff;
327 self
328 }
329}
330
331#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct NewTopic {
338 pub name: String,
340 pub num_partitions: i32,
343 pub replication_factor: i16,
346 pub assignments: Vec<(i32, Vec<i32>)>,
351 pub configs: Vec<(String, Option<String>)>,
353}
354
355impl NewTopic {
356 pub fn new(name: impl Into<String>, num_partitions: i32, replication_factor: i16) -> Self {
361 Self {
362 name: name.into(),
363 num_partitions,
364 replication_factor,
365 assignments: Vec::new(),
366 configs: Vec::new(),
367 }
368 }
369
370 #[must_use]
376 pub fn broker_defaults(name: impl Into<String>) -> Self {
377 Self::new(
378 name,
379 CreateTopicsRequest::NO_NUM_PARTITIONS,
380 CreateTopicsRequest::NO_REPLICATION_FACTOR,
381 )
382 }
383
384 #[must_use]
389 pub fn with_assignments<A, B>(name: impl Into<String>, assignments: A) -> Self
390 where
391 A: IntoIterator<Item = (i32, B)>,
392 B: IntoIterator<Item = i32>,
393 {
394 Self {
395 name: name.into(),
396 num_partitions: CreateTopicsRequest::NO_NUM_PARTITIONS,
397 replication_factor: CreateTopicsRequest::NO_REPLICATION_FACTOR,
398 assignments: assignments
399 .into_iter()
400 .map(|(partition, brokers)| (partition, brokers.into_iter().collect()))
401 .collect(),
402 configs: Vec::new(),
403 }
404 }
405
406 #[must_use]
408 pub fn config(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
409 self.configs.push((name.into(), Some(value.into())));
410 self
411 }
412
413 #[must_use]
418 pub fn configs<I, K, V>(mut self, configs: I) -> Self
419 where
420 I: IntoIterator<Item = (K, V)>,
421 K: Into<String>,
422 V: Into<String>,
423 {
424 self.configs = configs
425 .into_iter()
426 .map(|(k, v)| (k.into(), Some(v.into())))
427 .collect();
428 self
429 }
430
431 #[must_use]
433 pub fn name(&self) -> &str {
434 self.name.as_str()
435 }
436
437 #[must_use]
439 pub fn num_partitions(&self) -> i32 {
440 self.num_partitions
441 }
442
443 #[must_use]
445 pub fn replication_factor(&self) -> i16 {
446 self.replication_factor
447 }
448
449 #[must_use]
451 pub fn replicas_assignments(&self) -> Option<&[(i32, Vec<i32>)]> {
452 if self.assignments.is_empty() {
453 None
454 } else {
455 Some(self.assignments.as_slice())
456 }
457 }
458}
459
460impl fmt::Display for NewTopic {
461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462 write!(f, "(name={}, numPartitions=", self.name)?;
463 if self.num_partitions < 0 {
464 f.write_str("default")?;
465 } else {
466 write!(f, "{}", self.num_partitions)?;
467 }
468 f.write_str(", replicationFactor=")?;
469 if self.replication_factor < 0 {
470 f.write_str("default")?;
471 } else {
472 write!(f, "{}", self.replication_factor)?;
473 }
474 f.write_str(", replicasAssignments=")?;
475 if self.assignments.is_empty() {
476 f.write_str("null")?;
477 } else {
478 f.write_str("{")?;
479 for (i, (partition, brokers)) in self.assignments.iter().enumerate() {
480 if i > 0 {
481 f.write_str(", ")?;
482 }
483 write!(f, "{partition}=")?;
484 write_java_int_list(f, brokers)?;
485 }
486 f.write_str("}")?;
487 }
488 f.write_str(", configs=")?;
489 if self.configs.is_empty() {
490 f.write_str("null")?;
491 } else {
492 f.write_str("{")?;
493 for (i, (name, value)) in self.configs.iter().enumerate() {
494 if i > 0 {
495 f.write_str(", ")?;
496 }
497 write!(f, "{name}=")?;
498 match value {
499 Some(v) => f.write_str(v)?,
500 None => f.write_str("null")?,
501 }
502 }
503 f.write_str("}")?;
504 }
505 f.write_str(")")
506 }
507}
508
509#[derive(Clone, Copy, Debug, PartialEq, Eq)]
517pub struct RecordsToDelete {
518 offset: i64,
519}
520
521impl RecordsToDelete {
522 #[must_use]
524 pub const fn before_offset(offset: i64) -> Self {
525 Self { offset }
526 }
527
528 #[must_use]
530 pub const fn offset(self) -> i64 {
531 self.offset
532 }
533}
534
535impl fmt::Display for RecordsToDelete {
536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537 write!(f, "(beforeOffset = {})", self.offset)
538 }
539}
540
541impl From<RecordsToDelete> for i64 {
542 fn from(records: RecordsToDelete) -> Self {
543 records.offset
544 }
545}
546
547#[derive(Debug, Clone, Copy, PartialEq, Eq)]
554pub struct DeletedRecords {
555 pub low_watermark: i64,
557 pub error_code: i16,
559}
560
561impl DeletedRecords {
562 pub const INVALID_LOW_WATERMARK: i64 = -1;
564
565 #[must_use]
567 pub const fn new(low_watermark: i64) -> Self {
568 Self {
569 low_watermark,
570 error_code: 0,
571 }
572 }
573
574 #[must_use]
576 pub const fn with_error_code(low_watermark: i64, error_code: i16) -> Self {
577 Self {
578 low_watermark,
579 error_code,
580 }
581 }
582
583 #[must_use]
585 pub const fn low_watermark(self) -> i64 {
586 self.low_watermark
587 }
588
589 #[must_use]
591 pub const fn error_code(self) -> i16 {
592 self.error_code
593 }
594}
595
596impl From<(i64, i16)> for DeletedRecords {
597 fn from((low_watermark, error_code): (i64, i16)) -> Self {
598 Self {
599 low_watermark,
600 error_code,
601 }
602 }
603}
604
605impl From<DeletedRecords> for (i64, i16) {
606 fn from(deleted: DeletedRecords) -> Self {
607 (deleted.low_watermark, deleted.error_code)
608 }
609}
610
611#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
618pub struct Uuid([u8; 16]);
619
620impl Uuid {
621 pub const ZERO: Self = Self([0; 16]);
623
624 pub const ZERO_UUID: Self = Self::ZERO;
626
627 pub const ONE: Self = Self([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
629
630 pub const ONE_UUID: Self = Self::ONE;
632
633 pub const METADATA_TOPIC_ID: Self = Self::ONE;
635
636 pub const RESERVED: [Self; 2] = [Self::ZERO, Self::ONE];
640
641 #[must_use]
643 pub const fn from_bytes(bytes: [u8; 16]) -> Self {
644 Self(bytes)
645 }
646
647 #[must_use]
649 pub const fn to_bytes(self) -> [u8; 16] {
650 self.0
651 }
652
653 #[must_use]
655 pub const fn as_bytes(&self) -> &[u8; 16] {
656 &self.0
657 }
658
659 #[must_use]
661 pub fn from_parts(most: i64, least: i64) -> Self {
662 let mut bytes = [0u8; 16];
663 if let Some(hi) = bytes.first_chunk_mut::<8>() {
664 *hi = most.to_be_bytes();
665 }
666 if let Some(lo) = bytes.last_chunk_mut::<8>() {
667 *lo = least.to_be_bytes();
668 }
669 Self(bytes)
670 }
671
672 #[must_use]
674 pub fn most_significant_bits(self) -> i64 {
675 match self.0.first_chunk::<8>() {
676 Some(hi) => i64::from_be_bytes(*hi),
677 None => 0,
678 }
679 }
680
681 #[must_use]
683 pub fn least_significant_bits(self) -> i64 {
684 match self.0.last_chunk::<8>() {
685 Some(lo) => i64::from_be_bytes(*lo),
686 None => 0,
687 }
688 }
689
690 pub fn from_string(s: &str) -> Result<Self> {
692 if s.len() > 24 {
693 let prefix = s.get(..24).unwrap_or(s);
694 return Err(Error::protocol(format!(
695 "Input string with prefix `{prefix}` is too long to be decoded as a base64 UUID"
696 )));
697 }
698 let decoded =
699 match base64::Engine::decode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, s) {
700 Ok(b) => b,
701 Err(_) => base64::Engine::decode(&base64::engine::general_purpose::URL_SAFE, s)
702 .map_err(|_| {
703 Error::protocol(format!("Uuid string `{s}` is not a base64url UUID"))
704 })?,
705 };
706 let n = decoded.len();
707 let bytes = <[u8; 16]>::try_from(decoded).map_err(|_| {
708 Error::protocol(format!(
709 "Input string `{s}` decoded as {n} bytes, which is not equal to the expected 16 bytes of a base64-encoded UUID"
710 ))
711 })?;
712 Ok(Self(bytes))
713 }
714
715 #[must_use]
721 pub fn random_uuid() -> Self {
722 loop {
723 let uuid = Self::new_type4_uuid();
724 if Self::RESERVED.contains(&uuid) || uuid.to_string().starts_with('-') {
725 continue;
726 }
727 return uuid;
728 }
729 }
730
731 fn new_type4_uuid() -> Self {
732 let mut bytes = [0u8; 16];
733 loop {
734 if getrandom::getrandom(&mut bytes).is_ok() {
735 break;
736 }
737 }
738 if let Some(b6) = bytes.get_mut(6) {
739 *b6 = (*b6 & 0x0f) | 0x40;
740 }
741 if let Some(b8) = bytes.get_mut(8) {
742 *b8 = (*b8 & 0x3f) | 0x80;
743 }
744 Self(bytes)
745 }
746}
747
748impl fmt::Display for Uuid {
749 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
750 f.write_str(&base64::Engine::encode(
751 &base64::engine::general_purpose::URL_SAFE_NO_PAD,
752 self.0.as_slice(),
753 ))
754 }
755}
756
757impl From<[u8; 16]> for Uuid {
758 fn from(bytes: [u8; 16]) -> Self {
759 Self(bytes)
760 }
761}
762
763impl From<Uuid> for [u8; 16] {
764 fn from(id: Uuid) -> Self {
765 id.0
766 }
767}
768
769impl std::str::FromStr for Uuid {
770 type Err = Error;
771
772 fn from_str(s: &str) -> Result<Self> {
773 Self::from_string(s)
774 }
775}
776
777impl PartialOrd for Uuid {
778 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
779 Some(self.cmp(other))
780 }
781}
782
783impl Ord for Uuid {
784 fn cmp(&self, other: &Self) -> Ordering {
785 self.most_significant_bits()
787 .cmp(&other.most_significant_bits())
788 .then(
789 self.least_significant_bits()
790 .cmp(&other.least_significant_bits()),
791 )
792 }
793}
794
795#[derive(Debug, Clone, PartialEq, Eq)]
804pub enum TopicCollection {
805 Names(Vec<String>),
807 Ids(Vec<Uuid>),
809}
810
811impl TopicCollection {
812 #[must_use]
814 pub fn of_topic_names(names: impl IntoIterator<Item = impl Into<String>>) -> Self {
815 Self::Names(names.into_iter().map(Into::into).collect())
816 }
817
818 #[must_use]
824 pub fn of_topic_ids<I, Id>(ids: I) -> Self
825 where
826 I: IntoIterator<Item = Id>,
827 Id: Into<Uuid>,
828 {
829 Self::Ids(ids.into_iter().map(Into::into).collect())
830 }
831
832 #[must_use]
834 pub fn topic_names(&self) -> Option<&[String]> {
835 match self {
836 Self::Names(names) => Some(names.as_slice()),
837 Self::Ids(_) => None,
838 }
839 }
840
841 #[must_use]
843 pub fn topic_ids(&self) -> Option<&[Uuid]> {
844 match self {
845 Self::Ids(ids) => Some(ids.as_slice()),
846 Self::Names(_) => None,
847 }
848 }
849}
850
851#[derive(Debug, Clone, PartialEq, Eq)]
853pub struct TopicListing {
854 pub name: String,
856 pub topic_id: [u8; 16],
858 pub is_internal: bool,
860}
861
862impl TopicListing {
863 #[must_use]
865 pub fn new(name: impl Into<String>, topic_id: impl Into<[u8; 16]>, is_internal: bool) -> Self {
866 Self {
867 name: name.into(),
868 topic_id: topic_id.into(),
869 is_internal,
870 }
871 }
872
873 #[must_use]
875 pub fn name(&self) -> &str {
876 self.name.as_str()
877 }
878
879 #[must_use]
881 pub fn topic_id(&self) -> Uuid {
882 Uuid::from_bytes(self.topic_id)
883 }
884
885 #[must_use]
887 pub fn is_internal(&self) -> bool {
888 self.is_internal
889 }
890}
891
892impl fmt::Display for TopicListing {
893 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
894 write!(
895 f,
896 "(name={}, topicId={}, internal={})",
897 self.name,
898 Uuid::from_bytes(self.topic_id),
899 self.is_internal
900 )
901 }
902}
903
904#[derive(Debug, Clone, PartialEq, Eq)]
913pub enum ConsumerGroupDescription {
914 Consumer(DescribedConsumerGroup),
916 Classic(DescribedGroup),
918}
919
920impl ConsumerGroupDescription {
921 #[must_use]
923 pub fn group_id(&self) -> &str {
924 match self {
925 Self::Consumer(g) => g.group_id.as_str(),
926 Self::Classic(g) => g.group_id.as_str(),
927 }
928 }
929
930 #[must_use]
932 pub fn error_code(&self) -> i16 {
933 match self {
934 Self::Consumer(g) => g.error_code,
935 Self::Classic(g) => g.error_code,
936 }
937 }
938
939 #[must_use]
941 pub fn group_state(&self) -> &str {
942 match self {
943 Self::Consumer(g) => g.group_state.as_str(),
944 Self::Classic(g) => g.group_state.as_str(),
945 }
946 }
947
948 #[must_use]
950 pub fn authorized_operations(&self) -> i32 {
951 match self {
952 Self::Consumer(g) => g.authorized_operations,
953 Self::Classic(g) => g.authorized_operations,
954 }
955 }
956
957 #[must_use]
959 pub fn is_consumer_protocol(&self) -> bool {
960 matches!(self, Self::Consumer(_))
961 }
962
963 #[must_use]
968 pub fn is_simple_consumer_group(&self) -> bool {
969 match self {
970 Self::Consumer(_) => false,
971 Self::Classic(g) => g.is_simple_consumer_group(),
972 }
973 }
974
975 #[must_use]
977 pub fn partition_assignor(&self) -> &str {
978 match self {
979 Self::Consumer(g) => g.assignor_name(),
980 Self::Classic(g) => g.protocol_data(),
981 }
982 }
983
984 #[must_use]
986 pub fn group_type(&self) -> GroupType {
987 match self {
988 Self::Consumer(_) => GroupType::Consumer,
989 Self::Classic(_) => GroupType::Classic,
990 }
991 }
992
993 #[must_use]
995 pub fn group_epoch(&self) -> Option<i32> {
996 match self {
997 Self::Consumer(g) => Some(g.group_epoch()),
998 Self::Classic(_) => None,
999 }
1000 }
1001
1002 #[must_use]
1004 pub fn target_assignment_epoch(&self) -> Option<i32> {
1005 match self {
1006 Self::Consumer(g) => Some(g.assignment_epoch()),
1007 Self::Classic(_) => None,
1008 }
1009 }
1010}
1011
1012#[derive(Debug, Clone, PartialEq, Eq)]
1014pub struct TopicDescription {
1015 pub name: String,
1017 pub topic_id: [u8; 16],
1019 pub is_internal: bool,
1021 pub error_code: i16,
1023 pub partitions: Vec<crate::PartitionInfo>,
1025 pub authorized_operations: i32,
1028}
1029
1030impl TopicDescription {
1031 #[must_use]
1033 pub fn new(
1034 name: impl Into<String>,
1035 topic_id: impl Into<[u8; 16]>,
1036 is_internal: bool,
1037 error_code: i16,
1038 partitions: Vec<crate::PartitionInfo>,
1039 ) -> Self {
1040 Self {
1041 name: name.into(),
1042 topic_id: topic_id.into(),
1043 is_internal,
1044 error_code,
1045 partitions,
1046 authorized_operations: AUTHORIZED_OPERATIONS_OMITTED,
1047 }
1048 }
1049
1050 #[must_use]
1052 pub fn name(&self) -> &str {
1053 self.name.as_str()
1054 }
1055
1056 #[must_use]
1058 pub fn topic_id(&self) -> Uuid {
1059 Uuid::from_bytes(self.topic_id)
1060 }
1061
1062 #[must_use]
1064 pub fn is_internal(&self) -> bool {
1065 self.is_internal
1066 }
1067
1068 #[must_use]
1071 pub fn error_code(&self) -> i16 {
1072 self.error_code
1073 }
1074
1075 #[must_use]
1077 pub fn partitions(&self) -> &[crate::PartitionInfo] {
1078 &self.partitions
1079 }
1080
1081 #[must_use]
1084 pub fn authorized_operations(&self) -> i32 {
1085 self.authorized_operations
1086 }
1087}
1088
1089impl TopicResult {
1090 #[must_use]
1094 pub fn topic_id(&self) -> Uuid {
1095 Uuid::from_bytes(self.topic_id)
1096 }
1097}
1098
1099impl GetTelemetrySubscriptionsResponse {
1100 #[must_use]
1102 pub fn client_instance_id(&self) -> Uuid {
1103 Uuid::from_bytes(self.client_instance_id)
1104 }
1105}
1106
1107impl PushTelemetryRequest {
1108 #[must_use]
1110 pub fn client_instance_id(&self) -> Uuid {
1111 Uuid::from_bytes(self.client_instance_id)
1112 }
1113}
1114
1115impl AssignReplicasToDirsTopic {
1116 #[must_use]
1118 pub fn topic_id(&self) -> Uuid {
1119 Uuid::from_bytes(self.topic_id)
1120 }
1121}
1122
1123impl AssignReplicasToDirsDirectory {
1124 #[must_use]
1126 pub fn id(&self) -> Uuid {
1127 Uuid::from_bytes(self.id)
1128 }
1129}
1130
1131impl AssignReplicasToDirsResponseTopic {
1132 #[must_use]
1134 pub fn topic_id(&self) -> Uuid {
1135 Uuid::from_bytes(self.topic_id)
1136 }
1137}
1138
1139impl AssignReplicasToDirsResponseDirectory {
1140 #[must_use]
1142 pub fn id(&self) -> Uuid {
1143 Uuid::from_bytes(self.id)
1144 }
1145}
1146
1147#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1149pub struct TopicPartitionReplica {
1150 pub topic: String,
1152 pub partition: i32,
1154 pub broker_id: i32,
1156}
1157
1158impl TopicPartitionReplica {
1159 #[must_use]
1161 pub fn new(topic: impl Into<String>, partition: i32, broker_id: i32) -> Self {
1162 Self {
1163 topic: topic.into(),
1164 partition,
1165 broker_id,
1166 }
1167 }
1168
1169 #[must_use]
1171 pub fn topic(&self) -> &str {
1172 self.topic.as_str()
1173 }
1174
1175 #[must_use]
1177 pub fn partition(&self) -> i32 {
1178 self.partition
1179 }
1180
1181 #[must_use]
1183 pub fn broker_id(&self) -> i32 {
1184 self.broker_id
1185 }
1186}
1187
1188impl fmt::Display for TopicPartitionReplica {
1189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1190 write!(f, "{}-{}-{}", self.topic, self.partition, self.broker_id)
1191 }
1192}
1193
1194#[derive(Debug, Clone, PartialEq, Eq)]
1198pub struct ReplicaLogDirInfo {
1199 pub current_log_dir: Option<String>,
1201 pub current_offset_lag: i64,
1203 pub future_log_dir: Option<String>,
1205 pub future_offset_lag: i64,
1207}
1208
1209impl ReplicaLogDirInfo {
1210 #[must_use]
1212 pub fn new(
1213 current_log_dir: Option<String>,
1214 current_offset_lag: i64,
1215 future_log_dir: Option<String>,
1216 future_offset_lag: i64,
1217 ) -> Self {
1218 Self {
1219 current_log_dir,
1220 current_offset_lag,
1221 future_log_dir,
1222 future_offset_lag,
1223 }
1224 }
1225
1226 #[must_use]
1228 pub fn unknown() -> Self {
1229 Self::new(None, -1, None, -1)
1230 }
1231
1232 #[must_use]
1234 pub fn current_log_dir(&self) -> Option<&str> {
1235 self.current_log_dir.as_deref()
1236 }
1237
1238 #[must_use]
1240 pub fn current_offset_lag(&self) -> i64 {
1241 self.current_offset_lag
1242 }
1243
1244 #[must_use]
1246 pub fn future_log_dir(&self) -> Option<&str> {
1247 self.future_log_dir.as_deref()
1248 }
1249
1250 #[must_use]
1252 pub fn future_offset_lag(&self) -> i64 {
1253 self.future_offset_lag
1254 }
1255}
1256
1257impl fmt::Display for ReplicaLogDirInfo {
1258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1259 match self.future_log_dir.as_deref() {
1260 Some(future) => {
1261 f.write_str("(currentReplicaLogDir=")?;
1262 write_java_nullable_str(f, self.current_log_dir.as_deref())?;
1263 f.write_str(", futureReplicaLogDir=")?;
1264 f.write_str(future)?;
1265 write!(f, ", futureReplicaOffsetLag={})", self.future_offset_lag)
1266 }
1267 None => {
1268 f.write_str("ReplicaLogDirInfo(currentReplicaLogDir=")?;
1269 write_java_nullable_str(f, self.current_log_dir.as_deref())?;
1270 f.write_str(")")
1271 }
1272 }
1273 }
1274}
1275
1276fn write_java_nullable_str(f: &mut fmt::Formatter<'_>, s: Option<&str>) -> fmt::Result {
1277 match s {
1278 Some(s) => f.write_str(s),
1279 None => f.write_str("null"),
1280 }
1281}
1282
1283fn write_java_int_list(f: &mut fmt::Formatter<'_>, ids: &[i32]) -> fmt::Result {
1285 f.write_str("[")?;
1286 for (i, id) in ids.iter().enumerate() {
1287 if i > 0 {
1288 f.write_str(", ")?;
1289 }
1290 write!(f, "{id}")?;
1291 }
1292 f.write_str("]")
1293}
1294
1295fn write_java_feature_map<T, N>(f: &mut fmt::Formatter<'_>, items: &[T], name: N) -> fmt::Result
1297where
1298 T: fmt::Display,
1299 N: Fn(&T) -> &str,
1300{
1301 f.write_str("{")?;
1302 for (i, item) in items.iter().enumerate() {
1303 if i > 0 {
1304 f.write_str(", ")?;
1305 }
1306 f.write_str("(")?;
1307 f.write_str(name(item))?;
1308 f.write_str(" -> ")?;
1309 write!(f, "{item}")?;
1310 f.write_str(")")?;
1311 }
1312 f.write_str("}")
1313}
1314
1315#[derive(Debug, Clone, PartialEq, Eq)]
1320pub struct NewPartitions {
1321 pub name: String,
1323 pub total_count: i32,
1325 pub assignments: Option<Vec<Vec<i32>>>,
1331}
1332
1333impl NewPartitions {
1334 #[must_use]
1338 pub fn increase_to(name: impl Into<String>, total_count: i32) -> Self {
1339 Self {
1340 name: name.into(),
1341 total_count,
1342 assignments: None,
1343 }
1344 }
1345
1346 #[must_use]
1350 pub fn with_assignments(
1351 mut self,
1352 assignments: impl IntoIterator<Item = impl IntoIterator<Item = i32>>,
1353 ) -> Self {
1354 self.assignments = Some(
1355 assignments
1356 .into_iter()
1357 .map(|brokers| brokers.into_iter().collect())
1358 .collect(),
1359 );
1360 self
1361 }
1362
1363 #[must_use]
1365 pub fn name(&self) -> &str {
1366 self.name.as_str()
1367 }
1368
1369 #[must_use]
1371 pub fn total_count(&self) -> i32 {
1372 self.total_count
1373 }
1374
1375 #[must_use]
1377 pub fn assignments(&self) -> Option<&[Vec<i32>]> {
1378 self.assignments.as_deref()
1379 }
1380}
1381
1382impl fmt::Display for NewPartitions {
1383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1384 write!(f, "(totalCount={}, newAssignments=", self.total_count)?;
1385 match &self.assignments {
1386 None => f.write_str("null")?,
1387 Some(asns) => {
1388 f.write_str("[")?;
1389 for (i, brokers) in asns.iter().enumerate() {
1390 if i > 0 {
1391 f.write_str(", ")?;
1392 }
1393 write_java_int_list(f, brokers)?;
1394 }
1395 f.write_str("]")?;
1396 }
1397 }
1398 f.write_str(")")
1399 }
1400}
1401
1402#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1404#[repr(i8)]
1405pub enum ConfigResourceType {
1406 Topic = RESOURCE_TOPIC,
1408 Broker = RESOURCE_BROKER,
1410 BrokerLogger = RESOURCE_BROKER_LOGGER,
1412 ClientMetrics = RESOURCE_CLIENT_METRICS,
1414 Group = RESOURCE_GROUP,
1416}
1417
1418impl From<ConfigResourceType> for i8 {
1419 fn from(ty: ConfigResourceType) -> Self {
1420 ty as i8
1421 }
1422}
1423
1424impl ConfigResourceType {
1425 #[must_use]
1427 pub const fn id(self) -> i8 {
1428 self as i8
1429 }
1430
1431 #[must_use]
1433 pub const fn from_id(id: i8) -> Option<Self> {
1434 match id {
1435 RESOURCE_TOPIC => Some(Self::Topic),
1436 RESOURCE_BROKER => Some(Self::Broker),
1437 RESOURCE_BROKER_LOGGER => Some(Self::BrokerLogger),
1438 RESOURCE_CLIENT_METRICS => Some(Self::ClientMetrics),
1439 RESOURCE_GROUP => Some(Self::Group),
1440 _ => None,
1441 }
1442 }
1443}
1444
1445impl fmt::Display for ConfigResourceType {
1446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1447 f.write_str(match *self {
1448 Self::Topic => "TOPIC",
1449 Self::Broker => "BROKER",
1450 Self::BrokerLogger => "BROKER_LOGGER",
1451 Self::ClientMetrics => "CLIENT_METRICS",
1452 Self::Group => "GROUP",
1453 })
1454 }
1455}
1456
1457#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1459pub struct ConfigResource {
1460 pub resource_type: i8,
1462 pub name: String,
1464 pub keys: Option<Vec<String>>,
1466}
1467
1468impl ConfigResource {
1469 #[must_use]
1471 pub fn of(ty: ConfigResourceType, name: impl Into<String>) -> Self {
1472 Self {
1473 resource_type: i8::from(ty),
1474 name: name.into(),
1475 keys: None,
1476 }
1477 }
1478
1479 #[must_use]
1481 pub fn topic(name: impl Into<String>) -> Self {
1482 Self::of(ConfigResourceType::Topic, name)
1483 }
1484
1485 #[must_use]
1487 pub fn broker(id: i32) -> Self {
1488 Self::of(ConfigResourceType::Broker, id.to_string())
1489 }
1490
1491 #[must_use]
1493 pub fn group(name: impl Into<String>) -> Self {
1494 Self::of(ConfigResourceType::Group, name)
1495 }
1496
1497 #[must_use]
1499 pub fn keys(mut self, keys: impl IntoIterator<Item = impl Into<String>>) -> Self {
1500 self.keys = Some(keys.into_iter().map(Into::into).collect());
1501 self
1502 }
1503
1504 #[must_use]
1506 pub fn name(&self) -> &str {
1507 self.name.as_str()
1508 }
1509
1510 #[must_use]
1512 pub fn resource_type(&self) -> Option<ConfigResourceType> {
1513 ConfigResourceType::from_id(self.resource_type)
1514 }
1515
1516 #[must_use]
1518 pub fn is_default(&self) -> bool {
1519 self.name.is_empty()
1520 }
1521}
1522
1523impl fmt::Display for ConfigResource {
1524 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1525 f.write_str("ConfigResource(type=")?;
1526 match self.resource_type() {
1527 Some(ty) => write!(f, "{ty}")?,
1528 None => f.write_str("UNKNOWN")?,
1529 }
1530 write!(f, ", name='{}')", self.name)
1531 }
1532}
1533
1534fn config_resource_from_response(resource_type: i8, name: impl Into<String>) -> ConfigResource {
1538 ConfigResource {
1539 resource_type: ConfigResourceType::from_id(resource_type)
1540 .map(ConfigResourceType::id)
1541 .unwrap_or(0),
1542 name: name.into(),
1543 keys: None,
1544 }
1545}
1546
1547fn alter_config_api_errors(
1548 results: &[AlterConfigsResourceResult],
1549) -> HashMap<ConfigResource, crate::error::ApiError> {
1550 let mut map = HashMap::new();
1551 for result in results {
1552 map.extend([(
1553 config_resource_from_response(result.resource_type, result.name.clone()),
1554 crate::error::ApiError::from_code(result.error_code, result.error_message.clone()),
1555 )]);
1556 }
1557 map
1558}
1559
1560impl crate::protocol::admin::AlterConfigsResponse {
1561 #[must_use]
1566 pub fn errors(
1567 results: &[AlterConfigsResourceResult],
1568 ) -> HashMap<ConfigResource, crate::error::ApiError> {
1569 alter_config_api_errors(results)
1570 }
1571}
1572
1573impl crate::protocol::admin::AlterConfigsRequest {
1574 #[must_use]
1582 pub fn configs(resources: &[AlterConfigsResource]) -> HashMap<ConfigResource, Config> {
1583 let mut map = HashMap::new();
1584 for resource in resources {
1585 map.extend([(
1586 config_resource_from_response(resource.resource_type, resource.name.clone()),
1587 Config::new(
1588 resource
1589 .configs
1590 .iter()
1591 .map(|c| ConfigEntry::new(c.name.clone(), c.value.clone())),
1592 ),
1593 )]);
1594 }
1595 map
1596 }
1597}
1598
1599impl crate::protocol::admin::IncrementalAlterConfigsResponse {
1600 #[must_use]
1604 pub fn from_response_data(
1605 results: &[AlterConfigsResourceResult],
1606 ) -> HashMap<ConfigResource, crate::error::ApiError> {
1607 alter_config_api_errors(results)
1608 }
1609}
1610
1611impl crate::protocol::admin::DescribeConfigsResponse {
1612 #[must_use]
1617 pub fn result_map(
1618 results: &[DescribeConfigsResult],
1619 ) -> HashMap<ConfigResource, DescribeConfigsResult> {
1620 let mut map = HashMap::new();
1621 for result in results {
1622 map.extend([(
1623 config_resource_from_response(result.resource_type, result.name.clone()),
1624 result.clone(),
1625 )]);
1626 }
1627 map
1628 }
1629}
1630
1631impl crate::protocol::admin::ListConfigResourcesResponse {
1632 #[must_use]
1642 pub fn config_resources(&self) -> Vec<ConfigResource> {
1643 self.config_resources
1644 .iter()
1645 .map(|r| config_resource_from_response(r.resource_type, r.resource_name.clone()))
1646 .collect()
1647 }
1648}
1649
1650impl ListedConfigResource {
1651 #[must_use]
1653 pub fn name(&self) -> &str {
1654 self.resource_name.as_str()
1655 }
1656
1657 #[must_use]
1659 pub fn resource_type(&self) -> Option<ConfigResourceType> {
1660 ConfigResourceType::from_id(self.resource_type)
1661 }
1662
1663 #[must_use]
1666 pub fn to_config_resource(&self) -> ConfigResource {
1667 ConfigResource {
1668 resource_type: self.resource_type,
1669 name: self.resource_name.clone(),
1670 keys: None,
1671 }
1672 }
1673
1674 #[must_use]
1676 pub fn is_default(&self) -> bool {
1677 self.resource_name.is_empty()
1678 }
1679}
1680
1681impl fmt::Display for ListedConfigResource {
1682 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1683 fmt::Display::fmt(&self.to_config_resource(), f)
1684 }
1685}
1686
1687impl From<ListedConfigResource> for ConfigResource {
1688 fn from(listed: ListedConfigResource) -> Self {
1689 Self {
1690 resource_type: listed.resource_type,
1691 name: listed.resource_name,
1692 keys: None,
1693 }
1694 }
1695}
1696
1697#[derive(Debug, Clone, PartialEq, Eq)]
1700pub struct ConfigResourceUpdate {
1701 pub resource: ConfigResource,
1703 pub configs: Vec<AlterConfig>,
1705}
1706
1707impl ConfigResourceUpdate {
1708 #[must_use]
1710 pub fn new(resource: ConfigResource, configs: impl IntoIterator<Item = AlterConfig>) -> Self {
1711 Self {
1712 resource,
1713 configs: configs.into_iter().collect(),
1714 }
1715 }
1716}
1717
1718#[derive(Debug, Clone, PartialEq, Eq)]
1721pub struct ConfigReplacement {
1722 pub resource: ConfigResource,
1724 pub configs: Vec<(String, Option<String>)>,
1726}
1727
1728impl ConfigReplacement {
1729 #[must_use]
1731 pub fn new(
1732 resource: ConfigResource,
1733 configs: impl IntoIterator<Item = (String, Option<String>)>,
1734 ) -> Self {
1735 Self {
1736 resource,
1737 configs: configs.into_iter().collect(),
1738 }
1739 }
1740
1741 #[must_use]
1743 pub fn from_config(resource: ConfigResource, config: &Config) -> Self {
1744 Self {
1745 resource,
1746 configs: config
1747 .entries()
1748 .iter()
1749 .map(|e| (e.name.clone(), e.value.clone()))
1750 .collect(),
1751 }
1752 }
1753}
1754
1755#[derive(Debug, Clone, PartialEq, Eq)]
1761pub struct NewPartitionReassignment {
1762 pub target_replicas: Vec<i32>,
1764}
1765
1766impl NewPartitionReassignment {
1767 pub fn new(target_replicas: impl IntoIterator<Item = i32>) -> Result<Self> {
1769 let target_replicas: Vec<i32> = target_replicas.into_iter().collect();
1770 if target_replicas.is_empty() {
1771 return Err(Error::protocol(
1772 "Cannot create a new partition reassignment without any replicas",
1773 ));
1774 }
1775 Ok(Self { target_replicas })
1776 }
1777
1778 #[must_use]
1780 pub fn target_replicas(&self) -> &[i32] {
1781 &self.target_replicas
1782 }
1783}
1784
1785#[derive(Debug, Clone, PartialEq, Eq)]
1789pub struct PartitionReassignment {
1790 pub topic: String,
1792 pub partition: i32,
1794 pub replicas: Option<Vec<i32>>,
1796}
1797
1798impl PartitionReassignment {
1799 #[must_use]
1801 pub fn assign(
1802 partition: impl Into<crate::TopicPartition>,
1803 replicas: impl IntoIterator<Item = i32>,
1804 ) -> Self {
1805 let tp = partition.into();
1806 Self {
1807 topic: tp.topic,
1808 partition: tp.partition,
1809 replicas: Some(replicas.into_iter().collect()),
1810 }
1811 }
1812
1813 #[must_use]
1815 pub fn cancel(partition: impl Into<crate::TopicPartition>) -> Self {
1816 let tp = partition.into();
1817 Self {
1818 topic: tp.topic,
1819 partition: tp.partition,
1820 replicas: None,
1821 }
1822 }
1823
1824 #[must_use]
1827 pub fn from_new(
1828 partition: impl Into<crate::TopicPartition>,
1829 assignment: Option<NewPartitionReassignment>,
1830 ) -> Self {
1831 match assignment {
1832 Some(n) => Self::assign(partition, n.target_replicas),
1833 None => Self::cancel(partition),
1834 }
1835 }
1836
1837 #[must_use]
1839 pub fn topic(&self) -> &str {
1840 self.topic.as_str()
1841 }
1842
1843 #[must_use]
1845 pub fn partition(&self) -> i32 {
1846 self.partition
1847 }
1848
1849 #[must_use]
1851 pub fn replicas(&self) -> Option<&[i32]> {
1852 self.replicas.as_deref()
1853 }
1854}
1855
1856#[derive(Debug, Clone, PartialEq, Eq)]
1858pub struct ReassignmentResult {
1859 pub topic: String,
1861 pub partition: i32,
1863 pub error_code: i16,
1865 pub error_message: Option<String>,
1867}
1868
1869impl ReassignmentResult {
1870 #[must_use]
1872 pub fn topic(&self) -> &str {
1873 self.topic.as_str()
1874 }
1875
1876 #[must_use]
1878 pub fn partition(&self) -> i32 {
1879 self.partition
1880 }
1881
1882 #[must_use]
1884 pub fn error_code(&self) -> i16 {
1885 self.error_code
1886 }
1887
1888 #[must_use]
1890 pub fn error_message(&self) -> Option<&str> {
1891 self.error_message.as_deref()
1892 }
1893}
1894
1895#[derive(Debug, Clone, PartialEq, Eq)]
1901pub struct OngoingReassignment {
1902 pub topic: String,
1904 pub partition: i32,
1906 pub replicas: Vec<i32>,
1908 pub adding_replicas: Vec<i32>,
1910 pub removing_replicas: Vec<i32>,
1912}
1913
1914impl OngoingReassignment {
1915 #[must_use]
1917 pub fn topic(&self) -> &str {
1918 self.topic.as_str()
1919 }
1920
1921 #[must_use]
1923 pub fn partition(&self) -> i32 {
1924 self.partition
1925 }
1926
1927 #[must_use]
1929 pub fn replicas(&self) -> &[i32] {
1930 &self.replicas
1931 }
1932
1933 #[must_use]
1935 pub fn adding_replicas(&self) -> &[i32] {
1936 &self.adding_replicas
1937 }
1938
1939 #[must_use]
1941 pub fn removing_replicas(&self) -> &[i32] {
1942 &self.removing_replicas
1943 }
1944}
1945
1946impl fmt::Display for OngoingReassignment {
1947 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1948 f.write_str("PartitionReassignment(replicas=")?;
1949 write_java_int_list(f, &self.replicas)?;
1950 f.write_str(", addingReplicas=")?;
1951 write_java_int_list(f, &self.adding_replicas)?;
1952 f.write_str(", removingReplicas=")?;
1953 write_java_int_list(f, &self.removing_replicas)?;
1954 f.write_str(")")
1955 }
1956}
1957
1958#[derive(Debug, Clone, PartialEq, Eq)]
1968pub struct FeatureUpdate {
1969 pub name: String,
1971 pub max_version_level: i16,
1973 pub allow_downgrade: bool,
1976 pub upgrade_type: i8,
1978}
1979
1980impl FeatureUpdate {
1981 #[must_use]
1983 pub fn new(name: impl Into<String>, max_version_level: i16) -> Self {
1984 Self {
1985 name: name.into(),
1986 max_version_level,
1987 allow_downgrade: false,
1988 upgrade_type: UPGRADE_TYPE_UPGRADE,
1989 }
1990 }
1991
1992 #[must_use]
1996 pub fn allow_downgrade(mut self, allow: bool) -> Self {
1997 self.allow_downgrade = allow;
1998 self.upgrade_type = if allow {
1999 UPGRADE_TYPE_SAFE_DOWNGRADE
2000 } else {
2001 UPGRADE_TYPE_UPGRADE
2002 };
2003 self
2004 }
2005
2006 #[must_use]
2009 pub fn upgrade_type(mut self, upgrade_type: impl Into<i8>) -> Self {
2010 let upgrade_type = upgrade_type.into();
2011 self.upgrade_type = upgrade_type;
2012 self.allow_downgrade = upgrade_type != UPGRADE_TYPE_UPGRADE;
2013 self
2014 }
2015
2016 #[must_use]
2018 pub fn name(&self) -> &str {
2019 self.name.as_str()
2020 }
2021
2022 #[must_use]
2024 pub fn max_version_level(&self) -> i16 {
2025 self.max_version_level
2026 }
2027
2028 #[must_use]
2034 pub fn is_delete_request(&self) -> bool {
2035 self.max_version_level < 1 && self.upgrade_type != UPGRADE_TYPE_UPGRADE
2036 }
2037}
2038
2039impl fmt::Display for FeatureUpdate {
2040 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2041 f.write_str("FeatureUpdate{maxVersionLevel:")?;
2042 write!(f, "{}", self.max_version_level)?;
2043 f.write_str(", upgradeType:")?;
2044 f.write_str(match self.upgrade_type {
2045 UPGRADE_TYPE_UPGRADE => "UPGRADE",
2046 UPGRADE_TYPE_SAFE_DOWNGRADE => "SAFE_DOWNGRADE",
2047 UPGRADE_TYPE_UNSAFE_DOWNGRADE => "UNSAFE_DOWNGRADE",
2048 _ => "UNKNOWN",
2049 })?;
2050 f.write_str("}")
2051 }
2052}
2053
2054#[derive(Debug, Clone, PartialEq, Eq)]
2056pub struct FeatureUpdateResult {
2057 pub name: String,
2059 pub error_code: i16,
2061 pub error_message: Option<String>,
2063}
2064
2065impl FeatureUpdateResult {
2066 #[must_use]
2068 pub fn name(&self) -> &str {
2069 self.name.as_str()
2070 }
2071
2072 #[must_use]
2074 pub fn error_code(&self) -> i16 {
2075 self.error_code
2076 }
2077
2078 #[must_use]
2080 pub fn error_message(&self) -> Option<&str> {
2081 self.error_message.as_deref()
2082 }
2083}
2084
2085#[derive(Debug, Clone, PartialEq, Eq)]
2091pub struct SupportedVersionRange {
2092 pub name: String,
2094 pub min_version: i16,
2096 pub max_version: i16,
2098}
2099
2100impl SupportedVersionRange {
2101 pub fn new(name: impl Into<String>, min_version: i16, max_version: i16) -> Result<Self> {
2106 if min_version < 0 || max_version < 0 || max_version < min_version {
2107 return Err(Error::protocol(format!(
2108 "Expected 0 <= minVersion <= maxVersion but received minVersion:{min_version}, maxVersion:{max_version}."
2109 )));
2110 }
2111 Ok(Self {
2112 name: name.into(),
2113 min_version,
2114 max_version,
2115 })
2116 }
2117
2118 #[must_use]
2120 pub fn name(&self) -> &str {
2121 self.name.as_str()
2122 }
2123
2124 #[must_use]
2126 pub fn min_version(&self) -> i16 {
2127 self.min_version
2128 }
2129
2130 #[must_use]
2132 pub fn max_version(&self) -> i16 {
2133 self.max_version
2134 }
2135}
2136
2137impl fmt::Display for SupportedVersionRange {
2138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2139 f.write_str("SupportedVersionRange[min_version:")?;
2140 write!(f, "{}", self.min_version())?;
2141 f.write_str(", max_version:")?;
2142 write!(f, "{}", self.max_version())?;
2143 f.write_str("]")
2144 }
2145}
2146
2147#[derive(Debug, Clone, PartialEq, Eq)]
2153pub struct FinalizedVersionRange {
2154 pub name: String,
2156 pub min_version_level: i16,
2158 pub max_version_level: i16,
2160}
2161
2162impl FinalizedVersionRange {
2163 pub fn new(
2168 name: impl Into<String>,
2169 min_version_level: i16,
2170 max_version_level: i16,
2171 ) -> Result<Self> {
2172 if min_version_level < 0 || max_version_level < 0 || max_version_level < min_version_level {
2173 return Err(Error::protocol(format!(
2174 "Expected minVersionLevel >= 0, maxVersionLevel >= 0 and maxVersionLevel >= minVersionLevel, but received minVersionLevel: {min_version_level}, maxVersionLevel: {max_version_level}"
2175 )));
2176 }
2177 Ok(Self {
2178 name: name.into(),
2179 min_version_level,
2180 max_version_level,
2181 })
2182 }
2183
2184 #[must_use]
2186 pub fn name(&self) -> &str {
2187 self.name.as_str()
2188 }
2189
2190 #[must_use]
2192 pub fn min_version_level(&self) -> i16 {
2193 self.min_version_level
2194 }
2195
2196 #[must_use]
2198 pub fn max_version_level(&self) -> i16 {
2199 self.max_version_level
2200 }
2201}
2202
2203impl fmt::Display for FinalizedVersionRange {
2204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2205 f.write_str("FinalizedVersionRange[min_version_level:")?;
2206 write!(f, "{}", self.min_version_level())?;
2207 f.write_str(", max_version_level:")?;
2208 write!(f, "{}", self.max_version_level())?;
2209 f.write_str("]")
2210 }
2211}
2212
2213#[derive(Debug, Clone, PartialEq, Eq)]
2221pub struct FeatureMetadata {
2222 pub supported_features: Vec<SupportedVersionRange>,
2224 pub finalized_features: Vec<FinalizedVersionRange>,
2226 pub finalized_features_epoch: Option<i64>,
2228 pub zk_migration_ready: bool,
2230}
2231
2232impl FeatureMetadata {
2233 #[must_use]
2235 pub fn supported_features(&self) -> &[SupportedVersionRange] {
2236 &self.supported_features
2237 }
2238
2239 #[must_use]
2241 pub fn finalized_features(&self) -> &[FinalizedVersionRange] {
2242 &self.finalized_features
2243 }
2244
2245 #[must_use]
2247 pub fn finalized_features_epoch(&self) -> Option<i64> {
2248 self.finalized_features_epoch
2249 }
2250
2251 #[must_use]
2253 pub fn zk_migration_ready(&self) -> bool {
2254 self.zk_migration_ready
2255 }
2256}
2257
2258impl fmt::Display for FeatureMetadata {
2259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2260 f.write_str("FeatureMetadata{finalizedFeatures:")?;
2261 write_java_feature_map(f, self.finalized_features(), FinalizedVersionRange::name)?;
2262 f.write_str(", finalizedFeaturesEpoch:")?;
2263 match self.finalized_features_epoch() {
2264 Some(epoch) => write!(f, "{epoch}")?,
2265 None => f.write_str(" ")?,
2266 }
2267 f.write_str(", supportedFeatures:")?;
2268 write_java_feature_map(f, self.supported_features(), SupportedVersionRange::name)?;
2269 f.write_str("}")
2270 }
2271}
2272
2273#[derive(Debug, Clone, PartialEq, Eq)]
2275pub struct UserScramCredentialDeletion {
2276 pub name: String,
2278 pub mechanism: i8,
2280}
2281
2282impl UserScramCredentialDeletion {
2283 #[must_use]
2285 pub fn new(name: impl Into<String>, mechanism: impl Into<i8>) -> Self {
2286 Self {
2287 name: name.into(),
2288 mechanism: mechanism.into(),
2289 }
2290 }
2291
2292 #[must_use]
2294 pub fn user(&self) -> &str {
2295 self.name.as_str()
2296 }
2297
2298 #[must_use]
2300 pub fn mechanism(&self) -> ScramMechanism {
2301 ScramMechanism::from_id(self.mechanism)
2302 }
2303}
2304
2305#[derive(Clone, PartialEq, Eq)]
2311pub struct UserScramCredentialUpsertion {
2312 pub name: String,
2314 pub mechanism: i8,
2316 pub iterations: i32,
2318 pub salt: Vec<u8>,
2320 pub salted_password: Vec<u8>,
2322}
2323
2324impl UserScramCredentialUpsertion {
2325 #[must_use]
2327 pub fn new(
2328 name: impl Into<String>,
2329 mechanism: impl Into<i8>,
2330 iterations: i32,
2331 salt: impl Into<Vec<u8>>,
2332 salted_password: impl Into<Vec<u8>>,
2333 ) -> Self {
2334 Self {
2335 name: name.into(),
2336 mechanism: mechanism.into(),
2337 iterations,
2338 salt: salt.into(),
2339 salted_password: salted_password.into(),
2340 }
2341 }
2342
2343 #[must_use]
2345 pub fn user(&self) -> &str {
2346 self.name.as_str()
2347 }
2348
2349 #[must_use]
2351 pub fn credential_info(&self) -> ScramCredentialInfo {
2352 ScramCredentialInfo::new(self.mechanism, self.iterations)
2353 }
2354
2355 #[must_use]
2357 pub fn salt(&self) -> &[u8] {
2358 &self.salt
2359 }
2360}
2361
2362impl std::fmt::Debug for UserScramCredentialUpsertion {
2363 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2364 f.debug_struct("UserScramCredentialUpsertion")
2365 .field("name", &self.name)
2366 .field("mechanism", &self.mechanism)
2367 .field("iterations", &self.iterations)
2368 .field("salt", &"<redacted>")
2369 .field("salted_password", &"<redacted>")
2370 .finish()
2371 }
2372}
2373
2374#[derive(Clone, PartialEq, Eq, Debug)]
2381pub enum UserScramCredentialAlteration {
2382 Deletion(UserScramCredentialDeletion),
2384 Upsertion(UserScramCredentialUpsertion),
2386}
2387
2388impl UserScramCredentialAlteration {
2389 #[must_use]
2391 pub fn user(&self) -> &str {
2392 match self {
2393 Self::Deletion(d) => d.name.as_str(),
2394 Self::Upsertion(u) => u.name.as_str(),
2395 }
2396 }
2397}
2398
2399impl From<UserScramCredentialDeletion> for UserScramCredentialAlteration {
2400 fn from(deletion: UserScramCredentialDeletion) -> Self {
2401 Self::Deletion(deletion)
2402 }
2403}
2404
2405impl From<UserScramCredentialUpsertion> for UserScramCredentialAlteration {
2406 fn from(upsertion: UserScramCredentialUpsertion) -> Self {
2407 Self::Upsertion(upsertion)
2408 }
2409}
2410
2411#[derive(Debug, Clone, PartialEq, Eq)]
2413pub struct UserScramCredentialResult {
2414 pub user: String,
2416 pub error_code: i16,
2418 pub error_message: Option<String>,
2420}
2421
2422impl UserScramCredentialResult {
2423 #[must_use]
2425 pub fn user(&self) -> &str {
2426 self.user.as_str()
2427 }
2428
2429 #[must_use]
2431 pub fn error_code(&self) -> i16 {
2432 self.error_code
2433 }
2434
2435 #[must_use]
2437 pub fn error_message(&self) -> Option<&str> {
2438 self.error_message.as_deref()
2439 }
2440}
2441
2442#[derive(Debug, Clone, PartialEq, Eq)]
2446pub struct ProducerIdBlock {
2447 pub producer_id_start: i64,
2449 pub producer_id_len: i32,
2451}
2452
2453impl ProducerIdBlock {
2454 #[must_use]
2456 pub fn producer_id_start(&self) -> i64 {
2457 self.producer_id_start
2458 }
2459
2460 #[must_use]
2462 pub fn producer_id_len(&self) -> i32 {
2463 self.producer_id_len
2464 }
2465}
2466
2467#[derive(Debug, Clone, PartialEq, Eq)]
2472pub struct FencedProducer {
2473 pub transactional_id: String,
2475 pub producer_id: i64,
2477 pub epoch: i16,
2479}
2480
2481impl FencedProducer {
2482 #[must_use]
2484 pub fn transactional_id(&self) -> &str {
2485 self.transactional_id.as_str()
2486 }
2487
2488 #[must_use]
2490 pub fn producer_id(&self) -> i64 {
2491 self.producer_id
2492 }
2493
2494 #[must_use]
2496 pub fn epoch(&self) -> i16 {
2497 self.epoch
2498 }
2499}
2500
2501#[derive(Debug, Clone, PartialEq, Eq)]
2509pub struct AbortTransactionSpec {
2510 pub topic: String,
2512 pub partition: i32,
2514 pub producer_id: i64,
2516 pub producer_epoch: i16,
2518 pub coordinator_epoch: i32,
2520}
2521
2522impl AbortTransactionSpec {
2523 #[must_use]
2525 pub fn new(
2526 partition: impl Into<crate::TopicPartition>,
2527 producer_id: i64,
2528 producer_epoch: i16,
2529 coordinator_epoch: i32,
2530 ) -> Self {
2531 let tp = partition.into();
2532 Self {
2533 topic: tp.topic,
2534 partition: tp.partition,
2535 producer_id,
2536 producer_epoch,
2537 coordinator_epoch,
2538 }
2539 }
2540
2541 #[must_use]
2543 pub fn topic_partition(&self) -> crate::TopicPartition {
2544 crate::TopicPartition::new(self.topic.clone(), self.partition)
2545 }
2546
2547 #[must_use]
2549 pub fn producer_id(&self) -> i64 {
2550 self.producer_id
2551 }
2552
2553 #[must_use]
2555 pub fn producer_epoch(&self) -> i16 {
2556 self.producer_epoch
2557 }
2558
2559 #[must_use]
2561 pub fn coordinator_epoch(&self) -> i32 {
2562 self.coordinator_epoch
2563 }
2564}
2565
2566impl fmt::Display for AbortTransactionSpec {
2567 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2568 f.write_str("AbortTransactionSpec(topicPartition=")?;
2569 write!(f, "{}-{}", self.topic, self.partition)?;
2570 f.write_str(", producerId=")?;
2571 write!(f, "{}", self.producer_id())?;
2572 f.write_str(", producerEpoch=")?;
2573 write!(f, "{}", self.producer_epoch())?;
2574 f.write_str(", coordinatorEpoch=")?;
2575 write!(f, "{}", self.coordinator_epoch())?;
2576 f.write_str(")")
2577 }
2578}
2579
2580pub const DEFAULT_LEAVE_GROUP_REASON: &str = "member was removed by an admin";
2582
2583fn admin_leave_reason(reason: Option<&str>) -> String {
2585 match reason {
2586 None | Some("") => DEFAULT_LEAVE_GROUP_REASON.to_string(),
2587 Some(r) => crate::group::truncate_group_reason(r),
2588 }
2589}
2590
2591#[derive(Debug, Clone, PartialEq, Eq)]
2595pub struct MemberToRemove {
2596 pub group_instance_id: String,
2598}
2599
2600impl MemberToRemove {
2601 #[must_use]
2603 pub fn new(group_instance_id: impl Into<String>) -> Self {
2604 Self {
2605 group_instance_id: group_instance_id.into(),
2606 }
2607 }
2608
2609 #[must_use]
2611 pub fn group_instance_id(&self) -> &str {
2612 self.group_instance_id.as_str()
2613 }
2614}
2615
2616impl From<&str> for MemberToRemove {
2617 fn from(id: &str) -> Self {
2618 Self::new(id)
2619 }
2620}
2621
2622impl From<String> for MemberToRemove {
2623 fn from(id: String) -> Self {
2624 Self::new(id)
2625 }
2626}
2627
2628#[derive(Debug, Clone, PartialEq, Eq, Default)]
2636pub struct ListConsumerGroupOffsetsSpec {
2637 pub partitions: Option<Vec<crate::TopicPartition>>,
2639}
2640
2641impl ListConsumerGroupOffsetsSpec {
2642 #[must_use]
2644 pub fn all() -> Self {
2645 Self { partitions: None }
2646 }
2647
2648 #[must_use]
2650 pub fn topic_partitions(
2651 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
2652 ) -> Self {
2653 Self {
2654 partitions: Some(partitions.into_iter().map(Into::into).collect()),
2655 }
2656 }
2657
2658 #[must_use]
2660 pub fn partitions(&self) -> Option<&[crate::TopicPartition]> {
2661 self.partitions.as_deref()
2662 }
2663}
2664
2665impl fmt::Display for ListConsumerGroupOffsetsSpec {
2666 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2667 f.write_str("ListConsumerGroupOffsetsSpec(topicPartitions=")?;
2668 match &self.partitions {
2669 None => f.write_str("null")?,
2670 Some(tps) => {
2671 f.write_str("[")?;
2672 for (i, tp) in tps.iter().enumerate() {
2673 if i > 0 {
2674 f.write_str(", ")?;
2675 }
2676 write!(f, "{tp}")?;
2677 }
2678 f.write_str("]")?;
2679 }
2680 }
2681 f.write_str(")")
2682 }
2683}
2684
2685#[derive(Debug, Clone, PartialEq, Eq)]
2687pub struct RemovedMember {
2688 pub member_id: String,
2690 pub group_instance_id: Option<String>,
2692 pub error_code: i16,
2694}
2695
2696impl RemovedMember {
2697 #[must_use]
2699 pub fn member_id(&self) -> &str {
2700 self.member_id.as_str()
2701 }
2702
2703 #[must_use]
2705 pub fn group_instance_id(&self) -> Option<&str> {
2706 self.group_instance_id.as_deref()
2707 }
2708
2709 #[must_use]
2711 pub fn error_code(&self) -> i16 {
2712 self.error_code
2713 }
2714}
2715
2716pub struct Admin {
2718 cfg: AdminConfig,
2719 conn: BrokerConn,
2720 versions: HashMap<i16, ApiVersion>,
2721 create_version: i16,
2722 delete_version: i16,
2723 describe_version: i16,
2724 partitions_version: i16,
2725 alter_version: Option<i16>,
2726 legacy_alter_version: i16,
2727 delete_records_version: i16,
2728 describe_producers_version: Option<i16>,
2729 describe_cluster_version: Option<i16>,
2730 create_acls_version: i16,
2731 describe_acls_version: i16,
2732 delete_acls_version: i16,
2733 metadata_version: i16,
2734 find_coord_version: i16,
2735 offset_delete_version: Option<i16>,
2736 reassign_version: Option<i16>,
2737 list_reassign_version: Option<i16>,
2738 update_features_version: Option<i16>,
2739 alter_user_scram_version: Option<i16>,
2740 describe_user_scram_version: Option<i16>,
2741 unregister_broker_version: Option<i16>,
2742 describe_client_quotas_version: Option<i16>,
2743 alter_client_quotas_version: Option<i16>,
2744 allocate_producer_ids_version: Option<i16>,
2745 describe_transactions_version: Option<i16>,
2746 list_transactions_version: Option<i16>,
2747 consumer_group_describe_version: Option<i16>,
2748 describe_groups_version: i16,
2749 list_groups_version: i16,
2750 delete_groups_version: i16,
2751 share_group_describe_version: Option<i16>,
2752 describe_share_group_offsets_version: Option<i16>,
2753 alter_share_group_offsets_version: Option<i16>,
2754 delete_share_group_offsets_version: Option<i16>,
2755 describe_topic_partitions_version: Option<i16>,
2756 list_config_resources_version: Option<i16>,
2757 get_telemetry_subscriptions_version: Option<i16>,
2758 cached_client_instance_id: Option<[u8; 16]>,
2760 push_telemetry_version: Option<i16>,
2761 assign_replicas_to_dirs_version: Option<i16>,
2762 alter_replica_log_dirs_version: Option<i16>,
2763 describe_log_dirs_version: Option<i16>,
2764 create_delegation_token_version: Option<i16>,
2765 renew_delegation_token_version: Option<i16>,
2766 expire_delegation_token_version: Option<i16>,
2767 describe_delegation_token_version: Option<i16>,
2768 cluster: Cluster,
2769 conns: HashMap<i32, BrokerConn>,
2770 reconnect_fails: HashMap<i32, u32>,
2771 group_coord: Option<(String, i32)>,
2772 group_coords: HashMap<String, i32>,
2773 txn_coord: Option<(String, i32)>,
2774 txn_coords: HashMap<String, i32>,
2775 stats: Arc<crate::metrics::AdminTracker>,
2776}
2777
2778pub(crate) async fn fetch_client_instance_id(
2779 conn: &mut BrokerConn,
2780 version: i16,
2781 timeout: Duration,
2782 known: [u8; 16],
2783) -> Result<[u8; 16]> {
2784 let body = conn
2785 .roundtrip(
2786 GET_TELEMETRY_SUBSCRIPTIONS,
2787 version,
2788 |buf| encode_get_telemetry_subscriptions_request(buf, &known),
2789 timeout,
2790 )
2791 .await?;
2792 let resp = decode_get_telemetry_subscriptions_response(&mut body.clone())?;
2793 if resp.error_code != 0 {
2794 return Err(Error::broker(resp.error_code, "GetTelemetrySubscriptions"));
2795 }
2796 Ok(resp.client_instance_id)
2797}
2798
2799fn offset_fetch_topics_for_spec(
2800 spec: &ListConsumerGroupOffsetsSpec,
2801) -> Option<Vec<crate::protocol::group::OffsetFetchTopic>> {
2802 spec.partitions.as_ref().map(|ps| {
2803 let wanted: Vec<(String, i32)> = ps
2804 .iter()
2805 .map(|tp| (tp.topic.clone(), tp.partition))
2806 .collect();
2807 crate::group::group_offset_fetch_topics(&wanted)
2808 })
2809}
2810
2811fn listed_group_offsets(
2812 spec: &ListConsumerGroupOffsetsSpec,
2813 fetched: &[crate::protocol::group::FetchedOffsetTopic],
2814) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>> {
2815 let map = crate::group::committed_offset_map(fetched)?;
2816 match &spec.partitions {
2817 None => Ok(map
2818 .into_iter()
2819 .map(|((topic, partition), md)| (crate::TopicPartition::new(topic, partition), md))
2820 .collect()),
2821 Some(ps) => Ok(ps
2822 .iter()
2823 .map(|tp| {
2824 let md = map
2825 .get(&(tp.topic.clone(), tp.partition))
2826 .cloned()
2827 .unwrap_or_else(|| crate::OffsetAndMetadata::new(-1));
2828 (tp.clone(), md)
2829 })
2830 .collect()),
2831 }
2832}
2833
2834fn delete_records_topics(
2835 records: &[(crate::TopicPartition, i64)],
2836 idxs: &[usize],
2837) -> Vec<DeleteRecordsTopic> {
2838 let mut by_topic: HashMap<String, Vec<DeleteRecordsPartition>> = HashMap::new();
2839 let mut order: Vec<String> = Vec::new();
2840 for &i in idxs {
2841 let Some((tp, offset)) = records.get(i) else {
2842 continue;
2843 };
2844 match by_topic.entry(tp.topic.clone()) {
2845 std::collections::hash_map::Entry::Vacant(slot) => {
2846 order.push(tp.topic.clone());
2847 let _ = slot.insert(vec![DeleteRecordsPartition {
2848 partition: tp.partition,
2849 offset: *offset,
2850 }]);
2851 }
2852 std::collections::hash_map::Entry::Occupied(mut slot) => {
2853 slot.get_mut().push(DeleteRecordsPartition {
2854 partition: tp.partition,
2855 offset: *offset,
2856 });
2857 }
2858 }
2859 }
2860 order
2861 .into_iter()
2862 .filter_map(|topic| {
2863 by_topic
2864 .remove(&topic)
2865 .map(|partitions| DeleteRecordsTopic { topic, partitions })
2866 })
2867 .collect()
2868}
2869
2870fn describe_producers_topics(
2871 partitions: &[crate::TopicPartition],
2872 idxs: &[usize],
2873) -> Vec<DescribeProducersTopicRequest> {
2874 let mut by_topic: HashMap<String, Vec<i32>> = HashMap::new();
2875 let mut order: Vec<String> = Vec::new();
2876 for &i in idxs {
2877 let Some(tp) = partitions.get(i) else {
2878 continue;
2879 };
2880 match by_topic.entry(tp.topic.clone()) {
2881 std::collections::hash_map::Entry::Vacant(slot) => {
2882 order.push(tp.topic.clone());
2883 let _ = slot.insert(vec![tp.partition]);
2884 }
2885 std::collections::hash_map::Entry::Occupied(mut slot) => {
2886 slot.get_mut().push(tp.partition);
2887 }
2888 }
2889 }
2890 order
2891 .into_iter()
2892 .filter_map(|name| {
2893 by_topic
2894 .remove(&name)
2895 .map(|partition_indexes| DescribeProducersTopicRequest {
2896 name,
2897 partition_indexes,
2898 })
2899 })
2900 .collect()
2901}
2902
2903type DescribeProducersNodeOutcome = (Vec<(usize, DescribeProducersPartition)>, Vec<usize>);
2904
2905fn creatable_from_new(topics: &[NewTopic]) -> Vec<CreatableTopic> {
2906 topics
2907 .iter()
2908 .map(|t| CreatableTopic {
2909 name: t.name.clone(),
2910 num_partitions: t.num_partitions,
2911 replication_factor: t.replication_factor,
2912 assignments: t
2913 .assignments
2914 .iter()
2915 .map(|(partition_index, broker_ids)| ReplicaAssignment {
2916 partition_index: *partition_index,
2917 broker_ids: broker_ids.clone(),
2918 })
2919 .collect(),
2920 configs: t
2921 .configs
2922 .iter()
2923 .map(|(n, v)| TopicConfig {
2924 name: n.clone(),
2925 value: v.clone(),
2926 })
2927 .collect(),
2928 })
2929 .collect()
2930}
2931
2932fn delete_state_matches(topic: &DeleteTopicState, result: &TopicResult) -> bool {
2933 match &topic.name {
2934 Some(name) => name == &result.name,
2935 None => topic.topic_id != [0; 16] && topic.topic_id == result.topic_id,
2936 }
2937}
2938
2939fn partitions_from_new(topics: &[NewPartitions]) -> Vec<CreatePartitionsTopic> {
2940 topics
2941 .iter()
2942 .map(|t| CreatePartitionsTopic {
2943 name: t.name.clone(),
2944 count: t.total_count,
2945 assignments: t.assignments.clone(),
2946 })
2947 .collect()
2948}
2949
2950impl Admin {
2951 pub async fn connect(bootstrap: impl Into<String>) -> Result<Self> {
2953 Self::new(AdminConfig::bootstrap([bootstrap.into()])).await
2954 }
2955
2956 pub async fn new(cfg: AdminConfig) -> Result<Self> {
2970 let mut cfg = cfg;
2971 cfg.bootstrap = crate::net::parse_and_validate_addresses(&cfg.bootstrap)?;
2972 let stats = Arc::new(crate::metrics::AdminTracker::default());
2973 let mut conn = BrokerConn::connect_tls_any(
2974 &cfg.bootstrap,
2975 &cfg.client_id,
2976 cfg.connect_timeout,
2977 cfg.tls.as_ref(),
2978 )
2979 .await?;
2980 conn.set_stats(Arc::clone(&stats));
2981 let resp =
2982 crate::protocol::api::negotiate_api_versions(&mut conn, cfg.request_timeout).await?;
2983 sasl::apply_api_keys(&mut conn, &resp.api_keys);
2984 let mut versions = HashMap::new();
2985 for api in resp.api_keys {
2986 let _prev = versions.insert(api.api_key, api);
2987 }
2988 sasl::authenticate(
2989 &mut conn,
2990 cfg.sasl_plain.as_ref(),
2991 cfg.sasl_scram.as_ref(),
2992 cfg.sasl_scram_sha512.as_ref(),
2993 cfg.sasl_oauthbearer.as_deref(),
2994 cfg.sasl_oauthbearer_oidc.as_ref(),
2995 cfg.request_timeout,
2996 )
2997 .await?;
2998 let create_version = versions
2999 .get(&CREATE_TOPICS)
3000 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 7))
3001 .ok_or_else(|| {
3002 Error::Unsupported("broker does not support CreateTopics v0-7".into())
3003 })?;
3004 let delete_version = versions
3005 .get(&DELETE_TOPICS)
3006 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 6))
3007 .ok_or_else(|| {
3008 Error::Unsupported("broker does not support DeleteTopics v0-6".into())
3009 })?;
3010 let describe_version = versions
3011 .get(&DESCRIBE_CONFIGS)
3012 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 4))
3013 .ok_or_else(|| {
3014 Error::Unsupported("broker does not support DescribeConfigs v0-4".into())
3015 })?;
3016 let partitions_version = versions
3017 .get(&CREATE_PARTITIONS)
3018 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 3))
3019 .ok_or_else(|| {
3020 Error::Unsupported("broker does not support CreatePartitions v0-3".into())
3021 })?;
3022 let alter_version = versions
3023 .get(&INCREMENTAL_ALTER_CONFIGS)
3024 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 1));
3025 let legacy_alter_version = versions
3026 .get(&ALTER_CONFIGS)
3027 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 2))
3028 .ok_or_else(|| {
3029 Error::Unsupported("broker does not support AlterConfigs v0-2".into())
3030 })?;
3031 let delete_records_version = versions
3032 .get(&DELETE_RECORDS)
3033 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 2))
3034 .ok_or_else(|| {
3035 Error::Unsupported("broker does not support DeleteRecords v0-2".into())
3036 })?;
3037 let describe_producers_version = versions
3038 .get(&DESCRIBE_PRODUCERS)
3039 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3040 let describe_cluster_version = versions
3041 .get(&DESCRIBE_CLUSTER)
3042 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 2));
3043 let create_acls_version = versions
3044 .get(&CREATE_ACLS)
3045 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 3))
3046 .ok_or_else(|| Error::Unsupported("broker does not support CreateAcls v0-3".into()))?;
3047 let describe_acls_version = versions
3048 .get(&DESCRIBE_ACLS)
3049 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 3))
3050 .ok_or_else(|| {
3051 Error::Unsupported("broker does not support DescribeAcls v0-3".into())
3052 })?;
3053 let delete_acls_version = versions
3054 .get(&DELETE_ACLS)
3055 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 3))
3056 .ok_or_else(|| Error::Unsupported("broker does not support DeleteAcls v0-3".into()))?;
3057 let metadata_version = versions
3058 .get(&METADATA)
3059 .and_then(|v| pick_version(v.min_version, v.max_version, 1, 13))
3060 .ok_or_else(|| Error::Unsupported("broker does not support Metadata".into()))?;
3061 let find_coord_version = versions
3062 .get(&FIND_COORDINATOR)
3063 .and_then(|v| pick_version(v.min_version, v.max_version, 1, 6))
3064 .ok_or_else(|| {
3065 Error::Unsupported("broker does not support FindCoordinator v1-6".into())
3066 })?;
3067 let offset_delete_version = versions
3068 .get(&OFFSET_DELETE)
3069 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3070 let reassign_version = versions
3071 .get(&ALTER_PARTITION_REASSIGNMENTS)
3072 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3073 let list_reassign_version = versions
3074 .get(&LIST_PARTITION_REASSIGNMENTS)
3075 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3076 let update_features_version = versions
3077 .get(&UPDATE_FEATURES)
3078 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 2));
3079 let alter_user_scram_version = versions
3080 .get(&ALTER_USER_SCRAM_CREDENTIALS)
3081 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3082 let describe_user_scram_version = versions
3083 .get(&DESCRIBE_USER_SCRAM_CREDENTIALS)
3084 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3085 let unregister_broker_version = versions
3086 .get(&UNREGISTER_BROKER)
3087 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3088 let describe_client_quotas_version = versions
3089 .get(&DESCRIBE_CLIENT_QUOTAS)
3090 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 1));
3091 let alter_client_quotas_version = versions
3092 .get(&ALTER_CLIENT_QUOTAS)
3093 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 1));
3094 let allocate_producer_ids_version = versions
3095 .get(&ALLOCATE_PRODUCER_IDS)
3096 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3097 let describe_transactions_version = versions
3098 .get(&DESCRIBE_TRANSACTIONS)
3099 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3100 let list_transactions_version = versions
3101 .get(&LIST_TRANSACTIONS)
3102 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 1));
3103 let consumer_group_describe_version = versions
3104 .get(&CONSUMER_GROUP_DESCRIBE)
3105 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 1));
3106 let describe_groups_version = versions
3107 .get(&DESCRIBE_GROUPS)
3108 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 6))
3109 .ok_or_else(|| {
3110 Error::Unsupported("broker does not support DescribeGroups v0-6".into())
3111 })?;
3112 let list_groups_version = versions
3113 .get(&LIST_GROUPS)
3114 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 5))
3115 .ok_or_else(|| Error::Unsupported("broker does not support ListGroups v0-5".into()))?;
3116 let delete_groups_version = versions
3117 .get(&DELETE_GROUPS)
3118 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 2))
3119 .ok_or_else(|| {
3120 Error::Unsupported("broker does not support DeleteGroups v0-2".into())
3121 })?;
3122 let share_group_describe_version = versions
3123 .get(&SHARE_GROUP_DESCRIBE)
3124 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 1));
3125 let describe_share_group_offsets_version = versions
3126 .get(&DESCRIBE_SHARE_GROUP_OFFSETS)
3127 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3128 let alter_share_group_offsets_version = versions
3129 .get(&ALTER_SHARE_GROUP_OFFSETS)
3130 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3131 let delete_share_group_offsets_version = versions
3132 .get(&DELETE_SHARE_GROUP_OFFSETS)
3133 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3134 let describe_topic_partitions_version = versions
3135 .get(&DESCRIBE_TOPIC_PARTITIONS)
3136 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3137 let list_config_resources_version = versions
3138 .get(&LIST_CONFIG_RESOURCES)
3139 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 1));
3140 let get_telemetry_subscriptions_version = versions
3141 .get(&GET_TELEMETRY_SUBSCRIPTIONS)
3142 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3143 let push_telemetry_version = versions
3144 .get(&PUSH_TELEMETRY)
3145 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3146 let assign_replicas_to_dirs_version = versions
3147 .get(&ASSIGN_REPLICAS_TO_DIRS)
3148 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 0));
3149 let alter_replica_log_dirs_version = versions
3150 .get(&ALTER_REPLICA_LOG_DIRS)
3151 .and_then(|v| pick_version(v.min_version, v.max_version, 1, 2));
3152 let describe_log_dirs_version = versions
3153 .get(&DESCRIBE_LOG_DIRS)
3154 .and_then(|v| pick_version(v.min_version, v.max_version, 1, 4));
3155 let create_delegation_token_version = versions
3156 .get(&CREATE_DELEGATION_TOKEN)
3157 .and_then(|v| pick_version(v.min_version, v.max_version, 1, 3));
3158 let renew_delegation_token_version = versions
3159 .get(&RENEW_DELEGATION_TOKEN)
3160 .and_then(|v| pick_version(v.min_version, v.max_version, 1, 2));
3161 let expire_delegation_token_version = versions
3162 .get(&EXPIRE_DELEGATION_TOKEN)
3163 .and_then(|v| pick_version(v.min_version, v.max_version, 1, 2));
3164 let describe_delegation_token_version = versions
3165 .get(&DESCRIBE_DELEGATION_TOKEN)
3166 .and_then(|v| pick_version(v.min_version, v.max_version, 1, 3));
3167 Ok(Self {
3168 cfg,
3169 conn,
3170 versions,
3171 create_version,
3172 delete_version,
3173 describe_version,
3174 partitions_version,
3175 alter_version,
3176 legacy_alter_version,
3177 delete_records_version,
3178 describe_producers_version,
3179 describe_cluster_version,
3180 create_acls_version,
3181 describe_acls_version,
3182 delete_acls_version,
3183 metadata_version,
3184 find_coord_version,
3185 offset_delete_version,
3186 reassign_version,
3187 list_reassign_version,
3188 update_features_version,
3189 alter_user_scram_version,
3190 describe_user_scram_version,
3191 unregister_broker_version,
3192 describe_client_quotas_version,
3193 alter_client_quotas_version,
3194 allocate_producer_ids_version,
3195 describe_transactions_version,
3196 list_transactions_version,
3197 consumer_group_describe_version,
3198 describe_groups_version,
3199 list_groups_version,
3200 delete_groups_version,
3201 share_group_describe_version,
3202 describe_share_group_offsets_version,
3203 alter_share_group_offsets_version,
3204 delete_share_group_offsets_version,
3205 describe_topic_partitions_version,
3206 list_config_resources_version,
3207 get_telemetry_subscriptions_version,
3208 cached_client_instance_id: None,
3209 push_telemetry_version,
3210 assign_replicas_to_dirs_version,
3211 alter_replica_log_dirs_version,
3212 describe_log_dirs_version,
3213 create_delegation_token_version,
3214 renew_delegation_token_version,
3215 expire_delegation_token_version,
3216 describe_delegation_token_version,
3217 cluster: Cluster::default(),
3218 conns: HashMap::new(),
3219 reconnect_fails: HashMap::new(),
3220 group_coord: None,
3221 group_coords: HashMap::new(),
3222 txn_coord: None,
3223 txn_coords: HashMap::new(),
3224 stats,
3225 })
3226 }
3227
3228 #[must_use]
3230 pub fn versions(&self) -> &HashMap<i16, ApiVersion> {
3231 &self.versions
3232 }
3233
3234 #[must_use]
3242 pub fn metrics(&self) -> crate::AdminMetrics {
3243 self.stats.snapshot(1 + self.conns.len() as u64)
3244 }
3245
3246 pub async fn close(self) -> Result<()> {
3248 Ok(())
3249 }
3250
3251 pub async fn close_timeout(self, _timeout: Duration) -> Result<()> {
3256 self.close().await
3257 }
3258
3259 pub async fn client_instance_id(&mut self) -> Result<Uuid> {
3266 let timeout = self.cfg.request_timeout;
3267 self.client_instance_id_timeout(timeout).await
3268 }
3269
3270 pub async fn client_instance_id_timeout(&mut self, timeout: Duration) -> Result<Uuid> {
3276 if let Some(id) = self.cached_client_instance_id {
3277 return Ok(Uuid::from_bytes(id));
3278 }
3279 self.ensure_bootstrap().await?;
3280 let version = self.get_telemetry_subscriptions_version.ok_or_else(|| {
3281 Error::Unsupported("broker does not support GetTelemetrySubscriptions".into())
3282 })?;
3283 let id = fetch_client_instance_id(&mut self.conn, version, timeout, [0; 16]).await?;
3284 self.cached_client_instance_id = Some(id);
3285 Ok(Uuid::from_bytes(id))
3286 }
3287
3288 pub async fn create_topics(
3301 &mut self,
3302 topics: &[NewTopic],
3303 timeout_ms: i32,
3304 validate_only: bool,
3305 ) -> Result<Vec<TopicResult>> {
3306 let timeout = self.cfg.request_timeout;
3307 self.create_topics_with(topics, timeout_ms, validate_only, timeout, true)
3308 .await
3309 }
3310
3311 pub async fn create_topics_timeout(
3316 &mut self,
3317 topics: &[NewTopic],
3318 timeout: Duration,
3319 validate_only: bool,
3320 ) -> Result<Vec<TopicResult>> {
3321 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
3322 self.create_topics_with(topics, timeout_ms, validate_only, timeout, true)
3323 .await
3324 }
3325
3326 pub async fn create_topics_with_quota_retry(
3332 &mut self,
3333 topics: &[NewTopic],
3334 timeout_ms: i32,
3335 validate_only: bool,
3336 retry_on_quota_violation: bool,
3337 ) -> Result<Vec<TopicResult>> {
3338 let timeout = self.cfg.request_timeout;
3339 self.create_topics_with(
3340 topics,
3341 timeout_ms,
3342 validate_only,
3343 timeout,
3344 retry_on_quota_violation,
3345 )
3346 .await
3347 }
3348
3349 pub async fn create_topics_timeout_with_quota_retry(
3354 &mut self,
3355 topics: &[NewTopic],
3356 timeout: Duration,
3357 validate_only: bool,
3358 retry_on_quota_violation: bool,
3359 ) -> Result<Vec<TopicResult>> {
3360 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
3361 self.create_topics_with(
3362 topics,
3363 timeout_ms,
3364 validate_only,
3365 timeout,
3366 retry_on_quota_violation,
3367 )
3368 .await
3369 }
3370
3371 async fn create_topics_with(
3372 &mut self,
3373 topics: &[NewTopic],
3374 timeout_ms: i32,
3375 validate_only: bool,
3376 timeout: Duration,
3377 retry_on_quota_violation: bool,
3378 ) -> Result<Vec<TopicResult>> {
3379 let mut pending: Vec<NewTopic> = topics.to_vec();
3380 let mut finished: HashMap<String, TopicResult> = HashMap::new();
3381 let version = self.create_version;
3382 let deadline = Instant::now() + timeout;
3383 let mut attempt = 0u32;
3384 loop {
3385 let req = CreateTopicsRequest {
3386 topics: creatable_from_new(&pending),
3387 timeout_ms,
3388 validate_only,
3389 };
3390 if self.cluster.controller().is_err() {
3391 self.refresh_metadata(None).await?;
3392 }
3393 let node = self.cluster.controller()?;
3394 self.connect_node(node).await?;
3395 let body = {
3396 let conn = self
3397 .conns
3398 .get_mut(&node)
3399 .ok_or_else(|| Error::protocol("missing create_topics conn"))?;
3400 conn.roundtrip(
3401 CREATE_TOPICS,
3402 version,
3403 |buf| encode_create_topics_request(buf, version, &req),
3404 timeout,
3405 )
3406 .await
3407 };
3408 let body = match body {
3409 Ok(b) => b,
3410 Err(e) if e.is_retriable() => {
3411 let _ = self.conns.remove(&node);
3412 self.cluster.invalidate_controller();
3413 self.wait_retry(&mut attempt, deadline).await?;
3414 continue;
3415 }
3416 Err(e) => return Err(e),
3417 };
3418 let (results, ..) = decode_create_topics_response(&mut body.clone(), version)?;
3419 if results
3420 .iter()
3421 .any(|r| r.error_code == error::NOT_CONTROLLER)
3422 {
3423 self.cluster.invalidate_controller();
3425 let _ = self.conns.remove(&node);
3426 self.wait_retry(&mut attempt, deadline).await?;
3427 self.refresh_metadata(None).await?;
3428 continue;
3429 }
3430 let mut next_pending = Vec::new();
3431 for r in results {
3432 if retry_on_quota_violation && r.error_code == error::THROTTLING_QUOTA_EXCEEDED {
3433 if let Some(t) = pending.iter().find(|t| t.name == r.name) {
3434 next_pending.push(t.clone());
3435 }
3436 } else {
3437 let name = r.name.clone();
3438 let _prev = finished.insert(name, r);
3439 }
3440 }
3441 if next_pending.is_empty() {
3442 let mut out = Vec::with_capacity(topics.len());
3443 for t in topics {
3444 let r = finished.remove(&t.name).ok_or_else(|| {
3445 Error::protocol(format!("missing create_topics result for {}", t.name))
3446 })?;
3447 out.push(r);
3448 }
3449 return Ok(out);
3450 }
3451 pending = next_pending;
3452 self.wait_retry(&mut attempt, deadline).await?;
3453 }
3454 }
3455
3456 pub async fn delete_topics(
3475 &mut self,
3476 names: &[impl AsRef<str>],
3477 timeout_ms: i32,
3478 ) -> Result<Vec<TopicResult>> {
3479 let names: Vec<String> = names.iter().map(|n| n.as_ref().to_string()).collect();
3480 let timeout = self.cfg.request_timeout;
3481 self.delete_topics_with(names, timeout_ms, timeout, true)
3482 .await
3483 }
3484
3485 pub async fn delete_topics_timeout(
3490 &mut self,
3491 names: &[impl AsRef<str>],
3492 timeout: Duration,
3493 ) -> Result<Vec<TopicResult>> {
3494 let names: Vec<String> = names.iter().map(|n| n.as_ref().to_string()).collect();
3495 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
3496 self.delete_topics_with(names, timeout_ms, timeout, true)
3497 .await
3498 }
3499
3500 pub async fn delete_topics_with_quota_retry(
3506 &mut self,
3507 names: &[impl AsRef<str>],
3508 timeout_ms: i32,
3509 retry_on_quota_violation: bool,
3510 ) -> Result<Vec<TopicResult>> {
3511 let names: Vec<String> = names.iter().map(|n| n.as_ref().to_string()).collect();
3512 let timeout = self.cfg.request_timeout;
3513 self.delete_topics_with(names, timeout_ms, timeout, retry_on_quota_violation)
3514 .await
3515 }
3516
3517 pub async fn delete_topics_timeout_with_quota_retry(
3522 &mut self,
3523 names: &[impl AsRef<str>],
3524 timeout: Duration,
3525 retry_on_quota_violation: bool,
3526 ) -> Result<Vec<TopicResult>> {
3527 let names: Vec<String> = names.iter().map(|n| n.as_ref().to_string()).collect();
3528 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
3529 self.delete_topics_with(names, timeout_ms, timeout, retry_on_quota_violation)
3530 .await
3531 }
3532
3533 pub async fn delete_topics_by_id(
3545 &mut self,
3546 ids: &[[u8; 16]],
3547 timeout_ms: i32,
3548 ) -> Result<Vec<TopicResult>> {
3549 let timeout = self.cfg.request_timeout;
3550 self.delete_topics_by_id_with(ids, timeout_ms, timeout, true)
3551 .await
3552 }
3553
3554 pub async fn delete_topics_by_id_timeout(
3557 &mut self,
3558 ids: &[[u8; 16]],
3559 timeout: Duration,
3560 ) -> Result<Vec<TopicResult>> {
3561 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
3562 self.delete_topics_by_id_with(ids, timeout_ms, timeout, true)
3563 .await
3564 }
3565
3566 pub async fn delete_topics_by_id_with_quota_retry(
3573 &mut self,
3574 ids: &[[u8; 16]],
3575 timeout_ms: i32,
3576 retry_on_quota_violation: bool,
3577 ) -> Result<Vec<TopicResult>> {
3578 let timeout = self.cfg.request_timeout;
3579 self.delete_topics_by_id_with(ids, timeout_ms, timeout, retry_on_quota_violation)
3580 .await
3581 }
3582
3583 pub async fn delete_topics_by_id_timeout_with_quota_retry(
3589 &mut self,
3590 ids: &[[u8; 16]],
3591 timeout: Duration,
3592 retry_on_quota_violation: bool,
3593 ) -> Result<Vec<TopicResult>> {
3594 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
3595 self.delete_topics_by_id_with(ids, timeout_ms, timeout, retry_on_quota_violation)
3596 .await
3597 }
3598
3599 pub async fn delete_topics_for(
3609 &mut self,
3610 topics: &TopicCollection,
3611 timeout_ms: i32,
3612 ) -> Result<Vec<TopicResult>> {
3613 let timeout = self.cfg.request_timeout;
3614 self.delete_topics_for_inner(topics, timeout_ms, timeout, true)
3615 .await
3616 }
3617
3618 pub async fn delete_topics_for_timeout(
3623 &mut self,
3624 topics: &TopicCollection,
3625 timeout: Duration,
3626 ) -> Result<Vec<TopicResult>> {
3627 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
3628 self.delete_topics_for_inner(topics, timeout_ms, timeout, true)
3629 .await
3630 }
3631
3632 pub async fn delete_topics_for_with_quota_retry(
3635 &mut self,
3636 topics: &TopicCollection,
3637 timeout_ms: i32,
3638 retry_on_quota_violation: bool,
3639 ) -> Result<Vec<TopicResult>> {
3640 let timeout = self.cfg.request_timeout;
3641 self.delete_topics_for_inner(topics, timeout_ms, timeout, retry_on_quota_violation)
3642 .await
3643 }
3644
3645 pub async fn delete_topics_for_timeout_with_quota_retry(
3649 &mut self,
3650 topics: &TopicCollection,
3651 timeout: Duration,
3652 retry_on_quota_violation: bool,
3653 ) -> Result<Vec<TopicResult>> {
3654 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
3655 self.delete_topics_for_inner(topics, timeout_ms, timeout, retry_on_quota_violation)
3656 .await
3657 }
3658
3659 async fn delete_topics_for_inner(
3660 &mut self,
3661 topics: &TopicCollection,
3662 timeout_ms: i32,
3663 timeout: Duration,
3664 retry_on_quota_violation: bool,
3665 ) -> Result<Vec<TopicResult>> {
3666 match topics {
3667 TopicCollection::Names(names) => {
3668 self.delete_topics_with(
3669 names.clone(),
3670 timeout_ms,
3671 timeout,
3672 retry_on_quota_violation,
3673 )
3674 .await
3675 }
3676 TopicCollection::Ids(ids) => {
3677 let ids: Vec<[u8; 16]> = ids.iter().copied().map(Uuid::to_bytes).collect();
3678 self.delete_topics_by_id_with(&ids, timeout_ms, timeout, retry_on_quota_violation)
3679 .await
3680 }
3681 }
3682 }
3683
3684 async fn delete_topics_by_id_with(
3685 &mut self,
3686 ids: &[[u8; 16]],
3687 timeout_ms: i32,
3688 timeout: Duration,
3689 retry_on_quota_violation: bool,
3690 ) -> Result<Vec<TopicResult>> {
3691 if ids.is_empty() {
3692 return Ok(Vec::new());
3693 }
3694 if self.delete_version < 6 {
3695 return Err(Error::Unsupported(
3696 "broker does not support DeleteTopics v6 topic IDs".into(),
3697 ));
3698 }
3699 let topics: Vec<DeleteTopicState> =
3700 ids.iter().copied().map(DeleteTopicState::by_id).collect();
3701 self.delete_topics_states_with(topics, timeout_ms, timeout, retry_on_quota_violation)
3702 .await
3703 }
3704
3705 async fn delete_topics_with(
3706 &mut self,
3707 names: Vec<String>,
3708 timeout_ms: i32,
3709 timeout: Duration,
3710 retry_on_quota_violation: bool,
3711 ) -> Result<Vec<TopicResult>> {
3712 let topics: Vec<DeleteTopicState> =
3713 names.into_iter().map(DeleteTopicState::by_name).collect();
3714 self.delete_topics_states_with(topics, timeout_ms, timeout, retry_on_quota_violation)
3715 .await
3716 }
3717
3718 async fn delete_topics_states_with(
3719 &mut self,
3720 topics: Vec<DeleteTopicState>,
3721 timeout_ms: i32,
3722 timeout: Duration,
3723 retry_on_quota_violation: bool,
3724 ) -> Result<Vec<TopicResult>> {
3725 let original = topics;
3726 let mut pending = original.clone();
3727 let mut finished_names: HashMap<String, TopicResult> = HashMap::new();
3728 let mut finished_ids: HashMap<[u8; 16], TopicResult> = HashMap::new();
3729 let version = self.delete_version;
3730 let deadline = Instant::now() + timeout;
3731 let mut attempt = 0u32;
3732 loop {
3733 if self.cluster.controller().is_err() {
3734 self.refresh_metadata(None).await?;
3735 }
3736 let node = self.cluster.controller()?;
3737 self.connect_node(node).await?;
3738 let body = {
3739 let conn = self
3740 .conns
3741 .get_mut(&node)
3742 .ok_or_else(|| Error::protocol("missing delete_topics conn"))?;
3743 conn.roundtrip(
3744 DELETE_TOPICS,
3745 version,
3746 |buf| encode_delete_topics_states_request(buf, version, &pending, timeout_ms),
3747 timeout,
3748 )
3749 .await
3750 };
3751 let body = match body {
3752 Ok(b) => b,
3753 Err(e) if e.is_retriable() => {
3754 let _ = self.conns.remove(&node);
3755 self.cluster.invalidate_controller();
3756 self.wait_retry(&mut attempt, deadline).await?;
3757 continue;
3758 }
3759 Err(e) => return Err(e),
3760 };
3761 let (results, ..) = decode_delete_topics_response(&mut body.clone(), version)?;
3762 if results
3763 .iter()
3764 .any(|r| r.error_code == error::NOT_CONTROLLER)
3765 {
3766 self.cluster.invalidate_controller();
3768 let _ = self.conns.remove(&node);
3769 self.wait_retry(&mut attempt, deadline).await?;
3770 self.refresh_metadata(None).await?;
3771 continue;
3772 }
3773 let mut next_pending = Vec::new();
3774 for r in results {
3775 let matched = pending.iter().find(|t| delete_state_matches(t, &r));
3776 if retry_on_quota_violation && r.error_code == error::THROTTLING_QUOTA_EXCEEDED {
3777 if let Some(t) = matched {
3778 next_pending.push(t.clone());
3779 }
3780 } else if let Some(t) = matched {
3781 if let Some(n) = &t.name {
3782 let _prev = finished_names.insert(n.clone(), r);
3783 } else {
3784 let _prev = finished_ids.insert(t.topic_id, r);
3785 }
3786 }
3787 }
3788 if next_pending.is_empty() {
3789 let mut out = Vec::with_capacity(original.len());
3790 for t in &original {
3791 let r = if let Some(n) = &t.name {
3792 finished_names.remove(n)
3793 } else {
3794 finished_ids.remove(&t.topic_id)
3795 }
3796 .ok_or_else(|| {
3797 Error::protocol(format!(
3798 "missing delete_topics result for {}",
3799 t.name.as_deref().unwrap_or("topic id")
3800 ))
3801 })?;
3802 out.push(r);
3803 }
3804 return Ok(out);
3805 }
3806 pending = next_pending;
3807 self.wait_retry(&mut attempt, deadline).await?;
3808 }
3809 }
3810
3811 pub async fn list_topics(&mut self) -> Result<Vec<TopicListing>> {
3821 let timeout = self.cfg.request_timeout;
3822 self.list_topics_with_timeout(true, timeout).await
3823 }
3824
3825 pub async fn list_topics_with(&mut self, list_internal: bool) -> Result<Vec<TopicListing>> {
3831 let timeout = self.cfg.request_timeout;
3832 self.list_topics_with_timeout(list_internal, timeout).await
3833 }
3834
3835 pub async fn list_topics_timeout(&mut self, timeout: Duration) -> Result<Vec<TopicListing>> {
3841 self.list_topics_with_timeout(true, timeout).await
3842 }
3843
3844 pub async fn list_topics_with_timeout(
3849 &mut self,
3850 list_internal: bool,
3851 timeout: Duration,
3852 ) -> Result<Vec<TopicListing>> {
3853 let md = self
3854 .fetch_metadata_request_with(None, false, timeout)
3855 .await?;
3856 Ok(topic_listings_from(&md, list_internal))
3857 }
3858
3859 pub async fn describe_topics(
3877 &mut self,
3878 topics: impl IntoIterator<Item = impl AsRef<str>>,
3879 ) -> Result<Vec<TopicDescription>> {
3880 self.describe_topics_with(topics, false).await
3881 }
3882
3883 pub async fn describe_topics_timeout(
3886 &mut self,
3887 topics: impl IntoIterator<Item = impl AsRef<str>>,
3888 timeout: Duration,
3889 ) -> Result<Vec<TopicDescription>> {
3890 self.describe_topics_with_timeout(topics, false, timeout)
3891 .await
3892 }
3893
3894 pub async fn describe_topics_with(
3903 &mut self,
3904 topics: impl IntoIterator<Item = impl AsRef<str>>,
3905 include_authorized_operations: bool,
3906 ) -> Result<Vec<TopicDescription>> {
3907 let timeout = self.cfg.request_timeout;
3908 self.describe_topics_with_timeout(topics, include_authorized_operations, timeout)
3909 .await
3910 }
3911
3912 pub async fn describe_topics_with_timeout(
3922 &mut self,
3923 topics: impl IntoIterator<Item = impl AsRef<str>>,
3924 include_authorized_operations: bool,
3925 timeout: Duration,
3926 ) -> Result<Vec<TopicDescription>> {
3927 self.describe_topics_with_partition_limit_timeout(
3928 topics,
3929 include_authorized_operations,
3930 DESCRIBE_TOPIC_PARTITIONS_LIMIT,
3931 timeout,
3932 )
3933 .await
3934 }
3935
3936 pub async fn describe_topics_with_partition_limit(
3945 &mut self,
3946 topics: impl IntoIterator<Item = impl AsRef<str>>,
3947 include_authorized_operations: bool,
3948 partition_size_limit: i32,
3949 ) -> Result<Vec<TopicDescription>> {
3950 let timeout = self.cfg.request_timeout;
3951 self.describe_topics_with_partition_limit_timeout(
3952 topics,
3953 include_authorized_operations,
3954 partition_size_limit,
3955 timeout,
3956 )
3957 .await
3958 }
3959
3960 pub async fn describe_topics_with_partition_limit_timeout(
3966 &mut self,
3967 topics: impl IntoIterator<Item = impl AsRef<str>>,
3968 include_authorized_operations: bool,
3969 partition_size_limit: i32,
3970 timeout: Duration,
3971 ) -> Result<Vec<TopicDescription>> {
3972 let names: Vec<String> = topics.into_iter().map(|s| s.as_ref().to_string()).collect();
3973 if names.is_empty() {
3974 return Ok(Vec::new());
3975 }
3976 if self.describe_topic_partitions_version.is_some() {
3977 self.describe_topics_dtp(
3978 &names,
3979 include_authorized_operations,
3980 partition_size_limit,
3981 timeout,
3982 )
3983 .await
3984 } else {
3985 self.describe_topics_metadata(&names, include_authorized_operations, timeout)
3986 .await
3987 }
3988 }
3989
3990 pub async fn describe_topics_by_id(
4001 &mut self,
4002 ids: &[[u8; 16]],
4003 ) -> Result<Vec<TopicDescription>> {
4004 self.describe_topics_by_id_with(ids, false).await
4005 }
4006
4007 pub async fn describe_topics_by_id_with(
4015 &mut self,
4016 ids: &[[u8; 16]],
4017 include_authorized_operations: bool,
4018 ) -> Result<Vec<TopicDescription>> {
4019 let timeout = self.cfg.request_timeout;
4020 self.describe_topics_by_id_with_timeout(ids, include_authorized_operations, timeout)
4021 .await
4022 }
4023
4024 pub async fn describe_topics_by_id_timeout(
4027 &mut self,
4028 ids: &[[u8; 16]],
4029 timeout: Duration,
4030 ) -> Result<Vec<TopicDescription>> {
4031 self.describe_topics_by_id_with_timeout(ids, false, timeout)
4032 .await
4033 }
4034
4035 pub async fn describe_topics_by_id_with_timeout(
4040 &mut self,
4041 ids: &[[u8; 16]],
4042 include_authorized_operations: bool,
4043 timeout: Duration,
4044 ) -> Result<Vec<TopicDescription>> {
4045 if ids.is_empty() {
4046 return Ok(Vec::new());
4047 }
4048 if self.metadata_version < 12 {
4049 return Err(Error::Unsupported(
4050 "broker does not support Metadata v12 topic IDs".into(),
4051 ));
4052 }
4053 let topics = MetadataRequestTopic::convert_from_ids(ids.iter().copied());
4054 let md = self
4055 .fetch_metadata_request_with(Some(&topics), include_authorized_operations, timeout)
4056 .await?;
4057 Ok(topic_descriptions_including_unnamed(&md))
4058 }
4059
4060 pub async fn describe_topics_for(
4070 &mut self,
4071 topics: &TopicCollection,
4072 ) -> Result<Vec<TopicDescription>> {
4073 self.describe_topics_for_with(topics, false).await
4074 }
4075
4076 pub async fn describe_topics_for_timeout(
4079 &mut self,
4080 topics: &TopicCollection,
4081 timeout: Duration,
4082 ) -> Result<Vec<TopicDescription>> {
4083 self.describe_topics_for_with_timeout(topics, false, timeout)
4084 .await
4085 }
4086
4087 pub async fn describe_topics_for_with(
4090 &mut self,
4091 topics: &TopicCollection,
4092 include_authorized_operations: bool,
4093 ) -> Result<Vec<TopicDescription>> {
4094 let timeout = self.cfg.request_timeout;
4095 self.describe_topics_for_with_timeout(topics, include_authorized_operations, timeout)
4096 .await
4097 }
4098
4099 pub async fn describe_topics_for_with_timeout(
4105 &mut self,
4106 topics: &TopicCollection,
4107 include_authorized_operations: bool,
4108 timeout: Duration,
4109 ) -> Result<Vec<TopicDescription>> {
4110 match topics {
4111 TopicCollection::Names(names) => {
4112 self.describe_topics_with_timeout(names, include_authorized_operations, timeout)
4113 .await
4114 }
4115 TopicCollection::Ids(ids) => {
4116 let ids: Vec<[u8; 16]> = ids.iter().copied().map(Uuid::to_bytes).collect();
4117 self.describe_topics_by_id_with_timeout(
4118 &ids,
4119 include_authorized_operations,
4120 timeout,
4121 )
4122 .await
4123 }
4124 }
4125 }
4126
4127 pub async fn describe_configs(
4137 &mut self,
4138 resources: &[ConfigResource],
4139 include_synonyms: bool,
4140 ) -> Result<Vec<DescribeConfigsResult>> {
4141 self.describe_configs_with_documentation(resources, include_synonyms, false)
4142 .await
4143 }
4144
4145 pub async fn describe_configs_timeout(
4150 &mut self,
4151 resources: &[ConfigResource],
4152 include_synonyms: bool,
4153 timeout: Duration,
4154 ) -> Result<Vec<DescribeConfigsResult>> {
4155 self.describe_configs_with_documentation_timeout(
4156 resources,
4157 include_synonyms,
4158 false,
4159 timeout,
4160 )
4161 .await
4162 }
4163
4164 pub async fn describe_configs_with_documentation(
4173 &mut self,
4174 resources: &[ConfigResource],
4175 include_synonyms: bool,
4176 include_documentation: bool,
4177 ) -> Result<Vec<DescribeConfigsResult>> {
4178 let timeout = self.cfg.request_timeout;
4179 self.describe_configs_with_documentation_timeout(
4180 resources,
4181 include_synonyms,
4182 include_documentation,
4183 timeout,
4184 )
4185 .await
4186 }
4187
4188 pub async fn describe_configs_with_documentation_timeout(
4193 &mut self,
4194 resources: &[ConfigResource],
4195 include_synonyms: bool,
4196 include_documentation: bool,
4197 timeout: Duration,
4198 ) -> Result<Vec<DescribeConfigsResult>> {
4199 let req: Vec<DescribeConfigsResource> = resources
4200 .iter()
4201 .map(|r| DescribeConfigsResource {
4202 resource_type: r.resource_type,
4203 name: r.name.clone(),
4204 keys: r.keys.clone(),
4205 })
4206 .collect();
4207 let version = self.describe_version;
4208 let body = self
4209 .roundtrip_bootstrap(
4210 DESCRIBE_CONFIGS,
4211 version,
4212 |buf| {
4213 encode_describe_configs_request(
4214 buf,
4215 version,
4216 &req,
4217 include_synonyms,
4218 include_documentation,
4219 )
4220 },
4221 timeout,
4222 )
4223 .await?;
4224 let (results, ..) = decode_describe_configs_response(&mut body.clone(), version)?;
4225 Ok(results)
4226 }
4227
4228 pub async fn create_partitions(
4243 &mut self,
4244 topics: &[NewPartitions],
4245 timeout_ms: i32,
4246 validate_only: bool,
4247 ) -> Result<Vec<TopicResult>> {
4248 let timeout = self.cfg.request_timeout;
4249 self.create_partitions_with(topics, timeout_ms, validate_only, timeout, true)
4250 .await
4251 }
4252
4253 pub async fn create_partitions_timeout(
4258 &mut self,
4259 topics: &[NewPartitions],
4260 timeout: Duration,
4261 validate_only: bool,
4262 ) -> Result<Vec<TopicResult>> {
4263 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
4264 self.create_partitions_with(topics, timeout_ms, validate_only, timeout, true)
4265 .await
4266 }
4267
4268 pub async fn create_partitions_with_quota_retry(
4274 &mut self,
4275 topics: &[NewPartitions],
4276 timeout_ms: i32,
4277 validate_only: bool,
4278 retry_on_quota_violation: bool,
4279 ) -> Result<Vec<TopicResult>> {
4280 let timeout = self.cfg.request_timeout;
4281 self.create_partitions_with(
4282 topics,
4283 timeout_ms,
4284 validate_only,
4285 timeout,
4286 retry_on_quota_violation,
4287 )
4288 .await
4289 }
4290
4291 pub async fn create_partitions_timeout_with_quota_retry(
4296 &mut self,
4297 topics: &[NewPartitions],
4298 timeout: Duration,
4299 validate_only: bool,
4300 retry_on_quota_violation: bool,
4301 ) -> Result<Vec<TopicResult>> {
4302 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
4303 self.create_partitions_with(
4304 topics,
4305 timeout_ms,
4306 validate_only,
4307 timeout,
4308 retry_on_quota_violation,
4309 )
4310 .await
4311 }
4312
4313 async fn create_partitions_with(
4314 &mut self,
4315 topics: &[NewPartitions],
4316 timeout_ms: i32,
4317 validate_only: bool,
4318 timeout: Duration,
4319 retry_on_quota_violation: bool,
4320 ) -> Result<Vec<TopicResult>> {
4321 let mut pending: Vec<NewPartitions> = topics.to_vec();
4322 let mut finished: HashMap<String, TopicResult> = HashMap::new();
4323 let version = self.partitions_version;
4324 let deadline = Instant::now() + timeout;
4325 let mut attempt = 0u32;
4326 loop {
4327 let encoded = partitions_from_new(&pending);
4328 if self.cluster.controller().is_err() {
4329 self.refresh_metadata(None).await?;
4330 }
4331 let node = self.cluster.controller()?;
4332 self.connect_node(node).await?;
4333 let body = {
4334 let conn = self
4335 .conns
4336 .get_mut(&node)
4337 .ok_or_else(|| Error::protocol("missing create_partitions conn"))?;
4338 conn.roundtrip(
4339 CREATE_PARTITIONS,
4340 version,
4341 |buf| {
4342 encode_create_partitions_request(
4343 buf,
4344 version,
4345 &encoded,
4346 timeout_ms,
4347 validate_only,
4348 )
4349 },
4350 timeout,
4351 )
4352 .await
4353 };
4354 let body = match body {
4355 Ok(b) => b,
4356 Err(e) if e.is_retriable() => {
4357 let _ = self.conns.remove(&node);
4358 self.cluster.invalidate_controller();
4359 self.wait_retry(&mut attempt, deadline).await?;
4360 continue;
4361 }
4362 Err(e) => return Err(e),
4363 };
4364 let (results, ..) = decode_create_partitions_response(&mut body.clone(), version)?;
4365 if results
4366 .iter()
4367 .any(|r| r.error_code == error::NOT_CONTROLLER)
4368 {
4369 self.cluster.invalidate_controller();
4371 let _ = self.conns.remove(&node);
4372 self.wait_retry(&mut attempt, deadline).await?;
4373 self.refresh_metadata(None).await?;
4374 continue;
4375 }
4376 let mut next_pending = Vec::new();
4377 for r in results {
4378 if retry_on_quota_violation && r.error_code == error::THROTTLING_QUOTA_EXCEEDED {
4379 if let Some(t) = pending.iter().find(|t| t.name == r.name) {
4380 next_pending.push(t.clone());
4381 }
4382 } else {
4383 let name = r.name.clone();
4384 let _prev = finished.insert(name, r);
4385 }
4386 }
4387 if next_pending.is_empty() {
4388 let mut out = Vec::with_capacity(topics.len());
4389 for t in topics {
4390 let r = finished.remove(&t.name).ok_or_else(|| {
4391 Error::protocol(format!("missing create_partitions result for {}", t.name))
4392 })?;
4393 out.push(r);
4394 }
4395 return Ok(out);
4396 }
4397 pending = next_pending;
4398 self.wait_retry(&mut attempt, deadline).await?;
4399 }
4400 }
4401
4402 pub async fn incremental_alter_configs(
4417 &mut self,
4418 resource: &ConfigResource,
4419 configs: &[AlterConfig],
4420 validate_only: bool,
4421 ) -> Result<i16> {
4422 let results = self
4423 .incremental_alter_configs_for(
4424 &[ConfigResourceUpdate::new(
4425 resource.clone(),
4426 configs.iter().cloned(),
4427 )],
4428 validate_only,
4429 )
4430 .await?;
4431 Ok(results.first().map(|r| r.error_code).unwrap_or(0))
4432 }
4433
4434 pub async fn incremental_alter_configs_timeout(
4440 &mut self,
4441 resource: &ConfigResource,
4442 configs: &[AlterConfig],
4443 timeout: Duration,
4444 validate_only: bool,
4445 ) -> Result<i16> {
4446 let results = self
4447 .incremental_alter_configs_for_timeout(
4448 &[ConfigResourceUpdate::new(
4449 resource.clone(),
4450 configs.iter().cloned(),
4451 )],
4452 timeout,
4453 validate_only,
4454 )
4455 .await?;
4456 Ok(results.first().map(|r| r.error_code).unwrap_or(0))
4457 }
4458
4459 pub async fn incremental_alter_configs_for(
4464 &mut self,
4465 updates: &[ConfigResourceUpdate],
4466 validate_only: bool,
4467 ) -> Result<Vec<AlterConfigsResourceResult>> {
4468 let timeout = self.cfg.request_timeout;
4469 self.incremental_alter_configs_for_timeout(updates, timeout, validate_only)
4470 .await
4471 }
4472
4473 pub async fn incremental_alter_configs_for_timeout(
4480 &mut self,
4481 updates: &[ConfigResourceUpdate],
4482 timeout: Duration,
4483 validate_only: bool,
4484 ) -> Result<Vec<AlterConfigsResourceResult>> {
4485 if updates.is_empty() {
4486 return Ok(Vec::new());
4487 }
4488 let version = self.alter_version.ok_or_else(|| {
4489 Error::Unsupported("broker does not support IncrementalAlterConfigs v0-1".into())
4490 })?;
4491 let resources: Vec<AlterableResource> = updates
4492 .iter()
4493 .map(|u| AlterableResource {
4494 resource_type: u.resource.resource_type,
4495 name: u.resource.name.clone(),
4496 configs: u.configs.clone(),
4497 })
4498 .collect();
4499 let deadline = Instant::now() + timeout;
4500 let mut attempt = 0u32;
4501 loop {
4502 if self.cluster.controller().is_err() {
4503 self.refresh_metadata(None).await?;
4504 }
4505 let node = self.cluster.controller()?;
4506 self.connect_node(node).await?;
4507 let body = {
4508 let conn = self
4509 .conns
4510 .get_mut(&node)
4511 .ok_or_else(|| Error::protocol("missing incremental_alter_configs conn"))?;
4512 conn.roundtrip(
4513 INCREMENTAL_ALTER_CONFIGS,
4514 version,
4515 |buf| {
4516 encode_incremental_alter_configs_resources_request(
4517 buf,
4518 version,
4519 &resources,
4520 validate_only,
4521 )
4522 },
4523 timeout,
4524 )
4525 .await
4526 };
4527 let body = match body {
4528 Ok(b) => b,
4529 Err(e) if e.is_retriable() => {
4530 let _ = self.conns.remove(&node);
4531 self.cluster.invalidate_controller();
4532 self.wait_retry(&mut attempt, deadline).await?;
4533 continue;
4534 }
4535 Err(e) => return Err(e),
4536 };
4537 let (results, ..) =
4538 decode_incremental_alter_configs_resource_results(&mut body.clone(), version)?;
4539 if results
4540 .iter()
4541 .any(|r| r.error_code == error::NOT_CONTROLLER)
4542 {
4543 self.cluster.invalidate_controller();
4544 let _ = self.conns.remove(&node);
4545 self.wait_retry(&mut attempt, deadline).await?;
4546 self.refresh_metadata(None).await?;
4547 continue;
4548 }
4549 return Ok(results);
4550 }
4551 }
4552
4553 pub async fn create_acls(&mut self, acls: &[AclBinding]) -> Result<Vec<AclCreationResult>> {
4569 let timeout = self.cfg.request_timeout;
4570 self.create_acls_timeout(acls, timeout).await
4571 }
4572
4573 pub async fn create_acls_timeout(
4579 &mut self,
4580 acls: &[AclBinding],
4581 timeout: Duration,
4582 ) -> Result<Vec<AclCreationResult>> {
4583 let version = self.create_acls_version;
4584 let deadline = Instant::now() + timeout;
4585 let mut attempt = 0u32;
4586 let acls = acls.to_vec();
4587 loop {
4588 if self.cluster.controller().is_err() {
4589 self.refresh_metadata(None).await?;
4590 }
4591 let node = self.cluster.controller()?;
4592 self.connect_node(node).await?;
4593 let body = {
4594 let conn = self
4595 .conns
4596 .get_mut(&node)
4597 .ok_or_else(|| Error::protocol("missing create_acls conn"))?;
4598 conn.roundtrip(
4599 CREATE_ACLS,
4600 version,
4601 |buf| encode_create_acls_request(buf, version, &acls),
4602 timeout,
4603 )
4604 .await
4605 };
4606 let body = match body {
4607 Ok(b) => b,
4608 Err(e) if e.is_retriable() => {
4609 let _ = self.conns.remove(&node);
4610 self.cluster.invalidate_controller();
4611 self.wait_retry(&mut attempt, deadline).await?;
4612 continue;
4613 }
4614 Err(e) => return Err(e),
4615 };
4616 let (results, ..) = decode_create_acls_response(&mut body.clone(), version)?;
4617 if results
4618 .iter()
4619 .any(|r| r.error_code == error::NOT_CONTROLLER)
4620 {
4621 self.cluster.invalidate_controller();
4623 let _ = self.conns.remove(&node);
4624 self.wait_retry(&mut attempt, deadline).await?;
4625 self.refresh_metadata(None).await?;
4626 continue;
4627 }
4628 return Ok(results);
4629 }
4630 }
4631
4632 pub async fn alter_partition_reassignments(
4643 &mut self,
4644 assignments: &[PartitionReassignment],
4645 timeout_ms: i32,
4646 ) -> Result<Vec<ReassignmentResult>> {
4647 let timeout = self.cfg.request_timeout;
4648 self.alter_partition_reassignments_with(assignments, timeout_ms, timeout)
4649 .await
4650 }
4651
4652 pub async fn alter_partition_reassignments_timeout(
4658 &mut self,
4659 assignments: &[PartitionReassignment],
4660 timeout: Duration,
4661 ) -> Result<Vec<ReassignmentResult>> {
4662 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
4663 self.alter_partition_reassignments_with(assignments, timeout_ms, timeout)
4664 .await
4665 }
4666
4667 pub async fn alter_partition_reassignments_for<I, Tp>(
4675 &mut self,
4676 assignments: I,
4677 timeout_ms: i32,
4678 ) -> Result<Vec<ReassignmentResult>>
4679 where
4680 I: IntoIterator<Item = (Tp, Option<NewPartitionReassignment>)>,
4681 Tp: Into<crate::TopicPartition>,
4682 {
4683 let collected: Vec<PartitionReassignment> = assignments
4684 .into_iter()
4685 .map(|(tp, assignment)| PartitionReassignment::from_new(tp, assignment))
4686 .collect();
4687 self.alter_partition_reassignments(&collected, timeout_ms)
4688 .await
4689 }
4690
4691 pub async fn alter_partition_reassignments_for_timeout<I, Tp>(
4697 &mut self,
4698 assignments: I,
4699 timeout: Duration,
4700 ) -> Result<Vec<ReassignmentResult>>
4701 where
4702 I: IntoIterator<Item = (Tp, Option<NewPartitionReassignment>)>,
4703 Tp: Into<crate::TopicPartition>,
4704 {
4705 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
4706 let collected: Vec<PartitionReassignment> = assignments
4707 .into_iter()
4708 .map(|(tp, assignment)| PartitionReassignment::from_new(tp, assignment))
4709 .collect();
4710 self.alter_partition_reassignments_with(&collected, timeout_ms, timeout)
4711 .await
4712 }
4713
4714 async fn alter_partition_reassignments_with(
4715 &mut self,
4716 assignments: &[PartitionReassignment],
4717 timeout_ms: i32,
4718 timeout: Duration,
4719 ) -> Result<Vec<ReassignmentResult>> {
4720 let topics = group_reassignments(assignments);
4721 let version = self.reassign_version.ok_or_else(|| {
4722 Error::Unsupported("broker does not support AlterPartitionReassignments".into())
4723 })?;
4724 let deadline = Instant::now() + timeout;
4725 let mut attempt = 0u32;
4726 loop {
4727 if self.cluster.controller().is_err() {
4728 self.refresh_metadata(None).await?;
4729 }
4730 let node = self.cluster.controller()?;
4731 self.connect_node(node).await?;
4732 let body = {
4733 let conn = self
4734 .conns
4735 .get_mut(&node)
4736 .ok_or_else(|| Error::protocol("missing alter_partition_reassignments conn"))?;
4737 conn.roundtrip(
4738 ALTER_PARTITION_REASSIGNMENTS,
4739 version,
4740 |buf| encode_alter_partition_reassignments_request(buf, timeout_ms, &topics),
4741 timeout,
4742 )
4743 .await
4744 };
4745 let body = match body {
4746 Ok(b) => b,
4747 Err(e) if e.is_retriable() => {
4748 let _ = self.conns.remove(&node);
4749 self.cluster.invalidate_controller();
4750 self.wait_retry(&mut attempt, deadline).await?;
4751 continue;
4752 }
4753 Err(e) => return Err(e),
4754 };
4755 let resp = decode_alter_partition_reassignments_response(&mut body.clone())?;
4756 if resp.error_code == error::NOT_CONTROLLER
4757 || resp.results.iter().any(|t| {
4758 t.partitions
4759 .iter()
4760 .any(|p| p.error_code == error::NOT_CONTROLLER)
4761 })
4762 {
4763 self.cluster.invalidate_controller();
4765 let _ = self.conns.remove(&node);
4766 self.wait_retry(&mut attempt, deadline).await?;
4767 self.refresh_metadata(None).await?;
4768 continue;
4769 }
4770 if resp.error_code != 0 {
4771 return Err(Error::broker(
4772 resp.error_code,
4773 "AlterPartitionReassignments",
4774 ));
4775 }
4776 return Ok(flatten_reassignment_results(&resp.results));
4777 }
4778 }
4779
4780 pub async fn list_partition_reassignments(
4792 &mut self,
4793 partitions: Option<&[crate::TopicPartition]>,
4794 timeout_ms: i32,
4795 ) -> Result<Vec<OngoingReassignment>> {
4796 let timeout = self.cfg.request_timeout;
4797 self.list_partition_reassignments_with(partitions, timeout_ms, timeout)
4798 .await
4799 }
4800
4801 pub async fn list_partition_reassignments_timeout(
4807 &mut self,
4808 partitions: Option<&[crate::TopicPartition]>,
4809 timeout: Duration,
4810 ) -> Result<Vec<OngoingReassignment>> {
4811 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
4812 self.list_partition_reassignments_with(partitions, timeout_ms, timeout)
4813 .await
4814 }
4815
4816 pub async fn list_partition_reassignments_all(&mut self) -> Result<Vec<OngoingReassignment>> {
4822 let timeout = self.cfg.request_timeout;
4823 self.list_partition_reassignments_all_timeout(timeout).await
4824 }
4825
4826 pub async fn list_partition_reassignments_all_timeout(
4832 &mut self,
4833 timeout: Duration,
4834 ) -> Result<Vec<OngoingReassignment>> {
4835 self.list_partition_reassignments_timeout(None, timeout)
4836 .await
4837 }
4838
4839 pub async fn list_partition_reassignments_for(
4845 &mut self,
4846 partitions: &[crate::TopicPartition],
4847 ) -> Result<Vec<OngoingReassignment>> {
4848 let timeout = self.cfg.request_timeout;
4849 self.list_partition_reassignments_timeout(Some(partitions), timeout)
4850 .await
4851 }
4852
4853 async fn list_partition_reassignments_with(
4854 &mut self,
4855 partitions: Option<&[crate::TopicPartition]>,
4856 timeout_ms: i32,
4857 timeout: Duration,
4858 ) -> Result<Vec<OngoingReassignment>> {
4859 let topics = partitions.map(group_list_reassignments);
4860 let version = self.list_reassign_version.ok_or_else(|| {
4861 Error::Unsupported("broker does not support ListPartitionReassignments".into())
4862 })?;
4863 let deadline = Instant::now() + timeout;
4864 let mut attempt = 0u32;
4865 loop {
4866 if self.cluster.controller().is_err() {
4867 self.refresh_metadata(None).await?;
4868 }
4869 let node = self.cluster.controller()?;
4870 self.connect_node(node).await?;
4871 let body = {
4872 let conn = self
4873 .conns
4874 .get_mut(&node)
4875 .ok_or_else(|| Error::protocol("missing list_partition_reassignments conn"))?;
4876 conn.roundtrip(
4877 LIST_PARTITION_REASSIGNMENTS,
4878 version,
4879 |buf| {
4880 encode_list_partition_reassignments_request(
4881 buf,
4882 timeout_ms,
4883 topics.as_deref(),
4884 )
4885 },
4886 timeout,
4887 )
4888 .await
4889 };
4890 let body = match body {
4891 Ok(b) => b,
4892 Err(e) if e.is_retriable() => {
4893 let _ = self.conns.remove(&node);
4894 self.cluster.invalidate_controller();
4895 self.wait_retry(&mut attempt, deadline).await?;
4896 continue;
4897 }
4898 Err(e) => return Err(e),
4899 };
4900 let resp = decode_list_partition_reassignments_response(&mut body.clone())?;
4901 if resp.error_code == error::NOT_CONTROLLER {
4902 self.cluster.invalidate_controller();
4904 let _ = self.conns.remove(&node);
4905 self.wait_retry(&mut attempt, deadline).await?;
4906 self.refresh_metadata(None).await?;
4907 continue;
4908 }
4909 if resp.error_code != 0 {
4910 return Err(Error::broker(resp.error_code, "ListPartitionReassignments"));
4911 }
4912 return Ok(flatten_list_reassignments(&resp.topics));
4913 }
4914 }
4915
4916 pub async fn update_features(
4935 &mut self,
4936 updates: &[FeatureUpdate],
4937 timeout_ms: i32,
4938 ) -> Result<Vec<FeatureUpdateResult>> {
4939 let timeout = self.cfg.request_timeout;
4940 self.update_features_inner(updates, timeout_ms, false, timeout)
4941 .await
4942 }
4943
4944 pub async fn update_features_timeout(
4949 &mut self,
4950 updates: &[FeatureUpdate],
4951 timeout: Duration,
4952 ) -> Result<Vec<FeatureUpdateResult>> {
4953 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
4954 self.update_features_inner(updates, timeout_ms, false, timeout)
4955 .await
4956 }
4957
4958 pub async fn update_features_with(
4966 &mut self,
4967 updates: &[FeatureUpdate],
4968 timeout_ms: i32,
4969 validate_only: bool,
4970 ) -> Result<Vec<FeatureUpdateResult>> {
4971 let timeout = self.cfg.request_timeout;
4972 self.update_features_inner(updates, timeout_ms, validate_only, timeout)
4973 .await
4974 }
4975
4976 pub async fn update_features_with_timeout(
4981 &mut self,
4982 updates: &[FeatureUpdate],
4983 timeout: Duration,
4984 validate_only: bool,
4985 ) -> Result<Vec<FeatureUpdateResult>> {
4986 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
4987 self.update_features_inner(updates, timeout_ms, validate_only, timeout)
4988 .await
4989 }
4990
4991 fn reject_java_feature_updates(updates: &[FeatureUpdate]) -> Result<()> {
4993 if updates.is_empty() {
4994 return Err(Error::protocol("Feature updates can not be null or empty."));
4995 }
4996 for u in updates {
4997 if u.name.trim().is_empty() {
4998 return Err(Error::protocol("Provided feature can not be empty."));
4999 }
5000 }
5001 Ok(())
5002 }
5003
5004 async fn update_features_inner(
5005 &mut self,
5006 updates: &[FeatureUpdate],
5007 timeout_ms: i32,
5008 validate_only: bool,
5009 timeout: Duration,
5010 ) -> Result<Vec<FeatureUpdateResult>> {
5011 Self::reject_java_feature_updates(updates)?;
5012 let keys: Vec<FeatureUpdateKey> = updates
5013 .iter()
5014 .map(|u| FeatureUpdateKey {
5015 name: u.name.clone(),
5016 max_version_level: u.max_version_level,
5017 allow_downgrade: u.allow_downgrade,
5018 upgrade_type: u.upgrade_type,
5019 })
5020 .collect();
5021 let version = self.update_features_version.ok_or_else(|| {
5022 Error::Unsupported("broker does not support UpdateFeatures v0-2".into())
5023 })?;
5024 let deadline = Instant::now() + timeout;
5025 let mut attempt = 0u32;
5026 loop {
5027 if self.cluster.controller().is_err() {
5028 self.refresh_metadata(None).await?;
5029 }
5030 let node = self.cluster.controller()?;
5031 self.connect_node(node).await?;
5032 let body = {
5033 let conn = self
5034 .conns
5035 .get_mut(&node)
5036 .ok_or_else(|| Error::protocol("missing update_features conn"))?;
5037 conn.roundtrip(
5038 UPDATE_FEATURES,
5039 version,
5040 |buf| {
5041 encode_update_features_request(
5042 buf,
5043 version,
5044 timeout_ms,
5045 &keys,
5046 validate_only,
5047 )
5048 },
5049 timeout,
5050 )
5051 .await
5052 };
5053 let body = match body {
5054 Ok(b) => b,
5055 Err(e) if e.is_retriable() => {
5056 let _ = self.conns.remove(&node);
5057 self.cluster.invalidate_controller();
5058 self.wait_retry(&mut attempt, deadline).await?;
5059 continue;
5060 }
5061 Err(e) => return Err(e),
5062 };
5063 let resp = decode_update_features_response(&mut body.clone(), version)?;
5064 if resp.error_code == error::NOT_CONTROLLER
5065 || resp
5066 .results
5067 .iter()
5068 .any(|r| r.error_code == error::NOT_CONTROLLER)
5069 {
5070 self.cluster.invalidate_controller();
5072 let _ = self.conns.remove(&node);
5073 self.wait_retry(&mut attempt, deadline).await?;
5074 self.refresh_metadata(None).await?;
5075 continue;
5076 }
5077 if resp.error_code != 0 {
5078 return Err(Error::broker(resp.error_code, "UpdateFeatures"));
5079 }
5080 if version >= 2 {
5081 return Ok(keys
5082 .iter()
5083 .map(|k| FeatureUpdateResult {
5084 name: k.name.clone(),
5085 error_code: 0,
5086 error_message: None,
5087 })
5088 .collect());
5089 }
5090 return Ok(resp
5091 .results
5092 .into_iter()
5093 .map(|r| FeatureUpdateResult {
5094 name: r.name,
5095 error_code: r.error_code,
5096 error_message: r.error_message,
5097 })
5098 .collect());
5099 }
5100 }
5101
5102 pub async fn describe_features(&mut self) -> Result<FeatureMetadata> {
5114 let timeout = self.cfg.request_timeout;
5115 self.describe_features_timeout(timeout).await
5116 }
5117
5118 pub async fn describe_features_timeout(
5124 &mut self,
5125 timeout: Duration,
5126 ) -> Result<FeatureMetadata> {
5127 let version = self
5128 .versions
5129 .get(&API_VERSIONS)
5130 .and_then(|v| pick_version(v.min_version, v.max_version, 3, 4))
5131 .unwrap_or(3);
5132 let body = self
5133 .roundtrip_bootstrap(
5134 API_VERSIONS,
5135 version,
5136 |buf| encode_api_versions_request(buf, version, "partitionline", "0.1.0"),
5137 timeout,
5138 )
5139 .await?;
5140 let resp = decode_api_versions_response(&mut body.clone(), version)?;
5141 if resp.error_code != 0 {
5142 return Err(Error::broker(resp.error_code, "ApiVersions"));
5143 }
5144 Ok(FeatureMetadata {
5145 supported_features: resp
5146 .supported_features
5147 .into_iter()
5148 .map(|f| SupportedVersionRange::new(f.name, f.min_version, f.max_version))
5149 .collect::<Result<Vec<_>>>()?,
5150 finalized_features: resp
5151 .finalized_features
5152 .into_iter()
5153 .map(|f| {
5154 FinalizedVersionRange::new(f.name, f.min_version_level, f.max_version_level)
5155 })
5156 .collect::<Result<Vec<_>>>()?,
5157 finalized_features_epoch: resp.finalized_features_epoch,
5158 zk_migration_ready: resp.zk_migration_ready,
5159 })
5160 }
5161
5162 pub async fn alter_user_scram_credentials(
5175 &mut self,
5176 deletions: &[UserScramCredentialDeletion],
5177 upsertions: &[UserScramCredentialUpsertion],
5178 ) -> Result<Vec<UserScramCredentialResult>> {
5179 let timeout = self.cfg.request_timeout;
5180 self.alter_user_scram_credentials_timeout(deletions, upsertions, timeout)
5181 .await
5182 }
5183
5184 pub async fn alter_user_scram_credentials_with<A>(
5191 &mut self,
5192 alterations: impl IntoIterator<Item = A>,
5193 ) -> Result<Vec<UserScramCredentialResult>>
5194 where
5195 A: Into<UserScramCredentialAlteration>,
5196 {
5197 let timeout = self.cfg.request_timeout;
5198 self.alter_user_scram_credentials_with_timeout(alterations, timeout)
5199 .await
5200 }
5201
5202 pub async fn alter_user_scram_credentials_with_timeout<A>(
5208 &mut self,
5209 alterations: impl IntoIterator<Item = A>,
5210 timeout: Duration,
5211 ) -> Result<Vec<UserScramCredentialResult>>
5212 where
5213 A: Into<UserScramCredentialAlteration>,
5214 {
5215 let mut deletions = Vec::new();
5216 let mut upsertions = Vec::new();
5217 for item in alterations {
5218 match item.into() {
5219 UserScramCredentialAlteration::Deletion(d) => deletions.push(d),
5220 UserScramCredentialAlteration::Upsertion(u) => upsertions.push(u),
5221 }
5222 }
5223 self.alter_user_scram_credentials_timeout(&deletions, &upsertions, timeout)
5224 .await
5225 }
5226
5227 pub async fn alter_user_scram_credentials_timeout(
5233 &mut self,
5234 deletions: &[UserScramCredentialDeletion],
5235 upsertions: &[UserScramCredentialUpsertion],
5236 timeout: Duration,
5237 ) -> Result<Vec<UserScramCredentialResult>> {
5238 let deletions: Vec<ScramCredentialDeletion> = deletions
5239 .iter()
5240 .map(|d| ScramCredentialDeletion {
5241 name: d.name.clone(),
5242 mechanism: d.mechanism,
5243 })
5244 .collect();
5245 let upsertions: Vec<ScramCredentialUpsertion> = upsertions
5246 .iter()
5247 .map(|u| ScramCredentialUpsertion {
5248 name: u.name.clone(),
5249 mechanism: u.mechanism,
5250 iterations: u.iterations,
5251 salt: u.salt.clone(),
5252 salted_password: u.salted_password.clone(),
5253 })
5254 .collect();
5255 let version = self.alter_user_scram_version.ok_or_else(|| {
5256 Error::Unsupported("broker does not support AlterUserScramCredentials".into())
5257 })?;
5258 let deadline = Instant::now() + timeout;
5259 let mut attempt = 0u32;
5260 loop {
5261 if self.cluster.controller().is_err() {
5262 self.refresh_metadata(None).await?;
5263 }
5264 let node = self.cluster.controller()?;
5265 self.connect_node(node).await?;
5266 let body = {
5267 let conn = self
5268 .conns
5269 .get_mut(&node)
5270 .ok_or_else(|| Error::protocol("missing alter_user_scram_credentials conn"))?;
5271 conn.roundtrip(
5272 ALTER_USER_SCRAM_CREDENTIALS,
5273 version,
5274 |buf| encode_alter_user_scram_credentials_request(buf, &deletions, &upsertions),
5275 timeout,
5276 )
5277 .await
5278 };
5279 let body = match body {
5280 Ok(b) => b,
5281 Err(e) if e.is_retriable() => {
5282 let _ = self.conns.remove(&node);
5283 self.cluster.invalidate_controller();
5284 self.wait_retry(&mut attempt, deadline).await?;
5285 continue;
5286 }
5287 Err(e) => return Err(e),
5288 };
5289 let (results, ..) = decode_alter_user_scram_credentials_response(&mut body.clone())?;
5290 if results
5291 .iter()
5292 .any(|r| r.error_code == error::NOT_CONTROLLER)
5293 {
5294 self.cluster.invalidate_controller();
5296 let _ = self.conns.remove(&node);
5297 self.wait_retry(&mut attempt, deadline).await?;
5298 self.refresh_metadata(None).await?;
5299 continue;
5300 }
5301 return Ok(results
5302 .into_iter()
5303 .map(|r| UserScramCredentialResult {
5304 user: r.user,
5305 error_code: r.error_code,
5306 error_message: r.error_message,
5307 })
5308 .collect());
5309 }
5310 }
5311
5312 pub async fn describe_user_scram_credentials(
5326 &mut self,
5327 users: &[&str],
5328 ) -> Result<Vec<DescribeUserScramCredentialsResult>> {
5329 let timeout = self.cfg.request_timeout;
5330 self.describe_user_scram_credentials_timeout(users, timeout)
5331 .await
5332 }
5333
5334 pub async fn describe_user_scram_credentials_timeout(
5340 &mut self,
5341 users: &[&str],
5342 timeout: Duration,
5343 ) -> Result<Vec<DescribeUserScramCredentialsResult>> {
5344 let users: Vec<String> = users.iter().map(|s| (*s).to_string()).collect();
5345 let users_wire: Option<&[String]> = if users.is_empty() {
5346 None
5347 } else {
5348 Some(users.as_slice())
5349 };
5350 let version = self.describe_user_scram_version.ok_or_else(|| {
5351 Error::Unsupported("broker does not support DescribeUserScramCredentials".into())
5352 })?;
5353 let deadline = Instant::now() + timeout;
5354 let mut attempt = 0u32;
5355 loop {
5356 if self.cluster.controller().is_err() {
5357 self.refresh_metadata(None).await?;
5358 }
5359 let node = self.cluster.controller()?;
5360 self.connect_node(node).await?;
5361 let body = {
5362 let conn = self.conns.get_mut(&node).ok_or_else(|| {
5363 Error::protocol("missing describe_user_scram_credentials conn")
5364 })?;
5365 conn.roundtrip(
5366 DESCRIBE_USER_SCRAM_CREDENTIALS,
5367 version,
5368 |buf| encode_describe_user_scram_credentials_request(buf, users_wire),
5369 timeout,
5370 )
5371 .await
5372 };
5373 let body = match body {
5374 Ok(b) => b,
5375 Err(e) if e.is_retriable() => {
5376 let _ = self.conns.remove(&node);
5377 self.cluster.invalidate_controller();
5378 self.wait_retry(&mut attempt, deadline).await?;
5379 continue;
5380 }
5381 Err(e) => return Err(e),
5382 };
5383 let resp = decode_describe_user_scram_credentials_response(&mut body.clone())?;
5384 if resp.error_code == error::NOT_CONTROLLER {
5385 self.cluster.invalidate_controller();
5387 let _ = self.conns.remove(&node);
5388 self.wait_retry(&mut attempt, deadline).await?;
5389 self.refresh_metadata(None).await?;
5390 continue;
5391 }
5392 if resp.error_code != 0 {
5393 return Err(Error::broker(
5394 resp.error_code,
5395 "DescribeUserScramCredentials",
5396 ));
5397 }
5398 return Ok(resp.results);
5399 }
5400 }
5401
5402 pub async fn describe_user_scram_credentials_all(
5408 &mut self,
5409 ) -> Result<Vec<DescribeUserScramCredentialsResult>> {
5410 let timeout = self.cfg.request_timeout;
5411 self.describe_user_scram_credentials_all_timeout(timeout)
5412 .await
5413 }
5414
5415 pub async fn describe_user_scram_credentials_all_timeout(
5418 &mut self,
5419 timeout: Duration,
5420 ) -> Result<Vec<DescribeUserScramCredentialsResult>> {
5421 self.describe_user_scram_credentials_timeout(&[], timeout)
5422 .await
5423 }
5424
5425 pub async fn unregister_broker(&mut self, broker_id: i32) -> Result<()> {
5436 let timeout = self.cfg.request_timeout;
5437 self.unregister_broker_timeout(broker_id, timeout).await
5438 }
5439
5440 pub async fn unregister_broker_timeout(
5446 &mut self,
5447 broker_id: i32,
5448 timeout: Duration,
5449 ) -> Result<()> {
5450 let version = self
5451 .unregister_broker_version
5452 .ok_or_else(|| Error::Unsupported("broker does not support UnregisterBroker".into()))?;
5453 let deadline = Instant::now() + timeout;
5454 let mut attempt = 0u32;
5455 loop {
5456 if self.cluster.controller().is_err() {
5457 self.refresh_metadata(None).await?;
5458 }
5459 let node = self.cluster.controller()?;
5460 self.connect_node(node).await?;
5461 let body = {
5462 let conn = self
5463 .conns
5464 .get_mut(&node)
5465 .ok_or_else(|| Error::protocol("missing unregister_broker conn"))?;
5466 conn.roundtrip(
5467 UNREGISTER_BROKER,
5468 version,
5469 |buf| encode_unregister_broker_request(buf, broker_id),
5470 timeout,
5471 )
5472 .await
5473 };
5474 let body = match body {
5475 Ok(b) => b,
5476 Err(e) if e.is_retriable() => {
5477 let _ = self.conns.remove(&node);
5478 self.cluster.invalidate_controller();
5479 self.wait_retry(&mut attempt, deadline).await?;
5480 continue;
5481 }
5482 Err(e) => return Err(e),
5483 };
5484 let resp = decode_unregister_broker_response(&mut body.clone())?;
5485 if resp.error_code == error::NOT_CONTROLLER {
5486 self.cluster.invalidate_controller();
5488 let _ = self.conns.remove(&node);
5489 self.wait_retry(&mut attempt, deadline).await?;
5490 self.refresh_metadata(None).await?;
5491 continue;
5492 }
5493 if resp.error_code != 0 {
5494 return Err(Error::broker(resp.error_code, "UnregisterBroker"));
5495 }
5496 return Ok(());
5497 }
5498 }
5499
5500 pub async fn describe_client_quotas(
5517 &mut self,
5518 components: &[ClientQuotaFilterComponent],
5519 strict: bool,
5520 ) -> Result<Vec<ClientQuotaEntry>> {
5521 let timeout = self.cfg.request_timeout;
5522 self.describe_client_quotas_timeout(components, strict, timeout)
5523 .await
5524 }
5525
5526 pub async fn describe_client_quotas_timeout(
5531 &mut self,
5532 components: &[ClientQuotaFilterComponent],
5533 strict: bool,
5534 timeout: Duration,
5535 ) -> Result<Vec<ClientQuotaEntry>> {
5536 let components = components.to_vec();
5537 let version = self.describe_client_quotas_version.ok_or_else(|| {
5538 Error::Unsupported("broker does not support DescribeClientQuotas v0-1".into())
5539 })?;
5540 let body = self
5541 .roundtrip_bootstrap(
5542 DESCRIBE_CLIENT_QUOTAS,
5543 version,
5544 |buf| encode_describe_client_quotas_request(buf, version, &components, strict),
5545 timeout,
5546 )
5547 .await?;
5548 let resp = decode_describe_client_quotas_response(&mut body.clone(), version)?;
5549 if resp.error_code != 0 {
5550 return Err(Error::broker(resp.error_code, "DescribeClientQuotas"));
5551 }
5552 Ok(resp.entries.unwrap_or_default())
5553 }
5554
5555 pub async fn describe_client_quotas_with(
5561 &mut self,
5562 filter: &ClientQuotaFilter,
5563 ) -> Result<Vec<ClientQuotaEntry>> {
5564 self.describe_client_quotas(filter.components(), filter.strict())
5565 .await
5566 }
5567
5568 pub async fn describe_client_quotas_with_timeout(
5573 &mut self,
5574 filter: &ClientQuotaFilter,
5575 timeout: Duration,
5576 ) -> Result<Vec<ClientQuotaEntry>> {
5577 self.describe_client_quotas_timeout(filter.components(), filter.strict(), timeout)
5578 .await
5579 }
5580
5581 pub async fn describe_client_quotas_all(&mut self) -> Result<Vec<ClientQuotaEntry>> {
5586 self.describe_client_quotas_with(&ClientQuotaFilter::all())
5587 .await
5588 }
5589
5590 pub async fn describe_client_quotas_all_timeout(
5593 &mut self,
5594 timeout: Duration,
5595 ) -> Result<Vec<ClientQuotaEntry>> {
5596 self.describe_client_quotas_with_timeout(&ClientQuotaFilter::all(), timeout)
5597 .await
5598 }
5599
5600 pub async fn alter_client_quotas(
5611 &mut self,
5612 entries: &[ClientQuotaAlteration],
5613 validate_only: bool,
5614 ) -> Result<Vec<ClientQuotaAlterationResult>> {
5615 let timeout = self.cfg.request_timeout;
5616 self.alter_client_quotas_timeout(entries, timeout, validate_only)
5617 .await
5618 }
5619
5620 pub async fn alter_client_quotas_timeout(
5626 &mut self,
5627 entries: &[ClientQuotaAlteration],
5628 timeout: Duration,
5629 validate_only: bool,
5630 ) -> Result<Vec<ClientQuotaAlterationResult>> {
5631 let entries = entries.to_vec();
5632 let version = self.alter_client_quotas_version.ok_or_else(|| {
5633 Error::Unsupported("broker does not support AlterClientQuotas v0-1".into())
5634 })?;
5635 let deadline = Instant::now() + timeout;
5636 let mut attempt = 0u32;
5637 loop {
5638 if self.cluster.controller().is_err() {
5639 self.refresh_metadata(None).await?;
5640 }
5641 let node = self.cluster.controller()?;
5642 self.connect_node(node).await?;
5643 let body = {
5644 let conn = self
5645 .conns
5646 .get_mut(&node)
5647 .ok_or_else(|| Error::protocol("missing alter_client_quotas conn"))?;
5648 conn.roundtrip(
5649 ALTER_CLIENT_QUOTAS,
5650 version,
5651 |buf| encode_alter_client_quotas_request(buf, version, &entries, validate_only),
5652 timeout,
5653 )
5654 .await
5655 };
5656 let body = match body {
5657 Ok(b) => b,
5658 Err(e) if e.is_retriable() => {
5659 let _ = self.conns.remove(&node);
5660 self.cluster.invalidate_controller();
5661 self.wait_retry(&mut attempt, deadline).await?;
5662 continue;
5663 }
5664 Err(e) => return Err(e),
5665 };
5666 let (results, ..) = decode_alter_client_quotas_response(&mut body.clone(), version)?;
5667 if results
5668 .iter()
5669 .any(|r| r.error_code == error::NOT_CONTROLLER)
5670 {
5671 self.cluster.invalidate_controller();
5673 let _ = self.conns.remove(&node);
5674 self.wait_retry(&mut attempt, deadline).await?;
5675 self.refresh_metadata(None).await?;
5676 continue;
5677 }
5678 return Ok(results);
5679 }
5680 }
5681
5682 pub async fn allocate_producer_ids(
5691 &mut self,
5692 broker_id: i32,
5693 broker_epoch: i64,
5694 ) -> Result<ProducerIdBlock> {
5695 let timeout = self.cfg.request_timeout;
5696 self.allocate_producer_ids_timeout(broker_id, broker_epoch, timeout)
5697 .await
5698 }
5699
5700 pub async fn allocate_producer_ids_timeout(
5706 &mut self,
5707 broker_id: i32,
5708 broker_epoch: i64,
5709 timeout: Duration,
5710 ) -> Result<ProducerIdBlock> {
5711 let version = self.allocate_producer_ids_version.ok_or_else(|| {
5712 Error::Unsupported("broker does not support AllocateProducerIds".into())
5713 })?;
5714 let deadline = Instant::now() + timeout;
5715 let mut attempt = 0u32;
5716 loop {
5717 if self.cluster.controller().is_err() {
5718 self.refresh_metadata(None).await?;
5719 }
5720 let node = self.cluster.controller()?;
5721 self.connect_node(node).await?;
5722 let body = {
5723 let conn = self
5724 .conns
5725 .get_mut(&node)
5726 .ok_or_else(|| Error::protocol("missing allocate_producer_ids conn"))?;
5727 conn.roundtrip(
5728 ALLOCATE_PRODUCER_IDS,
5729 version,
5730 |buf| encode_allocate_producer_ids_request(buf, broker_id, broker_epoch),
5731 timeout,
5732 )
5733 .await
5734 };
5735 let body = match body {
5736 Ok(b) => b,
5737 Err(e) if e.is_retriable() => {
5738 let _ = self.conns.remove(&node);
5739 self.cluster.invalidate_controller();
5740 self.wait_retry(&mut attempt, deadline).await?;
5741 continue;
5742 }
5743 Err(e) => return Err(e),
5744 };
5745 let resp = decode_allocate_producer_ids_response(&mut body.clone())?;
5746 if resp.error_code == error::NOT_CONTROLLER {
5747 self.cluster.invalidate_controller();
5749 let _ = self.conns.remove(&node);
5750 self.wait_retry(&mut attempt, deadline).await?;
5751 self.refresh_metadata(None).await?;
5752 continue;
5753 }
5754 if resp.error_code != 0 {
5755 return Err(Error::broker(resp.error_code, "AllocateProducerIds"));
5756 }
5757 return Ok(ProducerIdBlock {
5758 producer_id_start: resp.producer_id_start,
5759 producer_id_len: resp.producer_id_len,
5760 });
5761 }
5762 }
5763
5764 pub async fn fence_producers(
5774 &mut self,
5775 transactional_ids: impl IntoIterator<Item = impl Into<String>>,
5776 ) -> Result<Vec<FencedProducer>> {
5777 let timeout = self.cfg.request_timeout;
5778 self.fence_producers_timeout(transactional_ids, timeout)
5779 .await
5780 }
5781
5782 pub async fn fence_producers_timeout(
5788 &mut self,
5789 transactional_ids: impl IntoIterator<Item = impl Into<String>>,
5790 timeout: Duration,
5791 ) -> Result<Vec<FencedProducer>> {
5792 let mut out = Vec::new();
5793 for id in transactional_ids {
5794 let transactional_id = id.into();
5795 let (producer_id, epoch) = self.fence_one(&transactional_id, timeout).await?;
5796 out.push(FencedProducer {
5797 transactional_id,
5798 producer_id,
5799 epoch,
5800 });
5801 }
5802 Ok(out)
5803 }
5804
5805 pub async fn force_terminate_transaction(
5814 &mut self,
5815 transactional_id: impl Into<String>,
5816 ) -> Result<FencedProducer> {
5817 let timeout = self.cfg.request_timeout;
5818 self.force_terminate_transaction_timeout(transactional_id, timeout)
5819 .await
5820 }
5821
5822 pub async fn force_terminate_transaction_timeout(
5826 &mut self,
5827 transactional_id: impl Into<String>,
5828 timeout: Duration,
5829 ) -> Result<FencedProducer> {
5830 let transactional_id = transactional_id.into();
5831 let (producer_id, epoch) = self.fence_one(&transactional_id, timeout).await?;
5832 Ok(FencedProducer {
5833 transactional_id,
5834 producer_id,
5835 epoch,
5836 })
5837 }
5838
5839 async fn fence_one(&mut self, transactional_id: &str, timeout: Duration) -> Result<(i64, i16)> {
5840 let version = self
5841 .versions
5842 .get(&INIT_PRODUCER_ID)
5843 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 5))
5844 .ok_or_else(|| Error::Unsupported("broker does not support InitProducerId".into()))?;
5845 let txn_timeout_ms = crate::consumer::duration_millis_i32(timeout);
5846 let deadline = Instant::now() + timeout;
5847 let mut attempt = 0u32;
5848 loop {
5849 let stale = self
5850 .txn_coord
5851 .as_ref()
5852 .is_none_or(|(k, _)| k != transactional_id);
5853 if stale {
5854 let node = self.discover_txn_coord(transactional_id).await?;
5855 self.txn_coord = Some((transactional_id.to_string(), node));
5856 }
5857 let node = self
5858 .txn_coord
5859 .as_ref()
5860 .map(|(_, n)| *n)
5861 .ok_or_else(|| Error::protocol("missing transaction coordinator"))?;
5862 self.connect_node(node).await?;
5863 let body = {
5864 let conn = self
5865 .conns
5866 .get_mut(&node)
5867 .ok_or_else(|| Error::protocol("missing fence_producers conn"))?;
5868 conn.roundtrip(
5869 INIT_PRODUCER_ID,
5870 version,
5871 |buf| {
5872 encode_init_producer_id_request(
5873 buf,
5874 version,
5875 Some(transactional_id),
5876 txn_timeout_ms,
5877 crate::RecordBatch::NO_PRODUCER_ID,
5878 crate::RecordBatch::NO_PRODUCER_EPOCH,
5879 )
5880 },
5881 timeout,
5882 )
5883 .await
5884 };
5885 let body = match body {
5886 Ok(b) => b,
5887 Err(e) if e.is_retriable() => {
5888 let _ = self.conns.remove(&node);
5889 self.txn_coord = None;
5890 self.wait_retry(&mut attempt, deadline).await?;
5891 continue;
5892 }
5893 Err(e) => return Err(e),
5894 };
5895 let (err, producer_id, epoch, ..) =
5896 decode_init_producer_id_response(&mut body.clone(), version)?;
5897 if err == 0 {
5898 return Ok((producer_id, epoch));
5899 }
5900 if error::coordinator_retriable(err) {
5901 self.txn_coord = None;
5902 let _ = self.conns.remove(&node);
5903 self.wait_retry(&mut attempt, deadline).await?;
5904 continue;
5905 }
5906 return Err(Error::broker(err, "InitProducerId"));
5907 }
5908 }
5909
5910 pub async fn abort_transaction(&mut self, spec: AbortTransactionSpec) -> Result<()> {
5921 let timeout = self.cfg.request_timeout;
5922 self.abort_transaction_timeout(spec, timeout).await
5923 }
5924
5925 pub async fn abort_transaction_timeout(
5931 &mut self,
5932 spec: AbortTransactionSpec,
5933 timeout: Duration,
5934 ) -> Result<()> {
5935 let version = self
5936 .versions
5937 .get(&WRITE_TXN_MARKERS)
5938 .and_then(|v| pick_version(v.min_version, v.max_version, 0, 1))
5939 .ok_or_else(|| Error::Unsupported("broker does not support WriteTxnMarkers".into()))?;
5940 let deadline = Instant::now() + timeout;
5941 let mut attempt = 0u32;
5942 let marker = WritableTxnMarker {
5943 producer_id: spec.producer_id,
5944 producer_epoch: spec.producer_epoch,
5945 transaction_result: TransactionResult::Abort.id(),
5946 topics: vec![WritableTxnMarkerTopic {
5947 name: spec.topic.clone(),
5948 partitions: vec![spec.partition],
5949 }],
5950 coordinator_epoch: spec.coordinator_epoch,
5951 };
5952 loop {
5953 if self.cluster.leader(&spec.topic, spec.partition).is_err() {
5954 let topics = [spec.topic.clone()];
5955 self.refresh_metadata(Some(&topics)).await?;
5956 }
5957 let (node, _) = self.cluster.leader(&spec.topic, spec.partition)?;
5958 self.connect_node(node).await?;
5959 let body = {
5960 let conn = self
5961 .conns
5962 .get_mut(&node)
5963 .ok_or_else(|| Error::protocol("missing abort_transaction conn"))?;
5964 conn.roundtrip(
5965 WRITE_TXN_MARKERS,
5966 version,
5967 |buf| {
5968 encode_write_txn_markers_request(
5969 buf,
5970 version,
5971 std::slice::from_ref(&marker),
5972 )
5973 },
5974 timeout,
5975 )
5976 .await
5977 };
5978 let body = match body {
5979 Ok(b) => b,
5980 Err(e) if e.is_retriable() => {
5981 let _ = self.conns.remove(&node);
5982 self.wait_retry(&mut attempt, deadline).await?;
5983 continue;
5984 }
5985 Err(e) => return Err(e),
5986 };
5987 let resp = decode_write_txn_markers_response(&mut body.clone(), version)?;
5988 let error_code = resp
5989 .iter()
5990 .flat_map(|m| m.topics.iter())
5991 .flat_map(|t| t.partitions.iter())
5992 .map(|p| p.error_code)
5993 .find(|&c| c != 0)
5994 .unwrap_or(0);
5995 if error_code == 0 {
5996 return Ok(());
5997 }
5998 let e = Error::broker(error_code, "WriteTxnMarkers");
5999 if matches!(
6000 error_code,
6001 error::FENCED_LEADER_EPOCH | error::UNKNOWN_LEADER_EPOCH
6002 ) || e.is_retriable()
6003 {
6004 self.cluster.invalidate_topic(&spec.topic);
6005 let _ = self.conns.remove(&node);
6006 self.wait_retry(&mut attempt, deadline).await?;
6007 let topics = [spec.topic.clone()];
6008 self.refresh_metadata(Some(&topics)).await?;
6009 continue;
6010 }
6011 return Err(e);
6012 }
6013 }
6014
6015 pub async fn describe_transactions(
6031 &mut self,
6032 transactional_ids: &[&str],
6033 ) -> Result<Vec<TransactionState>> {
6034 let timeout = self.cfg.request_timeout;
6035 self.describe_transactions_timeout(transactional_ids, timeout)
6036 .await
6037 }
6038
6039 pub async fn describe_transactions_timeout(
6045 &mut self,
6046 transactional_ids: &[&str],
6047 timeout: Duration,
6048 ) -> Result<Vec<TransactionState>> {
6049 let ids: Vec<String> = transactional_ids.iter().map(|s| (*s).to_string()).collect();
6050 if ids.is_empty() {
6051 return Ok(Vec::new());
6052 }
6053 let version = self.describe_transactions_version.ok_or_else(|| {
6054 Error::Unsupported("broker does not support DescribeTransactions".into())
6055 })?;
6056 let deadline = Instant::now() + timeout;
6057 let mut attempt = 0u32;
6058 let mut out: Vec<Option<TransactionState>> = vec![None; ids.len()];
6059 let mut pending: Vec<usize> = (0..ids.len()).collect();
6060 loop {
6061 let by_node = self.txn_coord_nodes(&ids, &pending).await?;
6062 let mut nodes: Vec<i32> = by_node.keys().copied().collect();
6063 nodes.sort_unstable();
6064 let mut still = Vec::new();
6065 for node in nodes {
6066 let idxs = by_node.get(&node).cloned().unwrap_or_default();
6067 match self
6068 .describe_transactions_on_node(node, &ids, &idxs, version, timeout)
6069 .await
6070 {
6071 Ok(done) => {
6072 for (i, t) in done {
6073 if error::coordinator_retriable(t.error_code) {
6074 self.invalidate_txn_coord_idxs(&ids, &[i], node);
6075 still.push(i);
6076 } else if let Some(slot) = out.get_mut(i) {
6077 *slot = Some(t);
6078 }
6079 }
6080 }
6081 Err(e) if e.is_retriable() => {
6082 self.invalidate_txn_coord_idxs(&ids, &idxs, node);
6083 still.extend(idxs);
6084 }
6085 Err(e) => return Err(e),
6086 }
6087 }
6088 pending = still;
6089 if pending.is_empty() {
6090 break;
6091 }
6092 self.wait_retry(&mut attempt, deadline).await?;
6093 }
6094 out.into_iter()
6095 .zip(ids)
6096 .map(|(t, id)| {
6097 t.ok_or_else(|| Error::protocol(format!("DescribeTransactions missing {id}")))
6098 })
6099 .collect()
6100 }
6101
6102 pub async fn list_transactions(
6117 &mut self,
6118 state_filters: &[&str],
6119 producer_id_filters: &[i64],
6120 ) -> Result<Vec<TransactionListing>> {
6121 let timeout = self.cfg.request_timeout;
6122 self.list_transactions_timeout(state_filters, producer_id_filters, timeout)
6123 .await
6124 }
6125
6126 pub async fn list_transactions_timeout(
6134 &mut self,
6135 state_filters: &[&str],
6136 producer_id_filters: &[i64],
6137 timeout: Duration,
6138 ) -> Result<Vec<TransactionListing>> {
6139 self.list_transactions_with_duration_timeout(
6140 state_filters,
6141 producer_id_filters,
6142 -1,
6143 timeout,
6144 )
6145 .await
6146 }
6147
6148 pub async fn list_transactions_all(&mut self) -> Result<Vec<TransactionListing>> {
6154 self.list_transactions(&[], &[]).await
6155 }
6156
6157 pub async fn list_transactions_all_timeout(
6160 &mut self,
6161 timeout: Duration,
6162 ) -> Result<Vec<TransactionListing>> {
6163 self.list_transactions_timeout(&[], &[], timeout).await
6164 }
6165
6166 pub async fn list_transactions_with_duration(
6179 &mut self,
6180 state_filters: &[&str],
6181 producer_id_filters: &[i64],
6182 duration_ms: i64,
6183 ) -> Result<Vec<TransactionListing>> {
6184 let timeout = self.cfg.request_timeout;
6185 self.list_transactions_with_duration_timeout(
6186 state_filters,
6187 producer_id_filters,
6188 duration_ms,
6189 timeout,
6190 )
6191 .await
6192 }
6193
6194 pub async fn list_transactions_with_duration_timeout(
6202 &mut self,
6203 state_filters: &[&str],
6204 producer_id_filters: &[i64],
6205 duration_ms: i64,
6206 timeout: Duration,
6207 ) -> Result<Vec<TransactionListing>> {
6208 let states: Vec<String> = state_filters.iter().map(|s| (*s).to_string()).collect();
6209 let pids = producer_id_filters.to_vec();
6210 const COORD_KEY: &str = "";
6213 let version = self
6214 .list_transactions_version
6215 .ok_or_else(|| Error::Unsupported("broker does not support ListTransactions".into()))?;
6216 let deadline = Instant::now() + timeout;
6217 let mut attempt = 0u32;
6218 loop {
6219 let stale = self.txn_coord.as_ref().is_none_or(|(k, _)| k != COORD_KEY);
6220 if stale {
6221 let node = self.discover_txn_coord(COORD_KEY).await?;
6222 self.txn_coord = Some((COORD_KEY.to_string(), node));
6223 }
6224 let node = self
6225 .txn_coord
6226 .as_ref()
6227 .map(|(_, n)| *n)
6228 .ok_or_else(|| Error::protocol("missing transaction coordinator"))?;
6229 self.connect_node(node).await?;
6230 let body = {
6231 let conn = self
6232 .conns
6233 .get_mut(&node)
6234 .ok_or_else(|| Error::protocol("missing list_transactions conn"))?;
6235 conn.roundtrip(
6236 LIST_TRANSACTIONS,
6237 version,
6238 |buf| {
6239 encode_list_transactions_request(buf, version, &states, &pids, duration_ms)
6240 },
6241 timeout,
6242 )
6243 .await
6244 };
6245 let body = match body {
6246 Ok(b) => b,
6247 Err(e) if e.is_retriable() => {
6248 let _ = self.conns.remove(&node);
6249 self.txn_coord = None;
6250 self.wait_retry(&mut attempt, deadline).await?;
6251 continue;
6252 }
6253 Err(e) => return Err(e),
6254 };
6255 let resp = decode_list_transactions_response(&mut body.clone(), version)?;
6256 if error::coordinator_retriable(resp.error_code) {
6257 self.txn_coord = None;
6259 let _ = self.conns.remove(&node);
6260 self.wait_retry(&mut attempt, deadline).await?;
6261 continue;
6262 }
6263 if resp.error_code != 0 {
6264 return Err(Error::broker(resp.error_code, "ListTransactions"));
6265 }
6266 return Ok(resp.transaction_states);
6267 }
6268 }
6269
6270 pub async fn describe_acls(&mut self, resource_type: impl Into<i8>) -> Result<Vec<AclBinding>> {
6283 self.describe_acls_with(&AclBindingFilter::resource_type(resource_type))
6284 .await
6285 }
6286
6287 pub async fn describe_acls_timeout(
6290 &mut self,
6291 resource_type: impl Into<i8>,
6292 timeout: Duration,
6293 ) -> Result<Vec<AclBinding>> {
6294 self.describe_acls_with_timeout(&AclBindingFilter::resource_type(resource_type), timeout)
6295 .await
6296 }
6297
6298 pub async fn describe_acls_with(
6304 &mut self,
6305 filter: &AclBindingFilter,
6306 ) -> Result<Vec<AclBinding>> {
6307 let timeout = self.cfg.request_timeout;
6308 self.describe_acls_with_timeout(filter, timeout).await
6309 }
6310
6311 pub async fn describe_acls_with_timeout(
6315 &mut self,
6316 filter: &AclBindingFilter,
6317 timeout: Duration,
6318 ) -> Result<Vec<AclBinding>> {
6319 let version = self.describe_acls_version;
6320 let body = self
6321 .roundtrip_bootstrap(
6322 DESCRIBE_ACLS,
6323 version,
6324 |buf| encode_describe_acls_request(buf, version, filter),
6325 timeout,
6326 )
6327 .await?;
6328 let (acls, ..) = decode_describe_acls_response(&mut body.clone(), version)?;
6329 Ok(acls)
6330 }
6331
6332 pub async fn describe_acls_any(&mut self) -> Result<Vec<AclBinding>> {
6339 self.describe_acls_with(&AclBindingFilter::any()).await
6340 }
6341
6342 pub async fn describe_acls_any_timeout(
6347 &mut self,
6348 timeout: Duration,
6349 ) -> Result<Vec<AclBinding>> {
6350 self.describe_acls_with_timeout(&AclBindingFilter::any(), timeout)
6351 .await
6352 }
6353
6354 pub async fn alter_configs(
6367 &mut self,
6368 resource: &ConfigResource,
6369 configs: &[(String, Option<String>)],
6370 validate_only: bool,
6371 ) -> Result<i16> {
6372 let results = self
6373 .alter_configs_for(
6374 &[ConfigReplacement::new(
6375 resource.clone(),
6376 configs.iter().cloned(),
6377 )],
6378 validate_only,
6379 )
6380 .await?;
6381 Ok(results.first().map(|r| r.error_code).unwrap_or(0))
6382 }
6383
6384 pub async fn alter_configs_timeout(
6389 &mut self,
6390 resource: &ConfigResource,
6391 configs: &[(String, Option<String>)],
6392 timeout: Duration,
6393 validate_only: bool,
6394 ) -> Result<i16> {
6395 let results = self
6396 .alter_configs_for_timeout(
6397 &[ConfigReplacement::new(
6398 resource.clone(),
6399 configs.iter().cloned(),
6400 )],
6401 timeout,
6402 validate_only,
6403 )
6404 .await?;
6405 Ok(results.first().map(|r| r.error_code).unwrap_or(0))
6406 }
6407
6408 pub async fn alter_configs_with(
6415 &mut self,
6416 resource: &ConfigResource,
6417 config: &Config,
6418 validate_only: bool,
6419 ) -> Result<i16> {
6420 let results = self
6421 .alter_configs_for(
6422 &[ConfigReplacement::from_config(resource.clone(), config)],
6423 validate_only,
6424 )
6425 .await?;
6426 Ok(results.first().map(|r| r.error_code).unwrap_or(0))
6427 }
6428
6429 pub async fn alter_configs_with_timeout(
6434 &mut self,
6435 resource: &ConfigResource,
6436 config: &Config,
6437 timeout: Duration,
6438 validate_only: bool,
6439 ) -> Result<i16> {
6440 let results = self
6441 .alter_configs_for_timeout(
6442 &[ConfigReplacement::from_config(resource.clone(), config)],
6443 timeout,
6444 validate_only,
6445 )
6446 .await?;
6447 Ok(results.first().map(|r| r.error_code).unwrap_or(0))
6448 }
6449
6450 pub async fn alter_configs_for(
6455 &mut self,
6456 updates: &[ConfigReplacement],
6457 validate_only: bool,
6458 ) -> Result<Vec<AlterConfigsResourceResult>> {
6459 let timeout = self.cfg.request_timeout;
6460 self.alter_configs_for_timeout(updates, timeout, validate_only)
6461 .await
6462 }
6463
6464 pub async fn alter_configs_for_timeout(
6469 &mut self,
6470 updates: &[ConfigReplacement],
6471 timeout: Duration,
6472 validate_only: bool,
6473 ) -> Result<Vec<AlterConfigsResourceResult>> {
6474 if updates.is_empty() {
6475 return Ok(Vec::new());
6476 }
6477 let resources: Vec<AlterConfigsResource> = updates
6478 .iter()
6479 .map(|u| AlterConfigsResource {
6480 resource_type: u.resource.resource_type,
6481 name: u.resource.name.clone(),
6482 configs: u
6483 .configs
6484 .iter()
6485 .map(|(n, v)| TopicConfig {
6486 name: n.clone(),
6487 value: v.clone(),
6488 })
6489 .collect(),
6490 })
6491 .collect();
6492 let version = self.legacy_alter_version;
6493 let body = self
6494 .roundtrip_bootstrap(
6495 ALTER_CONFIGS,
6496 version,
6497 |buf| {
6498 encode_alter_configs_resources_request(buf, version, &resources, validate_only)
6499 },
6500 timeout,
6501 )
6502 .await?;
6503 let (results, ..) = decode_alter_configs_resource_results(&mut body.clone(), version)?;
6504 Ok(results)
6505 }
6506
6507 async fn fetch_metadata(&mut self, topics: Option<&[String]>) -> Result<MetadataResponse> {
6508 self.fetch_metadata_with(topics, false).await
6509 }
6510
6511 async fn fetch_metadata_with(
6512 &mut self,
6513 topics: Option<&[String]>,
6514 include_topic_authorized_operations: bool,
6515 ) -> Result<MetadataResponse> {
6516 let owned =
6517 topics.map(|names| MetadataRequestTopic::convert_from_names(names.iter().cloned()));
6518 let timeout = self.cfg.request_timeout;
6519 self.fetch_metadata_request_with(
6520 owned.as_deref(),
6521 include_topic_authorized_operations,
6522 timeout,
6523 )
6524 .await
6525 }
6526
6527 async fn fetch_metadata_request_with(
6528 &mut self,
6529 topics: Option<&[MetadataRequestTopic]>,
6530 include_topic_authorized_operations: bool,
6531 timeout: Duration,
6532 ) -> Result<MetadataResponse> {
6533 let version = self.metadata_version;
6534 let body = self
6535 .roundtrip_bootstrap(
6536 METADATA,
6537 version,
6538 |buf| {
6539 encode_metadata_request_topics(
6540 buf,
6541 version,
6542 topics,
6543 false,
6544 include_topic_authorized_operations,
6545 )
6546 },
6547 timeout,
6548 )
6549 .await?;
6550 let md = decode_metadata_response(&mut body.clone(), version)?;
6551 md.check()?;
6552 self.cluster.apply(&md, version);
6553 Ok(md)
6554 }
6555
6556 async fn refresh_metadata(&mut self, topics: Option<&[String]>) -> Result<()> {
6557 self.fetch_metadata(topics).await.map(|_| ())
6558 }
6559
6560 async fn wait_retry(&self, attempt: &mut u32, deadline: Instant) -> Result<()> {
6562 if Instant::now() >= deadline {
6563 return Err(Error::Timeout);
6564 }
6565 crate::config::sleep_retry_backoff(
6566 self.cfg.retry_backoff,
6567 self.cfg.retry_backoff_max,
6568 *attempt,
6569 deadline,
6570 )
6571 .await;
6572 *attempt = attempt.saturating_add(1);
6573 if Instant::now() >= deadline {
6574 return Err(Error::Timeout);
6575 }
6576 Ok(())
6577 }
6578
6579 async fn ensure_bootstrap(&mut self) -> Result<()> {
6580 if !self.conn.idle_expired(self.cfg.connections_max_idle) {
6581 return Ok(());
6582 }
6583 let addr = self.conn.addr().to_string();
6584 self.conn = self.open_node_conn(&addr).await?;
6585 Ok(())
6586 }
6587
6588 async fn roundtrip_bootstrap(
6590 &mut self,
6591 api_key: i16,
6592 api_version: i16,
6593 encode_body: impl FnOnce(&mut BytesMut) -> Result<()>,
6594 timeout: Duration,
6595 ) -> Result<Bytes> {
6596 self.ensure_bootstrap().await?;
6597 self.conn
6598 .roundtrip(api_key, api_version, encode_body, timeout)
6599 .await
6600 }
6601
6602 async fn connect_node(&mut self, node: i32) -> Result<()> {
6603 if self
6604 .conns
6605 .get(&node)
6606 .is_some_and(|c| c.idle_expired(self.cfg.connections_max_idle))
6607 {
6608 let _ = self.conns.remove(&node);
6609 }
6610 if self.conns.contains_key(&node) {
6611 return Ok(());
6612 }
6613 let addr = self
6614 .cluster
6615 .brokers
6616 .get(&node)
6617 .cloned()
6618 .ok_or_else(|| Error::protocol(format!("unknown broker {node}")))?;
6619 let deadline = Instant::now() + self.cfg.request_timeout;
6620 loop {
6621 let fails = self.reconnect_fails.get(&node).copied().unwrap_or(0);
6622 crate::config::sleep_reconnect_backoff(
6623 self.cfg.reconnect_backoff,
6624 self.cfg.reconnect_backoff_max,
6625 fails,
6626 )
6627 .await;
6628 if Instant::now() >= deadline {
6629 return Err(Error::Timeout);
6630 }
6631 match self.open_node_conn(&addr).await {
6632 Ok(conn) => {
6633 let _ = self.reconnect_fails.remove(&node);
6634 let _prev = self.conns.insert(node, conn);
6635 return Ok(());
6636 }
6637 Err(e) if e.is_retriable() => {
6638 let _fails =
6639 crate::config::bump_reconnect_fails(&mut self.reconnect_fails, node);
6640 if Instant::now() >= deadline {
6641 return Err(Error::Timeout);
6642 }
6643 }
6644 Err(e) => {
6645 let _fails =
6646 crate::config::bump_reconnect_fails(&mut self.reconnect_fails, node);
6647 return Err(e);
6648 }
6649 }
6650 }
6651 }
6652
6653 async fn open_node_conn(&self, addr: &str) -> Result<BrokerConn> {
6654 let mut conn = BrokerConn::connect_tls(
6655 addr,
6656 &self.cfg.client_id,
6657 self.cfg.connect_timeout,
6658 self.cfg.tls.as_ref(),
6659 )
6660 .await?;
6661 conn.set_stats(Arc::clone(&self.stats));
6662 let versions_resp =
6663 crate::protocol::api::negotiate_api_versions(&mut conn, self.cfg.request_timeout)
6664 .await?;
6665 sasl::apply_api_keys(&mut conn, &versions_resp.api_keys);
6666 sasl::authenticate(
6667 &mut conn,
6668 self.cfg.sasl_plain.as_ref(),
6669 self.cfg.sasl_scram.as_ref(),
6670 self.cfg.sasl_scram_sha512.as_ref(),
6671 self.cfg.sasl_oauthbearer.as_deref(),
6672 self.cfg.sasl_oauthbearer_oidc.as_ref(),
6673 self.cfg.request_timeout,
6674 )
6675 .await?;
6676 Ok(conn)
6677 }
6678
6679 pub async fn delete_records(
6699 &mut self,
6700 partition: impl Into<crate::TopicPartition>,
6701 offset: impl Into<i64>,
6702 timeout_ms: i32,
6703 ) -> Result<DeletedRecords> {
6704 let timeout = self.cfg.request_timeout;
6705 self.delete_records_one(partition.into(), offset.into(), timeout_ms, timeout)
6706 .await
6707 .map(DeletedRecords::from)
6708 }
6709
6710 pub async fn delete_records_timeout(
6715 &mut self,
6716 partition: impl Into<crate::TopicPartition>,
6717 offset: impl Into<i64>,
6718 timeout: Duration,
6719 ) -> Result<DeletedRecords> {
6720 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
6721 self.delete_records_one(partition.into(), offset.into(), timeout_ms, timeout)
6722 .await
6723 .map(DeletedRecords::from)
6724 }
6725
6726 pub async fn delete_records_for<Tp, Off>(
6735 &mut self,
6736 records: impl IntoIterator<Item = (Tp, Off)>,
6737 ) -> Result<Vec<(crate::TopicPartition, DeletedRecords)>>
6738 where
6739 Tp: Into<crate::TopicPartition>,
6740 Off: Into<i64>,
6741 {
6742 let timeout = self.cfg.request_timeout;
6743 self.delete_records_for_timeout(records, timeout).await
6744 }
6745
6746 pub async fn delete_records_for_timeout<Tp, Off>(
6751 &mut self,
6752 records: impl IntoIterator<Item = (Tp, Off)>,
6753 timeout: Duration,
6754 ) -> Result<Vec<(crate::TopicPartition, DeletedRecords)>>
6755 where
6756 Tp: Into<crate::TopicPartition>,
6757 Off: Into<i64>,
6758 {
6759 let timeout_ms = crate::consumer::duration_millis_i32(timeout);
6760 let raw = self
6761 .delete_records_for_with(records, timeout_ms, timeout)
6762 .await?;
6763 Ok(raw
6764 .into_iter()
6765 .map(|(tp, low, err)| (tp, DeletedRecords::with_error_code(low, err)))
6766 .collect())
6767 }
6768
6769 async fn delete_records_one(
6770 &mut self,
6771 tp: crate::TopicPartition,
6772 offset: i64,
6773 timeout_ms: i32,
6774 timeout: Duration,
6775 ) -> Result<(i64, i16)> {
6776 let mut out = self
6777 .delete_records_for_with([(tp, offset)], timeout_ms, timeout)
6778 .await?;
6779 match out.pop() {
6780 Some((_, low, err)) => Ok((low, err)),
6781 None => Err(Error::protocol("missing DeleteRecords result")),
6782 }
6783 }
6784
6785 async fn delete_records_for_with<Tp, Off>(
6786 &mut self,
6787 records: impl IntoIterator<Item = (Tp, Off)>,
6788 timeout_ms: i32,
6789 timeout: Duration,
6790 ) -> Result<Vec<(crate::TopicPartition, i64, i16)>>
6791 where
6792 Tp: Into<crate::TopicPartition>,
6793 Off: Into<i64>,
6794 {
6795 let records: Vec<(crate::TopicPartition, i64)> = records
6796 .into_iter()
6797 .map(|(tp, off)| (tp.into(), off.into()))
6798 .collect();
6799 if records.is_empty() {
6800 return Ok(Vec::new());
6801 }
6802 let version = self.delete_records_version;
6803 let deadline = Instant::now() + timeout;
6804 let mut attempt = 0u32;
6805 let mut out: Vec<Option<(i64, i16)>> = vec![None; records.len()];
6806 let mut pending: Vec<usize> = (0..records.len()).collect();
6807 loop {
6808 if pending.is_empty() {
6809 break;
6810 }
6811 let mut need: Vec<String> = Vec::new();
6812 for &i in &pending {
6813 let Some((tp, _)) = records.get(i) else {
6814 continue;
6815 };
6816 if self.cluster.leader(&tp.topic, tp.partition).is_err()
6817 && !need.iter().any(|t| t == &tp.topic)
6818 {
6819 need.push(tp.topic.clone());
6820 }
6821 }
6822 if !need.is_empty() {
6823 self.refresh_metadata(Some(&need)).await?;
6824 }
6825 let mut by_node: HashMap<i32, Vec<usize>> = HashMap::new();
6826 let mut nodes: Vec<i32> = Vec::new();
6827 for &i in &pending {
6828 let (tp, _) = records
6829 .get(i)
6830 .ok_or_else(|| Error::protocol("missing DeleteRecords query"))?;
6831 let (node, _) = self.cluster.leader(&tp.topic, tp.partition)?;
6832 match by_node.entry(node) {
6833 std::collections::hash_map::Entry::Vacant(slot) => {
6834 nodes.push(node);
6835 let _ = slot.insert(vec![i]);
6836 }
6837 std::collections::hash_map::Entry::Occupied(mut slot) => {
6838 slot.get_mut().push(i);
6839 }
6840 }
6841 }
6842 let mut still = Vec::new();
6843 for node in nodes {
6844 let idxs = by_node.remove(&node).unwrap_or_default();
6845 match self
6846 .delete_records_on_node(node, version, timeout_ms, &records, &idxs, timeout)
6847 .await
6848 {
6849 Ok((done, retry)) => {
6850 for (i, low, err) in done {
6851 if let Some(slot) = out.get_mut(i) {
6852 *slot = Some((low, err));
6853 }
6854 }
6855 still.extend(retry);
6856 }
6857 Err(e) if e.is_retriable() => {
6858 let _ = self.conns.remove(&node);
6859 still.extend(idxs);
6860 }
6861 Err(e) => return Err(e),
6862 }
6863 }
6864 pending = still;
6865 if pending.is_empty() {
6866 break;
6867 }
6868 self.wait_retry(&mut attempt, deadline).await?;
6869 for &i in &pending {
6870 if let Some((tp, _)) = records.get(i) {
6871 self.cluster.invalidate_topic(&tp.topic);
6872 }
6873 }
6874 let topics: Vec<String> = {
6875 let mut t = Vec::new();
6876 for &i in &pending {
6877 if let Some((tp, _)) = records.get(i) {
6878 if !t.iter().any(|n| n == &tp.topic) {
6879 t.push(tp.topic.clone());
6880 }
6881 }
6882 }
6883 t
6884 };
6885 if !topics.is_empty() {
6886 self.refresh_metadata(Some(&topics)).await?;
6887 }
6888 }
6889 out.into_iter()
6890 .zip(records)
6891 .map(|(got, (tp, _))| {
6892 got.map(|(low, err)| (tp, low, err))
6893 .ok_or_else(|| Error::protocol("DeleteRecords missing result"))
6894 })
6895 .collect()
6896 }
6897
6898 async fn delete_records_on_node(
6899 &mut self,
6900 node: i32,
6901 version: i16,
6902 timeout_ms: i32,
6903 records: &[(crate::TopicPartition, i64)],
6904 idxs: &[usize],
6905 timeout: Duration,
6906 ) -> Result<(Vec<(usize, i64, i16)>, Vec<usize>)> {
6907 let topics = delete_records_topics(records, idxs);
6908 self.connect_node(node).await?;
6909 let body = {
6910 let conn = self
6911 .conns
6912 .get_mut(&node)
6913 .ok_or_else(|| Error::protocol("missing delete_records conn"))?;
6914 conn.roundtrip(
6915 DELETE_RECORDS,
6916 version,
6917 |buf| encode_delete_records_topics_request(buf, version, &topics, timeout_ms),
6918 timeout,
6919 )
6920 .await
6921 }?;
6922 let (resp, ..) = decode_delete_records_topics_response(&mut body.clone(), version)?;
6923 let mut by_key: HashMap<(String, i32), VecDeque<(i64, i16)>> = HashMap::new();
6924 for t in resp {
6925 for p in t.partitions {
6926 by_key
6927 .entry((t.topic.clone(), p.partition))
6928 .or_default()
6929 .push_back((p.low_watermark, p.error_code));
6930 }
6931 }
6932 let mut done = Vec::new();
6933 let mut retry = Vec::new();
6934 for &i in idxs {
6935 let (tp, _) = records
6936 .get(i)
6937 .ok_or_else(|| Error::protocol("missing DeleteRecords query"))?;
6938 let (low, err) = by_key
6939 .get_mut(&(tp.topic.clone(), tp.partition))
6940 .and_then(VecDeque::pop_front)
6941 .ok_or_else(|| {
6942 Error::protocol(format!(
6943 "DeleteRecords missing {}-{}",
6944 tp.topic, tp.partition
6945 ))
6946 })?;
6947 if err == 0 {
6948 done.push((i, low, err));
6949 continue;
6950 }
6951 let e = Error::broker(err, format!("{}-{}", tp.topic, tp.partition));
6952 if e.is_retriable() {
6953 self.cluster.invalidate_topic(&tp.topic);
6954 let _ = self.conns.remove(&node);
6955 retry.push(i);
6956 } else {
6957 done.push((i, low, err));
6958 }
6959 }
6960 Ok((done, retry))
6961 }
6962
6963 pub async fn list_offsets<Tp, Ts>(
6973 &mut self,
6974 queries: impl IntoIterator<Item = (Tp, Ts)>,
6975 ) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndTimestamp)>>
6976 where
6977 Tp: Into<crate::TopicPartition>,
6978 Ts: Into<i64>,
6979 {
6980 let timeout = self.cfg.request_timeout;
6981 self.list_offsets_timeout(queries, timeout).await
6982 }
6983
6984 pub async fn list_offsets_timeout<Tp, Ts>(
6989 &mut self,
6990 queries: impl IntoIterator<Item = (Tp, Ts)>,
6991 timeout: Duration,
6992 ) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndTimestamp)>>
6993 where
6994 Tp: Into<crate::TopicPartition>,
6995 Ts: Into<i64>,
6996 {
6997 self.list_offsets_with_isolation_timeout(
6998 queries,
6999 crate::IsolationLevel::ReadUncommitted,
7000 timeout,
7001 )
7002 .await
7003 }
7004
7005 pub async fn list_offsets_with_isolation<Tp, Ts>(
7026 &mut self,
7027 queries: impl IntoIterator<Item = (Tp, Ts)>,
7028 isolation: crate::IsolationLevel,
7029 ) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndTimestamp)>>
7030 where
7031 Tp: Into<crate::TopicPartition>,
7032 Ts: Into<i64>,
7033 {
7034 let timeout = self.cfg.request_timeout;
7035 self.list_offsets_with_isolation_timeout(queries, isolation, timeout)
7036 .await
7037 }
7038
7039 pub async fn list_offsets_with_isolation_timeout<Tp, Ts>(
7045 &mut self,
7046 queries: impl IntoIterator<Item = (Tp, Ts)>,
7047 isolation: crate::IsolationLevel,
7048 timeout: Duration,
7049 ) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndTimestamp)>>
7050 where
7051 Tp: Into<crate::TopicPartition>,
7052 Ts: Into<i64>,
7053 {
7054 let queries: Vec<(crate::TopicPartition, i64)> = queries
7055 .into_iter()
7056 .map(|(tp, ts)| (tp.into(), ts.into()))
7057 .collect();
7058 if queries.is_empty() {
7059 return Ok(Vec::new());
7060 }
7061 let version = self
7062 .versions
7063 .get(&LIST_OFFSETS)
7064 .and_then(|v| pick_version(v.min_version, v.max_version, 1, 10))
7065 .ok_or_else(|| Error::Unsupported("broker does not support ListOffsets".into()))?;
7066 let deadline = Instant::now() + timeout;
7067 let mut attempt = 0u32;
7068 let isolation = isolation.as_i8();
7069 let mut out: Vec<Option<crate::OffsetAndTimestamp>> = vec![None; queries.len()];
7070 let mut pending: Vec<usize> = (0..queries.len()).collect();
7071 loop {
7072 if pending.is_empty() {
7073 break;
7074 }
7075 let mut need: Vec<String> = Vec::new();
7076 for &i in &pending {
7077 let Some((tp, _)) = queries.get(i) else {
7078 continue;
7079 };
7080 if self.cluster.leader(&tp.topic, tp.partition).is_err()
7081 && !need.iter().any(|t| t == &tp.topic)
7082 {
7083 need.push(tp.topic.clone());
7084 }
7085 }
7086 if !need.is_empty() {
7087 self.refresh_metadata(Some(&need)).await?;
7088 }
7089 let mut by_node: HashMap<i32, Vec<usize>> = HashMap::new();
7090 let mut nodes: Vec<i32> = Vec::new();
7091 for &i in &pending {
7092 let (tp, _) = queries
7093 .get(i)
7094 .ok_or_else(|| Error::protocol("missing ListOffsets query"))?;
7095 let (node, _) = self.cluster.leader(&tp.topic, tp.partition)?;
7096 match by_node.entry(node) {
7097 std::collections::hash_map::Entry::Vacant(slot) => {
7098 nodes.push(node);
7099 let _ = slot.insert(vec![i]);
7100 }
7101 std::collections::hash_map::Entry::Occupied(mut slot) => {
7102 slot.get_mut().push(i);
7103 }
7104 }
7105 }
7106 let mut still = Vec::new();
7107 for node in nodes {
7108 let idxs = by_node.remove(&node).unwrap_or_default();
7109 match self
7110 .list_offsets_on_node(node, version, isolation, &queries, &idxs, timeout)
7111 .await
7112 {
7113 Ok((done, retry)) => {
7114 for (i, ot) in done {
7115 if let Some(slot) = out.get_mut(i) {
7116 *slot = Some(ot);
7117 }
7118 }
7119 still.extend(retry);
7120 }
7121 Err(e) if e.is_retriable() => {
7122 let _ = self.conns.remove(&node);
7123 still.extend(idxs);
7124 }
7125 Err(e) => return Err(e),
7126 }
7127 }
7128 pending = still;
7129 if pending.is_empty() {
7130 break;
7131 }
7132 self.wait_retry(&mut attempt, deadline).await?;
7133 for &i in &pending {
7134 if let Some((tp, _)) = queries.get(i) {
7135 self.cluster.invalidate_topic(&tp.topic);
7136 }
7137 }
7138 let topics: Vec<String> = {
7139 let mut t = Vec::new();
7140 for &i in &pending {
7141 if let Some((tp, _)) = queries.get(i) {
7142 if !t.iter().any(|n| n == &tp.topic) {
7143 t.push(tp.topic.clone());
7144 }
7145 }
7146 }
7147 t
7148 };
7149 if !topics.is_empty() {
7150 self.refresh_metadata(Some(&topics)).await?;
7151 }
7152 }
7153 out.into_iter()
7154 .zip(queries)
7155 .map(|(ot, (tp, _))| {
7156 ot.map(|ot| (tp, ot))
7157 .ok_or_else(|| Error::protocol("ListOffsets missing result"))
7158 })
7159 .collect()
7160 }
7161
7162 async fn list_offsets_on_node(
7163 &mut self,
7164 node: i32,
7165 version: i16,
7166 isolation: i8,
7167 queries: &[(crate::TopicPartition, i64)],
7168 idxs: &[usize],
7169 timeout: Duration,
7170 ) -> Result<(Vec<(usize, crate::OffsetAndTimestamp)>, Vec<usize>)> {
7171 let topics = list_offset_topic_requests(queries, idxs, &self.cluster);
7172 self.connect_node(node).await?;
7173 let body = {
7174 let conn = self
7175 .conns
7176 .get_mut(&node)
7177 .ok_or_else(|| Error::protocol("missing list_offsets conn"))?;
7178 conn.roundtrip(
7179 LIST_OFFSETS,
7180 version,
7181 |buf| {
7182 encode_list_offsets_topics_request(
7183 buf,
7184 version,
7185 isolation,
7186 &topics,
7187 crate::consumer::duration_millis_i32(timeout),
7188 )
7189 },
7190 timeout,
7191 )
7192 .await
7193 }?;
7194 let (resp, ..) = decode_list_offsets_topics_response(&mut body.clone(), version)?;
7195 let mut by_key: HashMap<(String, i32), VecDeque<ListOffsetsResponsePartition>> =
7196 HashMap::new();
7197 for t in resp {
7198 for p in t.partitions {
7199 by_key
7200 .entry((t.name.clone(), p.partition_index))
7201 .or_default()
7202 .push_back(p);
7203 }
7204 }
7205 let mut done = Vec::new();
7206 let mut retry = Vec::new();
7207 for &i in idxs {
7208 let (tp, _) = queries
7209 .get(i)
7210 .ok_or_else(|| Error::protocol("missing ListOffsets query"))?;
7211 let part = by_key
7212 .get_mut(&(tp.topic.clone(), tp.partition))
7213 .and_then(VecDeque::pop_front)
7214 .ok_or_else(|| {
7215 Error::protocol(format!("ListOffsets missing {}-{}", tp.topic, tp.partition))
7216 })?;
7217 if part.error_code == 0 {
7218 done.push((
7219 i,
7220 crate::OffsetAndTimestamp::new(part.offset, part.timestamp)
7221 .with_leader_epoch(part.leader_epoch),
7222 ));
7223 continue;
7224 }
7225 let e = Error::broker(part.error_code, format!("{}-{}", tp.topic, tp.partition));
7226 if matches!(
7227 part.error_code,
7228 error::FENCED_LEADER_EPOCH | error::UNKNOWN_LEADER_EPOCH
7229 ) || e.is_retriable()
7230 {
7231 self.cluster.invalidate_topic(&tp.topic);
7232 let _ = self.conns.remove(&node);
7233 retry.push(i);
7234 } else {
7235 return Err(e);
7236 }
7237 }
7238 Ok((done, retry))
7239 }
7240
7241 pub async fn describe_producers(
7262 &mut self,
7263 partition: impl Into<crate::TopicPartition>,
7264 ) -> Result<DescribeProducersPartition> {
7265 let timeout = self.cfg.request_timeout;
7266 self.describe_producers_timeout(partition, timeout).await
7267 }
7268
7269 pub async fn describe_producers_timeout(
7275 &mut self,
7276 partition: impl Into<crate::TopicPartition>,
7277 timeout: Duration,
7278 ) -> Result<DescribeProducersPartition> {
7279 let topics = self
7280 .describe_producers_for_timeout([partition.into()], timeout)
7281 .await?;
7282 topics
7283 .into_iter()
7284 .next()
7285 .and_then(|t| t.partitions.into_iter().next())
7286 .ok_or_else(|| Error::protocol("empty DescribeProducers response"))
7287 }
7288
7289 pub async fn describe_producers_for(
7300 &mut self,
7301 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7302 ) -> Result<Vec<DescribeProducersTopic>> {
7303 let timeout = self.cfg.request_timeout;
7304 self.describe_producers_for_with(partitions, timeout, None)
7305 .await
7306 }
7307
7308 pub async fn describe_producers_for_timeout(
7314 &mut self,
7315 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7316 timeout: Duration,
7317 ) -> Result<Vec<DescribeProducersTopic>> {
7318 self.describe_producers_for_with(partitions, timeout, None)
7319 .await
7320 }
7321
7322 pub async fn describe_producers_for_on_broker(
7329 &mut self,
7330 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7331 broker_id: i32,
7332 ) -> Result<Vec<DescribeProducersTopic>> {
7333 let timeout = self.cfg.request_timeout;
7334 self.describe_producers_for_with(partitions, timeout, Some(broker_id))
7335 .await
7336 }
7337
7338 pub async fn describe_producers_for_on_broker_timeout(
7344 &mut self,
7345 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7346 broker_id: i32,
7347 timeout: Duration,
7348 ) -> Result<Vec<DescribeProducersTopic>> {
7349 self.describe_producers_for_with(partitions, timeout, Some(broker_id))
7350 .await
7351 }
7352
7353 async fn describe_producers_for_with(
7354 &mut self,
7355 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7356 timeout: Duration,
7357 broker_id: Option<i32>,
7358 ) -> Result<Vec<DescribeProducersTopic>> {
7359 let partitions: Vec<crate::TopicPartition> =
7360 partitions.into_iter().map(Into::into).collect();
7361 if partitions.is_empty() {
7362 return Ok(Vec::new());
7363 }
7364 let version = self.describe_producers_version.ok_or_else(|| {
7365 Error::Unsupported("broker does not support DescribeProducers".into())
7366 })?;
7367 let deadline = Instant::now() + timeout;
7368 let mut attempt = 0u32;
7369 let mut out: Vec<Option<DescribeProducersPartition>> = vec![None; partitions.len()];
7370 let mut pending: Vec<usize> = (0..partitions.len()).collect();
7371 let pin_broker = broker_id.is_some();
7372 loop {
7373 if pending.is_empty() {
7374 break;
7375 }
7376 if !pin_broker {
7377 let mut need: Vec<String> = Vec::new();
7378 for &i in &pending {
7379 let Some(tp) = partitions.get(i) else {
7380 continue;
7381 };
7382 if self.cluster.leader(&tp.topic, tp.partition).is_err()
7383 && !need.iter().any(|t| t == &tp.topic)
7384 {
7385 need.push(tp.topic.clone());
7386 }
7387 }
7388 if !need.is_empty() {
7389 self.refresh_metadata(Some(&need)).await?;
7390 }
7391 }
7392 let mut by_node: HashMap<i32, Vec<usize>> = HashMap::new();
7393 let mut nodes: Vec<i32> = Vec::new();
7394 if let Some(id) = broker_id {
7395 let _prev = by_node.insert(id, pending.clone());
7396 nodes.push(id);
7397 } else {
7398 for &i in &pending {
7399 let tp = partitions
7400 .get(i)
7401 .ok_or_else(|| Error::protocol("missing DescribeProducers query"))?;
7402 let (node, _) = self.cluster.leader(&tp.topic, tp.partition)?;
7403 match by_node.entry(node) {
7404 std::collections::hash_map::Entry::Vacant(slot) => {
7405 nodes.push(node);
7406 let _ = slot.insert(vec![i]);
7407 }
7408 std::collections::hash_map::Entry::Occupied(mut slot) => {
7409 slot.get_mut().push(i);
7410 }
7411 }
7412 }
7413 }
7414 let mut still = Vec::new();
7415 for node in nodes {
7416 let idxs = by_node.remove(&node).unwrap_or_default();
7417 match self
7418 .describe_producers_on_node(
7419 node,
7420 version,
7421 &partitions,
7422 &idxs,
7423 timeout,
7424 pin_broker,
7425 )
7426 .await
7427 {
7428 Ok((done, retry)) => {
7429 for (i, part) in done {
7430 if let Some(slot) = out.get_mut(i) {
7431 *slot = Some(part);
7432 }
7433 }
7434 still.extend(retry);
7435 }
7436 Err(e) if e.is_retriable() => {
7437 let _ = self.conns.remove(&node);
7438 still.extend(idxs);
7439 }
7440 Err(e) => return Err(e),
7441 }
7442 }
7443 pending = still;
7444 if pending.is_empty() {
7445 break;
7446 }
7447 self.wait_retry(&mut attempt, deadline).await?;
7448 if pin_broker {
7449 continue;
7450 }
7451 for &i in &pending {
7452 if let Some(tp) = partitions.get(i) {
7453 self.cluster.invalidate_topic(&tp.topic);
7454 }
7455 }
7456 let topics: Vec<String> = {
7457 let mut t = Vec::new();
7458 for &i in &pending {
7459 if let Some(tp) = partitions.get(i) {
7460 if !t.iter().any(|n| n == &tp.topic) {
7461 t.push(tp.topic.clone());
7462 }
7463 }
7464 }
7465 t
7466 };
7467 if !topics.is_empty() {
7468 self.refresh_metadata(Some(&topics)).await?;
7469 }
7470 }
7471 let mut grouped: Vec<DescribeProducersTopic> = Vec::new();
7472 for (tp, part) in partitions.into_iter().zip(out) {
7473 let part = part.ok_or_else(|| Error::protocol("DescribeProducers missing result"))?;
7474 match grouped.last_mut() {
7475 Some(topic) if topic.name == tp.topic => topic.partitions.push(part),
7476 _ => grouped.push(DescribeProducersTopic::new(tp.topic, vec![part])),
7477 }
7478 }
7479 Ok(grouped)
7480 }
7481
7482 async fn describe_producers_on_node(
7483 &mut self,
7484 node: i32,
7485 version: i16,
7486 partitions: &[crate::TopicPartition],
7487 idxs: &[usize],
7488 timeout: Duration,
7489 pin_broker: bool,
7490 ) -> Result<DescribeProducersNodeOutcome> {
7491 let topics = describe_producers_topics(partitions, idxs);
7492 self.connect_node(node).await?;
7493 let body = {
7494 let conn = self
7495 .conns
7496 .get_mut(&node)
7497 .ok_or_else(|| Error::protocol("missing describe_producers conn"))?;
7498 conn.roundtrip(
7499 DESCRIBE_PRODUCERS,
7500 version,
7501 |buf| encode_describe_producers_topics_request(buf, &topics),
7502 timeout,
7503 )
7504 .await
7505 }?;
7506 let resp = decode_describe_producers_response(&mut body.clone())?;
7507 let mut by_tp: HashMap<(String, i32), DescribeProducersPartition> = HashMap::new();
7508 for topic in resp.topics {
7509 for part in topic.partitions {
7510 let _ = by_tp.insert((topic.name.clone(), part.partition_index), part);
7511 }
7512 }
7513 let mut done = Vec::new();
7514 let mut retry = Vec::new();
7515 for &i in idxs {
7516 let tp = partitions
7517 .get(i)
7518 .ok_or_else(|| Error::protocol("missing DescribeProducers query"))?;
7519 let part = by_tp
7520 .remove(&(tp.topic.clone(), tp.partition))
7521 .ok_or_else(|| {
7522 Error::protocol(format!(
7523 "DescribeProducers missing {}-{}",
7524 tp.topic, tp.partition
7525 ))
7526 })?;
7527 if part.error_code == 0 {
7528 done.push((i, part));
7529 continue;
7530 }
7531 let e = Error::broker(part.error_code, format!("{}-{}", tp.topic, tp.partition));
7532 if !pin_broker && e.is_retriable() {
7533 retry.push(i);
7534 } else {
7535 done.push((i, part));
7536 }
7537 }
7538 Ok((done, retry))
7539 }
7540
7541 pub async fn describe_cluster(&mut self) -> Result<ClusterDescription> {
7553 self.describe_cluster_with(false, ENDPOINT_TYPE_BROKERS, false)
7554 .await
7555 }
7556
7557 pub async fn describe_cluster_timeout(
7562 &mut self,
7563 timeout: Duration,
7564 ) -> Result<ClusterDescription> {
7565 self.describe_cluster_with_timeout(false, ENDPOINT_TYPE_BROKERS, false, timeout)
7566 .await
7567 }
7568
7569 pub async fn describe_cluster_with(
7580 &mut self,
7581 include_authorized_operations: bool,
7582 endpoint_type: impl Into<i8>,
7583 include_fenced_brokers: bool,
7584 ) -> Result<ClusterDescription> {
7585 let timeout = self.cfg.request_timeout;
7586 self.describe_cluster_with_timeout(
7587 include_authorized_operations,
7588 endpoint_type,
7589 include_fenced_brokers,
7590 timeout,
7591 )
7592 .await
7593 }
7594
7595 pub async fn describe_cluster_with_timeout(
7600 &mut self,
7601 include_authorized_operations: bool,
7602 endpoint_type: impl Into<i8>,
7603 include_fenced_brokers: bool,
7604 timeout: Duration,
7605 ) -> Result<ClusterDescription> {
7606 let endpoint_type = endpoint_type.into();
7607 let version = self.describe_cluster_version.ok_or_else(|| {
7608 Error::Unsupported("broker does not support DescribeCluster v0-2".into())
7609 })?;
7610 let body = self
7611 .roundtrip_bootstrap(
7612 DESCRIBE_CLUSTER,
7613 version,
7614 |buf| {
7615 encode_describe_cluster_request(
7616 buf,
7617 version,
7618 include_authorized_operations,
7619 endpoint_type,
7620 include_fenced_brokers,
7621 )
7622 },
7623 timeout,
7624 )
7625 .await?;
7626 decode_describe_cluster_response(&mut body.clone(), version)
7627 }
7628
7629 pub async fn delete_acls(&mut self, resource_type: impl Into<i8>) -> Result<i16> {
7643 let results = self
7644 .delete_acls_with(&[AclBindingFilter::resource_type(resource_type)])
7645 .await?;
7646 Ok(results.first().map(|r| r.error_code).unwrap_or(0))
7647 }
7648
7649 pub async fn delete_acls_timeout(
7652 &mut self,
7653 resource_type: impl Into<i8>,
7654 timeout: Duration,
7655 ) -> Result<i16> {
7656 let results = self
7657 .delete_acls_with_timeout(&[AclBindingFilter::resource_type(resource_type)], timeout)
7658 .await?;
7659 Ok(results.first().map(|r| r.error_code).unwrap_or(0))
7660 }
7661
7662 pub async fn delete_acls_with(
7668 &mut self,
7669 filters: &[AclBindingFilter],
7670 ) -> Result<Vec<DeletedAclsFilterResult>> {
7671 let timeout = self.cfg.request_timeout;
7672 self.delete_acls_with_timeout(filters, timeout).await
7673 }
7674
7675 pub async fn delete_acls_with_timeout(
7680 &mut self,
7681 filters: &[AclBindingFilter],
7682 timeout: Duration,
7683 ) -> Result<Vec<DeletedAclsFilterResult>> {
7684 if filters.is_empty() {
7685 return Ok(Vec::new());
7686 }
7687 let version = self.delete_acls_version;
7688 let body = self
7689 .roundtrip_bootstrap(
7690 DELETE_ACLS,
7691 version,
7692 |buf| encode_delete_acls_request(buf, version, filters),
7693 timeout,
7694 )
7695 .await?;
7696 let (results, ..) = decode_delete_acls_filter_results(&mut body.clone(), version)?;
7697 Ok(results)
7698 }
7699
7700 pub async fn delete_offsets(
7714 &mut self,
7715 group_id: &str,
7716 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7717 ) -> Result<Vec<OffsetDeleteResult>> {
7718 let timeout = self.cfg.request_timeout;
7719 self.delete_offsets_timeout(group_id, partitions, timeout)
7720 .await
7721 }
7722
7723 pub async fn delete_offsets_timeout(
7729 &mut self,
7730 group_id: &str,
7731 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7732 timeout: Duration,
7733 ) -> Result<Vec<OffsetDeleteResult>> {
7734 let partitions: Vec<(String, i32)> = partitions
7735 .into_iter()
7736 .map(|p| {
7737 let tp = p.into();
7738 (tp.topic, tp.partition)
7739 })
7740 .collect();
7741 let topics = offset_delete_topics(&partitions);
7742 let version = self
7743 .offset_delete_version
7744 .ok_or_else(|| Error::Unsupported("broker does not support OffsetDelete".into()))?;
7745 let deadline = Instant::now() + timeout;
7746 let mut attempt = 0u32;
7747 let group_id = group_id.to_string();
7748 loop {
7749 let stale = self
7750 .group_coord
7751 .as_ref()
7752 .is_none_or(|(g, _)| g != &group_id);
7753 if stale {
7754 let node = self.discover_group_coord(&group_id).await?;
7755 self.group_coord = Some((group_id.clone(), node));
7756 }
7757 let node = self
7758 .group_coord
7759 .as_ref()
7760 .map(|(_, n)| *n)
7761 .ok_or_else(|| Error::protocol("missing group coordinator"))?;
7762 self.connect_node(node).await?;
7763 let body = {
7764 let conn = self
7765 .conns
7766 .get_mut(&node)
7767 .ok_or_else(|| Error::protocol("missing delete_offsets conn"))?;
7768 conn.roundtrip(
7769 OFFSET_DELETE,
7770 version,
7771 |buf| encode_offset_delete_request(buf, &group_id, &topics),
7772 timeout,
7773 )
7774 .await
7775 };
7776 let body = match body {
7777 Ok(b) => b,
7778 Err(e) if e.is_retriable() => {
7779 let _ = self.conns.remove(&node);
7780 self.group_coord = None;
7781 self.wait_retry(&mut attempt, deadline).await?;
7782 continue;
7783 }
7784 Err(e) => return Err(e),
7785 };
7786 let (top, results, ..) = decode_offset_delete_response(&mut body.clone())?;
7787 if error::coordinator_retriable(top) {
7788 self.group_coord = None;
7790 let _ = self.conns.remove(&node);
7791 self.wait_retry(&mut attempt, deadline).await?;
7792 continue;
7793 }
7794 if top != 0 {
7795 return Err(Error::broker(top, "OffsetDelete"));
7796 }
7797 return Ok(results);
7798 }
7799 }
7800
7801 pub async fn delete_consumer_group_offsets(
7808 &mut self,
7809 group_id: &str,
7810 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7811 ) -> Result<Vec<OffsetDeleteResult>> {
7812 let timeout = self.cfg.request_timeout;
7813 self.delete_consumer_group_offsets_timeout(group_id, partitions, timeout)
7814 .await
7815 }
7816
7817 pub async fn delete_consumer_group_offsets_timeout(
7823 &mut self,
7824 group_id: &str,
7825 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7826 timeout: Duration,
7827 ) -> Result<Vec<OffsetDeleteResult>> {
7828 self.delete_offsets_timeout(group_id, partitions, timeout)
7829 .await
7830 }
7831
7832 pub async fn list_consumer_group_offsets(
7842 &mut self,
7843 group_id: &str,
7844 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7845 ) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>> {
7846 let timeout = self.cfg.request_timeout;
7847 self.list_consumer_group_offsets_timeout(group_id, partitions, timeout)
7848 .await
7849 }
7850
7851 pub async fn list_consumer_group_offsets_timeout(
7854 &mut self,
7855 group_id: &str,
7856 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7857 timeout: Duration,
7858 ) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>> {
7859 self.list_consumer_group_offsets_with(group_id, partitions, false, timeout)
7860 .await
7861 }
7862
7863 pub async fn list_consumer_group_offsets_with(
7869 &mut self,
7870 group_id: &str,
7871 partitions: impl IntoIterator<Item = impl Into<crate::TopicPartition>>,
7872 require_stable: bool,
7873 timeout: Duration,
7874 ) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>> {
7875 let partitions: Vec<crate::TopicPartition> =
7876 partitions.into_iter().map(Into::into).collect();
7877 if partitions.is_empty() {
7878 return Ok(Vec::new());
7879 }
7880 let wanted: Vec<(String, i32)> = partitions
7881 .iter()
7882 .map(|tp| (tp.topic.clone(), tp.partition))
7883 .collect();
7884 let topics = crate::group::group_offset_fetch_topics(&wanted);
7885 let fetched = self
7886 .fetch_consumer_group_offsets(group_id, Some(topics), require_stable, timeout)
7887 .await?;
7888 let map = crate::group::committed_offset_map(&fetched)?;
7889 Ok(partitions
7890 .iter()
7891 .map(|tp| {
7892 let md = map
7893 .get(&(tp.topic.clone(), tp.partition))
7894 .cloned()
7895 .unwrap_or_else(|| crate::OffsetAndMetadata::new(-1));
7896 (tp.clone(), md)
7897 })
7898 .collect())
7899 }
7900
7901 pub async fn list_all_consumer_group_offsets(
7910 &mut self,
7911 group_id: &str,
7912 ) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>> {
7913 let timeout = self.cfg.request_timeout;
7914 self.list_all_consumer_group_offsets_timeout(group_id, timeout)
7915 .await
7916 }
7917
7918 pub async fn list_all_consumer_group_offsets_timeout(
7925 &mut self,
7926 group_id: &str,
7927 timeout: Duration,
7928 ) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>> {
7929 self.list_all_consumer_group_offsets_with(group_id, false, timeout)
7930 .await
7931 }
7932
7933 pub async fn list_all_consumer_group_offsets_with(
7936 &mut self,
7937 group_id: &str,
7938 require_stable: bool,
7939 timeout: Duration,
7940 ) -> Result<Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>> {
7941 let fetched = self
7942 .fetch_consumer_group_offsets(group_id, None, require_stable, timeout)
7943 .await?;
7944 let map = crate::group::committed_offset_map(&fetched)?;
7945 Ok(map
7946 .into_iter()
7947 .map(|((topic, partition), md)| (crate::TopicPartition::new(topic, partition), md))
7948 .collect())
7949 }
7950
7951 pub async fn list_consumer_group_offsets_for_groups(
7967 &mut self,
7968 groups: impl IntoIterator<Item = (impl Into<String>, ListConsumerGroupOffsetsSpec)>,
7969 ) -> Result<
7970 Vec<(
7971 String,
7972 Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>,
7973 )>,
7974 > {
7975 let timeout = self.cfg.request_timeout;
7976 self.list_consumer_group_offsets_for_groups_timeout(groups, timeout)
7977 .await
7978 }
7979
7980 pub async fn list_consumer_group_offsets_for_groups_timeout(
7987 &mut self,
7988 groups: impl IntoIterator<Item = (impl Into<String>, ListConsumerGroupOffsetsSpec)>,
7989 timeout: Duration,
7990 ) -> Result<
7991 Vec<(
7992 String,
7993 Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>,
7994 )>,
7995 > {
7996 self.list_consumer_group_offsets_for_groups_with(groups, false, timeout)
7997 .await
7998 }
7999
8000 pub async fn list_consumer_group_offsets_for_groups_with(
8006 &mut self,
8007 groups: impl IntoIterator<Item = (impl Into<String>, ListConsumerGroupOffsetsSpec)>,
8008 require_stable: bool,
8009 timeout: Duration,
8010 ) -> Result<
8011 Vec<(
8012 String,
8013 Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>,
8014 )>,
8015 > {
8016 let jobs: Vec<(String, ListConsumerGroupOffsetsSpec)> = groups
8017 .into_iter()
8018 .map(|(g, spec)| (g.into(), spec))
8019 .collect();
8020 if jobs.is_empty() {
8021 return Ok(Vec::new());
8022 }
8023 let mut out: Vec<(
8024 String,
8025 Vec<(crate::TopicPartition, crate::OffsetAndMetadata)>,
8026 )> = jobs.iter().map(|(g, _)| (g.clone(), Vec::new())).collect();
8027 let mut remaining: Vec<usize> = Vec::new();
8028 for (i, (_, spec)) in jobs.iter().enumerate() {
8029 if spec.partitions.as_ref().is_some_and(Vec::is_empty) {
8030 continue;
8031 }
8032 remaining.push(i);
8033 }
8034 if remaining.is_empty() {
8035 return Ok(out);
8036 }
8037 if let Some(version) = self
8038 .versions
8039 .get(&OFFSET_FETCH)
8040 .and_then(|v| pick_version(v.min_version, v.max_version, 8, 9))
8041 {
8042 let deadline = Instant::now() + timeout;
8043 let mut attempt = 0u32;
8044 loop {
8045 let group_ids: Vec<String> = remaining
8046 .iter()
8047 .filter_map(|&i| jobs.get(i).map(|j| j.0.clone()))
8048 .collect();
8049 let coords = self.discover_group_coords(&group_ids).await?;
8050 let mut by_node: HashMap<i32, Vec<usize>> = HashMap::new();
8051 for &i in &remaining {
8052 let group_id = jobs
8053 .get(i)
8054 .ok_or_else(|| Error::protocol("missing group spec"))?
8055 .0
8056 .clone();
8057 let node = *coords.get(&group_id).ok_or_else(|| {
8058 Error::protocol(format!("missing coordinator for {group_id}"))
8059 })?;
8060 by_node.entry(node).or_default().push(i);
8061 }
8062 let mut nodes: Vec<i32> = by_node.keys().copied().collect();
8063 nodes.sort_unstable();
8064 let mut next_remaining = Vec::new();
8065 let mut retry = false;
8066 for node in nodes {
8067 let idxs = by_node.get(&node).cloned().unwrap_or_default();
8068 let mut groups = Vec::new();
8069 for &i in &idxs {
8070 let job = jobs
8071 .get(i)
8072 .ok_or_else(|| Error::protocol("missing group spec"))?;
8073 groups.push(OffsetFetchGroup::new(
8074 job.0.clone(),
8075 offset_fetch_topics_for_spec(&job.1),
8076 ));
8077 }
8078 self.connect_node(node).await?;
8079 let body = {
8080 let conn = self.conns.get_mut(&node).ok_or_else(|| {
8081 Error::protocol("missing list_consumer_group_offsets conn")
8082 })?;
8083 conn.roundtrip(
8084 OFFSET_FETCH,
8085 version,
8086 |buf| {
8087 encode_offset_fetch_groups_request(
8088 buf,
8089 version,
8090 &groups,
8091 require_stable,
8092 )
8093 },
8094 timeout,
8095 )
8096 .await
8097 };
8098 let body = match body {
8099 Ok(b) => b,
8100 Err(e) if e.is_retriable() => {
8101 let _ = self.conns.remove(&node);
8102 self.group_coord = None;
8103 next_remaining.extend(idxs);
8104 retry = true;
8105 continue;
8106 }
8107 Err(e) => return Err(e),
8108 };
8109 let results =
8110 match decode_offset_fetch_groups_response(&mut body.clone(), version) {
8111 Ok((r, ..)) => r,
8112 Err(e) if e.broker_code().is_some_and(error::coordinator_retriable) => {
8113 self.group_coord = None;
8114 let _ = self.conns.remove(&node);
8115 next_remaining.extend(idxs);
8116 retry = true;
8117 continue;
8118 }
8119 Err(e) => return Err(e),
8120 };
8121 if results
8122 .iter()
8123 .any(|r| error::coordinator_retriable(r.error_code))
8124 {
8125 self.group_coord = None;
8126 let _ = self.conns.remove(&node);
8127 next_remaining.extend(idxs);
8128 retry = true;
8129 continue;
8130 }
8131 let mut by_id: HashMap<String, crate::protocol::group::OffsetFetchGroupResult> =
8132 HashMap::new();
8133 for g in results {
8134 if g.error_code != 0 {
8135 return Err(Error::broker(g.error_code, g.group_id));
8136 }
8137 let _ = by_id.insert(g.group_id.clone(), g);
8138 }
8139 for i in idxs {
8140 let job = jobs
8141 .get(i)
8142 .ok_or_else(|| Error::protocol("missing group spec"))?;
8143 let Some(got) = by_id.remove(&job.0) else {
8144 return Err(Error::protocol(format!(
8145 "OffsetFetch response missing group {}",
8146 job.0
8147 )));
8148 };
8149 let listed = listed_group_offsets(&job.1, &got.topics)?;
8150 let slot = out
8151 .get_mut(i)
8152 .ok_or_else(|| Error::protocol("missing group result slot"))?;
8153 slot.1 = listed;
8154 }
8155 }
8156 if !retry {
8157 break;
8158 }
8159 remaining = next_remaining;
8160 self.wait_retry(&mut attempt, deadline).await?;
8161 }
8162 return Ok(out);
8163 }
8164 for i in remaining {
8165 let (group_id, topics, spec) = {
8166 let job = jobs
8167 .get(i)
8168 .ok_or_else(|| Error::protocol("missing group spec"))?;
8169 (
8170 job.0.clone(),
8171 offset_fetch_topics_for_spec(&job.1),
8172 job.1.clone(),
8173 )
8174 };
8175 let fetched = self
8176 .fetch_consumer_group_offsets(&group_id, topics, require_stable, timeout)
8177 .await?;
8178 let listed = listed_group_offsets(&spec, &fetched)?;
8179 let slot = out
8180 .get_mut(i)
8181 .ok_or_else(|| Error::protocol("missing group result slot"))?;
8182 slot.1 = listed;
8183 }
8184 Ok(out)
8185 }
8186
8187 async fn fetch_consumer_group_offsets(
8188 &mut self,
8189 group_id: &str,
8190 topics: Option<Vec<crate::protocol::group::OffsetFetchTopic>>,
8191 require_stable: bool,
8192 timeout: Duration,
8193 ) -> Result<Vec<crate::protocol::group::FetchedOffsetTopic>> {
8194 let client_min = if topics.is_none() { 2 } else { 1 };
8195 let version = self
8196 .versions
8197 .get(&OFFSET_FETCH)
8198 .and_then(|v| pick_version(v.min_version, v.max_version, client_min, 9))
8199 .ok_or_else(|| {
8200 Error::Unsupported(if topics.is_none() {
8201 "broker does not support OffsetFetch v2-9 (null Topics)".into()
8202 } else {
8203 "broker does not support OffsetFetch v1-9".into()
8204 })
8205 })?;
8206 let deadline = Instant::now() + timeout;
8207 let mut attempt = 0u32;
8208 let group_id = group_id.to_string();
8209 loop {
8210 let stale = self
8211 .group_coord
8212 .as_ref()
8213 .is_none_or(|(g, _)| g != &group_id);
8214 if stale {
8215 let node = self.discover_group_coord(&group_id).await?;
8216 self.group_coord = Some((group_id.clone(), node));
8217 }
8218 let node = self
8219 .group_coord
8220 .as_ref()
8221 .map(|(_, n)| *n)
8222 .ok_or_else(|| Error::protocol("missing group coordinator"))?;
8223 self.connect_node(node).await?;
8224 let body = {
8225 let conn = self
8226 .conns
8227 .get_mut(&node)
8228 .ok_or_else(|| Error::protocol("missing list_consumer_group_offsets conn"))?;
8229 conn.roundtrip(
8230 OFFSET_FETCH,
8231 version,
8232 |buf| {
8233 encode_offset_fetch_request(
8234 buf,
8235 version,
8236 &group_id,
8237 None,
8238 -1,
8239 require_stable,
8240 topics.as_deref(),
8241 )
8242 },
8243 timeout,
8244 )
8245 .await
8246 };
8247 let body = match body {
8248 Ok(b) => b,
8249 Err(e) if e.is_retriable() => {
8250 let _ = self.conns.remove(&node);
8251 self.group_coord = None;
8252 self.wait_retry(&mut attempt, deadline).await?;
8253 continue;
8254 }
8255 Err(e) => return Err(e),
8256 };
8257 match decode_offset_fetch_response(&mut body.clone(), version) {
8258 Ok(t) => return Ok(t),
8259 Err(e) if e.broker_code().is_some_and(error::coordinator_retriable) => {
8260 self.group_coord = None;
8261 let _ = self.conns.remove(&node);
8262 self.wait_retry(&mut attempt, deadline).await?;
8263 }
8264 Err(e) => return Err(e),
8265 }
8266 }
8267 }
8268
8269 pub async fn alter_consumer_group_offsets(
8278 &mut self,
8279 group_id: &str,
8280 offsets: impl IntoIterator<Item = (impl Into<crate::TopicPartition>, crate::OffsetAndMetadata)>,
8281 ) -> Result<()> {
8282 let timeout = self.cfg.request_timeout;
8283 self.alter_consumer_group_offsets_timeout(group_id, offsets, timeout)
8284 .await
8285 }
8286
8287 pub async fn alter_consumer_group_offsets_timeout(
8293 &mut self,
8294 group_id: &str,
8295 offsets: impl IntoIterator<Item = (impl Into<crate::TopicPartition>, crate::OffsetAndMetadata)>,
8296 timeout: Duration,
8297 ) -> Result<()> {
8298 let offsets: Vec<(crate::TopicPartition, crate::OffsetAndMetadata)> = offsets
8299 .into_iter()
8300 .map(|(tp, md)| (tp.into(), md))
8301 .collect();
8302 if offsets.is_empty() {
8303 return Ok(());
8304 }
8305 let topics = crate::group::group_offset_topics(&offsets);
8306 let version = self
8307 .versions
8308 .get(&OFFSET_COMMIT)
8309 .and_then(|v| pick_version(v.min_version, v.max_version, 2, 9))
8310 .ok_or_else(|| {
8311 Error::Unsupported("broker does not support OffsetCommit v2-9".into())
8312 })?;
8313 let deadline = Instant::now() + timeout;
8314 let mut attempt = 0u32;
8315 let group_id = group_id.to_string();
8316 loop {
8317 let stale = self
8318 .group_coord
8319 .as_ref()
8320 .is_none_or(|(g, _)| g != &group_id);
8321 if stale {
8322 let node = self.discover_group_coord(&group_id).await?;
8323 self.group_coord = Some((group_id.clone(), node));
8324 }
8325 let node = self
8326 .group_coord
8327 .as_ref()
8328 .map(|(_, n)| *n)
8329 .ok_or_else(|| Error::protocol("missing group coordinator"))?;
8330 self.connect_node(node).await?;
8331 let body = {
8332 let conn = self
8333 .conns
8334 .get_mut(&node)
8335 .ok_or_else(|| Error::protocol("missing alter_consumer_group_offsets conn"))?;
8336 conn.roundtrip(
8337 OFFSET_COMMIT,
8338 version,
8339 |buf| {
8340 encode_offset_commit_request(
8341 buf,
8342 version,
8343 &group_id,
8344 -1,
8345 "",
8346 None,
8347 DEFAULT_RETENTION_TIME,
8348 &topics,
8349 )
8350 },
8351 timeout,
8352 )
8353 .await
8354 };
8355 let body = match body {
8356 Ok(b) => b,
8357 Err(e) if e.is_retriable() => {
8358 let _ = self.conns.remove(&node);
8359 self.group_coord = None;
8360 self.wait_retry(&mut attempt, deadline).await?;
8361 continue;
8362 }
8363 Err(e) => return Err(e),
8364 };
8365 let err = decode_offset_commit_response(&mut body.clone(), version)?;
8366 if error::coordinator_retriable(err) {
8367 self.group_coord = None;
8368 let _ = self.conns.remove(&node);
8369 self.wait_retry(&mut attempt, deadline).await?;
8370 continue;
8371 }
8372 if err != 0 {
8373 return Err(Error::broker(err, "OffsetCommit"));
8374 }
8375 return Ok(());
8376 }
8377 }
8378
8379 pub async fn consumer_group_describe(
8402 &mut self,
8403 group_ids: &[&str],
8404 include_authorized_operations: bool,
8405 ) -> Result<Vec<DescribedConsumerGroup>> {
8406 let timeout = self.cfg.request_timeout;
8407 self.consumer_group_describe_timeout(group_ids, include_authorized_operations, timeout)
8408 .await
8409 }
8410
8411 pub async fn consumer_group_describe_timeout(
8419 &mut self,
8420 group_ids: &[&str],
8421 include_authorized_operations: bool,
8422 timeout: Duration,
8423 ) -> Result<Vec<DescribedConsumerGroup>> {
8424 let ids: Vec<String> = group_ids.iter().map(|s| (*s).to_string()).collect();
8425 if ids.is_empty() {
8426 return Ok(Vec::new());
8427 }
8428 let version = self.consumer_group_describe_version.ok_or_else(|| {
8429 Error::Unsupported("broker does not support ConsumerGroupDescribe v0-1".into())
8430 })?;
8431 let deadline = Instant::now() + timeout;
8432 let mut attempt = 0u32;
8433 let mut out: Vec<Option<DescribedConsumerGroup>> = vec![None; ids.len()];
8434 let mut pending: Vec<usize> = (0..ids.len()).collect();
8435 loop {
8436 let by_node = self.group_coord_nodes(&ids, &pending).await?;
8437 let mut nodes: Vec<i32> = by_node.keys().copied().collect();
8438 nodes.sort_unstable();
8439 let mut still = Vec::new();
8440 for node in nodes {
8441 let idxs = by_node.get(&node).cloned().unwrap_or_default();
8442 match self
8443 .consumer_group_describe_on_node(
8444 node,
8445 version,
8446 &ids,
8447 &idxs,
8448 include_authorized_operations,
8449 timeout,
8450 )
8451 .await
8452 {
8453 Ok(done) => {
8454 for (i, g) in done {
8455 if error::coordinator_retriable(g.error_code) {
8456 self.invalidate_group_coord_idxs(&ids, &[i], node);
8457 still.push(i);
8458 } else if let Some(slot) = out.get_mut(i) {
8459 *slot = Some(g);
8460 }
8461 }
8462 }
8463 Err(e) if e.is_retriable() => {
8464 self.invalidate_group_coord_idxs(&ids, &idxs, node);
8465 still.extend(idxs);
8466 }
8467 Err(e) => return Err(e),
8468 }
8469 }
8470 pending = still;
8471 if pending.is_empty() {
8472 break;
8473 }
8474 self.wait_retry(&mut attempt, deadline).await?;
8475 }
8476 out.into_iter()
8477 .zip(ids)
8478 .map(|(g, id)| {
8479 g.ok_or_else(|| Error::protocol(format!("ConsumerGroupDescribe missing {id}")))
8480 })
8481 .collect()
8482 }
8483
8484 pub async fn describe_groups(
8507 &mut self,
8508 group_ids: &[&str],
8509 include_authorized_operations: bool,
8510 ) -> Result<Vec<DescribedGroup>> {
8511 let timeout = self.cfg.request_timeout;
8512 self.describe_groups_timeout(group_ids, include_authorized_operations, timeout)
8513 .await
8514 }
8515
8516 pub async fn describe_groups_timeout(
8522 &mut self,
8523 group_ids: &[&str],
8524 include_authorized_operations: bool,
8525 timeout: Duration,
8526 ) -> Result<Vec<DescribedGroup>> {
8527 let ids: Vec<String> = group_ids.iter().map(|s| (*s).to_string()).collect();
8528 if ids.is_empty() {
8529 return Ok(Vec::new());
8530 }
8531 let version = self.describe_groups_version;
8532 let deadline = Instant::now() + timeout;
8533 let mut attempt = 0u32;
8534 let mut out: Vec<Option<DescribedGroup>> = vec![None; ids.len()];
8535 let mut pending: Vec<usize> = (0..ids.len()).collect();
8536 loop {
8537 let by_node = self.group_coord_nodes(&ids, &pending).await?;
8538 let mut nodes: Vec<i32> = by_node.keys().copied().collect();
8539 nodes.sort_unstable();
8540 let mut still = Vec::new();
8541 for node in nodes {
8542 let idxs = by_node.get(&node).cloned().unwrap_or_default();
8543 match self
8544 .describe_groups_on_node(
8545 node,
8546 version,
8547 &ids,
8548 &idxs,
8549 include_authorized_operations,
8550 timeout,
8551 )
8552 .await
8553 {
8554 Ok(done) => {
8555 for (i, g) in done {
8556 if error::coordinator_retriable(g.error_code) {
8557 self.invalidate_group_coord_idxs(&ids, &[i], node);
8558 still.push(i);
8559 } else if let Some(slot) = out.get_mut(i) {
8560 *slot = Some(g);
8561 }
8562 }
8563 }
8564 Err(e) if e.is_retriable() => {
8565 self.invalidate_group_coord_idxs(&ids, &idxs, node);
8566 still.extend(idxs);
8567 }
8568 Err(e) => return Err(e),
8569 }
8570 }
8571 pending = still;
8572 if pending.is_empty() {
8573 break;
8574 }
8575 self.wait_retry(&mut attempt, deadline).await?;
8576 }
8577 out.into_iter()
8578 .zip(ids)
8579 .map(|(g, id)| g.ok_or_else(|| Error::protocol(format!("DescribeGroups missing {id}"))))
8580 .collect()
8581 }
8582
8583 pub async fn describe_classic_groups(
8592 &mut self,
8593 group_ids: &[&str],
8594 include_authorized_operations: bool,
8595 ) -> Result<Vec<DescribedGroup>> {
8596 let timeout = self.cfg.request_timeout;
8597 self.describe_classic_groups_timeout(group_ids, include_authorized_operations, timeout)
8598 .await
8599 }
8600
8601 pub async fn describe_classic_groups_timeout(
8607 &mut self,
8608 group_ids: &[&str],
8609 include_authorized_operations: bool,
8610 timeout: Duration,
8611 ) -> Result<Vec<DescribedGroup>> {
8612 self.describe_groups_timeout(group_ids, include_authorized_operations, timeout)
8613 .await
8614 }
8615
8616 pub async fn describe_consumer_groups(
8629 &mut self,
8630 group_ids: &[&str],
8631 include_authorized_operations: bool,
8632 ) -> Result<Vec<ConsumerGroupDescription>> {
8633 let timeout = self.cfg.request_timeout;
8634 self.describe_consumer_groups_timeout(group_ids, include_authorized_operations, timeout)
8635 .await
8636 }
8637
8638 pub async fn describe_consumer_groups_timeout(
8645 &mut self,
8646 group_ids: &[&str],
8647 include_authorized_operations: bool,
8648 timeout: Duration,
8649 ) -> Result<Vec<ConsumerGroupDescription>> {
8650 if group_ids.is_empty() {
8651 return Ok(Vec::new());
8652 }
8653 let deadline = Instant::now() + timeout;
8654 let mut out: Vec<Option<ConsumerGroupDescription>> = vec![None; group_ids.len()];
8655 let mut classic_pending: Vec<usize> = Vec::new();
8656 if self.consumer_group_describe_version.is_some() {
8657 match self
8658 .consumer_group_describe_timeout(group_ids, include_authorized_operations, timeout)
8659 .await
8660 {
8661 Ok(groups) => {
8662 for (i, g) in groups.into_iter().enumerate() {
8663 if error::consumer_group_describe_classic_fallback(g.error_code) {
8664 classic_pending.push(i);
8665 } else if let Some(slot) = out.get_mut(i) {
8666 *slot = Some(ConsumerGroupDescription::Consumer(g));
8667 }
8668 }
8669 }
8670 Err(Error::Unsupported(_)) => {
8671 classic_pending.extend(0..group_ids.len());
8672 }
8673 Err(e) => return Err(e),
8674 }
8675 } else {
8676 classic_pending.extend(0..group_ids.len());
8677 }
8678 if !classic_pending.is_empty() {
8679 let classic_ids: Vec<&str> = classic_pending
8680 .iter()
8681 .filter_map(|&i| group_ids.get(i).copied())
8682 .collect();
8683 let remaining = deadline.saturating_duration_since(Instant::now());
8684 let classic = self
8685 .describe_groups_timeout(&classic_ids, include_authorized_operations, remaining)
8686 .await?;
8687 for (i, g) in classic_pending.iter().copied().zip(classic) {
8688 if let Some(slot) = out.get_mut(i) {
8689 *slot = Some(ConsumerGroupDescription::Classic(g));
8690 }
8691 }
8692 }
8693 out.into_iter()
8694 .enumerate()
8695 .map(|(i, g)| {
8696 g.ok_or_else(|| {
8697 let id = group_ids.get(i).copied().unwrap_or("");
8698 Error::protocol(format!("describeConsumerGroups missing {id}"))
8699 })
8700 })
8701 .collect()
8702 }
8703
8704 pub async fn list_groups(
8721 &mut self,
8722 states_filter: &[&str],
8723 types_filter: &[&str],
8724 ) -> Result<Vec<ListedGroup>> {
8725 let timeout = self.cfg.request_timeout;
8726 self.list_groups_timeout(states_filter, types_filter, timeout)
8727 .await
8728 }
8729
8730 pub async fn list_groups_timeout(
8735 &mut self,
8736 states_filter: &[&str],
8737 types_filter: &[&str],
8738 timeout: Duration,
8739 ) -> Result<Vec<ListedGroup>> {
8740 let states: Vec<String> = states_filter.iter().map(|s| (*s).to_string()).collect();
8741 let types: Vec<String> = types_filter.iter().map(|s| (*s).to_string()).collect();
8742 self.list_groups_owned(states, types, timeout).await
8743 }
8744
8745 async fn list_groups_owned(
8746 &mut self,
8747 states: Vec<String>,
8748 types: Vec<String>,
8749 timeout: Duration,
8750 ) -> Result<Vec<ListedGroup>> {
8751 let version = self.list_groups_version;
8752 let body = self
8753 .roundtrip_bootstrap(
8754 LIST_GROUPS,
8755 version,
8756 |buf| encode_list_groups_request(buf, version, &states, &types),
8757 timeout,
8758 )
8759 .await?;
8760 let (resp, ..) = decode_list_groups_response(&mut body.clone(), version)?;
8761 if resp.error_code != 0 {
8762 return Err(Error::broker(resp.error_code, "ListGroups"));
8763 }
8764 Ok(resp.groups)
8765 }
8766
8767 pub async fn list_groups_with(
8775 &mut self,
8776 states: impl IntoIterator<Item = GroupState>,
8777 types: impl IntoIterator<Item = GroupType>,
8778 ) -> Result<Vec<ListedGroup>> {
8779 let timeout = self.cfg.request_timeout;
8780 self.list_groups_with_timeout(states, types, timeout).await
8781 }
8782
8783 pub async fn list_groups_with_timeout(
8788 &mut self,
8789 states: impl IntoIterator<Item = GroupState>,
8790 types: impl IntoIterator<Item = GroupType>,
8791 timeout: Duration,
8792 ) -> Result<Vec<ListedGroup>> {
8793 let states: Vec<String> = states.into_iter().map(String::from).collect();
8794 let types: Vec<String> = types.into_iter().map(String::from).collect();
8795 self.list_groups_owned(states, types, timeout).await
8796 }
8797
8798 pub async fn list_groups_all(&mut self) -> Result<Vec<ListedGroup>> {
8803 self.list_groups(&[], &[]).await
8804 }
8805
8806 pub async fn list_groups_all_timeout(&mut self, timeout: Duration) -> Result<Vec<ListedGroup>> {
8809 self.list_groups_timeout(&[], &[], timeout).await
8810 }
8811
8812 pub async fn list_consumer_groups(
8820 &mut self,
8821 states_filter: &[&str],
8822 types_filter: &[&str],
8823 ) -> Result<Vec<ListedGroup>> {
8824 self.list_groups(states_filter, types_filter).await
8825 }
8826
8827 pub async fn list_consumer_groups_timeout(
8832 &mut self,
8833 states_filter: &[&str],
8834 types_filter: &[&str],
8835 timeout: Duration,
8836 ) -> Result<Vec<ListedGroup>> {
8837 self.list_groups_timeout(states_filter, types_filter, timeout)
8838 .await
8839 }
8840
8841 pub async fn list_consumer_groups_with(
8846 &mut self,
8847 states: impl IntoIterator<Item = GroupState>,
8848 types: impl IntoIterator<Item = GroupType>,
8849 ) -> Result<Vec<ListedGroup>> {
8850 self.list_groups_with(states, types).await
8851 }
8852
8853 pub async fn list_consumer_groups_with_timeout(
8856 &mut self,
8857 states: impl IntoIterator<Item = GroupState>,
8858 types: impl IntoIterator<Item = GroupType>,
8859 timeout: Duration,
8860 ) -> Result<Vec<ListedGroup>> {
8861 self.list_groups_with_timeout(states, types, timeout).await
8862 }
8863
8864 pub async fn list_consumer_groups_all(&mut self) -> Result<Vec<ListedGroup>> {
8869 self.list_groups_all().await
8870 }
8871
8872 pub async fn list_consumer_groups_all_timeout(
8875 &mut self,
8876 timeout: Duration,
8877 ) -> Result<Vec<ListedGroup>> {
8878 self.list_groups_all_timeout(timeout).await
8879 }
8880
8881 pub async fn delete_groups(&mut self, group_ids: &[&str]) -> Result<Vec<DeletableGroupResult>> {
8906 let timeout = self.cfg.request_timeout;
8907 self.delete_groups_timeout(group_ids, timeout).await
8908 }
8909
8910 pub async fn delete_groups_timeout(
8916 &mut self,
8917 group_ids: &[&str],
8918 timeout: Duration,
8919 ) -> Result<Vec<DeletableGroupResult>> {
8920 self.delete_group_ids(
8921 group_ids.iter().map(|s| (*s).to_string()).collect(),
8922 timeout,
8923 )
8924 .await
8925 }
8926
8927 async fn delete_group_ids(
8928 &mut self,
8929 ids: Vec<String>,
8930 timeout: Duration,
8931 ) -> Result<Vec<DeletableGroupResult>> {
8932 if ids.is_empty() {
8933 return Ok(Vec::new());
8934 }
8935 let version = self.delete_groups_version;
8936 let deadline = Instant::now() + timeout;
8937 let mut attempt = 0u32;
8938 let mut out: Vec<Option<DeletableGroupResult>> = vec![None; ids.len()];
8939 let mut pending: Vec<usize> = (0..ids.len()).collect();
8940 loop {
8941 let by_node = self.group_coord_nodes(&ids, &pending).await?;
8942 let mut nodes: Vec<i32> = by_node.keys().copied().collect();
8943 nodes.sort_unstable();
8944 let mut still = Vec::new();
8945 for node in nodes {
8946 let idxs = by_node.get(&node).cloned().unwrap_or_default();
8947 match self
8948 .delete_groups_on_node(node, version, &ids, &idxs, timeout)
8949 .await
8950 {
8951 Ok(done) => {
8952 for (i, g) in done {
8953 if error::coordinator_retriable(g.error_code) {
8954 self.invalidate_group_coord_idxs(&ids, &[i], node);
8955 still.push(i);
8956 } else if let Some(slot) = out.get_mut(i) {
8957 *slot = Some(g);
8958 }
8959 }
8960 }
8961 Err(e) if e.is_retriable() => {
8962 self.invalidate_group_coord_idxs(&ids, &idxs, node);
8963 still.extend(idxs);
8964 }
8965 Err(e) => return Err(e),
8966 }
8967 }
8968 pending = still;
8969 if pending.is_empty() {
8970 break;
8971 }
8972 self.wait_retry(&mut attempt, deadline).await?;
8973 }
8974 out.into_iter()
8975 .zip(ids)
8976 .map(|(g, id)| g.ok_or_else(|| Error::protocol(format!("DeleteGroups missing {id}"))))
8977 .collect()
8978 }
8979
8980 pub async fn delete_share_groups(
8989 &mut self,
8990 group_ids: impl IntoIterator<Item = impl AsRef<str>>,
8991 ) -> Result<Vec<DeletableGroupResult>> {
8992 let timeout = self.cfg.request_timeout;
8993 self.delete_share_groups_timeout(group_ids, timeout).await
8994 }
8995
8996 pub async fn delete_share_groups_timeout(
9002 &mut self,
9003 group_ids: impl IntoIterator<Item = impl AsRef<str>>,
9004 timeout: Duration,
9005 ) -> Result<Vec<DeletableGroupResult>> {
9006 let ids: Vec<String> = group_ids
9007 .into_iter()
9008 .map(|s| s.as_ref().to_string())
9009 .collect();
9010 self.delete_group_ids(ids, timeout).await
9011 }
9012
9013 pub async fn delete_consumer_groups(
9022 &mut self,
9023 group_ids: impl IntoIterator<Item = impl AsRef<str>>,
9024 ) -> Result<Vec<DeletableGroupResult>> {
9025 let timeout = self.cfg.request_timeout;
9026 self.delete_consumer_groups_timeout(group_ids, timeout)
9027 .await
9028 }
9029
9030 pub async fn delete_consumer_groups_timeout(
9036 &mut self,
9037 group_ids: impl IntoIterator<Item = impl AsRef<str>>,
9038 timeout: Duration,
9039 ) -> Result<Vec<DeletableGroupResult>> {
9040 let ids: Vec<String> = group_ids
9041 .into_iter()
9042 .map(|s| s.as_ref().to_string())
9043 .collect();
9044 self.delete_group_ids(ids, timeout).await
9045 }
9046
9047 pub async fn remove_members_from_consumer_group(
9063 &mut self,
9064 group_id: &str,
9065 members: impl IntoIterator<Item = impl Into<MemberToRemove>>,
9066 ) -> Result<Vec<RemovedMember>> {
9067 let timeout = self.cfg.request_timeout;
9068 self.remove_members_from_consumer_group_timeout(group_id, members, timeout)
9069 .await
9070 }
9071
9072 pub async fn remove_members_from_consumer_group_timeout(
9078 &mut self,
9079 group_id: &str,
9080 members: impl IntoIterator<Item = impl Into<MemberToRemove>>,
9081 timeout: Duration,
9082 ) -> Result<Vec<RemovedMember>> {
9083 self.remove_members_from_consumer_group_timeout_with_reason(group_id, members, timeout, "")
9084 .await
9085 }
9086
9087 pub async fn remove_members_from_consumer_group_with_reason(
9097 &mut self,
9098 group_id: &str,
9099 members: impl IntoIterator<Item = impl Into<MemberToRemove>>,
9100 reason: impl Into<String>,
9101 ) -> Result<Vec<RemovedMember>> {
9102 let timeout = self.cfg.request_timeout;
9103 self.remove_members_from_consumer_group_timeout_with_reason(
9104 group_id, members, timeout, reason,
9105 )
9106 .await
9107 }
9108
9109 pub async fn remove_members_from_consumer_group_timeout_with_reason(
9116 &mut self,
9117 group_id: &str,
9118 members: impl IntoIterator<Item = impl Into<MemberToRemove>>,
9119 timeout: Duration,
9120 reason: impl Into<String>,
9121 ) -> Result<Vec<RemovedMember>> {
9122 let reason = reason.into();
9123 let members: Vec<LeaveGroupMember> = members
9124 .into_iter()
9125 .map(|m| {
9126 let m = m.into();
9127 LeaveGroupMember {
9128 member_id: String::new(),
9129 group_instance_id: Some(m.group_instance_id),
9130 reason: Some(reason.clone()),
9131 }
9132 })
9133 .collect();
9134 if members.is_empty() {
9135 return Ok(Vec::new());
9136 }
9137 self.leave_group_members(group_id, members, timeout).await
9138 }
9139
9140 pub async fn remove_all_members_from_consumer_group(
9154 &mut self,
9155 group_id: &str,
9156 ) -> Result<Vec<RemovedMember>> {
9157 let timeout = self.cfg.request_timeout;
9158 self.remove_all_members_from_consumer_group_timeout(group_id, timeout)
9159 .await
9160 }
9161
9162 pub async fn remove_all_members_from_consumer_group_timeout(
9169 &mut self,
9170 group_id: &str,
9171 timeout: Duration,
9172 ) -> Result<Vec<RemovedMember>> {
9173 self.remove_all_members_from_consumer_group_timeout_with_reason(group_id, timeout, "")
9174 .await
9175 }
9176
9177 pub async fn remove_all_members_from_consumer_group_with_reason(
9187 &mut self,
9188 group_id: &str,
9189 reason: impl Into<String>,
9190 ) -> Result<Vec<RemovedMember>> {
9191 let timeout = self.cfg.request_timeout;
9192 self.remove_all_members_from_consumer_group_timeout_with_reason(group_id, timeout, reason)
9193 .await
9194 }
9195
9196 pub async fn remove_all_members_from_consumer_group_timeout_with_reason(
9205 &mut self,
9206 group_id: &str,
9207 timeout: Duration,
9208 reason: impl Into<String>,
9209 ) -> Result<Vec<RemovedMember>> {
9210 let described = self
9211 .describe_groups_timeout(&[group_id], false, timeout)
9212 .await?;
9213 let Some(g) = described.first() else {
9214 return Ok(Vec::new());
9215 };
9216 if g.error_code != 0 {
9217 return Err(Error::broker(g.error_code, "DescribeGroups"));
9218 }
9219 let reason = reason.into();
9220 let members: Vec<LeaveGroupMember> = g
9221 .members
9222 .iter()
9223 .map(|m| LeaveGroupMember {
9224 member_id: m.member_id.clone(),
9225 group_instance_id: m.group_instance_id.clone(),
9226 reason: Some(reason.clone()),
9227 })
9228 .collect();
9229 if members.is_empty() {
9230 return Ok(Vec::new());
9231 }
9232 self.leave_group_members(group_id, members, timeout).await
9233 }
9234
9235 async fn leave_group_members(
9236 &mut self,
9237 group_id: &str,
9238 members: Vec<LeaveGroupMember>,
9239 timeout: Duration,
9240 ) -> Result<Vec<RemovedMember>> {
9241 let version = self
9242 .versions
9243 .get(&LEAVE_GROUP)
9244 .and_then(|v| pick_version(v.min_version, v.max_version, 3, 5))
9245 .ok_or_else(|| Error::Unsupported("broker does not support LeaveGroup v3+".into()))?;
9246 let members: Vec<LeaveGroupMember> = members
9247 .into_iter()
9248 .map(|mut m| {
9249 m.reason = Some(admin_leave_reason(m.reason.as_deref()));
9250 m
9251 })
9252 .collect();
9253 let deadline = Instant::now() + timeout;
9254 let mut attempt = 0u32;
9255 let group_id = group_id.to_string();
9256 loop {
9257 let stale = self
9258 .group_coord
9259 .as_ref()
9260 .is_none_or(|(g, _)| g != &group_id);
9261 if stale {
9262 let node = self.discover_group_coord(&group_id).await?;
9263 self.group_coord = Some((group_id.clone(), node));
9264 }
9265 let node = self
9266 .group_coord
9267 .as_ref()
9268 .map(|(_, n)| *n)
9269 .ok_or_else(|| Error::protocol("missing group coordinator"))?;
9270 self.connect_node(node).await?;
9271 let body = {
9272 let conn = self.conns.get_mut(&node).ok_or_else(|| {
9273 Error::protocol("missing remove_members_from_consumer_group conn")
9274 })?;
9275 conn.roundtrip(
9276 LEAVE_GROUP,
9277 version,
9278 |buf| encode_leave_group_request_members(buf, version, &group_id, &members),
9279 timeout,
9280 )
9281 .await
9282 };
9283 let body = match body {
9284 Ok(b) => b,
9285 Err(e) if e.is_retriable() => {
9286 let _ = self.conns.remove(&node);
9287 self.group_coord = None;
9288 self.wait_retry(&mut attempt, deadline).await?;
9289 continue;
9290 }
9291 Err(e) => return Err(e),
9292 };
9293 let (err, results, ..) =
9294 decode_leave_group_response_version(&mut body.clone(), version)?;
9295 if error::coordinator_retriable(err) {
9296 self.group_coord = None;
9297 let _ = self.conns.remove(&node);
9298 self.wait_retry(&mut attempt, deadline).await?;
9299 continue;
9300 }
9301 if err != 0 {
9302 return Err(Error::broker(err, "LeaveGroup"));
9303 }
9304 return Ok(results
9305 .into_iter()
9306 .map(|m| RemovedMember {
9307 member_id: m.member_id,
9308 group_instance_id: m.group_instance_id,
9309 error_code: m.error_code,
9310 })
9311 .collect());
9312 }
9313 }
9314
9315 pub async fn share_group_describe(
9343 &mut self,
9344 group_ids: &[&str],
9345 include_authorized_operations: bool,
9346 ) -> Result<Vec<DescribedShareGroup>> {
9347 let timeout = self.cfg.request_timeout;
9348 self.share_group_describe_timeout(group_ids, include_authorized_operations, timeout)
9349 .await
9350 }
9351
9352 pub async fn share_group_describe_timeout(
9358 &mut self,
9359 group_ids: &[&str],
9360 include_authorized_operations: bool,
9361 timeout: Duration,
9362 ) -> Result<Vec<DescribedShareGroup>> {
9363 let ids: Vec<String> = group_ids.iter().map(|s| (*s).to_string()).collect();
9364 if ids.is_empty() {
9365 return Ok(Vec::new());
9366 }
9367 let version = self.share_group_describe_version.ok_or_else(|| {
9368 Error::Unsupported("broker does not support ShareGroupDescribe v0-1".into())
9369 })?;
9370 let deadline = Instant::now() + timeout;
9371 let mut attempt = 0u32;
9372 let mut out: Vec<Option<DescribedShareGroup>> = vec![None; ids.len()];
9373 let mut pending: Vec<usize> = (0..ids.len()).collect();
9374 loop {
9375 let by_node = self.group_coord_nodes(&ids, &pending).await?;
9376 let mut nodes: Vec<i32> = by_node.keys().copied().collect();
9377 nodes.sort_unstable();
9378 let mut still = Vec::new();
9379 for node in nodes {
9380 let idxs = by_node.get(&node).cloned().unwrap_or_default();
9381 match self
9382 .share_group_describe_on_node(
9383 node,
9384 version,
9385 &ids,
9386 &idxs,
9387 include_authorized_operations,
9388 timeout,
9389 )
9390 .await
9391 {
9392 Ok(done) => {
9393 for (i, g) in done {
9394 if error::coordinator_retriable(g.error_code) {
9395 self.invalidate_group_coord_idxs(&ids, &[i], node);
9396 still.push(i);
9397 } else if let Some(slot) = out.get_mut(i) {
9398 *slot = Some(g);
9399 }
9400 }
9401 }
9402 Err(e) if e.is_retriable() => {
9403 self.invalidate_group_coord_idxs(&ids, &idxs, node);
9404 still.extend(idxs);
9405 }
9406 Err(e) => return Err(e),
9407 }
9408 }
9409 pending = still;
9410 if pending.is_empty() {
9411 break;
9412 }
9413 self.wait_retry(&mut attempt, deadline).await?;
9414 }
9415 out.into_iter()
9416 .zip(ids)
9417 .map(|(g, id)| {
9418 g.ok_or_else(|| Error::protocol(format!("ShareGroupDescribe missing {id}")))
9419 })
9420 .collect()
9421 }
9422
9423 pub async fn describe_share_groups(
9432 &mut self,
9433 group_ids: &[&str],
9434 include_authorized_operations: bool,
9435 ) -> Result<Vec<DescribedShareGroup>> {
9436 let timeout = self.cfg.request_timeout;
9437 self.describe_share_groups_timeout(group_ids, include_authorized_operations, timeout)
9438 .await
9439 }
9440
9441 pub async fn describe_share_groups_timeout(
9447 &mut self,
9448 group_ids: &[&str],
9449 include_authorized_operations: bool,
9450 timeout: Duration,
9451 ) -> Result<Vec<DescribedShareGroup>> {
9452 self.share_group_describe_timeout(group_ids, include_authorized_operations, timeout)
9453 .await
9454 }
9455
9456 pub async fn describe_share_group_offsets(
9483 &mut self,
9484 groups: &[DescribeShareGroupOffsetsGroup],
9485 ) -> Result<Vec<DescribedShareGroupOffsets>> {
9486 let timeout = self.cfg.request_timeout;
9487 self.describe_share_group_offsets_timeout(groups, timeout)
9488 .await
9489 }
9490
9491 pub async fn describe_share_group_offsets_timeout(
9497 &mut self,
9498 groups: &[DescribeShareGroupOffsetsGroup],
9499 timeout: Duration,
9500 ) -> Result<Vec<DescribedShareGroupOffsets>> {
9501 if groups.is_empty() {
9502 return Ok(Vec::new());
9503 }
9504 let ids: Vec<String> = groups.iter().map(|g| g.group_id.clone()).collect();
9505 let version = self.describe_share_group_offsets_version.ok_or_else(|| {
9506 Error::Unsupported("broker does not support DescribeShareGroupOffsets".into())
9507 })?;
9508 let deadline = Instant::now() + timeout;
9509 let mut attempt = 0u32;
9510 let mut out: Vec<Option<DescribedShareGroupOffsets>> = vec![None; groups.len()];
9511 let mut pending: Vec<usize> = (0..groups.len()).collect();
9512 loop {
9513 let by_node = self.group_coord_nodes(&ids, &pending).await?;
9514 let mut nodes: Vec<i32> = by_node.keys().copied().collect();
9515 nodes.sort_unstable();
9516 let mut still = Vec::new();
9517 for node in nodes {
9518 let idxs = by_node.get(&node).cloned().unwrap_or_default();
9519 match self
9520 .describe_share_group_offsets_on_node(node, version, groups, &idxs, timeout)
9521 .await
9522 {
9523 Ok(done) => {
9524 for (i, g) in done {
9525 if error::coordinator_retriable(g.error_code) {
9526 self.invalidate_group_coord_idxs(&ids, &[i], node);
9527 still.push(i);
9528 } else if let Some(slot) = out.get_mut(i) {
9529 *slot = Some(g);
9530 }
9531 }
9532 }
9533 Err(e) if e.is_retriable() => {
9534 self.invalidate_group_coord_idxs(&ids, &idxs, node);
9535 still.extend(idxs);
9536 }
9537 Err(e) => return Err(e),
9538 }
9539 }
9540 pending = still;
9541 if pending.is_empty() {
9542 break;
9543 }
9544 self.wait_retry(&mut attempt, deadline).await?;
9545 }
9546 out.into_iter()
9547 .zip(ids)
9548 .map(|(g, id)| {
9549 g.ok_or_else(|| Error::protocol(format!("DescribeShareGroupOffsets missing {id}")))
9550 })
9551 .collect()
9552 }
9553
9554 pub async fn list_share_group_offsets(
9563 &mut self,
9564 groups: &[DescribeShareGroupOffsetsGroup],
9565 ) -> Result<Vec<DescribedShareGroupOffsets>> {
9566 let timeout = self.cfg.request_timeout;
9567 self.list_share_group_offsets_timeout(groups, timeout).await
9568 }
9569
9570 pub async fn list_share_group_offsets_timeout(
9576 &mut self,
9577 groups: &[DescribeShareGroupOffsetsGroup],
9578 timeout: Duration,
9579 ) -> Result<Vec<DescribedShareGroupOffsets>> {
9580 self.describe_share_group_offsets_timeout(groups, timeout)
9581 .await
9582 }
9583
9584 pub async fn alter_share_group_offsets(
9606 &mut self,
9607 group_id: &str,
9608 topics: &[AlterShareGroupOffsetsTopic],
9609 ) -> Result<AlteredShareGroupOffsets> {
9610 let timeout = self.cfg.request_timeout;
9611 self.alter_share_group_offsets_timeout(group_id, topics, timeout)
9612 .await
9613 }
9614
9615 pub async fn alter_share_group_offsets_timeout(
9621 &mut self,
9622 group_id: &str,
9623 topics: &[AlterShareGroupOffsetsTopic],
9624 timeout: Duration,
9625 ) -> Result<AlteredShareGroupOffsets> {
9626 let coord_key = group_id.to_string();
9627 let version = self.alter_share_group_offsets_version.ok_or_else(|| {
9628 Error::Unsupported("broker does not support AlterShareGroupOffsets".into())
9629 })?;
9630 let deadline = Instant::now() + timeout;
9631 let mut attempt = 0u32;
9632 loop {
9633 let stale = self
9634 .group_coord
9635 .as_ref()
9636 .is_none_or(|(g, _)| g != &coord_key);
9637 if stale {
9638 let node = self.discover_group_coord(&coord_key).await?;
9639 self.group_coord = Some((coord_key.clone(), node));
9640 }
9641 let node = self
9642 .group_coord
9643 .as_ref()
9644 .map(|(_, n)| *n)
9645 .ok_or_else(|| Error::protocol("missing group coordinator"))?;
9646 self.connect_node(node).await?;
9647 let body = {
9648 let conn = self
9649 .conns
9650 .get_mut(&node)
9651 .ok_or_else(|| Error::protocol("missing alter_share_group_offsets conn"))?;
9652 conn.roundtrip(
9653 ALTER_SHARE_GROUP_OFFSETS,
9654 version,
9655 |buf| encode_alter_share_group_offsets_request(buf, group_id, topics),
9656 timeout,
9657 )
9658 .await
9659 };
9660 let body = match body {
9661 Ok(b) => b,
9662 Err(e) if e.is_retriable() => {
9663 let _ = self.conns.remove(&node);
9664 self.group_coord = None;
9665 self.wait_retry(&mut attempt, deadline).await?;
9666 continue;
9667 }
9668 Err(e) => return Err(e),
9669 };
9670 let result = decode_alter_share_group_offsets_response(&mut body.clone())?;
9671 if error::coordinator_retriable(result.error_code) {
9672 self.group_coord = None;
9674 let _ = self.conns.remove(&node);
9675 self.wait_retry(&mut attempt, deadline).await?;
9676 continue;
9677 }
9678 return Ok(result);
9679 }
9680 }
9681
9682 pub async fn delete_share_group_offsets(
9705 &mut self,
9706 group_id: &str,
9707 topics: &[DeleteShareGroupOffsetsTopic],
9708 ) -> Result<DeletedShareGroupOffsets> {
9709 let timeout = self.cfg.request_timeout;
9710 self.delete_share_group_offsets_timeout(group_id, topics, timeout)
9711 .await
9712 }
9713
9714 pub async fn delete_share_group_offsets_timeout(
9720 &mut self,
9721 group_id: &str,
9722 topics: &[DeleteShareGroupOffsetsTopic],
9723 timeout: Duration,
9724 ) -> Result<DeletedShareGroupOffsets> {
9725 let coord_key = group_id.to_string();
9726 let version = self.delete_share_group_offsets_version.ok_or_else(|| {
9727 Error::Unsupported("broker does not support DeleteShareGroupOffsets".into())
9728 })?;
9729 let deadline = Instant::now() + timeout;
9730 let mut attempt = 0u32;
9731 loop {
9732 let stale = self
9733 .group_coord
9734 .as_ref()
9735 .is_none_or(|(g, _)| g != &coord_key);
9736 if stale {
9737 let node = self.discover_group_coord(&coord_key).await?;
9738 self.group_coord = Some((coord_key.clone(), node));
9739 }
9740 let node = self
9741 .group_coord
9742 .as_ref()
9743 .map(|(_, n)| *n)
9744 .ok_or_else(|| Error::protocol("missing group coordinator"))?;
9745 self.connect_node(node).await?;
9746 let body = {
9747 let conn = self
9748 .conns
9749 .get_mut(&node)
9750 .ok_or_else(|| Error::protocol("missing delete_share_group_offsets conn"))?;
9751 conn.roundtrip(
9752 DELETE_SHARE_GROUP_OFFSETS,
9753 version,
9754 |buf| encode_delete_share_group_offsets_request(buf, group_id, topics),
9755 timeout,
9756 )
9757 .await
9758 };
9759 let body = match body {
9760 Ok(b) => b,
9761 Err(e) if e.is_retriable() => {
9762 let _ = self.conns.remove(&node);
9763 self.group_coord = None;
9764 self.wait_retry(&mut attempt, deadline).await?;
9765 continue;
9766 }
9767 Err(e) => return Err(e),
9768 };
9769 let result = decode_delete_share_group_offsets_response(&mut body.clone())?;
9770 if error::coordinator_retriable(result.error_code) {
9771 self.group_coord = None;
9773 let _ = self.conns.remove(&node);
9774 self.wait_retry(&mut attempt, deadline).await?;
9775 continue;
9776 }
9777 return Ok(result);
9778 }
9779 }
9780
9781 pub async fn describe_topic_partitions(
9802 &mut self,
9803 topics: &[&str],
9804 response_partition_limit: i32,
9805 cursor: Option<&TopicPartitionCursor>,
9806 ) -> Result<DescribeTopicPartitionsResponse> {
9807 let timeout = self.cfg.request_timeout;
9808 self.describe_topic_partitions_timeout(topics, response_partition_limit, cursor, timeout)
9809 .await
9810 }
9811
9812 pub async fn describe_topic_partitions_timeout(
9819 &mut self,
9820 topics: &[&str],
9821 response_partition_limit: i32,
9822 cursor: Option<&TopicPartitionCursor>,
9823 timeout: Duration,
9824 ) -> Result<DescribeTopicPartitionsResponse> {
9825 let names: Vec<String> = topics.iter().map(|s| (*s).to_string()).collect();
9826 self.describe_topic_partitions_once(&names, response_partition_limit, cursor, timeout)
9827 .await
9828 }
9829
9830 async fn describe_topic_partitions_once(
9831 &mut self,
9832 names: &[String],
9833 response_partition_limit: i32,
9834 cursor: Option<&TopicPartitionCursor>,
9835 timeout: Duration,
9836 ) -> Result<DescribeTopicPartitionsResponse> {
9837 let version = self.describe_topic_partitions_version.ok_or_else(|| {
9838 Error::Unsupported("broker does not support DescribeTopicPartitions".into())
9839 })?;
9840 let body = self
9841 .roundtrip_bootstrap(
9842 DESCRIBE_TOPIC_PARTITIONS,
9843 version,
9844 |buf| {
9845 encode_describe_topic_partitions_request(
9846 buf,
9847 names,
9848 response_partition_limit,
9849 cursor,
9850 )
9851 },
9852 timeout,
9853 )
9854 .await?;
9855 decode_describe_topic_partitions_response(&mut body.clone())
9856 }
9857
9858 async fn describe_topics_dtp(
9859 &mut self,
9860 names: &[String],
9861 include_authorized_operations: bool,
9862 response_partition_limit: i32,
9863 timeout: Duration,
9864 ) -> Result<Vec<TopicDescription>> {
9865 let deadline = Instant::now() + timeout;
9866 let mut cursor: Option<TopicPartitionCursor> = None;
9867 let mut out: Vec<TopicDescription> = Vec::new();
9868 loop {
9869 if Instant::now() >= deadline {
9870 return Err(Error::Timeout);
9871 }
9872 let resp = self
9873 .describe_topic_partitions_once(
9874 names,
9875 response_partition_limit,
9876 cursor.as_ref(),
9877 timeout,
9878 )
9879 .await?;
9880 for t in &resp.topics {
9881 let desc = topic_description_from_dtp(t, include_authorized_operations);
9882 if let Some(existing) = out.iter_mut().find(|d| d.name == desc.name) {
9883 existing.partitions.extend(desc.partitions);
9884 } else {
9885 out.push(desc);
9886 }
9887 }
9888 match resp.next_cursor {
9889 Some(next) if cursor.as_ref() != Some(&next) => cursor = Some(next),
9890 _ => break,
9891 }
9892 }
9893 Ok(out)
9894 }
9895
9896 async fn describe_topics_metadata(
9897 &mut self,
9898 names: &[String],
9899 include_authorized_operations: bool,
9900 timeout: Duration,
9901 ) -> Result<Vec<TopicDescription>> {
9902 let owned = MetadataRequestTopic::convert_from_names(names.iter().cloned());
9903 let md = self
9904 .fetch_metadata_request_with(Some(&owned), include_authorized_operations, timeout)
9905 .await?;
9906 Ok(topic_descriptions_for_names(&md, names))
9907 }
9908
9909 pub async fn list_config_resources(
9940 &mut self,
9941 resource_types: impl IntoIterator<Item = impl Into<i8>>,
9942 ) -> Result<Vec<ListedConfigResource>> {
9943 let timeout = self.cfg.request_timeout;
9944 self.list_config_resources_timeout(resource_types, timeout)
9945 .await
9946 }
9947
9948 pub async fn list_config_resources_timeout(
9953 &mut self,
9954 resource_types: impl IntoIterator<Item = impl Into<i8>>,
9955 timeout: Duration,
9956 ) -> Result<Vec<ListedConfigResource>> {
9957 let types: Vec<i8> = resource_types.into_iter().map(Into::into).collect();
9958 let version = self.list_config_resources_version.ok_or_else(|| {
9959 Error::Unsupported("broker does not support ListConfigResources v0-1".into())
9960 })?;
9961 let body = self
9962 .roundtrip_bootstrap(
9963 LIST_CONFIG_RESOURCES,
9964 version,
9965 |buf| encode_list_config_resources_request(buf, version, &types),
9966 timeout,
9967 )
9968 .await?;
9969 let resp = decode_list_config_resources_response(&mut body.clone(), version)?;
9970 if resp.error_code != 0 {
9971 return Err(Error::broker(resp.error_code, "ListConfigResources"));
9972 }
9973 Ok(resp.config_resources)
9974 }
9975
9976 pub async fn list_config_resources_all(&mut self) -> Result<Vec<ListedConfigResource>> {
9982 self.list_config_resources(std::iter::empty::<i8>()).await
9983 }
9984
9985 pub async fn list_config_resources_all_timeout(
9990 &mut self,
9991 timeout: Duration,
9992 ) -> Result<Vec<ListedConfigResource>> {
9993 self.list_config_resources_timeout(std::iter::empty::<i8>(), timeout)
9994 .await
9995 }
9996
9997 pub async fn list_client_metrics_resources(&mut self) -> Result<Vec<ListedConfigResource>> {
10007 self.list_config_resources([ConfigResourceType::ClientMetrics])
10008 .await
10009 }
10010
10011 pub async fn list_client_metrics_resources_timeout(
10016 &mut self,
10017 timeout: Duration,
10018 ) -> Result<Vec<ListedConfigResource>> {
10019 self.list_config_resources_timeout([ConfigResourceType::ClientMetrics], timeout)
10020 .await
10021 }
10022
10023 pub async fn get_telemetry_subscriptions(
10043 &mut self,
10044 client_instance_id: impl Into<[u8; 16]>,
10045 ) -> Result<GetTelemetrySubscriptionsResponse> {
10046 let version = self.get_telemetry_subscriptions_version.ok_or_else(|| {
10047 Error::Unsupported("broker does not support GetTelemetrySubscriptions".into())
10048 })?;
10049 let timeout = self.cfg.request_timeout;
10050 let client_instance_id = client_instance_id.into();
10051 let body = self
10052 .roundtrip_bootstrap(
10053 GET_TELEMETRY_SUBSCRIPTIONS,
10054 version,
10055 |buf| encode_get_telemetry_subscriptions_request(buf, &client_instance_id),
10056 timeout,
10057 )
10058 .await?;
10059 let resp = decode_get_telemetry_subscriptions_response(&mut body.clone())?;
10060 if resp.error_code != 0 {
10061 return Err(Error::broker(resp.error_code, "GetTelemetrySubscriptions"));
10062 }
10063 Ok(resp)
10064 }
10065
10066 pub async fn push_telemetry(
10087 &mut self,
10088 client_instance_id: impl Into<[u8; 16]>,
10089 subscription_id: i32,
10090 terminating: bool,
10091 compression_type: i8,
10092 metrics: &[u8],
10093 ) -> Result<PushTelemetryResponse> {
10094 let version = self
10095 .push_telemetry_version
10096 .ok_or_else(|| Error::Unsupported("broker does not support PushTelemetry".into()))?;
10097 let timeout = self.cfg.request_timeout;
10098 let req = PushTelemetryRequest::new(
10099 client_instance_id,
10100 subscription_id,
10101 terminating,
10102 compression_type,
10103 metrics.to_vec(),
10104 );
10105 let body = self
10106 .roundtrip_bootstrap(
10107 PUSH_TELEMETRY,
10108 version,
10109 |buf| encode_push_telemetry_request(buf, &req),
10110 timeout,
10111 )
10112 .await?;
10113 let resp = decode_push_telemetry_response(&mut body.clone())?;
10114 if resp.error_code != 0 {
10115 return Err(Error::broker(resp.error_code, "PushTelemetry"));
10116 }
10117 Ok(resp)
10118 }
10119
10120 pub async fn assign_replicas_to_dirs(
10145 &mut self,
10146 broker_id: i32,
10147 broker_epoch: i64,
10148 directories: Vec<AssignReplicasToDirsDirectory>,
10149 ) -> Result<AssignReplicasToDirsResponse> {
10150 let timeout = self.cfg.request_timeout;
10151 self.assign_replicas_to_dirs_timeout(broker_id, broker_epoch, directories, timeout)
10152 .await
10153 }
10154
10155 pub async fn assign_replicas_to_dirs_timeout(
10161 &mut self,
10162 broker_id: i32,
10163 broker_epoch: i64,
10164 directories: Vec<AssignReplicasToDirsDirectory>,
10165 timeout: Duration,
10166 ) -> Result<AssignReplicasToDirsResponse> {
10167 let version = self.assign_replicas_to_dirs_version.ok_or_else(|| {
10168 Error::Unsupported("broker does not support AssignReplicasToDirs".into())
10169 })?;
10170 let deadline = Instant::now() + timeout;
10171 let mut attempt = 0u32;
10172 let req = AssignReplicasToDirsRequest::new(broker_id, broker_epoch, directories);
10173 loop {
10174 if self.cluster.controller().is_err() {
10175 self.refresh_metadata(None).await?;
10176 }
10177 let node = self.cluster.controller()?;
10178 self.connect_node(node).await?;
10179 let body = {
10180 let conn = self
10181 .conns
10182 .get_mut(&node)
10183 .ok_or_else(|| Error::protocol("missing assign_replicas_to_dirs conn"))?;
10184 conn.roundtrip(
10185 ASSIGN_REPLICAS_TO_DIRS,
10186 version,
10187 |buf| encode_assign_replicas_to_dirs_request(buf, &req),
10188 timeout,
10189 )
10190 .await
10191 };
10192 let body = match body {
10193 Ok(b) => b,
10194 Err(e) if e.is_retriable() => {
10195 let _ = self.conns.remove(&node);
10196 self.cluster.invalidate_controller();
10197 self.wait_retry(&mut attempt, deadline).await?;
10198 continue;
10199 }
10200 Err(e) => return Err(e),
10201 };
10202 let resp = decode_assign_replicas_to_dirs_response(&mut body.clone())?;
10203 if resp.error_code == error::NOT_CONTROLLER {
10204 self.cluster.invalidate_controller();
10206 let _ = self.conns.remove(&node);
10207 self.wait_retry(&mut attempt, deadline).await?;
10208 self.refresh_metadata(None).await?;
10209 continue;
10210 }
10211 if resp.error_code != 0 {
10212 return Err(Error::broker(resp.error_code, "AssignReplicasToDirs"));
10213 }
10214 return Ok(resp);
10215 }
10216 }
10217
10218 pub async fn alter_replica_log_dirs(
10250 &mut self,
10251 dirs: Vec<AlterReplicaLogDirsDirectory>,
10252 ) -> Result<AlterReplicaLogDirsResponse> {
10253 let timeout = self.cfg.request_timeout;
10254 self.alter_replica_log_dirs_timeout(dirs, timeout).await
10255 }
10256
10257 pub async fn alter_replica_log_dirs_timeout(
10262 &mut self,
10263 dirs: Vec<AlterReplicaLogDirsDirectory>,
10264 timeout: Duration,
10265 ) -> Result<AlterReplicaLogDirsResponse> {
10266 let version = self.alter_replica_log_dirs_version.ok_or_else(|| {
10267 Error::Unsupported("broker does not support AlterReplicaLogDirs".into())
10268 })?;
10269 let req = AlterReplicaLogDirsRequest::new(dirs);
10270 let body = self
10271 .roundtrip_bootstrap(
10272 ALTER_REPLICA_LOG_DIRS,
10273 version,
10274 |buf| encode_alter_replica_log_dirs_request(buf, version, &req),
10275 timeout,
10276 )
10277 .await?;
10278 decode_alter_replica_log_dirs_response(&mut body.clone(), version)
10279 }
10280
10281 pub async fn alter_replica_log_dirs_for<I, Dir>(
10292 &mut self,
10293 replica_assignment: I,
10294 ) -> Result<Vec<(TopicPartitionReplica, i16)>>
10295 where
10296 I: IntoIterator<Item = (TopicPartitionReplica, Dir)>,
10297 Dir: Into<String>,
10298 {
10299 let timeout = self.cfg.request_timeout;
10300 self.alter_replica_log_dirs_for_timeout(replica_assignment, timeout)
10301 .await
10302 }
10303
10304 pub async fn alter_replica_log_dirs_for_timeout<I, Dir>(
10310 &mut self,
10311 replica_assignment: I,
10312 timeout: Duration,
10313 ) -> Result<Vec<(TopicPartitionReplica, i16)>>
10314 where
10315 I: IntoIterator<Item = (TopicPartitionReplica, Dir)>,
10316 Dir: Into<String>,
10317 {
10318 let assignment: Vec<(TopicPartitionReplica, String)> = replica_assignment
10319 .into_iter()
10320 .map(|(replica, dir)| (replica, dir.into()))
10321 .collect();
10322 if assignment.is_empty() {
10323 return Ok(Vec::new());
10324 }
10325 let mut errors: HashMap<TopicPartitionReplica, i16> = HashMap::new();
10326 for (broker_id, dirs) in group_alter_replica_log_dirs(&assignment) {
10327 self.ensure_broker(broker_id).await?;
10328 let resp = self
10329 .alter_replica_log_dirs_on(broker_id, dirs, timeout)
10330 .await?;
10331 for (replica, _) in assignment.iter().filter(|(r, _)| r.broker_id == broker_id) {
10332 let code = alter_replica_partition_error(
10333 &resp.results,
10334 replica.topic(),
10335 replica.partition,
10336 );
10337 let _ = errors.insert(replica.clone(), code);
10338 }
10339 }
10340 let mut out = Vec::with_capacity(assignment.len());
10341 for (replica, _) in assignment {
10342 let code = errors.get(&replica).copied().unwrap_or(0);
10343 out.push((replica, code));
10344 }
10345 Ok(out)
10346 }
10347
10348 pub async fn describe_log_dirs(
10378 &mut self,
10379 topics: Option<Vec<DescribableLogDirTopic>>,
10380 ) -> Result<DescribeLogDirsResponse> {
10381 let timeout = self.cfg.request_timeout;
10382 self.describe_log_dirs_timeout(topics, timeout).await
10383 }
10384
10385 pub async fn describe_log_dirs_timeout(
10390 &mut self,
10391 topics: Option<Vec<DescribableLogDirTopic>>,
10392 timeout: Duration,
10393 ) -> Result<DescribeLogDirsResponse> {
10394 let version = self
10395 .describe_log_dirs_version
10396 .ok_or_else(|| Error::Unsupported("broker does not support DescribeLogDirs".into()))?;
10397 let req = DescribeLogDirsRequest::new(topics);
10398 let body = self
10399 .roundtrip_bootstrap(
10400 DESCRIBE_LOG_DIRS,
10401 version,
10402 |buf| encode_describe_log_dirs_request(buf, version, &req),
10403 timeout,
10404 )
10405 .await?;
10406 decode_describe_log_dirs_response(&mut body.clone(), version)
10407 }
10408
10409 pub async fn describe_replica_log_dirs(
10420 &mut self,
10421 replicas: impl IntoIterator<Item = TopicPartitionReplica>,
10422 ) -> Result<Vec<(TopicPartitionReplica, ReplicaLogDirInfo)>> {
10423 let timeout = self.cfg.request_timeout;
10424 self.describe_replica_log_dirs_timeout(replicas, timeout)
10425 .await
10426 }
10427
10428 pub async fn describe_replica_log_dirs_timeout(
10434 &mut self,
10435 replicas: impl IntoIterator<Item = TopicPartitionReplica>,
10436 timeout: Duration,
10437 ) -> Result<Vec<(TopicPartitionReplica, ReplicaLogDirInfo)>> {
10438 let replicas: Vec<TopicPartitionReplica> = replicas.into_iter().collect();
10439 if replicas.is_empty() {
10440 return Ok(Vec::new());
10441 }
10442 let mut infos: HashMap<(String, i32, i32), ReplicaLogDirInfo> = HashMap::new();
10443 for broker_id in replica_broker_ids(&replicas) {
10444 self.ensure_broker(broker_id).await?;
10445 let topics = describable_topics_for_broker(&replicas, broker_id);
10446 let resp = self
10447 .describe_log_dirs_on(broker_id, Some(topics), timeout)
10448 .await?;
10449 if resp.error_code != 0 {
10450 return Err(Error::broker(resp.error_code, "DescribeLogDirs"));
10451 }
10452 for r in replicas.iter().filter(|r| r.broker_id == broker_id) {
10453 let info = replica_log_dir_info_from(r, &resp.results);
10454 let _ = infos.insert((r.topic.clone(), r.partition, r.broker_id), info);
10455 }
10456 }
10457 let mut out = Vec::with_capacity(replicas.len());
10458 for r in replicas {
10459 let info = infos
10460 .get(&(r.topic.clone(), r.partition, r.broker_id))
10461 .cloned()
10462 .unwrap_or_else(ReplicaLogDirInfo::unknown);
10463 out.push((r, info));
10464 }
10465 Ok(out)
10466 }
10467
10468 pub async fn describe_broker_log_dirs(
10481 &mut self,
10482 brokers: impl IntoIterator<Item = i32>,
10483 ) -> Result<Vec<(i32, DescribeLogDirsResponse)>> {
10484 let timeout = self.cfg.request_timeout;
10485 self.describe_broker_log_dirs_timeout(brokers, timeout)
10486 .await
10487 }
10488
10489 pub async fn describe_broker_log_dirs_timeout(
10495 &mut self,
10496 brokers: impl IntoIterator<Item = i32>,
10497 timeout: Duration,
10498 ) -> Result<Vec<(i32, DescribeLogDirsResponse)>> {
10499 let mut ids = Vec::new();
10500 for id in brokers {
10501 if !ids.contains(&id) {
10502 ids.push(id);
10503 }
10504 }
10505 if ids.is_empty() {
10506 return Ok(Vec::new());
10507 }
10508 let mut out = Vec::with_capacity(ids.len());
10509 for broker_id in ids {
10510 self.ensure_broker(broker_id).await?;
10511 let resp = self.describe_log_dirs_on(broker_id, None, timeout).await?;
10512 out.push((broker_id, resp));
10513 }
10514 Ok(out)
10515 }
10516
10517 async fn describe_log_dirs_on(
10518 &mut self,
10519 node: i32,
10520 topics: Option<Vec<DescribableLogDirTopic>>,
10521 timeout: Duration,
10522 ) -> Result<DescribeLogDirsResponse> {
10523 let version = self
10524 .describe_log_dirs_version
10525 .ok_or_else(|| Error::Unsupported("broker does not support DescribeLogDirs".into()))?;
10526 let deadline = Instant::now() + timeout;
10527 let mut attempt = 0u32;
10528 let req = DescribeLogDirsRequest::new(topics);
10529 loop {
10530 self.connect_node(node).await?;
10531 let body = {
10532 let conn = self
10533 .conns
10534 .get_mut(&node)
10535 .ok_or_else(|| Error::protocol("missing describe_log_dirs conn"))?;
10536 conn.roundtrip(
10537 DESCRIBE_LOG_DIRS,
10538 version,
10539 |buf| encode_describe_log_dirs_request(buf, version, &req),
10540 timeout,
10541 )
10542 .await
10543 };
10544 let body = match body {
10545 Ok(b) => b,
10546 Err(e) if e.is_retriable() => {
10547 let _ = self.conns.remove(&node);
10548 self.wait_retry(&mut attempt, deadline).await?;
10549 continue;
10550 }
10551 Err(e) => return Err(e),
10552 };
10553 return decode_describe_log_dirs_response(&mut body.clone(), version);
10554 }
10555 }
10556
10557 async fn alter_replica_log_dirs_on(
10558 &mut self,
10559 node: i32,
10560 dirs: Vec<AlterReplicaLogDirsDirectory>,
10561 timeout: Duration,
10562 ) -> Result<AlterReplicaLogDirsResponse> {
10563 let version = self.alter_replica_log_dirs_version.ok_or_else(|| {
10564 Error::Unsupported("broker does not support AlterReplicaLogDirs".into())
10565 })?;
10566 let deadline = Instant::now() + timeout;
10567 let mut attempt = 0u32;
10568 let req = AlterReplicaLogDirsRequest::new(dirs);
10569 loop {
10570 self.connect_node(node).await?;
10571 let body = {
10572 let conn = self
10573 .conns
10574 .get_mut(&node)
10575 .ok_or_else(|| Error::protocol("missing alter_replica_log_dirs conn"))?;
10576 conn.roundtrip(
10577 ALTER_REPLICA_LOG_DIRS,
10578 version,
10579 |buf| encode_alter_replica_log_dirs_request(buf, version, &req),
10580 timeout,
10581 )
10582 .await
10583 };
10584 let body = match body {
10585 Ok(b) => b,
10586 Err(e) if e.is_retriable() => {
10587 let _ = self.conns.remove(&node);
10588 self.wait_retry(&mut attempt, deadline).await?;
10589 continue;
10590 }
10591 Err(e) => return Err(e),
10592 };
10593 return decode_alter_replica_log_dirs_response(&mut body.clone(), version);
10594 }
10595 }
10596
10597 async fn ensure_broker(&mut self, node: i32) -> Result<()> {
10598 if !self.cluster.brokers.contains_key(&node) {
10599 self.refresh_metadata(None).await?;
10600 }
10601 if self.cluster.brokers.contains_key(&node) {
10602 Ok(())
10603 } else {
10604 Err(Error::protocol(format!("unknown broker {node}")))
10605 }
10606 }
10607
10608 pub async fn create_delegation_token(
10638 &mut self,
10639 req: CreateDelegationTokenRequest,
10640 ) -> Result<CreateDelegationTokenResponse> {
10641 let timeout = self.cfg.request_timeout;
10642 self.create_delegation_token_timeout(req, timeout).await
10643 }
10644
10645 pub async fn create_delegation_token_timeout(
10650 &mut self,
10651 req: CreateDelegationTokenRequest,
10652 timeout: Duration,
10653 ) -> Result<CreateDelegationTokenResponse> {
10654 let version = self.create_delegation_token_version.ok_or_else(|| {
10655 Error::Unsupported("broker does not support CreateDelegationToken".into())
10656 })?;
10657 let body = self
10658 .roundtrip_bootstrap(
10659 CREATE_DELEGATION_TOKEN,
10660 version,
10661 |buf| encode_create_delegation_token_request(buf, version, &req),
10662 timeout,
10663 )
10664 .await?;
10665 decode_create_delegation_token_response(&mut body.clone(), version)
10666 }
10667
10668 pub async fn create_delegation_token_default(
10675 &mut self,
10676 ) -> Result<CreateDelegationTokenResponse> {
10677 let timeout = self.cfg.request_timeout;
10678 self.create_delegation_token_default_timeout(timeout).await
10679 }
10680
10681 pub async fn create_delegation_token_default_timeout(
10684 &mut self,
10685 timeout: Duration,
10686 ) -> Result<CreateDelegationTokenResponse> {
10687 self.create_delegation_token_timeout(CreateDelegationTokenRequest::default(), timeout)
10688 .await
10689 }
10690
10691 pub async fn renew_delegation_token(
10719 &mut self,
10720 req: RenewDelegationTokenRequest,
10721 ) -> Result<RenewDelegationTokenResponse> {
10722 let timeout = self.cfg.request_timeout;
10723 self.renew_delegation_token_timeout(req, timeout).await
10724 }
10725
10726 pub async fn renew_delegation_token_timeout(
10731 &mut self,
10732 req: RenewDelegationTokenRequest,
10733 timeout: Duration,
10734 ) -> Result<RenewDelegationTokenResponse> {
10735 let version = self.renew_delegation_token_version.ok_or_else(|| {
10736 Error::Unsupported("broker does not support RenewDelegationToken".into())
10737 })?;
10738 let body = self
10739 .roundtrip_bootstrap(
10740 RENEW_DELEGATION_TOKEN,
10741 version,
10742 |buf| encode_renew_delegation_token_request(buf, version, &req),
10743 timeout,
10744 )
10745 .await?;
10746 decode_renew_delegation_token_response(&mut body.clone(), version)
10747 }
10748
10749 pub async fn renew_delegation_token_hmac(
10755 &mut self,
10756 hmac: impl AsRef<[u8]>,
10757 ) -> Result<RenewDelegationTokenResponse> {
10758 let timeout = self.cfg.request_timeout;
10759 self.renew_delegation_token_hmac_timeout(hmac, timeout)
10760 .await
10761 }
10762
10763 pub async fn renew_delegation_token_hmac_timeout(
10766 &mut self,
10767 hmac: impl AsRef<[u8]>,
10768 timeout: Duration,
10769 ) -> Result<RenewDelegationTokenResponse> {
10770 self.renew_delegation_token_timeout(
10771 RenewDelegationTokenRequest::new(hmac.as_ref().to_vec(), -1),
10772 timeout,
10773 )
10774 .await
10775 }
10776
10777 pub async fn expire_delegation_token(
10805 &mut self,
10806 req: ExpireDelegationTokenRequest,
10807 ) -> Result<ExpireDelegationTokenResponse> {
10808 let timeout = self.cfg.request_timeout;
10809 self.expire_delegation_token_timeout(req, timeout).await
10810 }
10811
10812 pub async fn expire_delegation_token_timeout(
10817 &mut self,
10818 req: ExpireDelegationTokenRequest,
10819 timeout: Duration,
10820 ) -> Result<ExpireDelegationTokenResponse> {
10821 let version = self.expire_delegation_token_version.ok_or_else(|| {
10822 Error::Unsupported("broker does not support ExpireDelegationToken".into())
10823 })?;
10824 let body = self
10825 .roundtrip_bootstrap(
10826 EXPIRE_DELEGATION_TOKEN,
10827 version,
10828 |buf| encode_expire_delegation_token_request(buf, version, &req),
10829 timeout,
10830 )
10831 .await?;
10832 decode_expire_delegation_token_response(&mut body.clone(), version)
10833 }
10834
10835 pub async fn expire_delegation_token_hmac(
10840 &mut self,
10841 hmac: impl AsRef<[u8]>,
10842 ) -> Result<ExpireDelegationTokenResponse> {
10843 let timeout = self.cfg.request_timeout;
10844 self.expire_delegation_token_hmac_timeout(hmac, timeout)
10845 .await
10846 }
10847
10848 pub async fn expire_delegation_token_hmac_timeout(
10851 &mut self,
10852 hmac: impl AsRef<[u8]>,
10853 timeout: Duration,
10854 ) -> Result<ExpireDelegationTokenResponse> {
10855 self.expire_delegation_token_timeout(
10856 ExpireDelegationTokenRequest::new(hmac.as_ref().to_vec(), -1),
10857 timeout,
10858 )
10859 .await
10860 }
10861
10862 pub async fn describe_delegation_token(
10895 &mut self,
10896 req: DescribeDelegationTokenRequest,
10897 ) -> Result<DescribeDelegationTokenResponse> {
10898 let timeout = self.cfg.request_timeout;
10899 self.describe_delegation_token_timeout(req, timeout).await
10900 }
10901
10902 pub async fn describe_delegation_token_timeout(
10908 &mut self,
10909 req: DescribeDelegationTokenRequest,
10910 timeout: Duration,
10911 ) -> Result<DescribeDelegationTokenResponse> {
10912 let version = self.describe_delegation_token_version.ok_or_else(|| {
10913 Error::Unsupported("broker does not support DescribeDelegationToken".into())
10914 })?;
10915 let body = self
10916 .roundtrip_bootstrap(
10917 DESCRIBE_DELEGATION_TOKEN,
10918 version,
10919 |buf| encode_describe_delegation_token_request(buf, version, &req),
10920 timeout,
10921 )
10922 .await?;
10923 decode_describe_delegation_token_response(&mut body.clone(), version)
10924 }
10925
10926 pub async fn describe_delegation_tokens(&mut self) -> Result<DescribeDelegationTokenResponse> {
10932 let timeout = self.cfg.request_timeout;
10933 self.describe_delegation_tokens_timeout(timeout).await
10934 }
10935
10936 pub async fn describe_delegation_tokens_timeout(
10939 &mut self,
10940 timeout: Duration,
10941 ) -> Result<DescribeDelegationTokenResponse> {
10942 self.describe_delegation_token_timeout(DescribeDelegationTokenRequest::default(), timeout)
10943 .await
10944 }
10945
10946 async fn discover_group_coord(&mut self, group_id: &str) -> Result<i32> {
10947 if self.cluster.brokers.is_empty() {
10948 self.refresh_metadata(None).await?;
10949 }
10950 let version = self.find_coord_version;
10951 let timeout = self.cfg.request_timeout;
10952 let deadline = Instant::now() + timeout;
10953 let mut attempt = 0u32;
10954 loop {
10955 let body = self
10956 .roundtrip_bootstrap(
10957 FIND_COORDINATOR,
10958 version,
10959 |buf| {
10960 encode_find_coordinator_request_typed(
10961 buf,
10962 version,
10963 group_id,
10964 COORDINATOR_GROUP,
10965 )
10966 },
10967 timeout,
10968 )
10969 .await;
10970 let body = match body {
10971 Ok(b) => b,
10972 Err(e) if e.is_retriable() => {
10973 self.wait_retry(&mut attempt, deadline).await?;
10974 continue;
10975 }
10976 Err(e) => return Err(e),
10977 };
10978 let (err, node, _host, _port) =
10979 decode_find_coordinator_response(&mut body.clone(), version)?;
10980 if err == 0 {
10981 if !self.cluster.brokers.contains_key(&node) {
10982 self.refresh_metadata(None).await?;
10983 }
10984 return Ok(node);
10985 }
10986 if error::coordinator_retriable(err) {
10987 self.wait_retry(&mut attempt, deadline).await?;
10988 continue;
10989 }
10990 return Err(Error::broker(err, "FindCoordinator"));
10991 }
10992 }
10993
10994 async fn discover_group_coords(
10995 &mut self,
10996 group_ids: &[String],
10997 ) -> Result<HashMap<String, i32>> {
10998 self.discover_coords(group_ids, COORDINATOR_GROUP).await
10999 }
11000
11001 async fn discover_txn_coords(
11002 &mut self,
11003 transactional_ids: &[String],
11004 ) -> Result<HashMap<String, i32>> {
11005 self.discover_coords(transactional_ids, COORDINATOR_TRANSACTION)
11006 .await
11007 }
11008
11009 async fn discover_coords(
11010 &mut self,
11011 keys: &[String],
11012 key_type: i8,
11013 ) -> Result<HashMap<String, i32>> {
11014 let mut uniq: Vec<String> = Vec::new();
11015 for k in keys {
11016 if !uniq.iter().any(|u| u == k) {
11017 uniq.push(k.clone());
11018 }
11019 }
11020 if uniq.is_empty() {
11021 return Ok(HashMap::new());
11022 }
11023 let version = self.find_coord_version;
11024 if version < 4 {
11025 let mut out = HashMap::new();
11026 for k in &uniq {
11027 let node = if key_type == COORDINATOR_TRANSACTION {
11028 self.discover_txn_coord(k).await?
11029 } else {
11030 self.discover_group_coord(k).await?
11031 };
11032 let _prev = out.insert(k.clone(), node);
11033 }
11034 return Ok(out);
11035 }
11036 if self.cluster.brokers.is_empty() {
11037 self.refresh_metadata(None).await?;
11038 }
11039 let timeout = self.cfg.request_timeout;
11040 let deadline = Instant::now() + timeout;
11041 let mut attempt = 0u32;
11042 loop {
11043 let key_refs: Vec<&str> = uniq.iter().map(String::as_str).collect();
11044 let body = self
11045 .roundtrip_bootstrap(
11046 FIND_COORDINATOR,
11047 version,
11048 |buf| encode_find_coordinator_request_keys(buf, version, &key_refs, key_type),
11049 timeout,
11050 )
11051 .await;
11052 let body = match body {
11053 Ok(b) => b,
11054 Err(e) if e.is_retriable() => {
11055 self.wait_retry(&mut attempt, deadline).await?;
11056 continue;
11057 }
11058 Err(e) => return Err(e),
11059 };
11060 let (coords, ..) =
11061 decode_find_coordinator_response_coordinators(&mut body.clone(), version)?;
11062 let mut by_key: HashMap<String, (i16, i32)> = HashMap::new();
11063 for c in coords {
11064 let _prev = by_key.insert(c.key, (c.error_code, c.node_id));
11065 }
11066 let mut retry = false;
11067 let mut out = HashMap::new();
11068 for k in &uniq {
11069 let (err, node) = by_key
11070 .get(k)
11071 .copied()
11072 .ok_or_else(|| Error::protocol(format!("FindCoordinator missing {k}")))?;
11073 if err == 0 {
11074 if !self.cluster.brokers.contains_key(&node) {
11075 self.refresh_metadata(None).await?;
11076 }
11077 let _prev = out.insert(k.clone(), node);
11078 continue;
11079 }
11080 if error::coordinator_retriable(err) {
11081 retry = true;
11082 continue;
11083 }
11084 return Err(Error::broker(err, k.clone()));
11085 }
11086 if retry {
11087 self.wait_retry(&mut attempt, deadline).await?;
11088 continue;
11089 }
11090 return Ok(out);
11091 }
11092 }
11093
11094 async fn group_coord_nodes(
11095 &mut self,
11096 ids: &[String],
11097 pending: &[usize],
11098 ) -> Result<HashMap<i32, Vec<usize>>> {
11099 let mut need: Vec<String> = Vec::new();
11100 for &i in pending {
11101 let Some(id) = ids.get(i) else {
11102 continue;
11103 };
11104 if !self.group_coords.contains_key(id) && !need.iter().any(|k| k == id) {
11105 need.push(id.clone());
11106 }
11107 }
11108 if !need.is_empty() {
11109 let found = self.discover_group_coords(&need).await?;
11110 self.group_coords.extend(found);
11111 }
11112 let mut by_node: HashMap<i32, Vec<usize>> = HashMap::new();
11113 for &i in pending {
11114 let id = ids
11115 .get(i)
11116 .ok_or_else(|| Error::protocol("missing group id"))?;
11117 let node = *self
11118 .group_coords
11119 .get(id)
11120 .ok_or_else(|| Error::protocol(format!("missing coordinator for {id}")))?;
11121 by_node.entry(node).or_default().push(i);
11122 }
11123 Ok(by_node)
11124 }
11125
11126 fn invalidate_group_coord_idxs(&mut self, ids: &[String], idxs: &[usize], node: i32) {
11127 let _ = self.conns.remove(&node);
11128 for &i in idxs {
11129 if let Some(id) = ids.get(i) {
11130 let _ = self.group_coords.remove(id);
11131 }
11132 }
11133 }
11134
11135 async fn txn_coord_nodes(
11136 &mut self,
11137 ids: &[String],
11138 pending: &[usize],
11139 ) -> Result<HashMap<i32, Vec<usize>>> {
11140 let mut need: Vec<String> = Vec::new();
11141 for &i in pending {
11142 let Some(id) = ids.get(i) else {
11143 continue;
11144 };
11145 if !self.txn_coords.contains_key(id) && !need.iter().any(|k| k == id) {
11146 need.push(id.clone());
11147 }
11148 }
11149 if !need.is_empty() {
11150 let found = self.discover_txn_coords(&need).await?;
11151 self.txn_coords.extend(found);
11152 }
11153 let mut by_node: HashMap<i32, Vec<usize>> = HashMap::new();
11154 for &i in pending {
11155 let id = ids
11156 .get(i)
11157 .ok_or_else(|| Error::protocol("missing transactional id"))?;
11158 let node = *self
11159 .txn_coords
11160 .get(id)
11161 .ok_or_else(|| Error::protocol(format!("missing coordinator for {id}")))?;
11162 by_node.entry(node).or_default().push(i);
11163 }
11164 Ok(by_node)
11165 }
11166
11167 fn invalidate_txn_coord_idxs(&mut self, ids: &[String], idxs: &[usize], node: i32) {
11168 let _ = self.conns.remove(&node);
11169 for &i in idxs {
11170 if let Some(id) = ids.get(i) {
11171 let _ = self.txn_coords.remove(id);
11172 }
11173 }
11174 }
11175
11176 async fn describe_groups_on_node(
11177 &mut self,
11178 node: i32,
11179 version: i16,
11180 ids: &[String],
11181 idxs: &[usize],
11182 include_authorized_operations: bool,
11183 timeout: Duration,
11184 ) -> Result<Vec<(usize, DescribedGroup)>> {
11185 let subset: Vec<String> = idxs.iter().filter_map(|&i| ids.get(i).cloned()).collect();
11186 self.connect_node(node).await?;
11187 let body = {
11188 let conn = self
11189 .conns
11190 .get_mut(&node)
11191 .ok_or_else(|| Error::protocol("missing describe_groups conn"))?;
11192 conn.roundtrip(
11193 DESCRIBE_GROUPS,
11194 version,
11195 |buf| {
11196 encode_describe_groups_request(
11197 buf,
11198 version,
11199 &subset,
11200 include_authorized_operations,
11201 )
11202 },
11203 timeout,
11204 )
11205 .await
11206 }?;
11207 let (results, ..) = decode_describe_groups_response(&mut body.clone(), version)?;
11208 let mut by_id: HashMap<String, VecDeque<DescribedGroup>> = HashMap::new();
11209 for g in results {
11210 by_id.entry(g.group_id.clone()).or_default().push_back(g);
11211 }
11212 let mut out = Vec::new();
11213 for &i in idxs {
11214 let id = ids
11215 .get(i)
11216 .ok_or_else(|| Error::protocol("missing group id"))?;
11217 let g = by_id
11218 .get_mut(id)
11219 .and_then(VecDeque::pop_front)
11220 .ok_or_else(|| Error::protocol(format!("DescribeGroups missing {id}")))?;
11221 out.push((i, g));
11222 }
11223 Ok(out)
11224 }
11225
11226 async fn delete_groups_on_node(
11227 &mut self,
11228 node: i32,
11229 version: i16,
11230 ids: &[String],
11231 idxs: &[usize],
11232 timeout: Duration,
11233 ) -> Result<Vec<(usize, DeletableGroupResult)>> {
11234 let subset: Vec<String> = idxs.iter().filter_map(|&i| ids.get(i).cloned()).collect();
11235 self.connect_node(node).await?;
11236 let body = {
11237 let conn = self
11238 .conns
11239 .get_mut(&node)
11240 .ok_or_else(|| Error::protocol("missing delete_groups conn"))?;
11241 conn.roundtrip(
11242 DELETE_GROUPS,
11243 version,
11244 |buf| encode_delete_groups_request(buf, version, &subset),
11245 timeout,
11246 )
11247 .await
11248 }?;
11249 let (results, ..) = decode_delete_groups_response(&mut body.clone(), version)?;
11250 let mut by_id: HashMap<String, VecDeque<DeletableGroupResult>> = HashMap::new();
11251 for g in results {
11252 by_id.entry(g.group_id.clone()).or_default().push_back(g);
11253 }
11254 let mut out = Vec::new();
11255 for &i in idxs {
11256 let id = ids
11257 .get(i)
11258 .ok_or_else(|| Error::protocol("missing group id"))?;
11259 let g = by_id
11260 .get_mut(id)
11261 .and_then(VecDeque::pop_front)
11262 .ok_or_else(|| Error::protocol(format!("DeleteGroups missing {id}")))?;
11263 out.push((i, g));
11264 }
11265 Ok(out)
11266 }
11267
11268 async fn consumer_group_describe_on_node(
11269 &mut self,
11270 node: i32,
11271 version: i16,
11272 ids: &[String],
11273 idxs: &[usize],
11274 include_authorized_operations: bool,
11275 timeout: Duration,
11276 ) -> Result<Vec<(usize, DescribedConsumerGroup)>> {
11277 let subset: Vec<String> = idxs.iter().filter_map(|&i| ids.get(i).cloned()).collect();
11278 self.connect_node(node).await?;
11279 let body = {
11280 let conn = self
11281 .conns
11282 .get_mut(&node)
11283 .ok_or_else(|| Error::protocol("missing consumer_group_describe conn"))?;
11284 conn.roundtrip(
11285 CONSUMER_GROUP_DESCRIBE,
11286 version,
11287 |buf| {
11288 encode_consumer_group_describe_request(
11289 buf,
11290 version,
11291 &subset,
11292 include_authorized_operations,
11293 )
11294 },
11295 timeout,
11296 )
11297 .await
11298 }?;
11299 let (results, ..) = decode_consumer_group_describe_response(&mut body.clone(), version)?;
11300 let mut by_id: HashMap<String, VecDeque<DescribedConsumerGroup>> = HashMap::new();
11301 for g in results {
11302 by_id.entry(g.group_id.clone()).or_default().push_back(g);
11303 }
11304 let mut out = Vec::new();
11305 for &i in idxs {
11306 let id = ids
11307 .get(i)
11308 .ok_or_else(|| Error::protocol("missing group id"))?;
11309 let g = by_id
11310 .get_mut(id)
11311 .and_then(VecDeque::pop_front)
11312 .ok_or_else(|| Error::protocol(format!("ConsumerGroupDescribe missing {id}")))?;
11313 out.push((i, g));
11314 }
11315 Ok(out)
11316 }
11317
11318 async fn share_group_describe_on_node(
11319 &mut self,
11320 node: i32,
11321 version: i16,
11322 ids: &[String],
11323 idxs: &[usize],
11324 include_authorized_operations: bool,
11325 timeout: Duration,
11326 ) -> Result<Vec<(usize, DescribedShareGroup)>> {
11327 let subset: Vec<String> = idxs.iter().filter_map(|&i| ids.get(i).cloned()).collect();
11328 self.connect_node(node).await?;
11329 let body = {
11330 let conn = self
11331 .conns
11332 .get_mut(&node)
11333 .ok_or_else(|| Error::protocol("missing share_group_describe conn"))?;
11334 conn.roundtrip(
11335 SHARE_GROUP_DESCRIBE,
11336 version,
11337 |buf| {
11338 encode_share_group_describe_request(
11339 buf,
11340 version,
11341 &subset,
11342 include_authorized_operations,
11343 )
11344 },
11345 timeout,
11346 )
11347 .await
11348 }?;
11349 let (results, ..) = decode_share_group_describe_response(&mut body.clone(), version)?;
11350 let mut by_id: HashMap<String, VecDeque<DescribedShareGroup>> = HashMap::new();
11351 for g in results {
11352 by_id.entry(g.group_id.clone()).or_default().push_back(g);
11353 }
11354 let mut out = Vec::new();
11355 for &i in idxs {
11356 let id = ids
11357 .get(i)
11358 .ok_or_else(|| Error::protocol("missing group id"))?;
11359 let g = by_id
11360 .get_mut(id)
11361 .and_then(VecDeque::pop_front)
11362 .ok_or_else(|| Error::protocol(format!("ShareGroupDescribe missing {id}")))?;
11363 out.push((i, g));
11364 }
11365 Ok(out)
11366 }
11367
11368 async fn describe_share_group_offsets_on_node(
11369 &mut self,
11370 node: i32,
11371 version: i16,
11372 groups: &[DescribeShareGroupOffsetsGroup],
11373 idxs: &[usize],
11374 timeout: Duration,
11375 ) -> Result<Vec<(usize, DescribedShareGroupOffsets)>> {
11376 let subset: Vec<DescribeShareGroupOffsetsGroup> = idxs
11377 .iter()
11378 .filter_map(|&i| groups.get(i).cloned())
11379 .collect();
11380 self.connect_node(node).await?;
11381 let body = {
11382 let conn = self
11383 .conns
11384 .get_mut(&node)
11385 .ok_or_else(|| Error::protocol("missing describe_share_group_offsets conn"))?;
11386 conn.roundtrip(
11387 DESCRIBE_SHARE_GROUP_OFFSETS,
11388 version,
11389 |buf| encode_describe_share_group_offsets_request(buf, &subset),
11390 timeout,
11391 )
11392 .await
11393 }?;
11394 let (results, ..) = decode_describe_share_group_offsets_response(&mut body.clone())?;
11395 let mut by_id: HashMap<String, VecDeque<DescribedShareGroupOffsets>> = HashMap::new();
11396 for g in results {
11397 by_id.entry(g.group_id.clone()).or_default().push_back(g);
11398 }
11399 let mut out = Vec::new();
11400 for &i in idxs {
11401 let id = groups
11402 .get(i)
11403 .map(|g| g.group_id.as_str())
11404 .ok_or_else(|| Error::protocol("missing group id"))?;
11405 let g = by_id
11406 .get_mut(id)
11407 .and_then(VecDeque::pop_front)
11408 .ok_or_else(|| {
11409 Error::protocol(format!("DescribeShareGroupOffsets missing {id}"))
11410 })?;
11411 out.push((i, g));
11412 }
11413 Ok(out)
11414 }
11415
11416 async fn describe_transactions_on_node(
11417 &mut self,
11418 node: i32,
11419 ids: &[String],
11420 idxs: &[usize],
11421 version: i16,
11422 timeout: Duration,
11423 ) -> Result<Vec<(usize, TransactionState)>> {
11424 let subset: Vec<String> = idxs.iter().filter_map(|&i| ids.get(i).cloned()).collect();
11425 self.connect_node(node).await?;
11426 let body = {
11427 let conn = self
11428 .conns
11429 .get_mut(&node)
11430 .ok_or_else(|| Error::protocol("missing describe_transactions conn"))?;
11431 conn.roundtrip(
11432 DESCRIBE_TRANSACTIONS,
11433 version,
11434 |buf| encode_describe_transactions_request(buf, &subset),
11435 timeout,
11436 )
11437 .await
11438 }?;
11439 let (results, ..) = decode_describe_transactions_response(&mut body.clone())?;
11440 let mut by_id: HashMap<String, VecDeque<TransactionState>> = HashMap::new();
11441 for t in results {
11442 by_id
11443 .entry(t.transactional_id.clone())
11444 .or_default()
11445 .push_back(t);
11446 }
11447 let mut out = Vec::new();
11448 for &i in idxs {
11449 let id = ids
11450 .get(i)
11451 .ok_or_else(|| Error::protocol("missing transactional id"))?;
11452 let t = by_id
11453 .get_mut(id)
11454 .and_then(VecDeque::pop_front)
11455 .ok_or_else(|| Error::protocol(format!("DescribeTransactions missing {id}")))?;
11456 out.push((i, t));
11457 }
11458 Ok(out)
11459 }
11460
11461 async fn discover_txn_coord(&mut self, transactional_id: &str) -> Result<i32> {
11462 if self.cluster.brokers.is_empty() {
11463 self.refresh_metadata(None).await?;
11464 }
11465 let version = self.find_coord_version;
11466 let timeout = self.cfg.request_timeout;
11467 let deadline = Instant::now() + timeout;
11468 let mut attempt = 0u32;
11469 loop {
11470 let body = self
11471 .roundtrip_bootstrap(
11472 FIND_COORDINATOR,
11473 version,
11474 |buf| {
11475 encode_find_coordinator_request_typed(
11476 buf,
11477 version,
11478 transactional_id,
11479 COORDINATOR_TRANSACTION,
11480 )
11481 },
11482 timeout,
11483 )
11484 .await;
11485 let body = match body {
11486 Ok(b) => b,
11487 Err(e) if e.is_retriable() => {
11488 self.wait_retry(&mut attempt, deadline).await?;
11489 continue;
11490 }
11491 Err(e) => return Err(e),
11492 };
11493 let (err, node, _host, _port) =
11494 decode_find_coordinator_response(&mut body.clone(), version)?;
11495 if err == 0 {
11496 if !self.cluster.brokers.contains_key(&node) {
11497 self.refresh_metadata(None).await?;
11498 }
11499 return Ok(node);
11500 }
11501 if error::coordinator_retriable(err) {
11502 self.wait_retry(&mut attempt, deadline).await?;
11503 continue;
11504 }
11505 return Err(Error::broker(err, "FindCoordinator"));
11506 }
11507 }
11508}
11509
11510fn group_reassignments(assignments: &[PartitionReassignment]) -> Vec<ReassignableTopic> {
11511 let mut by_topic: HashMap<String, Vec<ReassignablePartition>> = HashMap::new();
11512 let mut order: Vec<String> = Vec::new();
11513 for a in assignments {
11514 match by_topic.entry(a.topic.clone()) {
11515 std::collections::hash_map::Entry::Vacant(slot) => {
11516 order.push(a.topic.clone());
11517 let _ = slot.insert(vec![ReassignablePartition::new(
11518 a.partition,
11519 a.replicas.clone(),
11520 )]);
11521 }
11522 std::collections::hash_map::Entry::Occupied(mut slot) => {
11523 slot.get_mut()
11524 .push(ReassignablePartition::new(a.partition, a.replicas.clone()));
11525 }
11526 }
11527 }
11528 order
11529 .into_iter()
11530 .map(|name| {
11531 let partitions = by_topic.remove(&name).unwrap_or_default();
11532 ReassignableTopic::new(name, partitions)
11533 })
11534 .collect()
11535}
11536
11537fn group_list_reassignments(partitions: &[crate::TopicPartition]) -> Vec<ListReassignmentTopic> {
11538 let mut by_topic: HashMap<String, Vec<i32>> = HashMap::new();
11539 let mut order: Vec<String> = Vec::new();
11540 for tp in partitions {
11541 match by_topic.entry(tp.topic.clone()) {
11542 std::collections::hash_map::Entry::Vacant(slot) => {
11543 order.push(tp.topic.clone());
11544 let _ = slot.insert(vec![tp.partition]);
11545 }
11546 std::collections::hash_map::Entry::Occupied(mut slot) => {
11547 slot.get_mut().push(tp.partition);
11548 }
11549 }
11550 }
11551 order
11552 .into_iter()
11553 .map(|name| {
11554 let partitions = by_topic.remove(&name).unwrap_or_default();
11555 ListReassignmentTopic::new(name, partitions)
11556 })
11557 .collect()
11558}
11559
11560fn flatten_list_reassignments(
11561 topics: &[crate::protocol::admin::OngoingTopicReassignment],
11562) -> Vec<OngoingReassignment> {
11563 let mut out = Vec::new();
11564 for t in topics {
11565 for p in &t.partitions {
11566 out.push(OngoingReassignment {
11567 topic: t.name.clone(),
11568 partition: p.partition_index,
11569 replicas: p.replicas.clone(),
11570 adding_replicas: p.adding_replicas.clone(),
11571 removing_replicas: p.removing_replicas.clone(),
11572 });
11573 }
11574 }
11575 out
11576}
11577
11578fn flatten_reassignment_results(
11579 results: &[crate::protocol::admin::ReassignmentTopicResult],
11580) -> Vec<ReassignmentResult> {
11581 let mut out = Vec::new();
11582 for t in results {
11583 for p in &t.partitions {
11584 out.push(ReassignmentResult {
11585 topic: t.name.clone(),
11586 partition: p.partition_index,
11587 error_code: p.error_code,
11588 error_message: p.error_message.clone(),
11589 });
11590 }
11591 }
11592 out
11593}
11594
11595fn offset_delete_topics(partitions: &[(String, i32)]) -> Vec<OffsetDeleteTopic> {
11596 let mut by_topic: HashMap<String, Vec<i32>> = HashMap::new();
11597 let mut order: Vec<String> = Vec::new();
11598 for (topic, part) in partitions {
11599 match by_topic.entry(topic.clone()) {
11600 std::collections::hash_map::Entry::Vacant(slot) => {
11601 order.push(topic.clone());
11602 let _ = slot.insert(vec![*part]);
11603 }
11604 std::collections::hash_map::Entry::Occupied(mut slot) => {
11605 slot.get_mut().push(*part);
11606 }
11607 }
11608 }
11609 order
11610 .into_iter()
11611 .map(|topic| OffsetDeleteTopic {
11612 partitions: by_topic.remove(&topic).unwrap_or_default(),
11613 topic,
11614 })
11615 .collect()
11616}
11617
11618const DESCRIBE_TOPIC_PARTITIONS_LIMIT: i32 = 2000;
11620
11621fn topic_listings_from(md: &MetadataResponse, list_internal: bool) -> Vec<TopicListing> {
11622 md.topics
11623 .iter()
11624 .filter(|t| t.error_code == 0)
11625 .filter(|t| list_internal || !t.is_internal)
11626 .filter_map(|t| {
11627 t.name.as_ref().map(|name| TopicListing {
11628 name: name.clone(),
11629 topic_id: t.topic_id,
11630 is_internal: t.is_internal,
11631 })
11632 })
11633 .collect()
11634}
11635
11636fn topic_descriptions_including_unnamed(md: &MetadataResponse) -> Vec<TopicDescription> {
11637 md.topics.iter().map(topic_description_from).collect()
11638}
11639
11640fn topic_descriptions_for_names(md: &MetadataResponse, names: &[String]) -> Vec<TopicDescription> {
11641 names
11642 .iter()
11643 .map(|name| {
11644 md.topics
11645 .iter()
11646 .find(|t| t.name.as_deref() == Some(name.as_str()))
11647 .map(topic_description_from)
11648 .unwrap_or_else(|| {
11649 TopicDescription::new(
11650 name.clone(),
11651 [0; 16],
11652 false,
11653 error::UNKNOWN_TOPIC_OR_PARTITION,
11654 Vec::new(),
11655 )
11656 })
11657 })
11658 .collect()
11659}
11660
11661fn topic_description_from(t: &crate::protocol::api::TopicMetadata) -> TopicDescription {
11662 let name = t.name.clone().unwrap_or_default();
11663 let partitions = if t.error_code == 0 {
11664 t.partitions
11665 .iter()
11666 .map(|p| crate::PartitionInfo::from_partition_metadata(name.as_str(), p))
11667 .collect()
11668 } else {
11669 Vec::new()
11670 };
11671 TopicDescription {
11672 name,
11673 topic_id: t.topic_id,
11674 is_internal: t.is_internal,
11675 error_code: t.error_code,
11676 partitions,
11677 authorized_operations: t.topic_authorized_operations,
11678 }
11679}
11680
11681fn topic_description_from_dtp(
11682 t: &DescribedTopicPartitions,
11683 include_authorized_operations: bool,
11684) -> TopicDescription {
11685 let name = t.name.clone().unwrap_or_default();
11686 let partitions = if t.error_code == 0 {
11687 t.partitions
11688 .iter()
11689 .map(|p| crate::PartitionInfo {
11690 topic: name.clone(),
11691 partition: p.partition_index,
11692 leader: p.leader_id,
11693 leader_epoch: p.leader_epoch,
11694 replicas: p.replica_nodes.clone(),
11695 isr: p.isr_nodes.clone(),
11696 offline_replicas: p.offline_replicas.clone(),
11697 })
11698 .collect()
11699 } else {
11700 Vec::new()
11701 };
11702 TopicDescription {
11703 name,
11704 topic_id: t.topic_id,
11705 is_internal: t.is_internal,
11706 error_code: t.error_code,
11707 partitions,
11708 authorized_operations: if include_authorized_operations {
11709 t.topic_authorized_operations
11710 } else {
11711 AUTHORIZED_OPERATIONS_OMITTED
11712 },
11713 }
11714}
11715
11716fn list_offset_topic_requests(
11717 queries: &[(crate::TopicPartition, i64)],
11718 idxs: &[usize],
11719 cluster: &Cluster,
11720) -> Vec<ListOffsetsTopicRequest> {
11721 let mut order: Vec<String> = Vec::new();
11722 let mut by_topic: HashMap<String, Vec<ListOffsetsPartitionRequest>> = HashMap::new();
11723 for &i in idxs {
11724 let Some((tp, ts)) = queries.get(i) else {
11725 continue;
11726 };
11727 let part = ListOffsetsPartitionRequest {
11728 partition: tp.partition,
11729 current_leader_epoch: cluster.leader_epoch(&tp.topic, tp.partition),
11730 timestamp: *ts,
11731 };
11732 match by_topic.entry(tp.topic.clone()) {
11733 std::collections::hash_map::Entry::Vacant(slot) => {
11734 order.push(tp.topic.clone());
11735 let _ = slot.insert(vec![part]);
11736 }
11737 std::collections::hash_map::Entry::Occupied(mut slot) => {
11738 slot.get_mut().push(part);
11739 }
11740 }
11741 }
11742 order
11743 .into_iter()
11744 .map(|name| ListOffsetsTopicRequest {
11745 partitions: by_topic.remove(&name).unwrap_or_default(),
11746 name,
11747 })
11748 .collect()
11749}
11750
11751fn replica_broker_ids(replicas: &[TopicPartitionReplica]) -> Vec<i32> {
11752 let mut ids = Vec::new();
11753 for r in replicas {
11754 if !ids.contains(&r.broker_id) {
11755 ids.push(r.broker_id);
11756 }
11757 }
11758 ids
11759}
11760
11761fn group_alter_replica_log_dirs(
11762 assignment: &[(TopicPartitionReplica, String)],
11763) -> Vec<(i32, Vec<AlterReplicaLogDirsDirectory>)> {
11764 let replicas: Vec<TopicPartitionReplica> = assignment.iter().map(|(r, _)| r.clone()).collect();
11765 replica_broker_ids(&replicas)
11766 .into_iter()
11767 .map(|broker_id| {
11768 (
11769 broker_id,
11770 alter_replica_dirs_for_broker(assignment, broker_id),
11771 )
11772 })
11773 .collect()
11774}
11775
11776fn alter_replica_dirs_for_broker(
11777 assignment: &[(TopicPartitionReplica, String)],
11778 broker_id: i32,
11779) -> Vec<AlterReplicaLogDirsDirectory> {
11780 let mut path_order: Vec<String> = Vec::new();
11781 let mut topics_by_path: HashMap<String, Vec<String>> = HashMap::new();
11782 let mut partitions_by_path_topic: HashMap<(String, String), Vec<i32>> = HashMap::new();
11783 for (replica, path) in assignment {
11784 if replica.broker_id != broker_id {
11785 continue;
11786 }
11787 if !path_order.contains(path) {
11788 path_order.push(path.clone());
11789 }
11790 match topics_by_path.entry(path.clone()) {
11791 std::collections::hash_map::Entry::Vacant(slot) => {
11792 let _ = slot.insert(vec![replica.topic.clone()]);
11793 }
11794 std::collections::hash_map::Entry::Occupied(mut slot) => {
11795 if !slot.get().contains(&replica.topic) {
11796 slot.get_mut().push(replica.topic.clone());
11797 }
11798 }
11799 }
11800 match partitions_by_path_topic.entry((path.clone(), replica.topic.clone())) {
11801 std::collections::hash_map::Entry::Vacant(slot) => {
11802 let _ = slot.insert(vec![replica.partition]);
11803 }
11804 std::collections::hash_map::Entry::Occupied(mut slot) => {
11805 if !slot.get().contains(&replica.partition) {
11806 slot.get_mut().push(replica.partition);
11807 }
11808 }
11809 }
11810 }
11811 path_order
11812 .into_iter()
11813 .map(|path| {
11814 let topics = topics_by_path.remove(&path).unwrap_or_default();
11815 let topics = topics
11816 .into_iter()
11817 .map(|name| {
11818 let partitions = partitions_by_path_topic
11819 .remove(&(path.clone(), name.clone()))
11820 .unwrap_or_default();
11821 AlterReplicaLogDirsTopic { name, partitions }
11822 })
11823 .collect();
11824 AlterReplicaLogDirsDirectory { path, topics }
11825 })
11826 .collect()
11827}
11828
11829fn alter_replica_partition_error(
11830 results: &[AlterReplicaLogDirsResponseTopic],
11831 topic: &str,
11832 partition: i32,
11833) -> i16 {
11834 for result in results {
11835 if result.topic_name != topic {
11836 continue;
11837 }
11838 for part in &result.partitions {
11839 if part.partition_index == partition {
11840 return part.error_code;
11841 }
11842 }
11843 }
11844 0
11845}
11846
11847fn describable_topics_for_broker(
11848 replicas: &[TopicPartitionReplica],
11849 broker_id: i32,
11850) -> Vec<DescribableLogDirTopic> {
11851 let mut order: Vec<String> = Vec::new();
11852 let mut by_topic: HashMap<String, Vec<i32>> = HashMap::new();
11853 for r in replicas {
11854 if r.broker_id != broker_id {
11855 continue;
11856 }
11857 match by_topic.entry(r.topic.clone()) {
11858 std::collections::hash_map::Entry::Vacant(slot) => {
11859 order.push(r.topic.clone());
11860 let _ = slot.insert(vec![r.partition]);
11861 }
11862 std::collections::hash_map::Entry::Occupied(mut slot) => {
11863 if !slot.get().contains(&r.partition) {
11864 slot.get_mut().push(r.partition);
11865 }
11866 }
11867 }
11868 }
11869 order
11870 .into_iter()
11871 .map(|name| DescribableLogDirTopic {
11872 partitions: by_topic.remove(&name).unwrap_or_default(),
11873 name,
11874 })
11875 .collect()
11876}
11877
11878fn replica_log_dir_info_from(
11879 replica: &TopicPartitionReplica,
11880 results: &[DescribeLogDirsResult],
11881) -> ReplicaLogDirInfo {
11882 let mut info = ReplicaLogDirInfo::unknown();
11883 for dir in results {
11884 if dir.error_code != 0 {
11885 continue;
11886 }
11887 for topic in &dir.topics {
11888 if topic.name != replica.topic {
11889 continue;
11890 }
11891 for part in &topic.partitions {
11892 if part.partition_index != replica.partition {
11893 continue;
11894 }
11895 if part.is_future_key {
11896 info.future_log_dir = Some(dir.log_dir.clone());
11897 info.future_offset_lag = part.offset_lag;
11898 } else {
11899 info.current_log_dir = Some(dir.log_dir.clone());
11900 info.current_offset_lag = part.offset_lag;
11901 }
11902 }
11903 }
11904 }
11905 info
11906}
11907
11908#[cfg(test)]
11909mod tests {
11910 use super::*;
11911 use crate::protocol::admin::{
11912 decode_list_config_resources_response, encode_list_config_resources_response,
11913 CreatedTopicConfig, ListConfigResourcesResponse,
11914 };
11915 use bytes::BytesMut;
11916
11917 #[test]
11918 fn records_to_delete_before_offset_converts_to_i64() {
11919 assert_eq!(i64::from(RecordsToDelete::before_offset(42)), 42);
11920 assert_eq!(RecordsToDelete::before_offset(7).offset(), 7);
11921 assert_eq!(
11922 RecordsToDelete::before_offset(42).to_string(),
11923 "(beforeOffset = 42)"
11924 );
11925 assert_eq!(
11926 RecordsToDelete::before_offset(
11927 crate::protocol::admin::DeleteRecordsRequest::HIGH_WATERMARK
11928 )
11929 .offset(),
11930 crate::protocol::admin::DeleteRecordsRequest::HIGH_WATERMARK
11931 );
11932 }
11933
11934 #[test]
11935 fn list_config_resources_response_config_resources_matches_java() {
11936 assert!(ListConfigResourcesResponse::new(0, Vec::new())
11945 .config_resources()
11946 .is_empty());
11947 let full = ListConfigResourcesResponse::new(
11948 crate::error::INVALID_REQUEST,
11949 vec![
11950 ListedConfigResource::new("t", CONFIG_RESOURCE_TOPIC),
11951 ListedConfigResource::new("1", CONFIG_RESOURCE_BROKER),
11952 ListedConfigResource::new("r", CONFIG_RESOURCE_CLIENT_METRICS),
11953 ListedConfigResource::new("g", CONFIG_RESOURCE_GROUP),
11954 ListedConfigResource::new("x", 99),
11955 ],
11956 );
11957 assert_eq!(
11958 full.config_resources(),
11959 vec![
11960 ConfigResource::topic("t"),
11961 ConfigResource::broker(1),
11962 ConfigResource::of(ConfigResourceType::ClientMetrics, "r"),
11963 ConfigResource::group("g"),
11964 ConfigResource {
11965 resource_type: 0,
11966 name: "x".into(),
11967 keys: None,
11968 },
11969 ]
11970 );
11971 assert!(full.config_resources().iter().all(|r| r.keys.is_none()));
11972 assert_eq!(
11973 ListedConfigResource::new("x", 99)
11974 .to_config_resource()
11975 .resource_type,
11976 99,
11977 "to_config_resource keeps the wire id; configResources uses Type.forId"
11978 );
11979
11980 let metrics = ListConfigResourcesResponse::new(
11981 0,
11982 vec![ListedConfigResource::new(
11983 "r",
11984 CONFIG_RESOURCE_CLIENT_METRICS,
11985 )],
11986 );
11987 for version in 0..=1_i16 {
11988 let mut buf = BytesMut::new();
11989 encode_list_config_resources_response(&mut buf, version, &metrics).unwrap();
11990 let mut cur = buf.as_ref();
11991 let decoded = decode_list_config_resources_response(&mut cur, version).unwrap();
11992 assert_eq!(
11993 decoded.config_resources(),
11994 vec![ConfigResource::of(ConfigResourceType::ClientMetrics, "r")],
11995 "ListConfigResources v{version} configResources must convert the decoded listing"
11996 );
11997 assert!(
11998 cur.is_empty(),
11999 "ListConfigResources v{version} configResources leftover-empty; leftover {} bytes",
12000 cur.len()
12001 );
12002 }
12003
12004 let unknown = ListConfigResourcesResponse::new(0, vec![ListedConfigResource::new("x", 99)]);
12005 let mut v1 = BytesMut::new();
12006 encode_list_config_resources_response(&mut v1, 1, &unknown).unwrap();
12007 let mut cur = v1.as_ref();
12008 let decoded = decode_list_config_resources_response(&mut cur, 1).unwrap();
12009 assert_eq!(
12010 decoded.config_resources.first().map(|r| r.resource_type),
12011 Some(99)
12012 );
12013 assert_eq!(
12014 decoded.config_resources(),
12015 vec![ConfigResource {
12016 resource_type: 0,
12017 name: "x".into(),
12018 keys: None,
12019 }]
12020 );
12021 assert!(
12022 cur.is_empty(),
12023 "ListConfigResources v1 configResources leftover-empty; leftover {} bytes",
12024 cur.len()
12025 );
12026 }
12027
12028 #[test]
12029 fn update_features_client_checks_match_java() {
12030 let empty = Admin::reject_java_feature_updates(&[])
12031 .unwrap_err()
12032 .to_string();
12033 assert!(
12034 empty.contains("Feature updates can not be null or empty."),
12035 "{empty}"
12036 );
12037 let blank = Admin::reject_java_feature_updates(&[FeatureUpdate::new(" ", 1)])
12038 .unwrap_err()
12039 .to_string();
12040 assert!(
12041 blank.contains("Provided feature can not be empty."),
12042 "{blank}"
12043 );
12044 let unnamed = Admin::reject_java_feature_updates(&[FeatureUpdate::new("", 1)])
12045 .unwrap_err()
12046 .to_string();
12047 assert!(
12048 unnamed.contains("Provided feature can not be empty."),
12049 "{unnamed}"
12050 );
12051 Admin::reject_java_feature_updates(&[FeatureUpdate::new("metadata.version", 17)]).unwrap();
12052 }
12053
12054 #[test]
12055 fn deleted_records_matches_java() {
12056 assert_eq!(DeletedRecords::INVALID_LOW_WATERMARK, -1);
12057 let ok = DeletedRecords::new(42);
12058 assert_eq!(ok.low_watermark(), 42);
12059 assert_eq!(ok.error_code(), 0);
12060 let with_err = DeletedRecords::with_error_code(7, 6);
12061 assert_eq!(with_err.low_watermark(), 7);
12062 assert_eq!(with_err.error_code(), 6);
12063 let pair: (i64, i16) = with_err.into();
12064 assert_eq!(pair, (7, 6));
12065 assert_eq!(DeletedRecords::from((9, 0)).low_watermark(), 9);
12066 assert_eq!(
12067 DeletedRecords::with_error_code(DeletedRecords::INVALID_LOW_WATERMARK, 6)
12068 .low_watermark(),
12069 DeletedRecords::INVALID_LOW_WATERMARK
12070 );
12071 }
12072
12073 #[test]
12074 fn topic_partition_replica_and_log_dir_getters() {
12075 let replica = TopicPartitionReplica::new("t", 2, 5);
12076 assert_eq!(replica.topic(), "t");
12077 assert_eq!(replica.partition(), 2);
12078 assert_eq!(replica.broker_id(), 5);
12079 assert_eq!(replica.to_string(), "t-2-5");
12080 let dirs = ReplicaLogDirInfo::new(Some("/data".into()), 3, Some("/next".into()), 4);
12081 assert_eq!(dirs.current_log_dir(), Some("/data"));
12082 assert_eq!(dirs.current_offset_lag(), 3);
12083 assert_eq!(dirs.future_log_dir(), Some("/next"));
12084 assert_eq!(dirs.future_offset_lag(), 4);
12085 assert_eq!(
12086 dirs.to_string(),
12087 "(currentReplicaLogDir=/data, futureReplicaLogDir=/next, futureReplicaOffsetLag=4)"
12088 );
12089 let unknown = ReplicaLogDirInfo::unknown();
12090 assert!(unknown.current_log_dir().is_none());
12091 assert_eq!(unknown.current_offset_lag(), -1);
12092 assert_eq!(
12093 unknown.to_string(),
12094 "ReplicaLogDirInfo(currentReplicaLogDir=null)"
12095 );
12096 assert_eq!(
12097 ReplicaLogDirInfo::new(None, -1, Some("/next".into()), 7).to_string(),
12098 "(currentReplicaLogDir=null, futureReplicaLogDir=/next, futureReplicaOffsetLag=7)"
12099 );
12100 let replica_info = DescribeLogDirsPartition::new(0, 10, 3, false);
12101 assert_eq!(replica_info.size(), 10);
12102 assert_eq!(replica_info.offset_lag(), 3);
12103 assert!(!replica_info.is_future());
12104 let log_dir = DescribeLogDirsResult::new(
12105 0,
12106 "/data",
12107 vec![DescribeLogDirsTopic::new("t", vec![replica_info])],
12108 4096,
12109 1024,
12110 );
12111 assert_eq!(log_dir.log_dir(), "/data");
12112 assert_eq!(log_dir.total_bytes(), Some(4096));
12113 assert_eq!(log_dir.usable_bytes(), Some(1024));
12114 assert_eq!(
12115 DescribeLogDirsResult::new(
12116 0,
12117 "/d",
12118 Vec::new(),
12119 UNKNOWN_VOLUME_BYTES,
12120 UNKNOWN_VOLUME_BYTES
12121 )
12122 .total_bytes(),
12123 None
12124 );
12125 let grouped = group_alter_replica_log_dirs(&[
12126 (TopicPartitionReplica::new("t", 0, 2), "/d1".into()),
12127 (TopicPartitionReplica::new("u", 1, 2), "/d1".into()),
12128 (TopicPartitionReplica::new("t", 0, 1), "/d2".into()),
12129 ]);
12130 assert_eq!(grouped.len(), 2);
12131 assert_eq!(grouped[0].0, 2);
12132 assert_eq!(grouped[0].1[0].path(), "/d1");
12133 assert_eq!(grouped[0].1[0].topics()[0].name(), "t");
12134 assert_eq!(grouped[0].1[0].topics()[0].partitions(), &[0]);
12135 assert_eq!(grouped[0].1[0].topics()[1].name(), "u");
12136 assert_eq!(grouped[1].0, 1);
12137 assert_eq!(grouped[1].1[0].path(), "/d2");
12138 let dir = AssignReplicasToDirsDirectory::new(
12139 Uuid::from_bytes([0x11; 16]),
12140 vec![AssignReplicasToDirsTopic::new(
12141 Uuid::from_bytes([0x22; 16]),
12142 vec![AssignReplicasToDirsPartition::new(0)],
12143 )],
12144 );
12145 assert_eq!(dir.id(), Uuid::from_bytes([0x11; 16]));
12146 assert_eq!(dir.topics()[0].topic_id(), Uuid::from_bytes([0x22; 16]));
12147 assert_eq!(dir.topics()[0].partitions()[0].partition_index(), 0);
12148 let assigned = AssignReplicasToDirsResponse::new(
12149 0,
12150 vec![AssignReplicasToDirsResponseDirectory::new(
12151 [0x11; 16],
12152 vec![AssignReplicasToDirsResponseTopic::new(
12153 [0x22; 16],
12154 vec![AssignReplicasToDirsResponsePartition::new(0, 0)],
12155 )],
12156 )],
12157 );
12158 assert_eq!(assigned.error_code(), 0);
12159 assert_eq!(assigned.throttle_time_ms(), 0);
12160 assert_eq!(assigned.directories()[0].id(), Uuid::from_bytes([0x11; 16]));
12161 assert_eq!(
12162 assigned.directories()[0].topics()[0].topic_id(),
12163 Uuid::from_bytes([0x22; 16])
12164 );
12165 assert_eq!(
12166 assigned.directories()[0].topics()[0].partitions()[0].error_code(),
12167 0
12168 );
12169 let altered =
12170 AlterReplicaLogDirsResponse::new(vec![AlterReplicaLogDirsResponseTopic::new(
12171 "t",
12172 vec![AlterReplicaLogDirsResponsePartition::new(0, 0)],
12173 )]);
12174 assert_eq!(altered.results()[0].topic_name(), "t");
12175 assert_eq!(altered.throttle_time_ms(), 0);
12176 assert_eq!(altered.results()[0].partitions()[0].partition_index(), 0);
12177 assert_eq!(altered.results()[0].partitions()[0].error_code(), 0);
12178 assert_eq!(alter_replica_partition_error(altered.results(), "t", 0), 0);
12179 assert_eq!(
12180 alter_replica_partition_error(altered.results(), "missing", 0),
12181 0
12182 );
12183 }
12184
12185 #[test]
12186 fn admin_java_spec_getters_match_fields() {
12187 let topic = NewTopic::new("orders", 3, 1);
12188 assert_eq!(topic.name(), "orders");
12189 assert_eq!(topic.num_partitions(), 3);
12190 assert_eq!(topic.replication_factor(), 1);
12191 assert!(topic.replicas_assignments().is_none());
12192 assert_eq!(
12193 topic.to_string(),
12194 "(name=orders, numPartitions=3, replicationFactor=1, replicasAssignments=null, configs=null)"
12195 );
12196 assert_eq!(
12197 NewTopic::broker_defaults("t").to_string(),
12198 "(name=t, numPartitions=default, replicationFactor=default, replicasAssignments=null, configs=null)"
12199 );
12200 let assigned = NewTopic::with_assignments("t", [(0, vec![1, 2])]);
12201 assert_eq!(
12202 assigned.num_partitions(),
12203 CreateTopicsRequest::NO_NUM_PARTITIONS
12204 );
12205 assert_eq!(
12206 assigned.replication_factor(),
12207 CreateTopicsRequest::NO_REPLICATION_FACTOR
12208 );
12209 assert_eq!(
12210 assigned.replicas_assignments(),
12211 Some(&[(0, vec![1, 2])][..])
12212 );
12213 assert_eq!(
12214 assigned.to_string(),
12215 "(name=t, numPartitions=default, replicationFactor=default, replicasAssignments={0=[1, 2]}, configs=null)"
12216 );
12217 assert_eq!(
12218 NewTopic::new("orders", 3, 1)
12219 .config("cleanup.policy", "compact")
12220 .to_string(),
12221 "(name=orders, numPartitions=3, replicationFactor=1, replicasAssignments=null, configs={cleanup.policy=compact})"
12222 );
12223 let parts = NewPartitions::increase_to("t", 5);
12224 assert_eq!(parts.name(), "t");
12225 assert_eq!(parts.total_count(), 5);
12226 assert!(parts.assignments().is_none());
12227 assert_eq!(parts.to_string(), "(totalCount=5, newAssignments=null)");
12228 let with = parts.with_assignments([vec![1, 2]]);
12229 assert_eq!(with.assignments().map(<[Vec<i32>]>::len), Some(1));
12230 assert_eq!(with.to_string(), "(totalCount=5, newAssignments=[[1, 2]])");
12231 assert_eq!(
12232 NewPartitions::increase_to("t", 5)
12233 .with_assignments(Vec::<Vec<i32>>::new())
12234 .to_string(),
12235 "(totalCount=5, newAssignments=[])"
12236 );
12237 let abort = AbortTransactionSpec::new(("events", 2), 9, 1, 3);
12238 assert_eq!(
12239 abort.topic_partition(),
12240 crate::TopicPartition::new("events", 2)
12241 );
12242 assert_eq!(abort.producer_id(), 9);
12243 assert_eq!(abort.producer_epoch(), 1);
12244 assert_eq!(abort.coordinator_epoch(), 3);
12245 assert_eq!(
12246 abort.to_string(),
12247 "AbortTransactionSpec(topicPartition=events-2, producerId=9, producerEpoch=1, coordinatorEpoch=3)"
12248 );
12249 let member = MemberToRemove::new("i-1");
12250 assert_eq!(member.group_instance_id(), "i-1");
12251 let removed = RemovedMember {
12252 member_id: "m".into(),
12253 group_instance_id: Some("i-1".into()),
12254 error_code: 0,
12255 };
12256 assert_eq!(removed.member_id(), "m");
12257 assert_eq!(removed.group_instance_id(), Some("i-1"));
12258 assert_eq!(removed.error_code(), 0);
12259 let spec = ListConsumerGroupOffsetsSpec::topic_partitions([("t", 0)]);
12260 assert_eq!(spec.partitions().map(<[_]>::len), Some(1));
12261 assert_eq!(
12262 spec.to_string(),
12263 "ListConsumerGroupOffsetsSpec(topicPartitions=[t-0])"
12264 );
12265 assert_eq!(
12266 ListConsumerGroupOffsetsSpec::topic_partitions([("t", 0), ("t", 1)]).to_string(),
12267 "ListConsumerGroupOffsetsSpec(topicPartitions=[t-0, t-1])"
12268 );
12269 assert_eq!(
12270 ListConsumerGroupOffsetsSpec {
12271 partitions: Some(Vec::new()),
12272 }
12273 .to_string(),
12274 "ListConsumerGroupOffsetsSpec(topicPartitions=[])"
12275 );
12276 assert!(ListConsumerGroupOffsetsSpec::all().partitions().is_none());
12277 assert_eq!(
12278 ListConsumerGroupOffsetsSpec::all().to_string(),
12279 "ListConsumerGroupOffsetsSpec(topicPartitions=null)"
12280 );
12281 let update = FeatureUpdate::new("metadata.version", 20);
12282 assert_eq!(update.name(), "metadata.version");
12283 assert_eq!(update.max_version_level(), 20);
12284 assert!(!update.is_delete_request());
12285 assert_eq!(
12286 update.to_string(),
12287 "FeatureUpdate{maxVersionLevel:20, upgradeType:UPGRADE}"
12288 );
12289 assert_eq!(
12290 FeatureUpdate::new("metadata.version", 20)
12291 .upgrade_type(UpgradeType::SafeDowngrade)
12292 .to_string(),
12293 "FeatureUpdate{maxVersionLevel:20, upgradeType:SAFE_DOWNGRADE}"
12294 );
12295 assert!(!FeatureUpdate::new("metadata.version", 20)
12296 .upgrade_type(UpgradeType::SafeDowngrade)
12297 .is_delete_request());
12298 assert_eq!(
12299 FeatureUpdate::new("metadata.version", 0)
12300 .upgrade_type(UpgradeType::UnsafeDowngrade)
12301 .to_string(),
12302 "FeatureUpdate{maxVersionLevel:0, upgradeType:UNSAFE_DOWNGRADE}"
12303 );
12304 assert!(FeatureUpdate::new("metadata.version", 0)
12305 .upgrade_type(UpgradeType::UnsafeDowngrade)
12306 .is_delete_request());
12307 assert!(FeatureUpdate::new("metadata.version", 0)
12308 .upgrade_type(UpgradeType::SafeDowngrade)
12309 .is_delete_request());
12310 assert!(!FeatureUpdate::new("metadata.version", 0).is_delete_request());
12311 assert_eq!(UpgradeType::Upgrade.to_string(), "UPGRADE");
12312 assert_eq!(UpgradeType::SafeDowngrade.to_string(), "SAFE_DOWNGRADE");
12313 assert_eq!(UpgradeType::UnsafeDowngrade.to_string(), "UNSAFE_DOWNGRADE");
12314 assert_eq!(UpgradeType::Upgrade.code(), UPGRADE_TYPE_UPGRADE);
12315 assert_eq!(
12316 UpgradeType::SafeDowngrade.code(),
12317 UPGRADE_TYPE_SAFE_DOWNGRADE
12318 );
12319 assert_eq!(
12320 UpgradeType::UnsafeDowngrade.code(),
12321 UPGRADE_TYPE_UNSAFE_DOWNGRADE
12322 );
12323 assert_eq!(UpgradeType::from_code(1), Some(UpgradeType::Upgrade));
12324 assert_eq!(UpgradeType::from_code(2), Some(UpgradeType::SafeDowngrade));
12325 assert_eq!(
12326 UpgradeType::from_code(3),
12327 Some(UpgradeType::UnsafeDowngrade)
12328 );
12329 assert!(UpgradeType::from_code(0).is_none());
12330 assert!(UpgradeType::from_code(99).is_none());
12331 assert_eq!(
12332 FeatureUpdate::new("metadata.version", 1)
12333 .upgrade_type(0_i8)
12334 .to_string(),
12335 "FeatureUpdate{maxVersionLevel:1, upgradeType:UNKNOWN}"
12336 );
12337 let range = SupportedVersionRange::new("metadata.version", 1, 20).unwrap();
12338 assert_eq!(range.min_version(), 1);
12339 assert_eq!(range.max_version(), 20);
12340 assert_eq!(
12341 range.to_string(),
12342 "SupportedVersionRange[min_version:1, max_version:20]"
12343 );
12344 let fin = FinalizedVersionRange::new("metadata.version", 1, 17).unwrap();
12345 assert_eq!(fin.min_version_level(), 1);
12346 assert_eq!(fin.max_version_level(), 17);
12347 assert_eq!(
12348 fin.to_string(),
12349 "FinalizedVersionRange[min_version_level:1, max_version_level:17]"
12350 );
12351 let kraft = SupportedVersionRange::new("kraft.version", 0, 1).unwrap();
12352 assert_eq!(
12353 kraft.to_string(),
12354 "SupportedVersionRange[min_version:0, max_version:1]"
12355 );
12356 let err = SupportedVersionRange::new("f", 0, -1).unwrap_err();
12357 assert!(
12358 matches!(err, Error::Protocol(_)),
12359 "negative maxVersion is Java IllegalArgumentException, got {err}"
12360 );
12361 assert!(
12362 err.to_string().contains(
12363 "Expected 0 <= minVersion <= maxVersion but received minVersion:0, maxVersion:-1."
12364 ),
12365 "got {err}"
12366 );
12367 let err = SupportedVersionRange::new("f", 2, 1).unwrap_err();
12368 assert!(
12369 err.to_string()
12370 .contains("but received minVersion:2, maxVersion:1."),
12371 "got {err}"
12372 );
12373 let err = FinalizedVersionRange::new("f", -1, 1).unwrap_err();
12374 assert!(
12375 matches!(err, Error::Protocol(_)),
12376 "negative minVersionLevel is Java IllegalArgumentException, got {err}"
12377 );
12378 assert!(
12379 err.to_string().contains(
12380 "Expected minVersionLevel >= 0, maxVersionLevel >= 0 and maxVersionLevel >= minVersionLevel, but received minVersionLevel: -1, maxVersionLevel: 1"
12381 ),
12382 "got {err}"
12383 );
12384 let err = FinalizedVersionRange::new("f", 2, 1).unwrap_err();
12385 assert!(
12386 err.to_string()
12387 .contains("minVersionLevel: 2, maxVersionLevel: 1"),
12388 "got {err}"
12389 );
12390 let zero = FinalizedVersionRange::new("f", 0, 0).unwrap();
12391 assert_eq!(zero.min_version_level(), 0);
12392 assert_eq!(zero.max_version_level(), 0);
12393 let md = FeatureMetadata {
12394 supported_features: vec![range],
12395 finalized_features: vec![fin],
12396 finalized_features_epoch: Some(8),
12397 zk_migration_ready: true,
12398 };
12399 assert_eq!(md.supported_features().len(), 1);
12400 assert_eq!(md.finalized_features().len(), 1);
12401 assert_eq!(md.finalized_features_epoch(), Some(8));
12402 assert!(md.zk_migration_ready());
12403 assert_eq!(
12404 md.to_string(),
12405 "FeatureMetadata{finalizedFeatures:{(metadata.version -> FinalizedVersionRange[min_version_level:1, max_version_level:17])}, finalizedFeaturesEpoch:8, supportedFeatures:{(metadata.version -> SupportedVersionRange[min_version:1, max_version:20])}}"
12406 );
12407 let empty_epoch = FeatureMetadata {
12408 finalized_features_epoch: None,
12409 supported_features: Vec::new(),
12410 finalized_features: Vec::new(),
12411 zk_migration_ready: false,
12412 };
12413 assert_eq!(
12414 empty_epoch.to_string(),
12415 "FeatureMetadata{finalizedFeatures:{}, finalizedFeaturesEpoch: , supportedFeatures:{}}"
12416 );
12417 let cluster = ClusterDescription::new(
12418 0,
12419 None,
12420 Some("mock".into()),
12421 1,
12422 1,
12423 AUTHORIZED_OPERATIONS_OMITTED,
12424 vec![DescribeClusterBroker::new(
12425 1,
12426 "127.0.0.1",
12427 9092,
12428 Some("r".into()),
12429 false,
12430 )],
12431 );
12432 assert_eq!(cluster.error_code(), 0);
12433 assert_eq!(cluster.cluster_id(), Some("mock"));
12434 assert_eq!(
12435 cluster.cluster_resource().to_string(),
12436 "ClusterResource(clusterId=mock)"
12437 );
12438 assert_eq!(cluster.cluster_resource().cluster_id(), Some("mock"));
12439 assert_eq!(
12440 ClusterResource::new(None::<String>).to_string(),
12441 "ClusterResource(clusterId=null)"
12442 );
12443 assert_eq!(cluster.controller_id(), 1);
12444 assert_eq!(cluster.brokers()[0].id(), 1);
12445 assert_eq!(cluster.brokers()[0].host(), "127.0.0.1");
12446 assert_eq!(cluster.brokers()[0].port(), 9092);
12447 assert_eq!(cluster.brokers()[0].rack(), Some("r"));
12448 assert!(cluster.brokers()[0].has_rack());
12449 assert!(!cluster.brokers()[0].is_fenced());
12450 assert_eq!(cluster.brokers()[0].id_string(), "1");
12451 assert!(!cluster.brokers()[0].is_empty());
12452 assert!(cluster.error_message().is_none());
12453 assert_eq!(
12454 cluster.brokers()[0].to_string(),
12455 "127.0.0.1:9092 (id: 1 rack: r isFenced: false)"
12456 );
12457 let empty = DescribeClusterBroker::no_node();
12458 assert_eq!(empty.id(), -1);
12459 assert!(empty.is_empty());
12460 assert_eq!(empty.id_string(), "-1");
12461 assert_eq!(empty.to_string(), ":-1 (id: -1 rack: null isFenced: false)");
12462 let from_broker: Node =
12463 crate::protocol::api::Broker::new(1, "127.0.0.1", 9092, Some("r".into())).into();
12464 assert_eq!(from_broker.id(), 1);
12465 assert_eq!(from_broker.host(), "127.0.0.1");
12466 assert!(!from_broker.is_fenced());
12467 let from_endpoint: Node =
12468 crate::protocol::api::NodeEndpoint::new(2, "h2", 9093, None).into();
12469 assert_eq!(from_endpoint.id_string(), "2");
12470 assert!(!from_endpoint.has_rack());
12471 assert_eq!(
12472 from_endpoint.to_string(),
12473 "h2:9093 (id: 2 rack: null isFenced: false)"
12474 );
12475 }
12476
12477 #[test]
12478 fn config_get_and_replacement_match_java() {
12479 let entry = ConfigEntry::new("retention.ms", Some("1000".into()));
12480 let config = Config::new([entry.clone()]);
12481 assert_eq!(config.entries(), std::slice::from_ref(&entry));
12482 assert_eq!(config.get("retention.ms"), Some(&entry));
12483 assert_eq!(config.get("missing"), None);
12484 let described = DescribeConfigsResult {
12485 error_code: 0,
12486 error_message: None,
12487 resource_type: CONFIG_RESOURCE_TOPIC,
12488 name: "t".into(),
12489 entries: vec![entry.clone()],
12490 };
12491 assert_eq!(described.config().get("retention.ms"), Some(&entry));
12492 assert_eq!(described.name(), "t");
12493 assert_eq!(described.error_code(), 0);
12494 assert_eq!(described.entries().len(), 1);
12495 assert_eq!(entry.name(), "retention.ms");
12496 assert_eq!(entry.value(), Some("1000"));
12497 let replacement = ConfigReplacement::from_config(ConfigResource::topic("t"), &config);
12498 assert_eq!(replacement.resource.name, "t");
12499 assert_eq!(
12500 replacement.configs,
12501 vec![("retention.ms".into(), Some("1000".into()))]
12502 );
12503 let by_resource = crate::protocol::admin::DescribeConfigsResponse::result_map(
12504 std::slice::from_ref(&described),
12505 );
12506 assert_eq!(
12507 by_resource.get(&ConfigResource::topic("t")),
12508 Some(&described)
12509 );
12510 let unknown_type = DescribeConfigsResult {
12511 error_code: 0,
12512 error_message: None,
12513 resource_type: 99,
12514 name: "x".into(),
12515 entries: Vec::new(),
12516 };
12517 let unknown_map = crate::protocol::admin::DescribeConfigsResponse::result_map(
12518 std::slice::from_ref(&unknown_type),
12519 );
12520 assert_eq!(
12521 unknown_map.get(&ConfigResource {
12522 resource_type: 0,
12523 name: "x".into(),
12524 keys: None,
12525 }),
12526 Some(&unknown_type)
12527 );
12528 }
12529
12530 #[test]
12531 fn alter_configs_errors_match_java() {
12532 let ok = AlterConfigsResourceResult::error(CONFIG_RESOURCE_TOPIC, "t", 0);
12533 let bad = AlterConfigsResourceResult::error(
12534 CONFIG_RESOURCE_BROKER,
12535 "1",
12536 crate::error::INVALID_REQUEST,
12537 );
12538 let results = [ok, bad];
12539 let errors = crate::protocol::admin::AlterConfigsResponse::errors(&results);
12540 assert_eq!(errors.len(), 2);
12541 assert_eq!(
12542 errors.get(&ConfigResource::topic("t")),
12543 Some(&crate::error::ApiError::NONE)
12544 );
12545 let broker = errors.get(&ConfigResource::broker(1)).unwrap();
12546 assert_eq!(broker.error(), crate::error::INVALID_REQUEST);
12547 assert!(broker.message().is_none());
12548 assert_eq!(
12549 crate::protocol::admin::IncrementalAlterConfigsResponse::from_response_data(&results),
12550 errors
12551 );
12552 }
12553
12554 #[test]
12555 fn alter_configs_request_configs_match_java() {
12556 let resources = [
12557 AlterConfigsResource {
12558 resource_type: CONFIG_RESOURCE_TOPIC,
12559 name: "t".into(),
12560 configs: vec![TopicConfig {
12561 name: "retention.ms".into(),
12562 value: Some("1000".into()),
12563 }],
12564 },
12565 AlterConfigsResource {
12566 resource_type: 99,
12567 name: "x".into(),
12568 configs: vec![TopicConfig {
12569 name: "unset".into(),
12570 value: None,
12571 }],
12572 },
12573 ];
12574 let configs = crate::protocol::admin::AlterConfigsRequest::configs(&resources);
12575 assert_eq!(configs.len(), 2);
12576 assert_eq!(
12577 configs
12578 .get(&ConfigResource::topic("t"))
12579 .and_then(|c| c.get("retention.ms"))
12580 .and_then(ConfigEntry::value),
12581 Some("1000")
12582 );
12583 let unknown_key = ConfigResource {
12584 resource_type: 0,
12585 name: "x".into(),
12586 keys: None,
12587 };
12588 assert_eq!(
12589 configs
12590 .get(&unknown_key)
12591 .and_then(|c| c.get("unset"))
12592 .and_then(ConfigEntry::value),
12593 None
12594 );
12595 assert_eq!(
12596 configs.get(&unknown_key).map(|c| c.entries().len()),
12597 Some(1)
12598 );
12599 }
12600
12601 #[test]
12602 fn user_scram_credential_alteration_user_matches_java() {
12603 let d = UserScramCredentialDeletion::new("alice", SCRAM_SHA_256);
12604 assert_eq!(d.user(), "alice");
12605 assert_eq!(d.mechanism(), ScramMechanism::Sha256);
12606 assert_eq!(UserScramCredentialAlteration::from(d).user(), "alice");
12607 let u = UserScramCredentialUpsertion::new(
12608 "bob",
12609 SCRAM_SHA_256,
12610 4096,
12611 b"s".to_vec(),
12612 b"p".to_vec(),
12613 );
12614 assert_eq!(u.user(), "bob");
12615 assert_eq!(u.salt(), b"s");
12616 assert_eq!(u.credential_info().mechanism(), ScramMechanism::Sha256);
12617 assert_eq!(u.credential_info().iterations(), 4096);
12618 assert_eq!(UserScramCredentialAlteration::from(u).user(), "bob");
12619 let result = UserScramCredentialResult {
12620 user: "alice".into(),
12621 error_code: 0,
12622 error_message: None,
12623 };
12624 assert_eq!(result.user(), "alice");
12625 assert_eq!(result.error_code(), 0);
12626 assert!(result.error_message().is_none());
12627 }
12628
12629 #[test]
12630 fn config_resource_type_matches_protocol_consts() {
12631 assert_eq!(i8::from(ConfigResourceType::Topic), CONFIG_RESOURCE_TOPIC);
12632 assert_eq!(i8::from(ConfigResourceType::Broker), CONFIG_RESOURCE_BROKER);
12633 assert_eq!(
12634 i8::from(ConfigResourceType::BrokerLogger),
12635 CONFIG_RESOURCE_BROKER_LOGGER
12636 );
12637 assert_eq!(
12638 i8::from(ConfigResourceType::ClientMetrics),
12639 CONFIG_RESOURCE_CLIENT_METRICS
12640 );
12641 assert_eq!(i8::from(ConfigResourceType::Group), CONFIG_RESOURCE_GROUP);
12642 assert_eq!(
12643 ConfigResource::topic("t").resource_type,
12644 i8::from(ConfigResourceType::Topic)
12645 );
12646 assert_eq!(ConfigResource::topic("t").name(), "t");
12647 assert_eq!(
12648 ConfigResource::topic("t").resource_type(),
12649 Some(ConfigResourceType::Topic)
12650 );
12651 assert!(!ConfigResource::topic("t").is_default());
12652 assert_eq!(
12653 ConfigResource::topic("t").to_string(),
12654 "ConfigResource(type=TOPIC, name='t')"
12655 );
12656 assert!(ConfigResource::of(ConfigResourceType::Broker, "").is_default());
12657 assert_eq!(
12658 ConfigResource::of(ConfigResourceType::Broker, "").to_string(),
12659 "ConfigResource(type=BROKER, name='')"
12660 );
12661 assert_eq!(ConfigResourceType::Topic.to_string(), "TOPIC");
12662 assert_eq!(ConfigResourceType::Topic.id(), CONFIG_RESOURCE_TOPIC);
12663 assert_eq!(ConfigResourceType::Broker.id(), CONFIG_RESOURCE_BROKER);
12664 assert_eq!(
12665 ConfigResourceType::BrokerLogger.id(),
12666 CONFIG_RESOURCE_BROKER_LOGGER
12667 );
12668 assert_eq!(
12669 ConfigResourceType::ClientMetrics.id(),
12670 CONFIG_RESOURCE_CLIENT_METRICS
12671 );
12672 assert_eq!(ConfigResourceType::Group.id(), CONFIG_RESOURCE_GROUP);
12673 assert_eq!(
12674 ConfigResourceType::from_id(CONFIG_RESOURCE_TOPIC),
12675 Some(ConfigResourceType::Topic)
12676 );
12677 assert_eq!(ConfigResourceType::from_id(99), None);
12678 }
12679
12680 #[test]
12681 fn config_type_and_source_match_wire_ids() {
12682 assert_eq!(i8::from(ConfigType::Unknown), CONFIG_TYPE_UNKNOWN);
12683 assert_eq!(i8::from(ConfigType::String), CONFIG_TYPE_STRING);
12684 assert_eq!(i8::from(ConfigType::Password), CONFIG_TYPE_PASSWORD);
12685 assert_eq!(ConfigType::from_id(CONFIG_TYPE_STRING), ConfigType::String);
12686 assert_eq!(ConfigType::from_id(99), ConfigType::Unknown);
12687 assert_eq!(ConfigType::Unknown.to_string(), "UNKNOWN");
12688 assert_eq!(ConfigType::Int.to_string(), "INT");
12689 assert_eq!(ConfigSource::Default.to_string(), "DEFAULT_CONFIG");
12690 assert_eq!(
12691 ConfigSource::DynamicTopic.to_string(),
12692 "DYNAMIC_TOPIC_CONFIG"
12693 );
12694 assert_eq!(i8::from(ConfigSource::Unknown), CONFIG_SOURCE_UNKNOWN);
12695 assert_eq!(
12696 i8::from(ConfigSource::DynamicTopic),
12697 CONFIG_SOURCE_DYNAMIC_TOPIC
12698 );
12699 assert_eq!(i8::from(ConfigSource::Default), CONFIG_SOURCE_DEFAULT);
12700 assert_eq!(
12701 i8::from(ConfigSource::DynamicGroup),
12702 CONFIG_SOURCE_DYNAMIC_GROUP
12703 );
12704 assert_eq!(
12705 ConfigSource::from_id(CONFIG_SOURCE_DEFAULT),
12706 ConfigSource::Default
12707 );
12708 assert_eq!(ConfigSource::from_id(-1), ConfigSource::Unknown);
12709 assert_eq!(ConfigSource::from_id(99), ConfigSource::Unknown);
12710 let def = ConfigEntry {
12711 name: "k".into(),
12712 value: Some("v".into()),
12713 read_only: false,
12714 source: CONFIG_SOURCE_DEFAULT,
12715 is_sensitive: false,
12716 synonyms: Vec::new(),
12717 config_type: CONFIG_TYPE_INT,
12718 documentation: None,
12719 };
12720 assert!(def.is_default());
12721 assert_eq!(def.source(), ConfigSource::Default);
12722 assert_eq!(def.config_type(), ConfigType::Int);
12723 assert_eq!(def.name(), "k");
12724 assert_eq!(def.value(), Some("v"));
12725 assert!(!def.is_sensitive());
12726 assert!(!def.is_read_only());
12727 assert!(def.synonyms().is_empty());
12728 assert!(def.documentation().is_none());
12729 let syn = ConfigSynonym {
12730 name: "k".into(),
12731 value: Some("v".into()),
12732 source: CONFIG_SOURCE_DEFAULT,
12733 };
12734 assert_eq!(syn.name(), "k");
12735 assert_eq!(syn.value(), Some("v"));
12736 assert_eq!(syn.source(), ConfigSource::Default);
12737 assert_eq!(
12738 syn.to_string(),
12739 "ConfigSynonym(name=k, value=v, source=DEFAULT_CONFIG)"
12740 );
12741 assert_eq!(
12742 def.to_string(),
12743 "ConfigEntry(name=k, value=v, source=DEFAULT_CONFIG, isSensitive=false, isReadOnly=false, synonyms=[], type=INT, documentation=null)"
12744 );
12745 let with_syn = ConfigEntry {
12746 synonyms: vec![syn.clone()],
12747 ..def.clone()
12748 };
12749 assert_eq!(
12750 with_syn.to_string(),
12751 "ConfigEntry(name=k, value=v, source=DEFAULT_CONFIG, isSensitive=false, isReadOnly=false, synonyms=[ConfigSynonym(name=k, value=v, source=DEFAULT_CONFIG)], type=INT, documentation=null)"
12752 );
12753 }
12754
12755 #[test]
12756 fn alter_config_op_type_matches_java() {
12757 assert_eq!(i8::from(AlterConfigOpType::Set), ALTER_CONFIG_SET);
12758 assert_eq!(AlterConfigOpType::Set.id(), ALTER_CONFIG_SET);
12759 assert_eq!(AlterConfigOpType::Delete.id(), ALTER_CONFIG_DELETE);
12760 assert_eq!(AlterConfigOpType::Append.id(), ALTER_CONFIG_APPEND);
12761 assert_eq!(AlterConfigOpType::Subtract.id(), ALTER_CONFIG_SUBTRACT);
12762 assert_eq!(i8::from(AlterConfigOpType::Delete), ALTER_CONFIG_DELETE);
12763 assert_eq!(i8::from(AlterConfigOpType::Append), ALTER_CONFIG_APPEND);
12764 assert_eq!(i8::from(AlterConfigOpType::Subtract), ALTER_CONFIG_SUBTRACT);
12765 assert_eq!(
12766 AlterConfigOpType::from_id(ALTER_CONFIG_SET),
12767 Some(AlterConfigOpType::Set)
12768 );
12769 assert_eq!(AlterConfigOpType::from_id(99), None);
12770 assert_eq!(AlterConfigOpType::Set.to_string(), "SET");
12771 assert_eq!(AlterConfigOpType::Delete.to_string(), "DELETE");
12772 assert_eq!(AlterConfigOpType::Append.to_string(), "APPEND");
12773 assert_eq!(AlterConfigOpType::Subtract.to_string(), "SUBTRACT");
12774 let entry = ConfigEntry::new("retention.ms", Some("1000".into()));
12775 let op = AlterConfig::from_entry(&entry, AlterConfigOpType::Set);
12776 assert_eq!(op.op_type(), Some(AlterConfigOpType::Set));
12777 assert_eq!(op.config_entry().name, "retention.ms");
12778 assert_eq!(op.config_entry().value.as_deref(), Some("1000"));
12779 assert_eq!(
12780 op.to_string(),
12781 "AlterConfigOp{opType=SET, configEntry=ConfigEntry(name=retention.ms, value=1000, source=UNKNOWN, isSensitive=false, isReadOnly=false, synonyms=[], type=UNKNOWN, documentation=null)}"
12782 );
12783 let unknown = AlterConfig {
12784 name: "k".into(),
12785 op: 99,
12786 value: None,
12787 };
12788 assert!(unknown.op_type().is_none());
12789 assert_eq!(
12790 unknown.to_string(),
12791 "AlterConfigOp{opType=null, configEntry=ConfigEntry(name=k, value=null, source=UNKNOWN, isSensitive=false, isReadOnly=false, synonyms=[], type=UNKNOWN, documentation=null)}"
12792 );
12793 assert_eq!(AlterConfigOp::set("k", "v").op, ALTER_CONFIG_SET);
12794 }
12795
12796 #[test]
12797 fn group_type_and_state_match_java() {
12798 assert_eq!(GroupType::Classic.as_str(), "Classic");
12799 assert_eq!(GroupType::parse("classic"), GroupType::Classic);
12800 assert_eq!(GroupType::parse("CONSUMER"), GroupType::Consumer);
12801 assert_eq!(GroupType::parse("Unknown"), GroupType::Unknown);
12802 assert_eq!(GroupType::parse("Streams"), GroupType::Unknown);
12803 assert_eq!(GroupType::parse("nope"), GroupType::Unknown);
12804 assert_eq!(GroupState::Stable.as_str(), "Stable");
12805 assert_eq!(GroupState::parse("stable"), GroupState::Stable);
12806 assert_eq!(
12807 GroupState::parse("PreparingRebalance"),
12808 GroupState::PreparingRebalance
12809 );
12810 assert_eq!(
12813 GroupState::parse("PREPARING_REBALANCE"),
12814 GroupState::Unknown
12815 );
12816 assert_eq!(GroupState::parse("Unknown"), GroupState::Unknown);
12817 assert_eq!(GroupState::parse("nope"), GroupState::Unknown);
12818 assert_eq!(
12819 GroupState::group_states_for_type(GroupType::Share),
12820 &[GroupState::Stable, GroupState::Dead, GroupState::Empty]
12821 );
12822 assert!(GroupState::group_states_for_type(GroupType::Unknown).is_empty());
12823 let listed = ListedGroup {
12824 group_id: "g".into(),
12825 protocol_type: "consumer".into(),
12826 group_state: "Stable".into(),
12827 group_type: "classic".into(),
12828 };
12829 assert_eq!(listed.group_state(), GroupState::Stable);
12830 assert_eq!(listed.group_type(), GroupType::Classic);
12831 assert_eq!(listed.group_id(), "g");
12832 assert_eq!(listed.protocol(), "consumer");
12833 assert!(!listed.is_simple_consumer_group());
12834 assert_eq!(
12835 listed.to_string(),
12836 "(groupId='g', type=Classic, protocol='consumer', groupState=Stable)"
12837 );
12838 assert_eq!(
12839 ListedGroup::new("g").to_string(),
12840 "(groupId='g', type=none, protocol='', groupState=none)"
12841 );
12842 let simple = ListedGroup {
12843 group_id: "s".into(),
12844 protocol_type: String::new(),
12845 group_state: "Empty".into(),
12846 group_type: "classic".into(),
12847 };
12848 assert!(simple.is_simple_consumer_group());
12849 assert_eq!(simple.protocol(), "");
12850 let consumer = ConsumerGroupDescription::Consumer({
12851 let mut g = DescribedConsumerGroup::new("g-cons", 0);
12852 g.group_state = "Stable".into();
12853 g.group_epoch = 4;
12854 g.assignment_epoch = 5;
12855 g.assignor_name = "uniform".into();
12856 g
12857 });
12858 assert_eq!(consumer.group_id(), "g-cons");
12859 assert_eq!(consumer.group_state(), "Stable");
12860 assert_eq!(consumer.partition_assignor(), "uniform");
12861 assert_eq!(consumer.group_type(), GroupType::Consumer);
12862 assert_eq!(consumer.group_epoch(), Some(4));
12863 assert_eq!(consumer.target_assignment_epoch(), Some(5));
12864 assert!(!consumer.is_simple_consumer_group());
12865 assert!(consumer.is_consumer_protocol());
12866 let classic = ConsumerGroupDescription::Classic({
12867 let mut g = DescribedGroup::new("g-classic", 0);
12868 g.group_state = "Stable".into();
12869 g.protocol_type = "consumer".into();
12870 g.protocol_data = "range".into();
12871 g
12872 });
12873 assert_eq!(classic.partition_assignor(), "range");
12874 assert_eq!(classic.group_type(), GroupType::Classic);
12875 assert!(classic.group_epoch().is_none());
12876 assert!(classic.target_assignment_epoch().is_none());
12877 assert!(!classic.is_simple_consumer_group());
12878 assert!(!classic.is_consumer_protocol());
12879 let simple_desc = ConsumerGroupDescription::Classic(DescribedGroup::new("s", 0));
12880 assert!(simple_desc.is_simple_consumer_group());
12881 assert_eq!(simple_desc.partition_assignor(), "");
12882 }
12883
12884 #[test]
12885 fn remaining_admin_result_getters_match_java() {
12886 let pid = ProducerIdBlock {
12887 producer_id_start: 1000,
12888 producer_id_len: 1000,
12889 };
12890 assert_eq!(pid.producer_id_start(), 1000);
12891 assert_eq!(pid.producer_id_len(), 1000);
12892 let fenced = FencedProducer {
12893 transactional_id: "tid".into(),
12894 producer_id: 9,
12895 epoch: 1,
12896 };
12897 assert_eq!(fenced.transactional_id(), "tid");
12898 assert_eq!(fenced.producer_id(), 9);
12899 assert_eq!(fenced.epoch(), 1);
12900 let deleted = DeletedAclsFilterResult {
12901 error_code: 0,
12902 error_message: None,
12903 matching: vec![crate::protocol::acl::DeleteAclsResponse::matching_acl(
12904 &AclBinding::allow_topic("t", "User:alice"),
12905 &crate::error::ApiError::NONE,
12906 )],
12907 };
12908 assert_eq!(deleted.error_code(), 0);
12909 assert!(deleted.error_message().is_none());
12910 assert_eq!(deleted.matching().len(), 1);
12911 let altered = AlterConfigsResourceResult {
12912 error_code: 0,
12913 error_message: None,
12914 resource_type: CONFIG_RESOURCE_TOPIC,
12915 name: "t".into(),
12916 };
12917 assert_eq!(altered.error_code(), 0);
12918 assert_eq!(altered.name(), "t");
12919 assert!(altered.error_message().is_none());
12920 let created = TopicResult {
12921 name: "orders".into(),
12922 error_code: 0,
12923 error_message: None,
12924 topic_id: Uuid::ONE.to_bytes(),
12925 num_partitions: 3,
12926 replication_factor: 1,
12927 configs: vec![CreatedTopicConfig {
12928 name: "cleanup.policy".into(),
12929 value: Some("compact".into()),
12930 read_only: false,
12931 config_source: CONFIG_SOURCE_DYNAMIC_TOPIC,
12932 is_sensitive: false,
12933 }],
12934 };
12935 assert_eq!(created.name(), "orders");
12936 assert_eq!(created.error_code(), 0);
12937 assert!(created.error_message().is_none());
12938 assert_eq!(created.topic_id(), Uuid::ONE);
12939 assert_eq!(created.num_partitions(), 3);
12940 assert_eq!(created.replication_factor(), 1);
12941 assert_eq!(
12942 created
12943 .config()
12944 .get("cleanup.policy")
12945 .and_then(ConfigEntry::value),
12946 Some("compact")
12947 );
12948 let listed = ListedConfigResource::new("r", CONFIG_RESOURCE_CLIENT_METRICS);
12949 assert_eq!(listed.name(), "r");
12950 assert_eq!(
12951 listed.resource_type(),
12952 Some(ConfigResourceType::ClientMetrics)
12953 );
12954 assert!(!listed.is_default());
12955 assert_eq!(
12956 listed.to_string(),
12957 "ConfigResource(type=CLIENT_METRICS, name='r')"
12958 );
12959 assert!(ListedConfigResource::new("", CONFIG_RESOURCE_BROKER).is_default());
12960 assert_eq!(
12961 ListedConfigResource::new("t", CONFIG_RESOURCE_TOPIC).to_string(),
12962 "ConfigResource(type=TOPIC, name='t')"
12963 );
12964 let resource = listed.to_config_resource();
12965 assert_eq!(resource.name(), "r");
12966 assert_eq!(
12967 resource.resource_type(),
12968 Some(ConfigResourceType::ClientMetrics)
12969 );
12970 let owned = ConfigResource::from(listed);
12971 assert_eq!(owned.name(), "r");
12972 let deleted = DeletableGroupResult::new("g", 0);
12973 assert_eq!(deleted.group_id(), "g");
12974 assert_eq!(deleted.error_code(), 0);
12975 let telemetry = GetTelemetrySubscriptionsResponse::new(
12976 0,
12977 Uuid::from_bytes([0x11; 16]),
12978 1,
12979 vec![1],
12980 1000,
12981 100,
12982 true,
12983 vec!["m".into()],
12984 );
12985 assert_eq!(telemetry.error_code(), 0);
12986 assert_eq!(telemetry.throttle_time_ms(), 0);
12987 assert_eq!(telemetry.client_instance_id(), Uuid::from_bytes([0x11; 16]));
12988 assert_eq!(telemetry.subscription_id(), 1);
12989 assert_eq!(telemetry.accepted_compression_types(), &[1]);
12990 assert_eq!(telemetry.push_interval_ms(), 1000);
12991 assert_eq!(telemetry.telemetry_max_bytes(), 100);
12992 assert!(telemetry.delta_temporality());
12993 assert_eq!(telemetry.requested_metrics(), &["m".to_string()]);
12994 assert_eq!(PushTelemetryResponse::new(0).error_code(), 0);
12995 assert_eq!(PushTelemetryResponse::new(0).throttle_time_ms(), 0);
12996 let push =
12997 PushTelemetryRequest::new(Uuid::from_bytes([0x11; 16]), 1, false, 0, b"m".to_vec());
12998 assert_eq!(push.client_instance_id(), Uuid::from_bytes([0x11; 16]));
12999 assert_eq!(push.subscription_id(), 1);
13000 assert!(!push.terminating());
13001 assert_eq!(push.compression_type(), 0);
13002 assert_eq!(push.metrics(), b"m");
13003 let cluster = ClusterDescription::new(
13004 0,
13005 None,
13006 Some("c".into()),
13007 2,
13008 1,
13009 AUTHORIZED_OPERATIONS_OMITTED,
13010 vec![
13011 DescribeClusterBroker::new(1, "h1", 9092, None, false),
13012 DescribeClusterBroker::new(2, "h2", 9092, Some("r".into()), false),
13013 ],
13014 );
13015 assert_eq!(cluster.nodes().len(), 2);
13016 assert_eq!(cluster.controller().map(DescribeClusterBroker::id), Some(2));
13017 assert_eq!(
13018 cluster.authorized_operations(),
13019 AUTHORIZED_OPERATIONS_OMITTED
13020 );
13021 assert!(cluster.error_message().is_none());
13022 let controller = cluster.controller().unwrap();
13023 assert_eq!(controller.id_string(), "2");
13024 assert!(!controller.is_empty());
13025 assert_eq!(
13026 controller.to_string(),
13027 "h2:9092 (id: 2 rack: r isFenced: false)"
13028 );
13029 let no_controller = ClusterDescription {
13030 controller_id: -1,
13031 ..cluster.clone()
13032 };
13033 assert!(no_controller.controller().is_none());
13034 let unreg = UnregisterBrokerResponse::new(0, None);
13035 assert_eq!(unreg.error_code(), 0);
13036 assert!(unreg.error_message().is_none());
13037 }
13038
13039 #[test]
13040 fn delegation_token_getters_match_java() {
13041 let renewer = CreatableRenewer::new("User", "r");
13042 assert_eq!(renewer.principal_type(), "User");
13043 assert_eq!(renewer.principal_name(), "r");
13044 assert_eq!(renewer.to_string(), "User:r");
13045 assert_eq!(CreatableRenewer::USER_TYPE, "User");
13046 assert_eq!(CreatableRenewer::anonymous().to_string(), "User:ANONYMOUS");
13047 assert_eq!(
13048 DescribeDelegationTokenOwner::anonymous().principal_name(),
13049 "ANONYMOUS"
13050 );
13051 assert_eq!(
13052 DescribedDelegationTokenRenewer::anonymous().to_string(),
13053 "User:ANONYMOUS"
13054 );
13055 let req = CreateDelegationTokenRequest::new(
13056 Some("User".into()),
13057 Some("alice".into()),
13058 vec![renewer],
13059 -1,
13060 );
13061 assert_eq!(req.owner_principal_type(), Some("User"));
13062 assert_eq!(req.owner_principal_name(), Some("alice"));
13063 assert_eq!(req.renewers().len(), 1);
13064 assert_eq!(req.max_lifetime_ms(), -1);
13065 let created = CreateDelegationTokenResponse::new(
13066 0,
13067 "User",
13068 "alice",
13069 "User",
13070 "bob",
13071 1,
13072 2,
13073 3,
13074 "tid",
13075 vec![0xaa],
13076 );
13077 assert_eq!(created.error_code(), 0);
13078 assert_eq!(created.principal_type(), "User");
13079 assert_eq!(created.principal_name(), "alice");
13080 assert_eq!(created.owner_as_string(), "User:alice");
13081 assert_eq!(created.token_requester_as_string(), "User:bob");
13082 assert_eq!(created.issue_timestamp(), 1);
13083 assert_eq!(created.expiry_timestamp(), 2);
13084 assert_eq!(created.max_timestamp(), 3);
13085 assert_eq!(created.token_id(), "tid");
13086 assert_eq!(created.hmac(), &[0xaa]);
13087 assert_eq!(created.hmac_as_base64_string(), "qg==");
13088 let created_debug = format!("{created:?}");
13089 assert!(
13090 created_debug.contains("[*******]"),
13091 "Java DelegationToken.toString redacts hmac: {created_debug}"
13092 );
13093 assert!(
13094 !created_debug.contains("aa") && !created_debug.contains("170"),
13095 "Debug must not leak hmac bytes: {created_debug}"
13096 );
13097 let owner = DescribeDelegationTokenOwner::new("User", "alice");
13098 assert_eq!(owner.to_string(), "User:alice");
13099 let described_req = DescribeDelegationTokenRequest::new(Some(vec![owner]));
13100 assert_eq!(
13101 described_req
13102 .owners()
13103 .map(<[DescribeDelegationTokenOwner]>::len),
13104 Some(1)
13105 );
13106 let token = DescribedDelegationToken::new(
13107 "User",
13108 "alice",
13109 "User",
13110 "bob",
13111 1,
13112 2,
13113 3,
13114 "tid",
13115 vec![0xaa],
13116 vec![DescribedDelegationTokenRenewer::new("User", "r")],
13117 );
13118 assert_eq!(token.owner_as_string(), "User:alice");
13119 assert_eq!(token.hmac_as_base64_string(), "qg==");
13120 assert_eq!(token.renewers()[0].to_string(), "User:r");
13121 assert_eq!(token.renewers_as_string(), vec!["User:r".to_string()]);
13122 assert!(token.owner_or_renewer("User", "alice"));
13123 assert!(token.owner_or_renewer("User", "bob"));
13124 assert!(token.owner_or_renewer("User", "r"));
13125 assert!(!token.owner_or_renewer("User", "other"));
13126 assert_eq!(
13127 token.to_string(),
13128 "DelegationToken{tokenInformation=TokenInformation{owner=User:alice, tokenRequester=User:bob, renewers=[User:r], issueTimestamp=1, maxTimestamp=3, expiryTimestamp=2, tokenId='tid'}, hmac=[*******]}"
13129 );
13130 let empty_renewers = DescribedDelegationToken::new(
13131 "User",
13132 "alice",
13133 "User",
13134 "bob",
13135 1,
13136 2,
13137 3,
13138 "tid",
13139 vec![0xaa],
13140 vec![],
13141 );
13142 assert_eq!(
13143 empty_renewers.to_string(),
13144 "DelegationToken{tokenInformation=TokenInformation{owner=User:alice, tokenRequester=User:bob, renewers=[], issueTimestamp=1, maxTimestamp=3, expiryTimestamp=2, tokenId='tid'}, hmac=[*******]}"
13145 );
13146 let two_renewers = DescribedDelegationToken::new(
13147 "User",
13148 "alice",
13149 "User",
13150 "bob",
13151 1,
13152 2,
13153 3,
13154 "tid",
13155 vec![0xaa],
13156 vec![
13157 DescribedDelegationTokenRenewer::new("User", "r"),
13158 DescribedDelegationTokenRenewer::new("User", "s"),
13159 ],
13160 );
13161 assert_eq!(
13162 two_renewers.to_string(),
13163 "DelegationToken{tokenInformation=TokenInformation{owner=User:alice, tokenRequester=User:bob, renewers=[User:r, User:s], issueTimestamp=1, maxTimestamp=3, expiryTimestamp=2, tokenId='tid'}, hmac=[*******]}"
13164 );
13165 let token_debug = format!("{token:?}");
13166 assert!(token_debug.contains("[*******]"));
13167 assert!(!token_debug.contains("170"));
13168 let listed = DescribeDelegationTokenResponse::new(0, vec![token]);
13169 assert_eq!(listed.error_code(), 0);
13170 assert_eq!(listed.tokens().len(), 1);
13171 let renewed = RenewDelegationTokenResponse::new(0, 9);
13172 assert_eq!(renewed.error_code(), 0);
13173 assert_eq!(renewed.expiry_timestamp(), 9);
13174 let expired = ExpireDelegationTokenResponse::new(0, 8);
13175 assert_eq!(expired.error_code(), 0);
13176 assert_eq!(expired.expiry_timestamp(), 8);
13177 let renew_req = RenewDelegationTokenRequest::new(vec![0xaa], -1);
13178 assert_eq!(renew_req.hmac(), &[0xaa]);
13179 assert_eq!(renew_req.renew_period_ms(), -1);
13180 let expire_req = ExpireDelegationTokenRequest::new(vec![0xaa], -1);
13181 assert_eq!(expire_req.hmac(), &[0xaa]);
13182 assert_eq!(expire_req.expiry_time_period_ms(), -1);
13183 }
13184
13185 #[test]
13186 fn new_partition_reassignment_matches_java() {
13187 let neu = NewPartitionReassignment::new([2, 1]).unwrap();
13188 assert_eq!(neu.target_replicas(), &[2, 1]);
13189 let err = NewPartitionReassignment::new(Vec::<i32>::new()).unwrap_err();
13190 assert!(
13191 err.to_string().contains("without any replicas"),
13192 "Java NewPartitionReassignment rejects an empty replica list: {err}"
13193 );
13194 let assigned = PartitionReassignment::from_new(("t", 0), Some(neu));
13195 assert_eq!(assigned.topic(), "t");
13196 assert_eq!(assigned.partition(), 0);
13197 assert_eq!(assigned.replicas(), Some(&[2, 1][..]));
13198 let cancelled = PartitionReassignment::from_new(("t", 0), None);
13199 assert!(cancelled.replicas().is_none());
13200 let ongoing = OngoingReassignment {
13201 topic: "t".into(),
13202 partition: 0,
13203 replicas: vec![2, 1],
13204 adding_replicas: vec![2],
13205 removing_replicas: vec![3],
13206 };
13207 assert_eq!(ongoing.topic(), "t");
13208 assert_eq!(ongoing.partition(), 0);
13209 assert_eq!(ongoing.replicas(), &[2, 1]);
13210 assert_eq!(ongoing.adding_replicas(), &[2]);
13211 assert_eq!(ongoing.removing_replicas(), &[3]);
13212 assert_eq!(
13213 ongoing.to_string(),
13214 "PartitionReassignment(replicas=[2, 1], addingReplicas=[2], removingReplicas=[3])"
13215 );
13216 let result = ReassignmentResult {
13217 topic: "t".into(),
13218 partition: 0,
13219 error_code: 0,
13220 error_message: None,
13221 };
13222 assert_eq!(result.topic(), "t");
13223 assert_eq!(result.partition(), 0);
13224 assert_eq!(result.error_code(), 0);
13225 assert!(result.error_message().is_none());
13226 }
13227
13228 #[test]
13229 fn uuid_matches_java() {
13230 assert_eq!(Uuid::ZERO.to_string(), "AAAAAAAAAAAAAAAAAAAAAA");
13231 assert_eq!(Uuid::ONE.to_string(), "AAAAAAAAAAAAAAAAAAAAAQ");
13232 assert_eq!(Uuid::ZERO_UUID, Uuid::ZERO);
13233 assert_eq!(Uuid::ONE_UUID, Uuid::ONE);
13234 assert_eq!(Uuid::METADATA_TOPIC_ID, Uuid::ONE);
13235 assert_eq!(Uuid::ZERO.most_significant_bits(), 0);
13236 assert_eq!(Uuid::ZERO.least_significant_bits(), 0);
13237 assert_eq!(Uuid::ONE.most_significant_bits(), 0);
13238 assert_eq!(Uuid::ONE.least_significant_bits(), 1);
13239 assert_eq!(
13240 Uuid::from_string("AAAAAAAAAAAAAAAAAAAAAA").unwrap(),
13241 Uuid::ZERO
13242 );
13243 assert_eq!(
13244 Uuid::from_string("AAAAAAAAAAAAAAAAAAAAAQ").unwrap(),
13245 Uuid::ONE
13246 );
13247 assert_eq!(
13248 Uuid::from_string("AAAAAAAAAAAAAAAAAAAAAQ==").unwrap(),
13249 Uuid::ONE,
13250 "Java fromString accepts URL-safe padding"
13251 );
13252 let invalid = Uuid::from_string("!!!!").unwrap_err();
13253 assert!(
13254 invalid
13255 .to_string()
13256 .contains("Uuid string `!!!!` is not a base64url UUID"),
13257 "{invalid}"
13258 );
13259 let too_long = Uuid::from_string("AAAAAAAAAAAAAAAAAAAAAAAAA").unwrap_err();
13260 assert!(
13261 too_long.to_string().contains(
13262 "Input string with prefix `AAAAAAAAAAAAAAAAAAAAAAAA` is too long to be decoded as a base64 UUID"
13263 ),
13264 "{too_long}"
13265 );
13266 let wrong_len = Uuid::from_string("AAAA").unwrap_err();
13267 assert!(
13268 wrong_len.to_string().contains(
13269 "Input string `AAAA` decoded as 3 bytes, which is not equal to the expected 16 bytes of a base64-encoded UUID"
13270 ),
13271 "{wrong_len}"
13272 );
13273 let parsed: Uuid = "AAAAAAAAAAAAAAAAAAAAAA".parse().unwrap();
13274 assert_eq!(parsed, Uuid::ZERO);
13275 assert_eq!(<[u8; 16]>::from(Uuid::ONE), Uuid::ONE.to_bytes());
13276 let neg = Uuid::from_parts(i64::MIN, 0);
13277 assert!(neg < Uuid::ZERO, "Java compareTo uses signed longs");
13278 let listing = TopicListing::new("t", Uuid::ONE, false);
13279 assert_eq!(listing.topic_id(), Uuid::ONE);
13280 assert_eq!(listing.name(), "t");
13281 assert!(!listing.is_internal());
13282 assert_eq!(
13283 listing.to_string(),
13284 "(name=t, topicId=AAAAAAAAAAAAAAAAAAAAAQ, internal=false)"
13285 );
13286 assert!(Uuid::RESERVED.contains(&Uuid::ZERO));
13287 assert!(Uuid::RESERVED.contains(&Uuid::ONE));
13288 assert!(Uuid::RESERVED.contains(&Uuid::METADATA_TOPIC_ID));
13289 let samples: Vec<Uuid> = (0..32).map(|_| Uuid::random_uuid()).collect();
13290 for (i, u) in samples.iter().enumerate() {
13291 assert_ne!(*u, Uuid::ZERO);
13292 assert_ne!(*u, Uuid::ONE);
13293 assert!(
13294 !u.to_string().starts_with('-'),
13295 "Java randomUuid skips dash-prefixed base64url"
13296 );
13297 assert_eq!(
13298 u.as_bytes().get(6).map(|b| b >> 4),
13299 Some(4),
13300 "Java randomUuid is RFC 4122 version 4"
13301 );
13302 assert_eq!(
13303 u.as_bytes().get(8).map(|b| b >> 6),
13304 Some(2),
13305 "Java randomUuid is RFC 4122 variant 2"
13306 );
13307 for (j, other) in samples.iter().enumerate() {
13308 if i != j {
13309 assert_ne!(*u, *other);
13310 }
13311 }
13312 }
13313 }
13314
13315 #[test]
13316 fn topic_collection_matches_java() {
13317 let names = TopicCollection::of_topic_names(["orders", "events"]);
13318 assert_eq!(names.topic_names().map(<[String]>::len), Some(2));
13319 assert!(names.topic_ids().is_none());
13320 let ids = TopicCollection::of_topic_ids([Uuid::ONE, Uuid::ZERO]);
13321 assert_eq!(ids.topic_ids(), Some(&[Uuid::ONE, Uuid::ZERO][..]));
13322 assert!(ids.topic_names().is_none());
13323 let from_bytes = TopicCollection::of_topic_ids([[1u8; 16]]);
13324 assert_eq!(
13325 from_bytes.topic_ids().and_then(|ids| ids.first().copied()),
13326 Some(Uuid::from_bytes([1u8; 16]))
13327 );
13328 let empty = TopicCollection::of_topic_ids(Vec::<Uuid>::new());
13329 assert_eq!(empty, TopicCollection::Ids(Vec::new()));
13330 assert!(empty.topic_ids().unwrap().is_empty());
13331 }
13332
13333 #[test]
13334 fn scram_mechanism_matches_protocol_consts() {
13335 assert_eq!(i8::from(ScramMechanism::Unknown), SCRAM_UNKNOWN);
13336 assert_eq!(i8::from(ScramMechanism::Sha256), SCRAM_SHA_256);
13337 assert_eq!(i8::from(ScramMechanism::Sha512), SCRAM_SHA_512);
13338 assert_eq!(ScramMechanism::Unknown.id(), SCRAM_UNKNOWN);
13339 assert_eq!(ScramMechanism::Sha256.id(), SCRAM_SHA_256);
13340 assert_eq!(ScramMechanism::Sha512.id(), SCRAM_SHA_512);
13341 assert_eq!(
13342 ScramMechanism::from_id(SCRAM_SHA_256),
13343 ScramMechanism::Sha256
13344 );
13345 assert_eq!(ScramMechanism::from_id(0), ScramMechanism::Unknown);
13346 assert_eq!(ScramMechanism::from_id(99), ScramMechanism::Unknown);
13347 assert_eq!(
13348 ScramMechanism::from_mechanism_name("SCRAM-SHA-256"),
13349 ScramMechanism::Sha256
13350 );
13351 assert_eq!(
13352 ScramMechanism::from_mechanism_name("SCRAM_SHA_256"),
13353 ScramMechanism::Unknown
13354 );
13355 assert_eq!(ScramMechanism::Sha256.mechanism_name(), "SCRAM-SHA-256");
13356 assert_eq!(ScramMechanism::Unknown.to_string(), "UNKNOWN");
13357 assert_eq!(ScramMechanism::Sha256.to_string(), "SCRAM_SHA_256");
13358 assert_eq!(ScramMechanism::Sha512.to_string(), "SCRAM_SHA_512");
13359 let info = ScramCredentialInfo::new(ScramMechanism::Sha512, 8192);
13360 assert_eq!(info.mechanism(), ScramMechanism::Sha512);
13361 assert_eq!(info.iterations(), 8192);
13362 assert_eq!(
13363 info.to_string(),
13364 "ScramCredentialInfo{mechanism=SCRAM_SHA_512, iterations=8192}"
13365 );
13366 let described = DescribeUserScramCredentialsResult {
13367 user: "alice".into(),
13368 error_code: 0,
13369 error_message: None,
13370 credential_infos: vec![ScramCredentialInfo::new(ScramMechanism::Sha256, 4096)],
13371 };
13372 assert_eq!(
13373 described.to_string(),
13374 "UserScramCredentialsDescription{name='alice', credentialInfos=[ScramCredentialInfo{mechanism=SCRAM_SHA_256, iterations=4096}]}"
13375 );
13376 }
13377
13378 #[test]
13379 fn topic_listings_skip_errors_and_unnamed() {
13380 use crate::protocol::api::{PartitionMetadata, TopicMetadata};
13381
13382 let md = MetadataResponse {
13383 throttle_time_ms: 0,
13384 brokers: Vec::new(),
13385 cluster_id: None,
13386 controller_id: 1,
13387 topics: vec![
13388 TopicMetadata {
13389 error_code: 0,
13390 name: Some("ok".into()),
13391 topic_id: [1; 16],
13392 is_internal: false,
13393 partitions: vec![PartitionMetadata {
13394 error_code: 0,
13395 partition_index: 0,
13396 leader_id: 1,
13397 leader_epoch: 3,
13398 replica_nodes: vec![1],
13399 isr_nodes: vec![1],
13400 offline_replicas: Vec::new(),
13401 }],
13402 topic_authorized_operations: i32::MIN,
13403 },
13404 TopicMetadata {
13405 error_code: error::UNKNOWN_TOPIC_OR_PARTITION,
13406 name: Some("gone".into()),
13407 topic_id: [0; 16],
13408 is_internal: false,
13409 partitions: vec![PartitionMetadata {
13410 error_code: error::UNKNOWN_TOPIC_OR_PARTITION,
13411 partition_index: 0,
13412 leader_id: -1,
13413 leader_epoch: -1,
13414 replica_nodes: Vec::new(),
13415 isr_nodes: Vec::new(),
13416 offline_replicas: Vec::new(),
13417 }],
13418 topic_authorized_operations: i32::MIN,
13419 },
13420 TopicMetadata {
13421 error_code: 0,
13422 name: None,
13423 topic_id: [2; 16],
13424 is_internal: true,
13425 partitions: Vec::new(),
13426 topic_authorized_operations: i32::MIN,
13427 },
13428 TopicMetadata {
13429 error_code: 0,
13430 name: Some("__consumer_offsets".into()),
13431 topic_id: [3; 16],
13432 is_internal: true,
13433 partitions: Vec::new(),
13434 topic_authorized_operations: i32::MIN,
13435 },
13436 ],
13437 cluster_authorized_operations: MetadataResponse::AUTHORIZED_OPERATIONS_OMITTED,
13438 error_code: 0,
13439 };
13440 let listed = topic_listings_from(&md, true);
13441 assert_eq!(listed.len(), 2);
13442 assert_eq!(listed[0].name, "ok");
13443 assert_eq!(listed[0].name(), "ok");
13444 assert_eq!(listed[0].topic_id, [1; 16]);
13445 assert_eq!(listed[0].topic_id(), Uuid::from_bytes([1; 16]));
13446 assert!(!listed[0].is_internal());
13447 assert_eq!(listed[1].name, "__consumer_offsets");
13448 assert!(listed[1].is_internal);
13449 assert!(listed[1].is_internal());
13450 let listed = topic_listings_from(&md, false);
13451 assert_eq!(listed.len(), 1);
13452 assert_eq!(listed[0].name, "ok");
13453 let described = topic_descriptions_including_unnamed(&md);
13454 assert_eq!(described.len(), 4);
13455 assert_eq!(described[0].name, "ok");
13456 assert_eq!(described[0].partitions.len(), 1);
13457 assert_eq!(described[0].partitions().len(), 1);
13458 assert_eq!(described[0].error_code(), 0);
13459 assert_eq!(
13460 described[0].authorized_operations(),
13461 AUTHORIZED_OPERATIONS_OMITTED
13462 );
13463 assert_eq!(described[0].partitions[0].leader_epoch, 3);
13464 assert_eq!(described[1].name, "gone");
13465 assert_eq!(described[1].error_code, error::UNKNOWN_TOPIC_OR_PARTITION);
13466 assert_eq!(described[1].error_code(), error::UNKNOWN_TOPIC_OR_PARTITION);
13467 assert!(described[1].partitions.is_empty());
13468 assert!(described[1].partitions().is_empty());
13469 assert!(described[2].name.is_empty());
13470 assert_eq!(described[2].topic_id, [2; 16]);
13471 assert_eq!(described[2].topic_id(), Uuid::from_bytes([2; 16]));
13472 assert_eq!(described[3].name, "__consumer_offsets");
13473 assert_eq!(described[3].name(), "__consumer_offsets");
13474 assert!(described[3].is_internal);
13475 assert!(described[3].is_internal());
13476 let named =
13477 topic_descriptions_for_names(&md, &["ok".into(), "gone".into(), "missing".into()]);
13478 assert_eq!(named.len(), 3);
13479 assert_eq!(named[0].name, "ok");
13480 assert_eq!(named[0].error_code, 0);
13481 assert_eq!(named[1].name, "gone");
13482 assert_eq!(named[1].error_code, error::UNKNOWN_TOPIC_OR_PARTITION);
13483 assert_eq!(named[2].name, "missing");
13484 assert_eq!(named[2].error_code, error::UNKNOWN_TOPIC_OR_PARTITION);
13485 }
13486
13487 #[test]
13488 fn replica_log_dirs_group_and_map_current_future() {
13489 let replicas = [
13490 TopicPartitionReplica::new("t", 0, 2),
13491 TopicPartitionReplica::new("t", 1, 2),
13492 TopicPartitionReplica::new("u", 0, 1),
13493 TopicPartitionReplica::new("t", 0, 2),
13494 ];
13495 assert_eq!(replica_broker_ids(&replicas), vec![2, 1]);
13496 let on_two = describable_topics_for_broker(&replicas, 2);
13497 assert_eq!(on_two.len(), 1);
13498 assert_eq!(on_two[0].name, "t");
13499 assert_eq!(on_two[0].partitions, vec![0, 1]);
13500 let on_one = describable_topics_for_broker(&replicas, 1);
13501 assert_eq!(on_one.len(), 1);
13502 assert_eq!(on_one[0].name, "u");
13503 assert_eq!(on_one[0].partitions, vec![0]);
13504
13505 let dirs = vec![
13506 DescribeLogDirsResult::new(
13507 0,
13508 "/current",
13509 vec![DescribeLogDirsTopic::new(
13510 "t",
13511 vec![DescribeLogDirsPartition::new(0, 10, 3, false)],
13512 )],
13513 -1,
13514 -1,
13515 ),
13516 DescribeLogDirsResult::new(
13517 0,
13518 "/future",
13519 vec![DescribeLogDirsTopic::new(
13520 "t",
13521 vec![DescribeLogDirsPartition::new(0, 4, 7, true)],
13522 )],
13523 -1,
13524 -1,
13525 ),
13526 DescribeLogDirsResult::new(56, "/offline", Vec::new(), -1, -1),
13527 ];
13528 let replica = TopicPartitionReplica::new("t", 0, 2);
13529 let info = replica_log_dir_info_from(&replica, &dirs);
13530 assert_eq!(info.current_log_dir.as_deref(), Some("/current"));
13531 assert_eq!(info.current_offset_lag, 3);
13532 assert_eq!(info.future_log_dir.as_deref(), Some("/future"));
13533 assert_eq!(info.future_offset_lag, 7);
13534 let missing = replica_log_dir_info_from(&TopicPartitionReplica::new("t", 9, 2), &dirs);
13535 assert_eq!(missing, ReplicaLogDirInfo::unknown());
13536 }
13537
13538 #[test]
13539 fn list_offset_topic_requests_keeps_duplicate_partitions() {
13540 let cluster = Cluster::default();
13541 let queries = [
13542 (
13543 crate::TopicPartition::new("t", 0),
13544 crate::EARLIEST_TIMESTAMP,
13545 ),
13546 (crate::TopicPartition::new("t", 0), crate::LATEST_TIMESTAMP),
13547 (crate::TopicPartition::new("t", 1), crate::LATEST_TIMESTAMP),
13548 ];
13549 let topics = list_offset_topic_requests(&queries, &[0, 1, 2], &cluster);
13550 assert_eq!(topics.len(), 1);
13551 assert_eq!(topics[0].name, "t");
13552 assert_eq!(topics[0].partitions.len(), 3);
13553 assert_eq!(topics[0].partitions[0].timestamp, crate::EARLIEST_TIMESTAMP);
13554 assert_eq!(topics[0].partitions[1].timestamp, crate::LATEST_TIMESTAMP);
13555 assert_eq!(topics[0].partitions[2].partition, 1);
13556 }
13557}