Browse Source

KAFKA-9507; AdminClient should check for missing committed offsets (#8057)

Addresses exception being thrown by `AdminClient` when `listConsumerGroupOffsets` returns a negative offset. A negative offset indicates the absence of a committed offset for a requested partition, and should result in a null in the returned offset map.

Reviewers: Anna Povzner <anna@confluent.io>, Jason Gustafson <jason@confluent.io>
pull/8070/head
David Mao 5 years ago committed by GitHub
parent
commit
7a2a198d1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      clients/src/main/java/org/apache/kafka/clients/admin/KafkaAdminClient.java
  2. 1
      clients/src/main/java/org/apache/kafka/clients/admin/ListConsumerGroupOffsetsResult.java
  3. 7
      clients/src/test/java/org/apache/kafka/clients/admin/KafkaAdminClientTest.java

5
clients/src/main/java/org/apache/kafka/clients/admin/KafkaAdminClient.java

@ -3100,7 +3100,12 @@ public class KafkaAdminClient extends AdminClient {
final Long offset = partitionData.offset; final Long offset = partitionData.offset;
final String metadata = partitionData.metadata; final String metadata = partitionData.metadata;
final Optional<Integer> leaderEpoch = partitionData.leaderEpoch; final Optional<Integer> leaderEpoch = partitionData.leaderEpoch;
// Negative offset indicates that the group has no committed offset for this partition
if (offset < 0) {
groupOffsetsListing.put(topicPartition, null);
} else {
groupOffsetsListing.put(topicPartition, new OffsetAndMetadata(offset, leaderEpoch, metadata)); groupOffsetsListing.put(topicPartition, new OffsetAndMetadata(offset, leaderEpoch, metadata));
}
} else { } else {
log.warn("Skipping return offset for {} due to error {}.", topicPartition, error); log.warn("Skipping return offset for {} due to error {}.", topicPartition, error);
} }

1
clients/src/main/java/org/apache/kafka/clients/admin/ListConsumerGroupOffsetsResult.java

@ -40,6 +40,7 @@ public class ListConsumerGroupOffsetsResult {
/** /**
* Return a future which yields a map of topic partitions to OffsetAndMetadata objects. * Return a future which yields a map of topic partitions to OffsetAndMetadata objects.
* If the group does not have a committed offset for this partition, the corresponding value in the returned map will be null.
*/ */
public KafkaFuture<Map<TopicPartition, OffsetAndMetadata>> partitionsToOffsetAndMetadata() { public KafkaFuture<Map<TopicPartition, OffsetAndMetadata>> partitionsToOffsetAndMetadata() {
return future; return future;

7
clients/src/test/java/org/apache/kafka/clients/admin/KafkaAdminClientTest.java

@ -1494,6 +1494,7 @@ public class KafkaAdminClientTest {
TopicPartition myTopicPartition0 = new TopicPartition("my_topic", 0); TopicPartition myTopicPartition0 = new TopicPartition("my_topic", 0);
TopicPartition myTopicPartition1 = new TopicPartition("my_topic", 1); TopicPartition myTopicPartition1 = new TopicPartition("my_topic", 1);
TopicPartition myTopicPartition2 = new TopicPartition("my_topic", 2); TopicPartition myTopicPartition2 = new TopicPartition("my_topic", 2);
TopicPartition myTopicPartition3 = new TopicPartition("my_topic", 3);
final Map<TopicPartition, OffsetFetchResponse.PartitionData> responseData = new HashMap<>(); final Map<TopicPartition, OffsetFetchResponse.PartitionData> responseData = new HashMap<>();
responseData.put(myTopicPartition0, new OffsetFetchResponse.PartitionData(10, responseData.put(myTopicPartition0, new OffsetFetchResponse.PartitionData(10,
@ -1502,15 +1503,19 @@ public class KafkaAdminClientTest {
Optional.empty(), "", Errors.NONE)); Optional.empty(), "", Errors.NONE));
responseData.put(myTopicPartition2, new OffsetFetchResponse.PartitionData(20, responseData.put(myTopicPartition2, new OffsetFetchResponse.PartitionData(20,
Optional.empty(), "", Errors.NONE)); Optional.empty(), "", Errors.NONE));
responseData.put(myTopicPartition3, new OffsetFetchResponse.PartitionData(OffsetFetchResponse.INVALID_OFFSET,
Optional.empty(), "", Errors.NONE));
env.kafkaClient().prepareResponse(new OffsetFetchResponse(Errors.NONE, responseData)); env.kafkaClient().prepareResponse(new OffsetFetchResponse(Errors.NONE, responseData));
final ListConsumerGroupOffsetsResult result = env.adminClient().listConsumerGroupOffsets("group-0"); final ListConsumerGroupOffsetsResult result = env.adminClient().listConsumerGroupOffsets("group-0");
final Map<TopicPartition, OffsetAndMetadata> partitionToOffsetAndMetadata = result.partitionsToOffsetAndMetadata().get(); final Map<TopicPartition, OffsetAndMetadata> partitionToOffsetAndMetadata = result.partitionsToOffsetAndMetadata().get();
assertEquals(3, partitionToOffsetAndMetadata.size()); assertEquals(4, partitionToOffsetAndMetadata.size());
assertEquals(10, partitionToOffsetAndMetadata.get(myTopicPartition0).offset()); assertEquals(10, partitionToOffsetAndMetadata.get(myTopicPartition0).offset());
assertEquals(0, partitionToOffsetAndMetadata.get(myTopicPartition1).offset()); assertEquals(0, partitionToOffsetAndMetadata.get(myTopicPartition1).offset());
assertEquals(20, partitionToOffsetAndMetadata.get(myTopicPartition2).offset()); assertEquals(20, partitionToOffsetAndMetadata.get(myTopicPartition2).offset());
assertTrue(partitionToOffsetAndMetadata.containsKey(myTopicPartition3));
assertNull(partitionToOffsetAndMetadata.get(myTopicPartition3));
} }
} }

Loading…
Cancel
Save