diff --git a/checkstyle/suppressions.xml b/checkstyle/suppressions.xml
index 20224a48c74e2..c1f9b340ddf4a 100644
--- a/checkstyle/suppressions.xml
+++ b/checkstyle/suppressions.xml
@@ -68,7 +68,7 @@
files="(Utils|Topic|KafkaLZ4BlockOutputStream|AclData|JoinGroupRequest).java"/>
+ files="(ConsumerCoordinator|Fetcher|KafkaProducer|ConfigDef|KerberosLogin|AbstractRequest|AbstractResponse|Selector|SslFactory|SslTransportLayer|SaslClientAuthenticator|SaslClientCallbackHandler|SaslServerAuthenticator|AbstractCoordinator|TransactionManager|AbstractStickyAssignor|DefaultSslEngineFactory|Authorizer|RecordAccumulator|MemoryRecords|FetchSessionHandler).java"/>
diff --git a/clients/src/main/java/org/apache/kafka/clients/FetchSessionHandler.java b/clients/src/main/java/org/apache/kafka/clients/FetchSessionHandler.java
index 8e29f9c4dc19b..aca847c67be8a 100644
--- a/clients/src/main/java/org/apache/kafka/clients/FetchSessionHandler.java
+++ b/clients/src/main/java/org/apache/kafka/clients/FetchSessionHandler.java
@@ -17,6 +17,7 @@
package org.apache.kafka.clients;
+import org.apache.kafka.common.TopicIdPartition;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.Uuid;
import org.apache.kafka.common.protocol.Errors;
@@ -76,11 +77,6 @@ public FetchSessionHandler(LogContext logContext, int node) {
private LinkedHashMap sessionPartitions =
new LinkedHashMap<>(0);
- /**
- * All of the topic ids mapped to topic names for topics which exist in the fetch request session.
- */
- private Map sessionTopicIds = new HashMap<>(0);
-
/**
* All of the topic names mapped to topic ids for topics which exist in the fetch request session.
*/
@@ -99,22 +95,18 @@ public static class FetchRequestData {
/**
* The partitions to send in the request's "forget" list.
*/
- private final List toForget;
-
- /**
- * All of the partitions which exist in the fetch request session.
- */
- private final Map sessionPartitions;
+ private final List toForget;
/**
- * All of the topic IDs for topics which exist in the fetch request session.
+ * The partitions to send in the request's "forget" list if
+ * the version is >= 13.
*/
- private final Map topicIds;
+ private final List toReplace;
/**
- * All of the topic IDs for topics which exist in the fetch request session
+ * All of the partitions which exist in the fetch request session.
*/
- private final Map topicNames;
+ private final Map sessionPartitions;
/**
* The metadata to use in this fetch request.
@@ -128,17 +120,15 @@ public static class FetchRequestData {
private final boolean canUseTopicIds;
FetchRequestData(Map toSend,
- List toForget,
+ List toForget,
+ List toReplace,
Map sessionPartitions,
- Map topicIds,
- Map topicNames,
FetchMetadata metadata,
boolean canUseTopicIds) {
this.toSend = toSend;
this.toForget = toForget;
+ this.toReplace = toReplace;
this.sessionPartitions = sessionPartitions;
- this.topicIds = topicIds;
- this.topicNames = topicNames;
this.metadata = metadata;
this.canUseTopicIds = canUseTopicIds;
}
@@ -153,10 +143,17 @@ public Map toSend() {
/**
* Get a list of partitions to forget in this fetch request.
*/
- public List toForget() {
+ public List toForget() {
return toForget;
}
+ /**
+ * Get a list of partitions to forget in this fetch request.
+ */
+ public List toReplace() {
+ return toReplace;
+ }
+
/**
* Get the full set of partitions involved in this fetch request.
*/
@@ -164,14 +161,6 @@ public Map sessionPartitions() {
return sessionPartitions;
}
- public Map topicIds() {
- return topicIds;
- }
-
- public Map topicNames() {
- return topicNames;
- }
-
public FetchMetadata metadata() {
return metadata;
}
@@ -201,7 +190,14 @@ public String toString() {
}
bld.append("), toForget=(");
prefix = "";
- for (TopicPartition partition : toForget) {
+ for (TopicIdPartition partition : toForget) {
+ bld.append(prefix);
+ bld.append(partition);
+ prefix = ", ";
+ }
+ bld.append("), toReplace=(");
+ prefix = "";
+ for (TopicIdPartition partition : toReplace) {
bld.append(prefix);
bld.append(partition);
prefix = ", ";
@@ -216,15 +212,6 @@ public String toString() {
}
}
}
- bld.append("), topicIds=(");
- String prefix = "";
- for (Map.Entry entry : topicIds.entrySet()) {
- bld.append(prefix);
- bld.append(entry.getKey());
- bld.append(": ");
- bld.append(entry.getValue());
- prefix = ", ";
- }
if (canUseTopicIds) {
bld.append("), canUseTopicIds=True");
} else {
@@ -250,32 +237,32 @@ public class Builder {
* incremental fetch requests (see below).
*/
private LinkedHashMap next;
- private Map topicIds;
+ private Map topicNames;
private final boolean copySessionPartitions;
private int partitionsWithoutTopicIds = 0;
Builder() {
this.next = new LinkedHashMap<>();
- this.topicIds = new HashMap<>();
+ this.topicNames = new HashMap<>();
this.copySessionPartitions = true;
}
Builder(int initialSize, boolean copySessionPartitions) {
this.next = new LinkedHashMap<>(initialSize);
- this.topicIds = new HashMap<>(initialSize);
+ this.topicNames = new HashMap<>();
this.copySessionPartitions = copySessionPartitions;
}
/**
* Mark that we want data from this partition in the upcoming fetch.
*/
- public void add(TopicPartition topicPartition, Uuid topicId, PartitionData data) {
+ public void add(TopicPartition topicPartition, PartitionData data) {
next.put(topicPartition, data);
// topicIds should not change between adding partitions and building, so we can use putIfAbsent
- if (!topicId.equals(Uuid.ZERO_UUID)) {
- topicIds.putIfAbsent(topicPartition.topic(), topicId);
- } else {
+ if (data.topicId.equals(Uuid.ZERO_UUID)) {
partitionsWithoutTopicIds++;
+ } else {
+ topicNames.putIfAbsent(data.topicId, topicPartition.topic());
}
}
@@ -285,52 +272,55 @@ public FetchRequestData build() {
if (nextMetadata.isFull()) {
if (log.isDebugEnabled()) {
log.debug("Built full fetch {} for node {} with {}.",
- nextMetadata, node, partitionsToLogString(next.keySet()));
+ nextMetadata, node, topicPartitionsToLogString(next.keySet()));
}
sessionPartitions = next;
next = null;
// Only add topic IDs to the session if we are using topic IDs.
if (canUseTopicIds) {
- sessionTopicIds = topicIds;
- sessionTopicNames = new HashMap<>(topicIds.size());
- topicIds.forEach((name, id) -> sessionTopicNames.put(id, name));
+ sessionTopicNames = topicNames;
} else {
- sessionTopicIds = new HashMap<>();
- sessionTopicNames = new HashMap<>();
+ sessionTopicNames = Collections.emptyMap();
}
- topicIds = null;
Map toSend =
- Collections.unmodifiableMap(new LinkedHashMap<>(sessionPartitions));
- Map toSendTopicIds =
- Collections.unmodifiableMap(new HashMap<>(sessionTopicIds));
- Map toSendTopicNames =
- Collections.unmodifiableMap(new HashMap<>(sessionTopicNames));
- return new FetchRequestData(toSend, Collections.emptyList(), toSend, toSendTopicIds, toSendTopicNames, nextMetadata, canUseTopicIds);
+ Collections.unmodifiableMap(new LinkedHashMap<>(sessionPartitions));
+ return new FetchRequestData(toSend, Collections.emptyList(), Collections.emptyList(), toSend, nextMetadata, canUseTopicIds);
}
- List added = new ArrayList<>();
- List removed = new ArrayList<>();
- List altered = new ArrayList<>();
+ List added = new ArrayList<>();
+ List removed = new ArrayList<>();
+ List altered = new ArrayList<>();
+ List replaced = new ArrayList<>();
for (Iterator> iter =
- sessionPartitions.entrySet().iterator(); iter.hasNext(); ) {
+ sessionPartitions.entrySet().iterator(); iter.hasNext(); ) {
Entry entry = iter.next();
TopicPartition topicPartition = entry.getKey();
PartitionData prevData = entry.getValue();
PartitionData nextData = next.remove(topicPartition);
if (nextData != null) {
- if (!prevData.equals(nextData)) {
+ // We basically check if the new partition had the same topic ID. If not,
+ // we add it to the "replaced" set. If the request is version 13 or higher, the replaced
+ // partition will be forgotten. In any case, we will send the new partition in the request.
+ if (!prevData.topicId.equals(nextData.topicId)
+ && !prevData.topicId.equals(Uuid.ZERO_UUID)
+ && !nextData.topicId.equals(Uuid.ZERO_UUID)) {
+ // Re-add the replaced partition to the end of 'next'
+ next.put(topicPartition, nextData);
+ entry.setValue(nextData);
+ replaced.add(new TopicIdPartition(prevData.topicId, topicPartition));
+ } else if (!prevData.equals(nextData)) {
// Re-add the altered partition to the end of 'next'
next.put(topicPartition, nextData);
entry.setValue(nextData);
- altered.add(topicPartition);
+ altered.add(new TopicIdPartition(nextData.topicId, topicPartition));
}
} else {
// Remove this partition from the session.
iter.remove();
// Indicate that we no longer want to listen to this partition.
- removed.add(topicPartition);
+ removed.add(new TopicIdPartition(prevData.topicId, topicPartition));
// If we do not have this topic ID in the builder or the session, we can not use topic IDs.
- if (canUseTopicIds && !topicIds.containsKey(topicPartition.topic()) && !sessionTopicIds.containsKey(topicPartition.topic()))
+ if (canUseTopicIds && prevData.topicId.equals(Uuid.ZERO_UUID))
canUseTopicIds = false;
}
}
@@ -346,38 +336,34 @@ public FetchRequestData build() {
break;
}
sessionPartitions.put(topicPartition, nextData);
- added.add(topicPartition);
+ added.add(new TopicIdPartition(nextData.topicId, topicPartition));
}
// Add topic IDs to session if we can use them. If an ID is inconsistent, we will handle in the receiving broker.
// If we switched from using topic IDs to not using them (or vice versa), that error will also be handled in the receiving broker.
if (canUseTopicIds) {
- for (Map.Entry topic : topicIds.entrySet()) {
- String topicName = topic.getKey();
- Uuid addedId = topic.getValue();
- sessionTopicIds.put(topicName, addedId);
- sessionTopicNames.put(addedId, topicName);
- }
+ sessionTopicNames = topicNames;
+ } else {
+ sessionTopicNames = Collections.emptyMap();
}
if (log.isDebugEnabled()) {
- log.debug("Built incremental fetch {} for node {}. Added {}, altered {}, removed {} " +
- "out of {}", nextMetadata, node, partitionsToLogString(added),
- partitionsToLogString(altered), partitionsToLogString(removed),
- partitionsToLogString(sessionPartitions.keySet()));
+ log.debug("Built incremental fetch {} for node {}. Added {}, altered {}, removed {}, " +
+ "replaced {} out of {}", nextMetadata, node, topicIdPartitionsToLogString(added),
+ topicIdPartitionsToLogString(altered), topicIdPartitionsToLogString(removed),
+ topicIdPartitionsToLogString(replaced), topicPartitionsToLogString(sessionPartitions.keySet()));
}
Map toSend = Collections.unmodifiableMap(next);
Map curSessionPartitions = copySessionPartitions
? Collections.unmodifiableMap(new LinkedHashMap<>(sessionPartitions))
: Collections.unmodifiableMap(sessionPartitions);
- Map toSendTopicIds =
- Collections.unmodifiableMap(new HashMap<>(sessionTopicIds));
- Map toSendTopicNames =
- Collections.unmodifiableMap(new HashMap<>(sessionTopicNames));
next = null;
- topicIds = null;
- return new FetchRequestData(toSend, Collections.unmodifiableList(removed), curSessionPartitions,
- toSendTopicIds, toSendTopicNames, nextMetadata, canUseTopicIds);
+ return new FetchRequestData(toSend,
+ Collections.unmodifiableList(removed),
+ Collections.unmodifiableList(replaced),
+ curSessionPartitions,
+ nextMetadata,
+ canUseTopicIds);
}
}
@@ -397,7 +383,14 @@ public Builder newBuilder(int size, boolean copySessionPartitions) {
return new Builder(size, copySessionPartitions);
}
- private String partitionsToLogString(Collection partitions) {
+ private String topicPartitionsToLogString(Collection partitions) {
+ if (!log.isTraceEnabled()) {
+ return String.format("%d partition(s)", partitions.size());
+ }
+ return "(" + Utils.join(partitions, ", ") + ")";
+ }
+
+ private String topicIdPartitionsToLogString(Collection partitions) {
if (!log.isTraceEnabled()) {
return String.format("%d partition(s)", partitions.size());
}
diff --git a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/Fetcher.java b/clients/src/main/java/org/apache/kafka/clients/consumer/internals/Fetcher.java
index 055f2d130a0ea..d567f5b5107c9 100644
--- a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/Fetcher.java
+++ b/clients/src/main/java/org/apache/kafka/clients/consumer/internals/Fetcher.java
@@ -262,11 +262,12 @@ public synchronized int sendFetches() {
maxVersion = ApiKeys.FETCH.latestVersion();
}
final FetchRequest.Builder request = FetchRequest.Builder
- .forConsumer(maxVersion, this.maxWaitMs, this.minBytes, data.toSend(), data.topicIds())
+ .forConsumer(maxVersion, this.maxWaitMs, this.minBytes, data.toSend())
.isolationLevel(isolationLevel)
.setMaxBytes(this.maxBytes)
.metadata(data.metadata())
- .toForget(data.toForget())
+ .removed(data.toForget())
+ .replaced(data.toReplace())
.rackId(clientRackId);
if (log.isDebugEnabled()) {
@@ -291,15 +292,13 @@ public void onSuccess(ClientResponse resp) {
return;
}
if (!handler.handleResponse(response, resp.requestHeader().apiVersion())) {
- if (response.error() == Errors.FETCH_SESSION_TOPIC_ID_ERROR
- || response.error() == Errors.UNKNOWN_TOPIC_ID
- || response.error() == Errors.INCONSISTENT_TOPIC_ID) {
+ if (response.error() == Errors.FETCH_SESSION_TOPIC_ID_ERROR) {
metadata.requestUpdate();
}
return;
}
- Map responseData = response.responseData(data.topicNames(), resp.requestHeader().apiVersion());
+ Map responseData = response.responseData(handler.sessionTopicNames(), resp.requestHeader().apiVersion());
Set partitions = new HashSet<>(responseData.keySet());
FetchResponseMetricAggregator metricAggregator = new FetchResponseMetricAggregator(sensors, partitions);
@@ -314,8 +313,8 @@ public void onSuccess(ClientResponse resp) {
new Object[]{partition, data.metadata()}).getMessage();
} else {
message = MessageFormatter.arrayFormat(
- "Response for missing session request partition: partition={}; metadata={}; toSend={}; toForget={}",
- new Object[]{partition, data.metadata(), data.toSend(), data.toForget()}).getMessage();
+ "Response for missing session request partition: partition={}; metadata={}; toSend={}; toForget={}; toReplace={}",
+ new Object[]{partition, data.metadata(), data.toSend(), data.toForget(), data.toReplace()}).getMessage();
}
// Received fetch response for missing session partition
@@ -1238,9 +1237,9 @@ private Map prepareFetchRequests() {
builder = handler.newBuilder();
fetchable.put(node, builder);
}
-
- builder.add(partition, topicIds.getOrDefault(partition.topic(), Uuid.ZERO_UUID), new FetchRequest.PartitionData(position.offset,
- FetchRequest.INVALID_LOG_START_OFFSET, this.fetchSize,
+ builder.add(partition, new FetchRequest.PartitionData(
+ topicIds.getOrDefault(partition.topic(), Uuid.ZERO_UUID),
+ position.offset, FetchRequest.INVALID_LOG_START_OFFSET, this.fetchSize,
position.currentLeader.epoch, Optional.empty()));
log.debug("Added {} fetch request for partition {} at position {} to node {}", isolationLevel,
@@ -1353,6 +1352,12 @@ private CompletedFetch initializeCompletedFetch(CompletedFetch nextCompletedFetc
} else if (error == Errors.UNKNOWN_TOPIC_OR_PARTITION) {
log.warn("Received unknown topic or partition error in fetch for partition {}", tp);
this.metadata.requestUpdate();
+ } else if (error == Errors.UNKNOWN_TOPIC_ID) {
+ log.warn("Received unknown topic ID error in fetch for partition {}", tp);
+ this.metadata.requestUpdate();
+ } else if (error == Errors.INCONSISTENT_TOPIC_ID) {
+ log.warn("Received inconsistent topic ID error in fetch for partition {}", tp);
+ this.metadata.requestUpdate();
} else if (error == Errors.OFFSET_OUT_OF_RANGE) {
Optional clearedReplicaId = subscriptions.clearPreferredReadReplica(tp);
if (!clearedReplicaId.isPresent()) {
diff --git a/clients/src/main/java/org/apache/kafka/common/requests/FetchRequest.java b/clients/src/main/java/org/apache/kafka/common/requests/FetchRequest.java
index 1531433fc30f6..48ba022610e43 100644
--- a/clients/src/main/java/org/apache/kafka/common/requests/FetchRequest.java
+++ b/clients/src/main/java/org/apache/kafka/common/requests/FetchRequest.java
@@ -17,10 +17,11 @@
package org.apache.kafka.common.requests;
import org.apache.kafka.common.IsolationLevel;
+import org.apache.kafka.common.TopicIdPartition;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.Uuid;
-import org.apache.kafka.common.errors.UnknownTopicIdException;
import org.apache.kafka.common.message.FetchRequestData;
+import org.apache.kafka.common.message.FetchRequestData.ForgottenTopic;
import org.apache.kafka.common.message.FetchResponseData;
import org.apache.kafka.common.protocol.ApiKeys;
import org.apache.kafka.common.protocol.ByteBufferAccessor;
@@ -47,13 +48,14 @@ public class FetchRequest extends AbstractRequest {
public static final long INVALID_LOG_START_OFFSET = -1L;
private final FetchRequestData data;
- private volatile LinkedHashMap fetchData = null;
- private volatile List toForget = null;
+ private volatile LinkedHashMap fetchData = null;
+ private volatile List toForget = null;
// This is an immutable read-only structures derived from FetchRequestData
private final FetchMetadata metadata;
public static final class PartitionData {
+ public final Uuid topicId;
public final long fetchOffset;
public final long logStartOffset;
public final int maxBytes;
@@ -61,21 +63,24 @@ public static final class PartitionData {
public final Optional lastFetchedEpoch;
public PartitionData(
+ Uuid topicId,
long fetchOffset,
long logStartOffset,
int maxBytes,
Optional currentLeaderEpoch
) {
- this(fetchOffset, logStartOffset, maxBytes, currentLeaderEpoch, Optional.empty());
+ this(topicId, fetchOffset, logStartOffset, maxBytes, currentLeaderEpoch, Optional.empty());
}
public PartitionData(
+ Uuid topicId,
long fetchOffset,
long logStartOffset,
int maxBytes,
Optional currentLeaderEpoch,
Optional lastFetchedEpoch
) {
+ this.topicId = topicId;
this.fetchOffset = fetchOffset;
this.logStartOffset = logStartOffset;
this.maxBytes = maxBytes;
@@ -88,7 +93,8 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PartitionData that = (PartitionData) o;
- return fetchOffset == that.fetchOffset &&
+ return Objects.equals(topicId, that.topicId) &&
+ fetchOffset == that.fetchOffset &&
logStartOffset == that.logStartOffset &&
maxBytes == that.maxBytes &&
Objects.equals(currentLeaderEpoch, that.currentLeaderEpoch) &&
@@ -97,13 +103,14 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
- return Objects.hash(fetchOffset, logStartOffset, maxBytes, currentLeaderEpoch, lastFetchedEpoch);
+ return Objects.hash(topicId, fetchOffset, logStartOffset, maxBytes, currentLeaderEpoch, lastFetchedEpoch);
}
@Override
public String toString() {
return "PartitionData(" +
- "fetchOffset=" + fetchOffset +
+ "topicId=" + topicId +
+ ", fetchOffset=" + fetchOffset +
", logStartOffset=" + logStartOffset +
", maxBytes=" + maxBytes +
", currentLeaderEpoch=" + currentLeaderEpoch +
@@ -124,33 +131,31 @@ public static class Builder extends AbstractRequest.Builder {
private final int maxWait;
private final int minBytes;
private final int replicaId;
- private final Map fetchData;
- private final Map topicIds;
+ private final Map toFetch;
private IsolationLevel isolationLevel = IsolationLevel.READ_UNCOMMITTED;
private int maxBytes = DEFAULT_RESPONSE_MAX_BYTES;
private FetchMetadata metadata = FetchMetadata.LEGACY;
- private List toForget = Collections.emptyList();
+ private List removed = Collections.emptyList();
+ private List replaced = Collections.emptyList();
private String rackId = "";
- public static Builder forConsumer(short maxVersion, int maxWait, int minBytes, Map fetchData,
- Map topicIds) {
+ public static Builder forConsumer(short maxVersion, int maxWait, int minBytes, Map fetchData) {
return new Builder(ApiKeys.FETCH.oldestVersion(), maxVersion,
- CONSUMER_REPLICA_ID, maxWait, minBytes, fetchData, topicIds);
+ CONSUMER_REPLICA_ID, maxWait, minBytes, fetchData);
}
public static Builder forReplica(short allowedVersion, int replicaId, int maxWait, int minBytes,
- Map fetchData, Map topicIds) {
- return new Builder(allowedVersion, allowedVersion, replicaId, maxWait, minBytes, fetchData, topicIds);
+ Map fetchData) {
+ return new Builder(allowedVersion, allowedVersion, replicaId, maxWait, minBytes, fetchData);
}
public Builder(short minVersion, short maxVersion, int replicaId, int maxWait, int minBytes,
- Map fetchData, Map topicIds) {
+ Map fetchData) {
super(ApiKeys.FETCH, minVersion, maxVersion);
this.replicaId = replicaId;
this.maxWait = maxWait;
this.minBytes = minBytes;
- this.fetchData = fetchData;
- this.topicIds = topicIds;
+ this.toFetch = fetchData;
}
public Builder isolationLevel(IsolationLevel isolationLevel) {
@@ -169,7 +174,7 @@ public Builder rackId(String rackId) {
}
public Map fetchData() {
- return this.fetchData;
+ return this.toFetch;
}
public Builder setMaxBytes(int maxBytes) {
@@ -177,15 +182,37 @@ public Builder setMaxBytes(int maxBytes) {
return this;
}
- public List toForget() {
- return toForget;
+ public List removed() {
+ return removed;
}
- public Builder toForget(List toForget) {
- this.toForget = toForget;
+ public Builder removed(List removed) {
+ this.removed = removed;
return this;
}
+ public List replaced() {
+ return replaced;
+ }
+
+ public Builder replaced(List replaced) {
+ this.replaced = replaced;
+ return this;
+ }
+
+ private void addToForgottenTopicMap(List toForget, Map forgottenTopicMap) {
+ toForget.forEach(topicIdPartition -> {
+ FetchRequestData.ForgottenTopic forgottenTopic = forgottenTopicMap.get(topicIdPartition.topic());
+ if (forgottenTopic == null) {
+ forgottenTopic = new ForgottenTopic()
+ .setTopic(topicIdPartition.topic())
+ .setTopicId(topicIdPartition.topicId());
+ forgottenTopicMap.put(topicIdPartition.topic(), forgottenTopic);
+ }
+ forgottenTopic.partitions().add(topicIdPartition.partition());
+ });
+ }
+
@Override
public FetchRequest build(short version) {
if (version < 3) {
@@ -199,26 +226,31 @@ public FetchRequest build(short version) {
fetchRequestData.setMaxBytes(maxBytes);
fetchRequestData.setIsolationLevel(isolationLevel.id());
fetchRequestData.setForgottenTopicsData(new ArrayList<>());
- toForget.stream()
- .collect(Collectors.groupingBy(TopicPartition::topic, LinkedHashMap::new, Collectors.toList()))
- .forEach((topic, partitions) ->
- fetchRequestData.forgottenTopicsData().add(new FetchRequestData.ForgottenTopic()
- .setTopic(topic)
- .setTopicId(topicIds.getOrDefault(topic, Uuid.ZERO_UUID))
- .setPartitions(partitions.stream().map(TopicPartition::partition).collect(Collectors.toList())))
- );
- fetchRequestData.setTopics(new ArrayList<>());
+
+ Map forgottenTopicMap = new LinkedHashMap<>();
+ addToForgottenTopicMap(removed, forgottenTopicMap);
+
+ // If a version older than v13 is used, topic-partition which were replaced
+ // by a topic-partition with the same name but a different topic ID are not
+ // sent out in the "forget" set in order to not remove the newly added
+ // partition in the "fetch" set.
+ if (version >= 13) {
+ addToForgottenTopicMap(replaced, forgottenTopicMap);
+ }
+
+ forgottenTopicMap.forEach((topic, forgottenTopic) -> fetchRequestData.forgottenTopicsData().add(forgottenTopic));
// We collect the partitions in a single FetchTopic only if they appear sequentially in the fetchData
+ fetchRequestData.setTopics(new ArrayList<>());
FetchRequestData.FetchTopic fetchTopic = null;
- for (Map.Entry entry : fetchData.entrySet()) {
+ for (Map.Entry entry : toFetch.entrySet()) {
TopicPartition topicPartition = entry.getKey();
PartitionData partitionData = entry.getValue();
if (fetchTopic == null || !topicPartition.topic().equals(fetchTopic.topic())) {
fetchTopic = new FetchRequestData.FetchTopic()
.setTopic(topicPartition.topic())
- .setTopicId(topicIds.getOrDefault(topicPartition.topic(), Uuid.ZERO_UUID))
+ .setTopicId(partitionData.topicId)
.setPartitions(new ArrayList<>());
fetchRequestData.topics().add(fetchTopic);
}
@@ -251,9 +283,10 @@ public String toString() {
append(", maxWait=").append(maxWait).
append(", minBytes=").append(minBytes).
append(", maxBytes=").append(maxBytes).
- append(", fetchData=").append(fetchData).
+ append(", fetchData=").append(toFetch).
append(", isolationLevel=").append(isolationLevel).
- append(", toForget=").append(Utils.join(toForget, ", ")).
+ append(", removed=").append(Utils.join(removed, ", ")).
+ append(", replaced=").append(Utils.join(replaced, ", ")).
append(", metadata=").append(metadata).
append(", rackId=").append(rackId).
append(")");
@@ -314,8 +347,7 @@ public int maxBytes() {
// For versions < 13, builds the partitionData map using only the FetchRequestData.
// For versions 13+, builds the partitionData map using both the FetchRequestData and a mapping of topic IDs to names.
- // Throws UnknownTopicIdException for versions 13+ if the topic ID was unknown to the server.
- public Map fetchData(Map topicNames) throws UnknownTopicIdException {
+ public Map fetchData(Map topicNames) {
if (fetchData == null) {
synchronized (this) {
if (fetchData == null) {
@@ -328,22 +360,19 @@ public Map fetchData(Map topicNames
} else {
name = topicNames.get(fetchTopic.topicId());
}
- if (name != null) {
- // If topic name is resolved, simply add to fetchData map
- fetchTopic.partitions().forEach(fetchPartition ->
- fetchData.put(new TopicPartition(name, fetchPartition.partition()),
- new PartitionData(
- fetchPartition.fetchOffset(),
- fetchPartition.logStartOffset(),
- fetchPartition.partitionMaxBytes(),
- optionalEpoch(fetchPartition.currentLeaderEpoch()),
- optionalEpoch(fetchPartition.lastFetchedEpoch())
- )
- )
- );
- } else {
- throw new UnknownTopicIdException(String.format("Topic Id %s in FetchRequest was unknown to the server", fetchTopic.topicId()));
- }
+ fetchTopic.partitions().forEach(fetchPartition ->
+ // Topic name may be null here if the topic name was unable to be resolved using the topicNames map.
+ fetchData.put(new TopicIdPartition(fetchTopic.topicId(), new TopicPartition(name, fetchPartition.partition())),
+ new PartitionData(
+ fetchTopic.topicId(),
+ fetchPartition.fetchOffset(),
+ fetchPartition.logStartOffset(),
+ fetchPartition.partitionMaxBytes(),
+ optionalEpoch(fetchPartition.currentLeaderEpoch()),
+ optionalEpoch(fetchPartition.lastFetchedEpoch())
+ )
+ )
+ );
});
}
}
@@ -351,8 +380,9 @@ public Map fetchData(Map topicNames
return fetchData;
}
- // For versions 13+, throws UnknownTopicIdException if the topic ID was unknown to the server.
- public List forgottenTopics(Map topicNames) throws UnknownTopicIdException {
+ // For versions < 13, builds the forgotten topics list using only the FetchRequestData.
+ // For versions 13+, builds the forgotten topics list using both the FetchRequestData and a mapping of topic IDs to names.
+ public List forgottenTopics(Map topicNames) {
if (toForget == null) {
synchronized (this) {
if (toForget == null) {
@@ -364,10 +394,8 @@ public List forgottenTopics(Map topicNames) throws
} else {
name = topicNames.get(forgottenTopic.topicId());
}
- if (name == null) {
- throw new UnknownTopicIdException(String.format("Topic Id %s in FetchRequest was unknown to the server", forgottenTopic.topicId()));
- }
- forgottenTopic.partitions().forEach(partitionId -> toForget.add(new TopicPartition(name, partitionId)));
+ // Topic name may be null here if the topic name was unable to be resolved using the topicNames map.
+ forgottenTopic.partitions().forEach(partitionId -> toForget.add(new TopicIdPartition(forgottenTopic.topicId(), new TopicPartition(name, partitionId))));
});
}
}
diff --git a/clients/src/main/java/org/apache/kafka/common/requests/FetchResponse.java b/clients/src/main/java/org/apache/kafka/common/requests/FetchResponse.java
index f5fa40f73f879..2e0a02ec16855 100644
--- a/clients/src/main/java/org/apache/kafka/common/requests/FetchResponse.java
+++ b/clients/src/main/java/org/apache/kafka/common/requests/FetchResponse.java
@@ -17,6 +17,7 @@
package org.apache.kafka.common.requests;
import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.TopicIdPartition;
import org.apache.kafka.common.Uuid;
import org.apache.kafka.common.message.FetchResponseData;
import org.apache.kafka.common.protocol.ApiKeys;
@@ -57,8 +58,7 @@
* not supported by the fetch request version
* - {@link Errors#CORRUPT_MESSAGE} If corrupt message encountered, e.g. when the broker scans the log to find
* the fetch offset after the index lookup
- * - {@link Errors#UNKNOWN_TOPIC_ID} If the request contains a topic ID unknown to the broker or a partition in the session has
- * an ID that differs from the broker
+ * - {@link Errors#UNKNOWN_TOPIC_ID} If the request contains a topic ID unknown to the broker
* - {@link Errors#FETCH_SESSION_TOPIC_ID_ERROR} If the request version supports topic IDs but the session does not or vice versa,
* or a topic ID in the request is inconsistent with a topic ID in the session
* - {@link Errors#INCONSISTENT_TOPIC_ID} If a topic ID in the session does not match the topic ID in the log
@@ -82,9 +82,9 @@ public FetchResponseData data() {
/**
* From version 3 or later, the authorized and existing entries in `FetchRequest.fetchData` should be in the same order in `responseData`.
* Version 13 introduces topic IDs which can lead to a few new errors. If there is any unknown topic ID in the request, the
- * response will contain a top-level UNKNOWN_TOPIC_ID error.
+ * response will contain a partition-level UNKNOWN_TOPIC_ID error for that partition.
* If a request's topic ID usage is inconsistent with the session, we will return a top level FETCH_SESSION_TOPIC_ID_ERROR error.
- * We may also return INCONSISTENT_TOPIC_ID error as a top-level error when a partition in the session has a topic ID
+ * We may also return INCONSISTENT_TOPIC_ID error as a partition-level error when a partition in the session has a topic ID
* inconsistent with the log.
*/
public FetchResponse(FetchResponseData fetchResponseData) {
@@ -110,7 +110,7 @@ public LinkedHashMap responseDa
}
if (name != null) {
topicResponse.partitions().forEach(partition ->
- responseData.put(new TopicPartition(name, partition.partitionIndex()), partition));
+ responseData.put(new TopicPartition(name, partition.partitionIndex()), partition));
}
});
}
@@ -154,16 +154,14 @@ public Set topicIds() {
*
* @param version The version of the response to use.
* @param partIterator The partition iterator.
- * @param topicIds The mapping from topic name to topic ID.
* @return The response size in bytes.
*/
public static int sizeOf(short version,
- Iterator> partIterator,
- Map topicIds) {
+ Iterator> partIterator) {
// Since the throttleTimeMs and metadata field sizes are constant and fixed, we can
// use arbitrary values here without affecting the result.
- FetchResponseData data = toMessage(Errors.NONE, 0, INVALID_SESSION_ID, partIterator, topicIds);
+ FetchResponseData data = toMessage(Errors.NONE, 0, INVALID_SESSION_ID, partIterator);
ObjectSerializationCache cache = new ObjectSerializationCache();
return 4 + data.size(cache, version);
}
@@ -191,6 +189,10 @@ public static boolean isPreferredReplica(FetchResponseData.PartitionData partiti
return partitionResponse.preferredReadReplica() != INVALID_PREFERRED_REPLICA_ID;
}
+ public static FetchResponseData.PartitionData partitionResponse(TopicIdPartition topicIdPartition, Errors error) {
+ return partitionResponse(topicIdPartition.topicPartition().partition(), error);
+ }
+
public static FetchResponseData.PartitionData partitionResponse(int partition, Errors error) {
return new FetchResponseData.PartitionData()
.setPartitionIndex(partition)
@@ -226,36 +228,45 @@ public static int recordsSize(FetchResponseData.PartitionData partition) {
public static FetchResponse of(Errors error,
int throttleTimeMs,
int sessionId,
- LinkedHashMap responseData,
- Map topicIds) {
- return new FetchResponse(toMessage(error, throttleTimeMs, sessionId, responseData.entrySet().iterator(), topicIds));
+ LinkedHashMap responseData) {
+ return new FetchResponse(toMessage(error, throttleTimeMs, sessionId, responseData.entrySet().iterator()));
+ }
+
+ private static boolean matchingTopic(FetchResponseData.FetchableTopicResponse previousTopic, TopicIdPartition currentTopic) {
+ if (previousTopic == null)
+ return false;
+ if (!previousTopic.topicId().equals(Uuid.ZERO_UUID))
+ return previousTopic.topicId().equals(currentTopic.topicId());
+ else
+ return previousTopic.topic().equals(currentTopic.topicPartition().topic());
+
}
private static FetchResponseData toMessage(Errors error,
int throttleTimeMs,
int sessionId,
- Iterator> partIterator,
- Map topicIds) {
+ Iterator> partIterator) {
List topicResponseList = new ArrayList<>();
- partIterator.forEachRemaining(entry -> {
+ while (partIterator.hasNext()) {
+ Map.Entry entry = partIterator.next();
FetchResponseData.PartitionData partitionData = entry.getValue();
// Since PartitionData alone doesn't know the partition ID, we set it here
- partitionData.setPartitionIndex(entry.getKey().partition());
+ partitionData.setPartitionIndex(entry.getKey().topicPartition().partition());
// We have to keep the order of input topic-partition. Hence, we batch the partitions only if the last
// batch is in the same topic group.
FetchResponseData.FetchableTopicResponse previousTopic = topicResponseList.isEmpty() ? null
: topicResponseList.get(topicResponseList.size() - 1);
- if (previousTopic != null && previousTopic.topic().equals(entry.getKey().topic()))
+ if (matchingTopic(previousTopic, entry.getKey()))
previousTopic.partitions().add(partitionData);
else {
List partitionResponses = new ArrayList<>();
partitionResponses.add(partitionData);
topicResponseList.add(new FetchResponseData.FetchableTopicResponse()
- .setTopic(entry.getKey().topic())
- .setTopicId(topicIds.getOrDefault(entry.getKey().topic(), Uuid.ZERO_UUID))
+ .setTopic(entry.getKey().topicPartition().topic())
+ .setTopicId(entry.getKey().topicId())
.setPartitions(partitionResponses));
}
- });
+ }
return new FetchResponseData()
.setThrottleTimeMs(throttleTimeMs)
diff --git a/clients/src/test/java/org/apache/kafka/clients/FetchSessionHandlerTest.java b/clients/src/test/java/org/apache/kafka/clients/FetchSessionHandlerTest.java
index 8eb09f0d7498f..4bf53d9608e2a 100644
--- a/clients/src/test/java/org/apache/kafka/clients/FetchSessionHandlerTest.java
+++ b/clients/src/test/java/org/apache/kafka/clients/FetchSessionHandlerTest.java
@@ -17,6 +17,7 @@
package org.apache.kafka.clients;
import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.TopicIdPartition;
import org.apache.kafka.common.Uuid;
import org.apache.kafka.common.protocol.ApiKeys;
import org.apache.kafka.common.message.FetchResponseData;
@@ -28,6 +29,8 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.ArrayList;
@@ -41,6 +44,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Stream;
import java.util.TreeSet;
import static org.apache.kafka.common.requests.FetchMetadata.INITIAL_EPOCH;
@@ -97,9 +101,9 @@ private static final class ReqEntry {
final TopicPartition part;
final FetchRequest.PartitionData data;
- ReqEntry(String topic, int partition, long fetchOffset, long logStartOffset, int maxBytes) {
+ ReqEntry(String topic, Uuid topicId, int partition, long fetchOffset, long logStartOffset, int maxBytes) {
this.part = new TopicPartition(topic, partition);
- this.data = new FetchRequest.PartitionData(fetchOffset, logStartOffset, maxBytes, Optional.empty());
+ this.data = new FetchRequest.PartitionData(topicId, fetchOffset, logStartOffset, maxBytes, Optional.empty());
}
}
@@ -143,13 +147,13 @@ private static void assertMapsEqual(Map expected, List actual) {
- for (TopicPartition expectedPart : expected) {
+ private static void assertListEquals(List expected, List actual) {
+ for (TopicIdPartition expectedPart : expected) {
if (!actual.contains(expectedPart)) {
fail("Failed to find expected partition " + expectedPart);
}
}
- for (TopicPartition actualPart : actual) {
+ for (TopicIdPartition actualPart : actual) {
if (!expected.contains(actualPart)) {
fail("Found unexpected partition " + actualPart);
}
@@ -157,11 +161,11 @@ private static void assertListEquals(List expected, List respMap(RespEntry... entries) {
- LinkedHashMap map = new LinkedHashMap<>();
+ private static LinkedHashMap respMap(RespEntry... entries) {
+ LinkedHashMap map = new LinkedHashMap<>();
for (RespEntry entry : entries) {
map.put(entry.part, entry.data);
}
@@ -202,29 +206,30 @@ public void testSessionless() {
FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
FetchSessionHandler.Builder builder = handler.newBuilder();
addTopicId(topicIds, topicNames, "foo", version);
- builder.add(new TopicPartition("foo", 0), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
- builder.add(new TopicPartition("foo", 1), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(10, 110, 210, Optional.empty()));
+ Uuid fooId = topicIds.getOrDefault("foo", Uuid.ZERO_UUID);
+ builder.add(new TopicPartition("foo", 0),
+ new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
+ builder.add(new TopicPartition("foo", 1),
+ new FetchRequest.PartitionData(fooId, 10, 110, 210, Optional.empty()));
FetchSessionHandler.FetchRequestData data = builder.build();
- assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200),
- new ReqEntry("foo", 1, 10, 110, 210)),
+ assertMapsEqual(reqMap(new ReqEntry("foo", fooId, 0, 0, 100, 200),
+ new ReqEntry("foo", fooId, 1, 10, 110, 210)),
data.toSend(), data.sessionPartitions());
assertEquals(INVALID_SESSION_ID, data.metadata().sessionId());
assertEquals(INITIAL_EPOCH, data.metadata().epoch());
FetchResponse resp = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID,
- respMap(new RespEntry("foo", 0, 0, 0),
- new RespEntry("foo", 1, 0, 0)), topicIds);
+ respMap(new RespEntry("foo", 0, fooId, 0, 0),
+ new RespEntry("foo", 1, fooId, 0, 0)));
handler.handleResponse(resp, version);
FetchSessionHandler.Builder builder2 = handler.newBuilder();
- builder2.add(new TopicPartition("foo", 0), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
+ builder2.add(new TopicPartition("foo", 0),
+ new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
FetchSessionHandler.FetchRequestData data2 = builder2.build();
assertEquals(INVALID_SESSION_ID, data2.metadata().sessionId());
assertEquals(INITIAL_EPOCH, data2.metadata().epoch());
- assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200)),
+ assertMapsEqual(reqMap(new ReqEntry("foo", fooId, 0, 0, 100, 200)),
data2.toSend(), data2.sessionPartitions());
});
}
@@ -242,65 +247,62 @@ public void testIncrementals() {
FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
FetchSessionHandler.Builder builder = handler.newBuilder();
addTopicId(topicIds, topicNames, "foo", version);
- builder.add(new TopicPartition("foo", 0), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
- builder.add(new TopicPartition("foo", 1), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(10, 110, 210, Optional.empty()));
+ Uuid fooId = topicIds.getOrDefault("foo", Uuid.ZERO_UUID);
+ TopicPartition foo0 = new TopicPartition("foo", 0);
+ TopicPartition foo1 = new TopicPartition("foo", 1);
+ builder.add(foo0, new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
+ builder.add(foo1, new FetchRequest.PartitionData(fooId, 10, 110, 210, Optional.empty()));
FetchSessionHandler.FetchRequestData data = builder.build();
- assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200),
- new ReqEntry("foo", 1, 10, 110, 210)),
+ assertMapsEqual(reqMap(new ReqEntry("foo", fooId, 0, 0, 100, 200),
+ new ReqEntry("foo", fooId, 1, 10, 110, 210)),
data.toSend(), data.sessionPartitions());
assertEquals(INVALID_SESSION_ID, data.metadata().sessionId());
assertEquals(INITIAL_EPOCH, data.metadata().epoch());
FetchResponse resp = FetchResponse.of(Errors.NONE, 0, 123,
- respMap(new RespEntry("foo", 0, 10, 20),
- new RespEntry("foo", 1, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, fooId, 10, 20),
+ new RespEntry("foo", 1, fooId, 10, 20)));
handler.handleResponse(resp, version);
// Test an incremental fetch request which adds one partition and modifies another.
FetchSessionHandler.Builder builder2 = handler.newBuilder();
addTopicId(topicIds, topicNames, "bar", version);
- builder2.add(new TopicPartition("foo", 0), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
- builder2.add(new TopicPartition("foo", 1), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(10, 120, 210, Optional.empty()));
- builder2.add(new TopicPartition("bar", 0), topicIds.getOrDefault("bar", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(20, 200, 200, Optional.empty()));
+ Uuid barId = topicIds.getOrDefault("bar", Uuid.ZERO_UUID);
+ TopicPartition bar0 = new TopicPartition("bar", 0);
+ builder2.add(foo0, new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
+ builder2.add(foo1, new FetchRequest.PartitionData(fooId, 10, 120, 210, Optional.empty()));
+ builder2.add(bar0, new FetchRequest.PartitionData(barId, 20, 200, 200, Optional.empty()));
FetchSessionHandler.FetchRequestData data2 = builder2.build();
assertFalse(data2.metadata().isFull());
- assertMapEquals(reqMap(new ReqEntry("foo", 0, 0, 100, 200),
- new ReqEntry("foo", 1, 10, 120, 210),
- new ReqEntry("bar", 0, 20, 200, 200)),
+ assertMapEquals(reqMap(new ReqEntry("foo", fooId, 0, 0, 100, 200),
+ new ReqEntry("foo", fooId, 1, 10, 120, 210),
+ new ReqEntry("bar", barId, 0, 20, 200, 200)),
data2.sessionPartitions());
- assertMapEquals(reqMap(new ReqEntry("bar", 0, 20, 200, 200),
- new ReqEntry("foo", 1, 10, 120, 210)),
+ assertMapEquals(reqMap(new ReqEntry("bar", barId, 0, 20, 200, 200),
+ new ReqEntry("foo", fooId, 1, 10, 120, 210)),
data2.toSend());
FetchResponse resp2 = FetchResponse.of(Errors.NONE, 0, 123,
- respMap(new RespEntry("foo", 1, 20, 20)), topicIds);
+ respMap(new RespEntry("foo", 1, fooId, 20, 20)));
handler.handleResponse(resp2, version);
// Skip building a new request. Test that handling an invalid fetch session epoch response results
// in a request which closes the session.
FetchResponse resp3 = FetchResponse.of(Errors.INVALID_FETCH_SESSION_EPOCH, 0, INVALID_SESSION_ID,
- respMap(), topicIds);
+ respMap());
handler.handleResponse(resp3, version);
FetchSessionHandler.Builder builder4 = handler.newBuilder();
- builder4.add(new TopicPartition("foo", 0), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
- builder4.add(new TopicPartition("foo", 1), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(10, 120, 210, Optional.empty()));
- builder4.add(new TopicPartition("bar", 0), topicIds.getOrDefault("bar", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(20, 200, 200, Optional.empty()));
+ builder4.add(foo0, new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
+ builder4.add(foo1, new FetchRequest.PartitionData(fooId, 10, 120, 210, Optional.empty()));
+ builder4.add(bar0, new FetchRequest.PartitionData(barId, 20, 200, 200, Optional.empty()));
FetchSessionHandler.FetchRequestData data4 = builder4.build();
assertTrue(data4.metadata().isFull());
assertEquals(data2.metadata().sessionId(), data4.metadata().sessionId());
assertEquals(INITIAL_EPOCH, data4.metadata().epoch());
- assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200),
- new ReqEntry("foo", 1, 10, 120, 210),
- new ReqEntry("bar", 0, 20, 200, 200)),
+ assertMapsEqual(reqMap(new ReqEntry("foo", fooId, 0, 0, 100, 200),
+ new ReqEntry("foo", fooId, 1, 10, 120, 210),
+ new ReqEntry("bar", barId, 0, 20, 200, 200)),
data4.sessionPartitions(), data4.toSend());
});
}
@@ -312,8 +314,8 @@ public void testIncrementals() {
public void testDoubleBuild() {
FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
FetchSessionHandler.Builder builder = handler.newBuilder();
- builder.add(new TopicPartition("foo", 0), Uuid.randomUuid(),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
+ builder.add(new TopicPartition("foo", 0),
+ new FetchRequest.PartitionData(Uuid.randomUuid(), 0, 100, 200, Optional.empty()));
builder.build();
try {
builder.build();
@@ -334,55 +336,55 @@ public void testIncrementalPartitionRemoval() {
FetchSessionHandler.Builder builder = handler.newBuilder();
addTopicId(topicIds, topicNames, "foo", version);
addTopicId(topicIds, topicNames, "bar", version);
- builder.add(new TopicPartition("foo", 0), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
- builder.add(new TopicPartition("foo", 1), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(10, 110, 210, Optional.empty()));
- builder.add(new TopicPartition("bar", 0), topicIds.getOrDefault("bar", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(20, 120, 220, Optional.empty()));
+ Uuid fooId = topicIds.getOrDefault("foo", Uuid.ZERO_UUID);
+ Uuid barId = topicIds.getOrDefault("bar", Uuid.ZERO_UUID);
+ TopicPartition foo0 = new TopicPartition("foo", 0);
+ TopicPartition foo1 = new TopicPartition("foo", 1);
+ TopicPartition bar0 = new TopicPartition("bar", 0);
+ builder.add(foo0, new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
+ builder.add(foo1, new FetchRequest.PartitionData(fooId, 10, 110, 210, Optional.empty()));
+ builder.add(bar0, new FetchRequest.PartitionData(barId, 20, 120, 220, Optional.empty()));
FetchSessionHandler.FetchRequestData data = builder.build();
- assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200),
- new ReqEntry("foo", 1, 10, 110, 210),
- new ReqEntry("bar", 0, 20, 120, 220)),
+ assertMapsEqual(reqMap(new ReqEntry("foo", fooId, 0, 0, 100, 200),
+ new ReqEntry("foo", fooId, 1, 10, 110, 210),
+ new ReqEntry("bar", barId, 0, 20, 120, 220)),
data.toSend(), data.sessionPartitions());
assertTrue(data.metadata().isFull());
FetchResponse resp = FetchResponse.of(Errors.NONE, 0, 123,
- respMap(new RespEntry("foo", 0, 10, 20),
- new RespEntry("foo", 1, 10, 20),
- new RespEntry("bar", 0, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, fooId, 10, 20),
+ new RespEntry("foo", 1, fooId, 10, 20),
+ new RespEntry("bar", 0, barId, 10, 20)));
handler.handleResponse(resp, version);
// Test an incremental fetch request which removes two partitions.
FetchSessionHandler.Builder builder2 = handler.newBuilder();
- builder2.add(new TopicPartition("foo", 1), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(10, 110, 210, Optional.empty()));
+ builder2.add(foo1, new FetchRequest.PartitionData(fooId, 10, 110, 210, Optional.empty()));
FetchSessionHandler.FetchRequestData data2 = builder2.build();
assertFalse(data2.metadata().isFull());
assertEquals(123, data2.metadata().sessionId());
assertEquals(1, data2.metadata().epoch());
- assertMapEquals(reqMap(new ReqEntry("foo", 1, 10, 110, 210)),
+ assertMapEquals(reqMap(new ReqEntry("foo", fooId, 1, 10, 110, 210)),
data2.sessionPartitions());
assertMapEquals(reqMap(), data2.toSend());
- ArrayList expectedToForget2 = new ArrayList<>();
- expectedToForget2.add(new TopicPartition("foo", 0));
- expectedToForget2.add(new TopicPartition("bar", 0));
+ ArrayList expectedToForget2 = new ArrayList<>();
+ expectedToForget2.add(new TopicIdPartition(fooId, foo0));
+ expectedToForget2.add(new TopicIdPartition(barId, bar0));
assertListEquals(expectedToForget2, data2.toForget());
// A FETCH_SESSION_ID_NOT_FOUND response triggers us to close the session.
// The next request is a session establishing FULL request.
FetchResponse resp2 = FetchResponse.of(Errors.FETCH_SESSION_ID_NOT_FOUND, 0, INVALID_SESSION_ID,
- respMap(), topicIds);
+ respMap());
handler.handleResponse(resp2, version);
FetchSessionHandler.Builder builder3 = handler.newBuilder();
- builder3.add(new TopicPartition("foo", 0), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
+ builder3.add(foo0, new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
FetchSessionHandler.FetchRequestData data3 = builder3.build();
assertTrue(data3.metadata().isFull());
assertEquals(INVALID_SESSION_ID, data3.metadata().sessionId());
assertEquals(INITIAL_EPOCH, data3.metadata().epoch());
- assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200)),
+ assertMapsEqual(reqMap(new ReqEntry("foo", fooId, 0, 0, 100, 200)),
data3.sessionPartitions(), data3.toSend());
});
}
@@ -396,28 +398,30 @@ public void testTopicIdUsageGrantedOnIdUpgrade() {
String testType = partition == 0 ? "updating a partition" : "adding a new partition";
FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
FetchSessionHandler.Builder builder = handler.newBuilder();
- builder.add(new TopicPartition("foo", 0), Uuid.ZERO_UUID,
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
+ builder.add(new TopicPartition("foo", 0),
+ new FetchRequest.PartitionData(Uuid.ZERO_UUID, 0, 100, 200, Optional.empty()));
FetchSessionHandler.FetchRequestData data = builder.build();
- assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200)),
+ assertMapsEqual(reqMap(new ReqEntry("foo", Uuid.ZERO_UUID, 0, 0, 100, 200)),
data.toSend(), data.sessionPartitions());
assertTrue(data.metadata().isFull());
assertFalse(data.canUseTopicIds());
FetchResponse resp = FetchResponse.of(Errors.NONE, 0, 123,
- respMap(new RespEntry("foo", 0, 10, 20)), Collections.emptyMap());
+ respMap(new RespEntry("foo", 0, Uuid.ZERO_UUID, 10, 20)));
handler.handleResponse(resp, (short) 12);
// Try to add a topic ID to an already existing topic partition (0) or a new partition (1) in the session.
+ Uuid topicId = Uuid.randomUuid();
FetchSessionHandler.Builder builder2 = handler.newBuilder();
- builder2.add(new TopicPartition("foo", partition), Uuid.randomUuid(),
- new FetchRequest.PartitionData(10, 110, 210, Optional.empty()));
+ builder2.add(new TopicPartition("foo", partition),
+ new FetchRequest.PartitionData(topicId, 10, 110, 210, Optional.empty()));
FetchSessionHandler.FetchRequestData data2 = builder2.build();
- // Should have the same session ID and next epoch, but we can now use topic IDs.
- // The receiving broker will close the session if we were previously not using topic IDs.
+ // Should have the same session ID, and next epoch and can only use topic IDs if the partition was updated.
+ boolean updated = partition == 0;
+ // The receiving broker will handle closing the session.
assertEquals(123, data2.metadata().sessionId(), "Did not use same session when " + testType);
assertEquals(1, data2.metadata().epoch(), "Did not have correct epoch when " + testType);
- assertTrue(data2.canUseTopicIds());
+ assertEquals(updated, data2.canUseTopicIds());
});
}
@@ -428,62 +432,173 @@ public void testIdUsageRevokedOnIdDowngrade() {
List partitions = Arrays.asList(0, 1);
partitions.forEach(partition -> {
String testType = partition == 0 ? "updating a partition" : "adding a new partition";
- Map topicIds = Collections.singletonMap("foo", Uuid.randomUuid());
+ Uuid fooId = Uuid.randomUuid();
FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
FetchSessionHandler.Builder builder = handler.newBuilder();
- builder.add(new TopicPartition("foo", 0), topicIds.get("foo"),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
+ builder.add(new TopicPartition("foo", 0),
+ new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
FetchSessionHandler.FetchRequestData data = builder.build();
- assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200)),
+ assertMapsEqual(reqMap(new ReqEntry("foo", fooId, 0, 0, 100, 200)),
data.toSend(), data.sessionPartitions());
assertTrue(data.metadata().isFull());
assertTrue(data.canUseTopicIds());
FetchResponse resp = FetchResponse.of(Errors.NONE, 0, 123,
- respMap(new RespEntry("foo", 0, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, fooId, 10, 20)));
handler.handleResponse(resp, ApiKeys.FETCH.latestVersion());
// Try to remove a topic ID from an existing topic partition (0) or add a new topic partition (1) without an ID.
FetchSessionHandler.Builder builder2 = handler.newBuilder();
- builder2.add(new TopicPartition("foo", partition), Uuid.ZERO_UUID,
- new FetchRequest.PartitionData(10, 110, 210, Optional.empty()));
+ builder2.add(new TopicPartition("foo", partition),
+ new FetchRequest.PartitionData(Uuid.ZERO_UUID, 10, 110, 210, Optional.empty()));
FetchSessionHandler.FetchRequestData data2 = builder2.build();
- // Should have the same session ID and next epoch, but can no longer use topic IDs.
- // The receiving broker will close the session if we were previously using topic IDs.
+ // Should have the same session ID, and next epoch and can no longer use topic IDs.
+ // The receiving broker will handle closing the session.
assertEquals(123, data2.metadata().sessionId(), "Did not use same session when " + testType);
assertEquals(1, data2.metadata().epoch(), "Did not have correct epoch when " + testType);
assertFalse(data2.canUseTopicIds());
});
}
+ private static Stream idUsageCombinations() {
+ return Stream.of(
+ Arguments.of(true, true),
+ Arguments.of(true, false),
+ Arguments.of(false, true),
+ Arguments.of(false, false)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("idUsageCombinations")
+ public void testTopicIdReplaced(boolean startsWithTopicIds, boolean endsWithTopicIds) {
+ TopicPartition tp = new TopicPartition("foo", 0);
+ FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
+ FetchSessionHandler.Builder builder = handler.newBuilder();
+ Uuid topicId1 = startsWithTopicIds ? Uuid.randomUuid() : Uuid.ZERO_UUID;
+ builder.add(tp, new FetchRequest.PartitionData(topicId1, 0, 100, 200, Optional.empty()));
+ FetchSessionHandler.FetchRequestData data = builder.build();
+ assertMapsEqual(reqMap(new ReqEntry("foo", topicId1, 0, 0, 100, 200)),
+ data.toSend(), data.sessionPartitions());
+ assertTrue(data.metadata().isFull());
+ assertEquals(startsWithTopicIds, data.canUseTopicIds());
+
+ FetchResponse resp = FetchResponse.of(Errors.NONE, 0, 123, respMap(new RespEntry("foo", 0, topicId1, 10, 20)));
+ short version = startsWithTopicIds ? ApiKeys.FETCH.latestVersion() : 12;
+ handler.handleResponse(resp, version);
+
+ // Try to add a new topic ID.
+ FetchSessionHandler.Builder builder2 = handler.newBuilder();
+ Uuid topicId2 = endsWithTopicIds ? Uuid.randomUuid() : Uuid.ZERO_UUID;
+ // Use the same data besides the topic ID.
+ FetchRequest.PartitionData partitionData = new FetchRequest.PartitionData(topicId2, 0, 100, 200, Optional.empty());
+ builder2.add(tp, partitionData);
+ FetchSessionHandler.FetchRequestData data2 = builder2.build();
+
+ if (startsWithTopicIds && endsWithTopicIds) {
+ // If we started with an ID, both a only a new ID will count towards replaced.
+ // The old topic ID partition should be in toReplace, and the new one should be in toSend.
+ assertEquals(Collections.singletonList(new TopicIdPartition(topicId1, tp)), data2.toReplace());
+ assertMapsEqual(reqMap(new ReqEntry("foo", topicId2, 0, 0, 100, 200)),
+ data2.toSend(), data2.sessionPartitions());
+
+ // sessionTopicNames should contain only the second topic ID.
+ assertEquals(Collections.singletonMap(topicId2, tp.topic()), handler.sessionTopicNames());
+
+ } else if (startsWithTopicIds || endsWithTopicIds) {
+ // If we downgraded to not using topic IDs we will want to send this data.
+ // However, we will not mark the partition as one replaced. In this scenario, we should see the session close due to
+ // changing request types.
+ // We will have the new topic ID in the session partition map
+ assertEquals(Collections.emptyList(), data2.toReplace());
+ assertMapsEqual(reqMap(new ReqEntry("foo", topicId2, 0, 0, 100, 200)),
+ data2.toSend(), data2.sessionPartitions());
+ // The topicNames map will have the new topic ID if it is valid.
+ // The old topic ID should be removed as the map will be empty if the request doesn't use topic IDs.
+ if (endsWithTopicIds) {
+ assertEquals(Collections.singletonMap(topicId2, tp.topic()), handler.sessionTopicNames());
+ } else {
+ assertEquals(Collections.emptyMap(), handler.sessionTopicNames());
+ }
+
+ } else {
+ // Otherwise, we have no partition in toReplace and since the partition and topic ID was not updated, there is no data to send.
+ assertEquals(Collections.emptyList(), data2.toReplace());
+ assertEquals(Collections.emptyMap(), data2.toSend());
+ assertMapsEqual(reqMap(new ReqEntry("foo", topicId2, 0, 0, 100, 200)), data2.sessionPartitions());
+ // There is also nothing in the sessionTopicNames map, as there are no topic IDs used.
+ assertEquals(Collections.emptyMap(), handler.sessionTopicNames());
+ }
+
+ // Should have the same session ID, and next epoch and can use topic IDs if it ended with topic IDs.
+ assertEquals(123, data2.metadata().sessionId(), "Did not use same session");
+ assertEquals(1, data2.metadata().epoch(), "Did not have correct epoch");
+ assertEquals(endsWithTopicIds, data2.canUseTopicIds());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testSessionEpochWhenMixedUsageOfTopicIDs(boolean startsWithTopicIds) {
+ Uuid fooId = startsWithTopicIds ? Uuid.randomUuid() : Uuid.ZERO_UUID;
+ Uuid barId = startsWithTopicIds ? Uuid.ZERO_UUID : Uuid.randomUuid();
+ short responseVersion = startsWithTopicIds ? ApiKeys.FETCH.latestVersion() : 12;
+
+ TopicPartition tp0 = new TopicPartition("foo", 0);
+ TopicPartition tp1 = new TopicPartition("bar", 1);
+
+ FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
+ FetchSessionHandler.Builder builder = handler.newBuilder();
+ builder.add(tp0, new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
+ FetchSessionHandler.FetchRequestData data = builder.build();
+ assertMapsEqual(reqMap(new ReqEntry("foo", fooId, 0, 0, 100, 200)),
+ data.toSend(), data.sessionPartitions());
+ assertTrue(data.metadata().isFull());
+ assertEquals(startsWithTopicIds, data.canUseTopicIds());
+
+ FetchResponse resp = FetchResponse.of(Errors.NONE, 0, 123,
+ respMap(new RespEntry("foo", 0, fooId, 10, 20)));
+ handler.handleResponse(resp, responseVersion);
+
+ // Re-add the first partition. Then add a partition with opposite ID usage.
+ FetchSessionHandler.Builder builder2 = handler.newBuilder();
+ builder2.add(tp0, new FetchRequest.PartitionData(fooId, 10, 110, 210, Optional.empty()));
+ builder2.add(tp1, new FetchRequest.PartitionData(barId, 0, 100, 200, Optional.empty()));
+ FetchSessionHandler.FetchRequestData data2 = builder2.build();
+ // Should have the same session ID, and the next epoch and can not use topic IDs.
+ // The receiving broker will handle closing the session.
+ assertEquals(123, data2.metadata().sessionId(), "Did not use same session");
+ assertEquals(1, data2.metadata().epoch(), "Did not have final epoch");
+ assertFalse(data2.canUseTopicIds());
+ }
+
+
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testIdUsageWithAllForgottenPartitions(boolean useTopicIds) {
// We want to test when all topics are removed from the session
+ TopicPartition foo0 = new TopicPartition("foo", 0);
Uuid topicId = useTopicIds ? Uuid.randomUuid() : Uuid.ZERO_UUID;
- Short responseVersion = useTopicIds ? ApiKeys.FETCH.latestVersion() : 12;
- Map topicIds = Collections.singletonMap("foo", topicId);
+ short responseVersion = useTopicIds ? ApiKeys.FETCH.latestVersion() : 12;
FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
// Add topic foo to the session
FetchSessionHandler.Builder builder = handler.newBuilder();
- builder.add(new TopicPartition("foo", 0), topicIds.get("foo"),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
+ builder.add(foo0, new FetchRequest.PartitionData(topicId, 0, 100, 200, Optional.empty()));
FetchSessionHandler.FetchRequestData data = builder.build();
- assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200)),
+ assertMapsEqual(reqMap(new ReqEntry("foo", topicId, 0, 0, 100, 200)),
data.toSend(), data.sessionPartitions());
assertTrue(data.metadata().isFull());
assertEquals(useTopicIds, data.canUseTopicIds());
FetchResponse resp = FetchResponse.of(Errors.NONE, 0, 123,
- respMap(new RespEntry("foo", 0, 10, 20)), topicIds);
- handler.handleResponse(resp, responseVersion.shortValue());
+ respMap(new RespEntry("foo", 0, topicId, 10, 20)));
+ handler.handleResponse(resp, responseVersion);
// Remove the topic from the session
FetchSessionHandler.Builder builder2 = handler.newBuilder();
FetchSessionHandler.FetchRequestData data2 = builder2.build();
- // Should have the same session ID and next epoch, but can no longer use topic IDs.
- // The receiving broker will close the session if we were previously using topic IDs.
+ assertEquals(Collections.singletonList(new TopicIdPartition(topicId, foo0)), data2.toForget());
+ // Should have the same session ID, next epoch, and same ID usage.
assertEquals(123, data2.metadata().sessionId(), "Did not use same session when useTopicIds was " + useTopicIds);
assertEquals(1, data2.metadata().epoch(), "Did not have correct epoch when useTopicIds was " + useTopicIds);
assertEquals(useTopicIds, data2.canUseTopicIds());
@@ -491,19 +606,19 @@ public void testIdUsageWithAllForgottenPartitions(boolean useTopicIds) {
@Test
public void testOkToAddNewIdAfterTopicRemovedFromSession() {
- Map topicIds = Collections.singletonMap("foo", Uuid.randomUuid());
+ Uuid topicId = Uuid.randomUuid();
FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
FetchSessionHandler.Builder builder = handler.newBuilder();
- builder.add(new TopicPartition("foo", 0), topicIds.get("foo"),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
+ builder.add(new TopicPartition("foo", 0),
+ new FetchRequest.PartitionData(topicId, 0, 100, 200, Optional.empty()));
FetchSessionHandler.FetchRequestData data = builder.build();
- assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200)),
+ assertMapsEqual(reqMap(new ReqEntry("foo", topicId, 0, 0, 100, 200)),
data.toSend(), data.sessionPartitions());
assertTrue(data.metadata().isFull());
assertTrue(data.canUseTopicIds());
FetchResponse resp = FetchResponse.of(Errors.NONE, 0, 123,
- respMap(new RespEntry("foo", 0, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, topicId, 10, 20)));
handler.handleResponse(resp, ApiKeys.FETCH.latestVersion());
// Remove the partition from the session. Return a session ID as though the session is still open.
@@ -512,13 +627,13 @@ public void testOkToAddNewIdAfterTopicRemovedFromSession() {
assertMapsEqual(new LinkedHashMap<>(),
data2.toSend(), data2.sessionPartitions());
FetchResponse resp2 = FetchResponse.of(Errors.NONE, 0, 123,
- new LinkedHashMap<>(), topicIds);
+ new LinkedHashMap<>());
handler.handleResponse(resp2, ApiKeys.FETCH.latestVersion());
// After the topic is removed, add a recreated topic with a new ID.
FetchSessionHandler.Builder builder3 = handler.newBuilder();
- builder3.add(new TopicPartition("foo", 0), Uuid.randomUuid(),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
+ builder3.add(new TopicPartition("foo", 0),
+ new FetchRequest.PartitionData(Uuid.randomUuid(), 0, 100, 200, Optional.empty()));
FetchSessionHandler.FetchRequestData data3 = builder3.build();
// Should have the same session ID and epoch 2.
assertEquals(123, data3.metadata().sessionId(), "Did not use same session");
@@ -536,32 +651,34 @@ public void testVerifyFullFetchResponsePartitions() {
FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
addTopicId(topicIds, topicNames, "foo", version);
addTopicId(topicIds, topicNames, "bar", version);
+ Uuid fooId = topicIds.getOrDefault("foo", Uuid.ZERO_UUID);
+ Uuid barId = topicIds.getOrDefault("bar", Uuid.ZERO_UUID);
+ TopicPartition foo0 = new TopicPartition("foo", 0);
+ TopicPartition foo1 = new TopicPartition("foo", 1);
+ TopicPartition bar0 = new TopicPartition("bar", 0);
FetchResponse resp1 = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID,
- respMap(new RespEntry("foo", 0, 10, 20),
- new RespEntry("foo", 1, 10, 20),
- new RespEntry("bar", 0, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, fooId, 10, 20),
+ new RespEntry("foo", 1, fooId, 10, 20),
+ new RespEntry("bar", 0, barId, 10, 20)));
String issue = handler.verifyFullFetchResponsePartitions(resp1.responseData(topicNames, version).keySet(),
resp1.topicIds(), version);
assertTrue(issue.contains("extraPartitions="));
assertFalse(issue.contains("omittedPartitions="));
FetchSessionHandler.Builder builder = handler.newBuilder();
- builder.add(new TopicPartition("foo", 0), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
- builder.add(new TopicPartition("foo", 1), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(10, 110, 210, Optional.empty()));
- builder.add(new TopicPartition("bar", 0), topicIds.getOrDefault("bar", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(20, 120, 220, Optional.empty()));
+ builder.add(foo0, new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
+ builder.add(foo1, new FetchRequest.PartitionData(fooId, 10, 110, 210, Optional.empty()));
+ builder.add(bar0, new FetchRequest.PartitionData(barId, 20, 120, 220, Optional.empty()));
builder.build();
FetchResponse resp2 = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID,
- respMap(new RespEntry("foo", 0, 10, 20),
- new RespEntry("foo", 1, 10, 20),
- new RespEntry("bar", 0, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, fooId, 10, 20),
+ new RespEntry("foo", 1, fooId, 10, 20),
+ new RespEntry("bar", 0, barId, 10, 20)));
String issue2 = handler.verifyFullFetchResponsePartitions(resp2.responseData(topicNames, version).keySet(),
resp2.topicIds(), version);
assertNull(issue2);
FetchResponse resp3 = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID,
- respMap(new RespEntry("foo", 0, 10, 20),
- new RespEntry("foo", 1, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, fooId, 10, 20),
+ new RespEntry("foo", 1, fooId, 10, 20)));
String issue3 = handler.verifyFullFetchResponsePartitions(resp3.responseData(topicNames, version).keySet(),
resp3.topicIds(), version);
assertFalse(issue3.contains("extraPartitions="));
@@ -576,36 +693,32 @@ public void testVerifyFullFetchResponsePartitionsWithTopicIds() {
FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
addTopicId(topicIds, topicNames, "foo", ApiKeys.FETCH.latestVersion());
addTopicId(topicIds, topicNames, "bar", ApiKeys.FETCH.latestVersion());
- Uuid extraId = Uuid.randomUuid();
- topicIds.put("extra2", extraId);
+ addTopicId(topicIds, topicNames, "extra2", ApiKeys.FETCH.latestVersion());
FetchResponse resp1 = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID,
- respMap(new RespEntry("foo", 0, 10, 20),
- new RespEntry("extra2", 1, 10, 20),
- new RespEntry("bar", 0, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, topicIds.get("foo"), 10, 20),
+ new RespEntry("extra2", 1, topicIds.get("extra2"), 10, 20),
+ new RespEntry("bar", 0, topicIds.get("bar"), 10, 20)));
String issue = handler.verifyFullFetchResponsePartitions(resp1.responseData(topicNames, ApiKeys.FETCH.latestVersion()).keySet(),
resp1.topicIds(), ApiKeys.FETCH.latestVersion());
assertTrue(issue.contains("extraPartitions="));
- assertTrue(issue.contains("extraIds="));
assertFalse(issue.contains("omittedPartitions="));
FetchSessionHandler.Builder builder = handler.newBuilder();
- builder.add(new TopicPartition("foo", 0), topicIds.get("foo"),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
- builder.add(new TopicPartition("bar", 0), topicIds.get("bar"),
- new FetchRequest.PartitionData(20, 120, 220, Optional.empty()));
+ builder.add(new TopicPartition("foo", 0),
+ new FetchRequest.PartitionData(topicIds.get("foo"), 0, 100, 200, Optional.empty()));
+ builder.add(new TopicPartition("bar", 0),
+ new FetchRequest.PartitionData(topicIds.get("bar"), 20, 120, 220, Optional.empty()));
builder.build();
FetchResponse resp2 = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID,
- respMap(new RespEntry("foo", 0, 10, 20),
- new RespEntry("extra2", 1, 10, 20),
- new RespEntry("bar", 0, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, topicIds.get("foo"), 10, 20),
+ new RespEntry("extra2", 1, topicIds.get("extra2"), 10, 20),
+ new RespEntry("bar", 0, topicIds.get("bar"), 10, 20)));
String issue2 = handler.verifyFullFetchResponsePartitions(resp2.responseData(topicNames, ApiKeys.FETCH.latestVersion()).keySet(),
resp2.topicIds(), ApiKeys.FETCH.latestVersion());
- assertFalse(issue2.contains("extraPartitions="));
- assertTrue(issue2.contains("extraIds="));
+ assertTrue(issue2.contains("extraPartitions="));
assertFalse(issue2.contains("omittedPartitions="));
- topicNames.put(extraId, "extra2");
FetchResponse resp3 = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID,
- respMap(new RespEntry("foo", 0, 10, 20),
- new RespEntry("bar", 0, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, topicIds.get("foo"), 10, 20),
+ new RespEntry("bar", 0, topicIds.get("bar"), 10, 20)));
String issue3 = handler.verifyFullFetchResponsePartitions(resp3.responseData(topicNames, ApiKeys.FETCH.latestVersion()).keySet(),
resp3.topicIds(), ApiKeys.FETCH.latestVersion());
assertNull(issue3);
@@ -618,24 +731,25 @@ public void testTopLevelErrorResetsMetadata() {
FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
FetchSessionHandler.Builder builder = handler.newBuilder();
addTopicId(topicIds, topicNames, "foo", ApiKeys.FETCH.latestVersion());
- builder.add(new TopicPartition("foo", 0), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
- builder.add(new TopicPartition("foo", 1), topicIds.getOrDefault("foo", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(10, 110, 210, Optional.empty()));
+ Uuid fooId = topicIds.getOrDefault("foo", Uuid.ZERO_UUID);
+ builder.add(new TopicPartition("foo", 0),
+ new FetchRequest.PartitionData(fooId, 0, 100, 200, Optional.empty()));
+ builder.add(new TopicPartition("foo", 1),
+ new FetchRequest.PartitionData(fooId, 10, 110, 210, Optional.empty()));
FetchSessionHandler.FetchRequestData data = builder.build();
assertEquals(INVALID_SESSION_ID, data.metadata().sessionId());
assertEquals(INITIAL_EPOCH, data.metadata().epoch());
FetchResponse resp = FetchResponse.of(Errors.NONE, 0, 123,
- respMap(new RespEntry("foo", 0, 10, 20),
- new RespEntry("foo", 1, 10, 20)), topicIds);
+ respMap(new RespEntry("foo", 0, topicIds.get("foo"), 10, 20),
+ new RespEntry("foo", 1, topicIds.get("foo"), 10, 20)));
handler.handleResponse(resp, ApiKeys.FETCH.latestVersion());
// Test an incremental fetch request which adds an ID unknown to the broker.
FetchSessionHandler.Builder builder2 = handler.newBuilder();
addTopicId(topicIds, topicNames, "unknown", ApiKeys.FETCH.latestVersion());
- builder2.add(new TopicPartition("unknown", 0), topicIds.getOrDefault("unknown", Uuid.ZERO_UUID),
- new FetchRequest.PartitionData(0, 100, 200, Optional.empty()));
+ builder2.add(new TopicPartition("unknown", 0),
+ new FetchRequest.PartitionData(topicIds.getOrDefault("unknown", Uuid.ZERO_UUID), 0, 100, 200, Optional.empty()));
FetchSessionHandler.FetchRequestData data2 = builder2.build();
assertFalse(data2.metadata().isFull());
assertEquals(123, data2.metadata().sessionId());
@@ -643,7 +757,7 @@ public void testTopLevelErrorResetsMetadata() {
// Return and handle a response with a top level error
FetchResponse resp2 = FetchResponse.of(Errors.UNKNOWN_TOPIC_ID, 0, 123,
- respMap(new RespEntry("unknown", 0, Errors.UNKNOWN_TOPIC_ID)), topicIds);
+ respMap(new RespEntry("unknown", 0, Uuid.randomUuid(), Errors.UNKNOWN_TOPIC_ID)));
assertFalse(handler.handleResponse(resp2, ApiKeys.FETCH.latestVersion()));
// Ensure we start with a new epoch. This will close the session in the next request.
diff --git a/clients/src/test/java/org/apache/kafka/clients/consumer/KafkaConsumerTest.java b/clients/src/test/java/org/apache/kafka/clients/consumer/KafkaConsumerTest.java
index bc7d506275e93..28729834c1d34 100644
--- a/clients/src/test/java/org/apache/kafka/clients/consumer/KafkaConsumerTest.java
+++ b/clients/src/test/java/org/apache/kafka/clients/consumer/KafkaConsumerTest.java
@@ -38,6 +38,7 @@
import org.apache.kafka.common.MetricName;
import org.apache.kafka.common.Node;
import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.TopicIdPartition;
import org.apache.kafka.common.Uuid;
import org.apache.kafka.common.config.SslConfigs;
import org.apache.kafka.common.errors.AuthenticationException;
@@ -715,9 +716,10 @@ public void testFetchProgressWithMissingPartitionPosition() {
client.prepareResponse(
body -> {
FetchRequest request = (FetchRequest) body;
- Map fetchData = request.fetchData(topicNames);
- return fetchData.keySet().equals(singleton(tp0)) &&
- fetchData.get(tp0).fetchOffset == 50L;
+ Map fetchData = request.fetchData(topicNames);
+ TopicIdPartition tidp0 = new TopicIdPartition(topicIds.get(tp0.topic()), tp0);
+ return fetchData.keySet().equals(singleton(tidp0)) &&
+ fetchData.get(tidp0).fetchOffset == 50L;
}, fetchResponse(tp0, 50L, 5));
@@ -1762,7 +1764,7 @@ public void testShouldAttemptToRejoinGroupAfterSyncGroupFailed() throws Exceptio
client.prepareResponseFrom(syncGroupResponse(singletonList(tp0), Errors.NONE), coordinator);
client.prepareResponseFrom(body -> body instanceof FetchRequest
- && ((FetchRequest) body).fetchData(topicNames).containsKey(tp0), fetchResponse(tp0, 1, 1), node);
+ && ((FetchRequest) body).fetchData(topicNames).containsKey(new TopicIdPartition(topicId, tp0)), fetchResponse(tp0, 1, 1), node);
time.sleep(heartbeatIntervalMs);
Thread.sleep(heartbeatIntervalMs);
consumer.updateAssignmentMetadataIfNeeded(time.timer(Long.MAX_VALUE));
@@ -2541,7 +2543,7 @@ private ListOffsetsResponse listOffsetsResponse(Map partit
}
private FetchResponse fetchResponse(Map fetches) {
- LinkedHashMap tpResponses = new LinkedHashMap<>();
+ LinkedHashMap tpResponses = new LinkedHashMap<>();
for (Map.Entry fetchEntry : fetches.entrySet()) {
TopicPartition partition = fetchEntry.getKey();
long fetchOffset = fetchEntry.getValue().offset;
@@ -2558,14 +2560,14 @@ private FetchResponse fetchResponse(Map fetches) {
builder.append(0L, ("key-" + i).getBytes(), ("value-" + i).getBytes());
records = builder.build();
}
- tpResponses.put(partition,
+ tpResponses.put(new TopicIdPartition(topicIds.get(partition.topic()), partition),
new FetchResponseData.PartitionData()
.setPartitionIndex(partition.partition())
.setHighWatermark(highWatermark)
.setLogStartOffset(logStartOffset)
.setRecords(records));
}
- return FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, tpResponses, topicIds);
+ return FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, tpResponses);
}
private FetchResponse fetchResponse(TopicPartition partition, long fetchOffset, int count) {
diff --git a/clients/src/test/java/org/apache/kafka/clients/consumer/internals/FetcherTest.java b/clients/src/test/java/org/apache/kafka/clients/consumer/internals/FetcherTest.java
index 7b9f33d8612eb..7509eb6d28b69 100644
--- a/clients/src/test/java/org/apache/kafka/clients/consumer/internals/FetcherTest.java
+++ b/clients/src/test/java/org/apache/kafka/clients/consumer/internals/FetcherTest.java
@@ -40,6 +40,7 @@
import org.apache.kafka.common.Node;
import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.TopicIdPartition;
import org.apache.kafka.common.Uuid;
import org.apache.kafka.common.errors.InvalidTopicException;
import org.apache.kafka.common.errors.RecordTooLargeException;
@@ -68,6 +69,7 @@
import org.apache.kafka.common.network.NetworkReceive;
import org.apache.kafka.common.protocol.ApiKeys;
import org.apache.kafka.common.protocol.Errors;
+import org.apache.kafka.common.requests.FetchRequest.PartitionData;
import org.apache.kafka.common.utils.BufferSupplier;
import org.apache.kafka.common.record.CompressionType;
import org.apache.kafka.common.record.ControlRecordType;
@@ -136,10 +138,12 @@
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.apache.kafka.common.requests.FetchMetadata.INVALID_SESSION_ID;
import static org.apache.kafka.common.requests.OffsetsForLeaderEpochResponse.UNDEFINED_EPOCH;
import static org.apache.kafka.common.requests.OffsetsForLeaderEpochResponse.UNDEFINED_EPOCH_OFFSET;
+import static org.apache.kafka.common.utils.Utils.mkSet;
import static org.apache.kafka.test.TestUtils.assertOptional;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -169,6 +173,10 @@ public class FetcherTest {
private TopicPartition tp1 = new TopicPartition(topicName, 1);
private TopicPartition tp2 = new TopicPartition(topicName, 2);
private TopicPartition tp3 = new TopicPartition(topicName, 3);
+ private TopicIdPartition tidp0 = new TopicIdPartition(topicId, tp0);
+ private TopicIdPartition tidp1 = new TopicIdPartition(topicId, tp1);
+ private TopicIdPartition tidp2 = new TopicIdPartition(topicId, tp2);
+ private TopicIdPartition tidp3 = new TopicIdPartition(topicId, tp3);
private int validLeaderEpoch = 0;
private MetadataResponse initialUpdateResponse =
RequestTestUtils.metadataUpdateWithIds(1, singletonMap(topicName, 4), topicIds);
@@ -247,7 +255,7 @@ public void testFetchNormal() {
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -265,28 +273,31 @@ public void testFetchNormal() {
}
@Test
- public void testFetchWithNoId() {
+ public void testFetchWithNoTopicId() {
// Should work and default to using old request type.
buildFetcher();
- TopicPartition noId = new TopicPartition("noId", 0);
- assignFromUserNoId(singleton(noId));
- subscriptions.seek(noId, 0);
+ TopicIdPartition noId = new TopicIdPartition(Uuid.ZERO_UUID, new TopicPartition("noId", 0));
+ assignFromUserNoId(singleton(noId.topicPartition()));
+ subscriptions.seek(noId.topicPartition(), 0);
- // fetch should use request version 12
+ // Fetch should use request version 12
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
- client.prepareResponse(fullFetchResponse(noId, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(
+ fetchRequestMatcher((short) 12, noId, 0, Optional.of(validLeaderEpoch)),
+ fullFetchResponse(noId, this.records, Errors.NONE, 100L, 0)
+ );
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
Map>> partitionRecords = fetchedRecords();
- assertTrue(partitionRecords.containsKey(noId));
+ assertTrue(partitionRecords.containsKey(noId.topicPartition()));
- List> records = partitionRecords.get(noId);
+ List> records = partitionRecords.get(noId.topicPartition());
assertEquals(3, records.size());
- assertEquals(4L, subscriptions.position(noId).offset); // this is the next fetching position
+ assertEquals(4L, subscriptions.position(noId.topicPartition()).offset); // this is the next fetching position
long offset = 1;
for (ConsumerRecord record : records) {
assertEquals(offset, record.offset());
@@ -294,6 +305,288 @@ public void testFetchWithNoId() {
}
}
+ @Test
+ public void testFetchWithTopicId() {
+ buildFetcher();
+
+ TopicIdPartition tp = new TopicIdPartition(topicId, new TopicPartition(topicName, 0));
+ assignFromUser(singleton(tp.topicPartition()));
+ subscriptions.seek(tp.topicPartition(), 0);
+
+ // Fetch should use latest version
+ assertEquals(1, fetcher.sendFetches());
+ assertFalse(fetcher.hasCompletedFetches());
+
+ client.prepareResponse(
+ fetchRequestMatcher(ApiKeys.FETCH.latestVersion(), tp, 0, Optional.of(validLeaderEpoch)),
+ fullFetchResponse(tp, this.records, Errors.NONE, 100L, 0)
+ );
+ consumerClient.poll(time.timer(0));
+ assertTrue(fetcher.hasCompletedFetches());
+
+ Map>> partitionRecords = fetchedRecords();
+ assertTrue(partitionRecords.containsKey(tp.topicPartition()));
+
+ List> records = partitionRecords.get(tp.topicPartition());
+ assertEquals(3, records.size());
+ assertEquals(4L, subscriptions.position(tp.topicPartition()).offset); // this is the next fetching position
+ long offset = 1;
+ for (ConsumerRecord record : records) {
+ assertEquals(offset, record.offset());
+ offset += 1;
+ }
+ }
+
+ @Test
+ public void testFetchForgetTopicIdWhenUnassigned() {
+ buildFetcher();
+
+ TopicIdPartition foo = new TopicIdPartition(Uuid.randomUuid(), new TopicPartition("foo", 0));
+ TopicIdPartition bar = new TopicIdPartition(Uuid.randomUuid(), new TopicPartition("bar", 0));
+
+ // Assign foo and bar.
+ subscriptions.assignFromUser(singleton(foo.topicPartition()));
+ client.updateMetadata(RequestTestUtils.metadataUpdateWithIds(1, singleton(foo), tp -> validLeaderEpoch));
+ subscriptions.seek(foo.topicPartition(), 0);
+
+ // Fetch should use latest version.
+ assertEquals(1, fetcher.sendFetches());
+
+ client.prepareResponse(
+ fetchRequestMatcher(ApiKeys.FETCH.latestVersion(),
+ singletonMap(foo, new PartitionData(
+ foo.topicId(),
+ 0,
+ FetchRequest.INVALID_LOG_START_OFFSET,
+ fetchSize,
+ Optional.of(validLeaderEpoch))
+ ),
+ emptyList()
+ ),
+ fullFetchResponse(1, foo, this.records, Errors.NONE, 100L, 0)
+ );
+ consumerClient.poll(time.timer(0));
+ assertTrue(fetcher.hasCompletedFetches());
+ fetchedRecords();
+
+ // Assign bar and unassign foo.
+ subscriptions.assignFromUser(singleton(bar.topicPartition()));
+ client.updateMetadata(RequestTestUtils.metadataUpdateWithIds(1, singleton(bar), tp -> validLeaderEpoch));
+ subscriptions.seek(bar.topicPartition(), 0);
+
+ // Fetch should use latest version.
+ assertEquals(1, fetcher.sendFetches());
+ assertFalse(fetcher.hasCompletedFetches());
+
+ client.prepareResponse(
+ fetchRequestMatcher(ApiKeys.FETCH.latestVersion(),
+ singletonMap(bar, new PartitionData(
+ bar.topicId(),
+ 0,
+ FetchRequest.INVALID_LOG_START_OFFSET,
+ fetchSize,
+ Optional.of(validLeaderEpoch))
+ ),
+ singletonList(foo)
+ ),
+ fullFetchResponse(1, bar, this.records, Errors.NONE, 100L, 0)
+ );
+ consumerClient.poll(time.timer(0));
+ assertTrue(fetcher.hasCompletedFetches());
+ fetchedRecords();
+ }
+
+ @Test
+ public void testFetchForgetTopicIdWhenReplaced() {
+ buildFetcher();
+
+ TopicIdPartition fooWithOldTopicId = new TopicIdPartition(Uuid.randomUuid(), new TopicPartition("foo", 0));
+ TopicIdPartition fooWithNewTopicId = new TopicIdPartition(Uuid.randomUuid(), new TopicPartition("foo", 0));
+
+ // Assign foo with old topic id.
+ subscriptions.assignFromUser(singleton(fooWithOldTopicId.topicPartition()));
+ client.updateMetadata(RequestTestUtils.metadataUpdateWithIds(1, singleton(fooWithOldTopicId), tp -> validLeaderEpoch));
+ subscriptions.seek(fooWithOldTopicId.topicPartition(), 0);
+
+ // Fetch should use latest version.
+ assertEquals(1, fetcher.sendFetches());
+
+ client.prepareResponse(
+ fetchRequestMatcher(ApiKeys.FETCH.latestVersion(),
+ singletonMap(fooWithOldTopicId, new PartitionData(
+ fooWithOldTopicId.topicId(),
+ 0,
+ FetchRequest.INVALID_LOG_START_OFFSET,
+ fetchSize,
+ Optional.of(validLeaderEpoch))
+ ),
+ emptyList()
+ ),
+ fullFetchResponse(1, fooWithOldTopicId, this.records, Errors.NONE, 100L, 0)
+ );
+ consumerClient.poll(time.timer(0));
+ assertTrue(fetcher.hasCompletedFetches());
+ fetchedRecords();
+
+ // Replace foo with old topic id with foo with new topic id.
+ subscriptions.assignFromUser(singleton(fooWithNewTopicId.topicPartition()));
+ client.updateMetadata(RequestTestUtils.metadataUpdateWithIds(1, singleton(fooWithNewTopicId), tp -> validLeaderEpoch));
+ subscriptions.seek(fooWithNewTopicId.topicPartition(), 0);
+
+ // Fetch should use latest version.
+ assertEquals(1, fetcher.sendFetches());
+ assertFalse(fetcher.hasCompletedFetches());
+
+ // foo with old topic id should be removed from the session.
+ client.prepareResponse(
+ fetchRequestMatcher(ApiKeys.FETCH.latestVersion(),
+ singletonMap(fooWithNewTopicId, new PartitionData(
+ fooWithNewTopicId.topicId(),
+ 0,
+ FetchRequest.INVALID_LOG_START_OFFSET,
+ fetchSize,
+ Optional.of(validLeaderEpoch))
+ ),
+ singletonList(fooWithOldTopicId)
+ ),
+ fullFetchResponse(1, fooWithNewTopicId, this.records, Errors.NONE, 100L, 0)
+ );
+ consumerClient.poll(time.timer(0));
+ assertTrue(fetcher.hasCompletedFetches());
+ fetchedRecords();
+ }
+
+ @Test
+ public void testFetchTopicIdUpgradeDowngrade() {
+ buildFetcher();
+
+ TopicIdPartition fooWithoutId = new TopicIdPartition(Uuid.ZERO_UUID, new TopicPartition("foo", 0));
+ TopicIdPartition fooWithId = new TopicIdPartition(Uuid.randomUuid(), new TopicPartition("foo", 0));
+
+ // Assign foo without a topic id.
+ subscriptions.assignFromUser(singleton(fooWithoutId.topicPartition()));
+ client.updateMetadata(RequestTestUtils.metadataUpdateWithIds(1, singleton(fooWithoutId), tp -> validLeaderEpoch));
+ subscriptions.seek(fooWithoutId.topicPartition(), 0);
+
+ // Fetch should use version 12.
+ assertEquals(1, fetcher.sendFetches());
+
+ client.prepareResponse(
+ fetchRequestMatcher((short) 12,
+ singletonMap(fooWithoutId, new PartitionData(
+ fooWithoutId.topicId(),
+ 0,
+ FetchRequest.INVALID_LOG_START_OFFSET,
+ fetchSize,
+ Optional.of(validLeaderEpoch))
+ ),
+ emptyList()
+ ),
+ fullFetchResponse(1, fooWithoutId, this.records, Errors.NONE, 100L, 0)
+ );
+ consumerClient.poll(time.timer(0));
+ assertTrue(fetcher.hasCompletedFetches());
+ fetchedRecords();
+
+ // Upgrade.
+ subscriptions.assignFromUser(singleton(fooWithId.topicPartition()));
+ client.updateMetadata(RequestTestUtils.metadataUpdateWithIds(1, singleton(fooWithId), tp -> validLeaderEpoch));
+ subscriptions.seek(fooWithId.topicPartition(), 0);
+
+ // Fetch should use latest version.
+ assertEquals(1, fetcher.sendFetches());
+ assertFalse(fetcher.hasCompletedFetches());
+
+ // foo with old topic id should be removed from the session.
+ client.prepareResponse(
+ fetchRequestMatcher(ApiKeys.FETCH.latestVersion(),
+ singletonMap(fooWithId, new PartitionData(
+ fooWithId.topicId(),
+ 0,
+ FetchRequest.INVALID_LOG_START_OFFSET,
+ fetchSize,
+ Optional.of(validLeaderEpoch))
+ ),
+ emptyList()
+ ),
+ fullFetchResponse(1, fooWithId, this.records, Errors.NONE, 100L, 0)
+ );
+ consumerClient.poll(time.timer(0));
+ assertTrue(fetcher.hasCompletedFetches());
+ fetchedRecords();
+
+ // Downgrade.
+ subscriptions.assignFromUser(singleton(fooWithoutId.topicPartition()));
+ client.updateMetadata(RequestTestUtils.metadataUpdateWithIds(1, singleton(fooWithoutId), tp -> validLeaderEpoch));
+ subscriptions.seek(fooWithoutId.topicPartition(), 0);
+
+ // Fetch should use version 12.
+ assertEquals(1, fetcher.sendFetches());
+ assertFalse(fetcher.hasCompletedFetches());
+
+ // foo with old topic id should be removed from the session.
+ client.prepareResponse(
+ fetchRequestMatcher((short) 12,
+ singletonMap(fooWithoutId, new PartitionData(
+ fooWithoutId.topicId(),
+ 0,
+ FetchRequest.INVALID_LOG_START_OFFSET,
+ fetchSize,
+ Optional.of(validLeaderEpoch))
+ ),
+ emptyList()
+ ),
+ fullFetchResponse(1, fooWithoutId, this.records, Errors.NONE, 100L, 0)
+ );
+ consumerClient.poll(time.timer(0));
+ assertTrue(fetcher.hasCompletedFetches());
+ fetchedRecords();
+ }
+
+ private MockClient.RequestMatcher fetchRequestMatcher(
+ short expectedVersion,
+ TopicIdPartition tp,
+ long expectedFetchOffset,
+ Optional expectedCurrentLeaderEpoch
+ ) {
+ return fetchRequestMatcher(
+ expectedVersion,
+ singletonMap(tp, new PartitionData(
+ tp.topicId(),
+ expectedFetchOffset,
+ FetchRequest.INVALID_LOG_START_OFFSET,
+ fetchSize,
+ expectedCurrentLeaderEpoch
+ )),
+ emptyList()
+ );
+ }
+
+ private MockClient.RequestMatcher fetchRequestMatcher(
+ short expectedVersion,
+ Map fetch,
+ List forgotten
+ ) {
+ return body -> {
+ if (body instanceof FetchRequest) {
+ FetchRequest fetchRequest = (FetchRequest) body;
+ assertEquals(expectedVersion, fetchRequest.version());
+ assertEquals(fetch, fetchRequest.fetchData(topicNames(new ArrayList<>(fetch.keySet()))));
+ assertEquals(forgotten, fetchRequest.forgottenTopics(topicNames(forgotten)));
+ return true;
+ } else {
+ fail("Should have seen FetchRequest");
+ return false;
+ }
+ };
+ }
+
+ private Map topicNames(List partitions) {
+ Map topicNames = new HashMap<>();
+ partitions.forEach(partition -> topicNames.putIfAbsent(partition.topicId(), partition.topic()));
+ return topicNames;
+ }
+
@Test
public void testMissingLeaderEpochInRecords() {
buildFetcher();
@@ -312,7 +605,7 @@ public void testMissingLeaderEpochInRecords() {
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
- client.prepareResponse(fullFetchResponse(tp0, records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -363,7 +656,7 @@ public void testLeaderEpochInConsumerRecord() {
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
- client.prepareResponse(fullFetchResponse(tp0, records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -388,7 +681,7 @@ public void testClearBufferedDataForTopicPartitions() {
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
Set newAssignedTopicPartitions = new HashSet<>();
@@ -440,7 +733,7 @@ public void testFetcherIgnoresControlRecords() {
buffer.flip();
- client.prepareResponse(fullFetchResponse(tp0, MemoryRecords.readableRecords(buffer), Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, MemoryRecords.readableRecords(buffer), Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -465,7 +758,7 @@ public void testFetchError() {
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NOT_LEADER_OR_FOLLOWER, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NOT_LEADER_OR_FOLLOWER, 100L, 0));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -473,10 +766,10 @@ public void testFetchError() {
assertFalse(partitionRecords.containsKey(tp0));
}
- private MockClient.RequestMatcher matchesOffset(final TopicPartition tp, final long offset) {
+ private MockClient.RequestMatcher matchesOffset(final TopicIdPartition tp, final long offset) {
return body -> {
FetchRequest fetch = (FetchRequest) body;
- Map fetchData = fetch.fetchData(topicNames);
+ Map fetchData = fetch.fetchData(topicNames);
return fetchData.containsKey(tp) &&
fetchData.get(tp).fetchOffset == offset;
};
@@ -504,7 +797,7 @@ public byte[] deserialize(String topic, byte[] data) {
assignFromUser(singleton(tp0));
subscriptions.seek(tp0, 1);
- client.prepareResponse(matchesOffset(tp0, 1), fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(matchesOffset(tidp0, 1), fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
assertEquals(1, fetcher.sendFetches());
consumerClient.poll(time.timer(0));
@@ -568,7 +861,7 @@ public void testParseCorruptedRecord() throws Exception {
// normal fetch
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, MemoryRecords.readableRecords(buffer), Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, MemoryRecords.readableRecords(buffer), Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
// the first fetchedRecords() should return the first valid message
@@ -606,7 +899,7 @@ private void seekAndConsumeRecord(ByteBuffer responseBuffer, long toOffset) {
// Should not throw exception after the seek.
fetcher.fetchedRecords();
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, MemoryRecords.readableRecords(responseBuffer), Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, MemoryRecords.readableRecords(responseBuffer), Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
Map>> recordsByPartition = fetchedRecords();
@@ -642,7 +935,7 @@ public void testInvalidDefaultRecordBatch() {
// normal fetch
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, MemoryRecords.readableRecords(buffer), Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, MemoryRecords.readableRecords(buffer), Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
// the fetchedRecords() should always throw exception due to the bad batch.
@@ -674,7 +967,7 @@ public void testParseInvalidRecordBatch() {
// normal fetch
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, MemoryRecords.readableRecords(buffer), Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, MemoryRecords.readableRecords(buffer), Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
try {
fetcher.fetchedRecords();
@@ -707,7 +1000,7 @@ public void testHeaders() {
assignFromUser(singleton(tp0));
subscriptions.seek(tp0, 1);
- client.prepareResponse(matchesOffset(tp0, 1), fullFetchResponse(tp0, memoryRecords, Errors.NONE, 100L, 0));
+ client.prepareResponse(matchesOffset(tidp0, 1), fullFetchResponse(tidp0, memoryRecords, Errors.NONE, 100L, 0));
assertEquals(1, fetcher.sendFetches());
consumerClient.poll(time.timer(0));
@@ -738,8 +1031,8 @@ public void testFetchMaxPollRecords() {
assignFromUser(singleton(tp0));
subscriptions.seek(tp0, 1);
- client.prepareResponse(matchesOffset(tp0, 1), fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
- client.prepareResponse(matchesOffset(tp0, 4), fullFetchResponse(tp0, this.nextRecords, Errors.NONE, 100L, 0));
+ client.prepareResponse(matchesOffset(tidp0, 1), fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(matchesOffset(tidp0, 4), fullFetchResponse(tidp0, this.nextRecords, Errors.NONE, 100L, 0));
assertEquals(1, fetcher.sendFetches());
consumerClient.poll(time.timer(0));
@@ -782,7 +1075,7 @@ public void testFetchAfterPartitionWithFetchedRecordsIsUnassigned() {
subscriptions.seek(tp0, 1);
// Returns 3 records while `max.poll.records` is configured to 2
- client.prepareResponse(matchesOffset(tp0, 1), fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(matchesOffset(tidp0, 1), fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
assertEquals(1, fetcher.sendFetches());
consumerClient.poll(time.timer(0));
@@ -794,7 +1087,7 @@ public void testFetchAfterPartitionWithFetchedRecordsIsUnassigned() {
assertEquals(2, records.get(1).offset());
assignFromUser(singleton(tp1));
- client.prepareResponse(matchesOffset(tp1, 4), fullFetchResponse(tp1, this.nextRecords, Errors.NONE, 100L, 0));
+ client.prepareResponse(matchesOffset(tidp1, 4), fullFetchResponse(tidp1, this.nextRecords, Errors.NONE, 100L, 0));
subscriptions.seek(tp1, 4);
assertEquals(1, fetcher.sendFetches());
@@ -827,7 +1120,7 @@ public void testFetchNonContinuousRecords() {
// normal fetch
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
Map>> recordsByPartition = fetchedRecords();
consumerRecords = recordsByPartition.get(tp0);
@@ -890,7 +1183,7 @@ private void makeFetchRequestWithIncompleteRecord() {
assertFalse(fetcher.hasCompletedFetches());
MemoryRecords partialRecord = MemoryRecords.readableRecords(
ByteBuffer.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 0}));
- client.prepareResponse(fullFetchResponse(tp0, partialRecord, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, partialRecord, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
}
@@ -904,7 +1197,7 @@ public void testUnauthorizedTopic() {
// resize the limit of the buffer to pretend it is only fetch-size large
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.TOPIC_AUTHORIZATION_FAILED, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.TOPIC_AUTHORIZATION_FAILED, 100L, 0));
consumerClient.poll(time.timer(0));
try {
fetcher.fetchedRecords();
@@ -931,7 +1224,7 @@ public void testFetchDuringEagerRebalance() {
subscriptions.assignFromSubscribed(Collections.emptyList());
subscriptions.assignFromSubscribed(singleton(tp0));
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
// The active fetch should be ignored since its position is no longer valid
@@ -954,7 +1247,7 @@ public void testFetchDuringCooperativeRebalance() {
// Now the cooperative rebalance happens and fetch positions are NOT cleared for unrevoked partitions
subscriptions.assignFromSubscribed(singleton(tp0));
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
Map>> fetchedRecords = fetchedRecords();
@@ -974,7 +1267,7 @@ public void testInFlightFetchOnPausedPartition() {
assertEquals(1, fetcher.sendFetches());
subscriptions.pause(tp0);
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertNull(fetcher.fetchedRecords().get(tp0));
}
@@ -1002,7 +1295,7 @@ public void testFetchOnCompletedFetchesForPausedAndResumedPartitions() {
subscriptions.pause(tp0);
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
Map>> fetchedRecords = fetchedRecords();
@@ -1034,20 +1327,20 @@ public void testFetchOnCompletedFetchesForSomePausedPartitions() {
Map>> fetchedRecords;
- assignFromUser(Utils.mkSet(tp0, tp1));
+ assignFromUser(mkSet(tp0, tp1));
// seek to tp0 and tp1 in two polls to generate 2 complete requests and responses
// #1 seek, request, poll, response
subscriptions.seekUnvalidated(tp0, new SubscriptionState.FetchPosition(1, Optional.empty(), metadata.currentLeader(tp0)));
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
// #2 seek, request, poll, response
subscriptions.seekUnvalidated(tp1, new SubscriptionState.FetchPosition(1, Optional.empty(), metadata.currentLeader(tp1)));
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp1, this.nextRecords, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp1, this.nextRecords, Errors.NONE, 100L, 0));
subscriptions.pause(tp0);
consumerClient.poll(time.timer(0));
@@ -1069,20 +1362,20 @@ public void testFetchOnCompletedFetchesForAllPausedPartitions() {
Map>> fetchedRecords;
- assignFromUser(Utils.mkSet(tp0, tp1));
+ assignFromUser(mkSet(tp0, tp1));
// seek to tp0 and tp1 in two polls to generate 2 complete requests and responses
// #1 seek, request, poll, response
subscriptions.seekUnvalidated(tp0, new SubscriptionState.FetchPosition(1, Optional.empty(), metadata.currentLeader(tp0)));
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
// #2 seek, request, poll, response
subscriptions.seekUnvalidated(tp1, new SubscriptionState.FetchPosition(1, Optional.empty(), metadata.currentLeader(tp1)));
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp1, this.nextRecords, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp1, this.nextRecords, Errors.NONE, 100L, 0));
subscriptions.pause(tp0);
subscriptions.pause(tp1);
@@ -1104,11 +1397,11 @@ public void testPartialFetchWithPausedPartitions() {
Map>> fetchedRecords;
- assignFromUser(Utils.mkSet(tp0, tp1));
+ assignFromUser(mkSet(tp0, tp1));
subscriptions.seek(tp0, 1);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
fetchedRecords = fetchedRecords();
@@ -1143,7 +1436,7 @@ public void testFetchDiscardedAfterPausedPartitionResumedAndSeekedToNewOffset()
assertEquals(1, fetcher.sendFetches());
subscriptions.pause(tp0);
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
subscriptions.seek(tp0, 3);
subscriptions.resume(tp0);
@@ -1163,7 +1456,7 @@ public void testFetchNotLeaderOrFollower() {
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NOT_LEADER_OR_FOLLOWER, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NOT_LEADER_OR_FOLLOWER, 100L, 0));
consumerClient.poll(time.timer(0));
assertEquals(0, fetcher.fetchedRecords().size());
assertEquals(0L, metadata.timeToNextUpdate(time.milliseconds()));
@@ -1176,7 +1469,7 @@ public void testFetchUnknownTopicOrPartition() {
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.UNKNOWN_TOPIC_OR_PARTITION, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.UNKNOWN_TOPIC_OR_PARTITION, 100L, 0));
consumerClient.poll(time.timer(0));
assertEquals(0, fetcher.fetchedRecords().size());
assertEquals(0L, metadata.timeToNextUpdate(time.milliseconds()));
@@ -1189,7 +1482,7 @@ public void testFetchUnknownTopicId() {
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fetchResponseWithTopLevelError(tp0, Errors.UNKNOWN_TOPIC_ID, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.UNKNOWN_TOPIC_ID, -1L, 0));
consumerClient.poll(time.timer(0));
assertEquals(0, fetcher.fetchedRecords().size());
assertEquals(0L, metadata.timeToNextUpdate(time.milliseconds()));
@@ -1202,7 +1495,7 @@ public void testFetchSessionIdError() {
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fetchResponseWithTopLevelError(tp0, Errors.FETCH_SESSION_TOPIC_ID_ERROR, 0));
+ client.prepareResponse(fetchResponseWithTopLevelError(tidp0, Errors.FETCH_SESSION_TOPIC_ID_ERROR, 0));
consumerClient.poll(time.timer(0));
assertEquals(0, fetcher.fetchedRecords().size());
assertEquals(0L, metadata.timeToNextUpdate(time.milliseconds()));
@@ -1215,7 +1508,7 @@ public void testFetchInconsistentTopicId() {
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fetchResponseWithTopLevelError(tp0, Errors.INCONSISTENT_TOPIC_ID, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.INCONSISTENT_TOPIC_ID, -1L, 0));
consumerClient.poll(time.timer(0));
assertEquals(0, fetcher.fetchedRecords().size());
assertEquals(0L, metadata.timeToNextUpdate(time.milliseconds()));
@@ -1228,7 +1521,7 @@ public void testFetchFencedLeaderEpoch() {
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.FENCED_LEADER_EPOCH, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.FENCED_LEADER_EPOCH, 100L, 0));
consumerClient.poll(time.timer(0));
assertEquals(0, fetcher.fetchedRecords().size(), "Should not return any records");
@@ -1242,7 +1535,7 @@ public void testFetchUnknownLeaderEpoch() {
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.UNKNOWN_LEADER_EPOCH, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.UNKNOWN_LEADER_EPOCH, 100L, 0));
consumerClient.poll(time.timer(0));
assertEquals(0, fetcher.fetchedRecords().size(), "Should not return any records");
@@ -1274,7 +1567,7 @@ public void testEpochSetInFetchRequest() {
return false;
}
};
- client.prepareResponse(matcher, fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(matcher, fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.pollNoWakeup();
}
@@ -1285,7 +1578,7 @@ public void testFetchOffsetOutOfRange() {
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.OFFSET_OUT_OF_RANGE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.OFFSET_OUT_OF_RANGE, 100L, 0));
consumerClient.poll(time.timer(0));
assertEquals(0, fetcher.fetchedRecords().size());
assertTrue(subscriptions.isOffsetResetNeeded(tp0));
@@ -1302,7 +1595,7 @@ public void testStaleOutOfRangeError() {
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.OFFSET_OUT_OF_RANGE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.OFFSET_OUT_OF_RANGE, 100L, 0));
subscriptions.seek(tp0, 1);
consumerClient.poll(time.timer(0));
assertEquals(0, fetcher.fetchedRecords().size());
@@ -1319,7 +1612,7 @@ public void testFetchedRecordsAfterSeek() {
subscriptions.seek(tp0, 0);
assertTrue(fetcher.sendFetches() > 0);
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.OFFSET_OUT_OF_RANGE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.OFFSET_OUT_OF_RANGE, 100L, 0));
consumerClient.poll(time.timer(0));
assertFalse(subscriptions.isOffsetResetNeeded(tp0));
subscriptions.seek(tp0, 2);
@@ -1335,7 +1628,7 @@ public void testFetchOffsetOutOfRangeException() {
subscriptions.seek(tp0, 0);
fetcher.sendFetches();
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.OFFSET_OUT_OF_RANGE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.OFFSET_OUT_OF_RANGE, 100L, 0));
consumerClient.poll(time.timer(0));
assertFalse(subscriptions.isOffsetResetNeeded(tp0));
@@ -1353,22 +1646,22 @@ public void testFetchPositionAfterException() {
// some fetched partitions cause Exception. This ensures that consumer won't lose record upon exception
buildFetcher(OffsetResetStrategy.NONE, new ByteArrayDeserializer(),
new ByteArrayDeserializer(), Integer.MAX_VALUE, IsolationLevel.READ_UNCOMMITTED);
- assignFromUser(Utils.mkSet(tp0, tp1));
+ assignFromUser(mkSet(tp0, tp1));
subscriptions.seek(tp0, 1);
subscriptions.seek(tp1, 1);
assertEquals(1, fetcher.sendFetches());
- Map partitions = new LinkedHashMap<>();
- partitions.put(tp1, new FetchResponseData.PartitionData()
+ Map partitions = new LinkedHashMap<>();
+ partitions.put(tidp1, new FetchResponseData.PartitionData()
.setPartitionIndex(tp1.partition())
.setHighWatermark(100)
.setRecords(records));
- partitions.put(tp0, new FetchResponseData.PartitionData()
+ partitions.put(tidp0, new FetchResponseData.PartitionData()
.setPartitionIndex(tp0.partition())
.setErrorCode(Errors.OFFSET_OUT_OF_RANGE.code())
.setHighWatermark(100));
- client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, new LinkedHashMap<>(partitions), topicIds));
+ client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, new LinkedHashMap<>(partitions)));
consumerClient.poll(time.timer(0));
List> allFetchedRecords = new ArrayList<>();
@@ -1399,7 +1692,7 @@ public void testCompletedFetchRemoval() {
// Ensure the removal of completed fetches that cause an Exception if and only if they contain empty records.
buildFetcher(OffsetResetStrategy.NONE, new ByteArrayDeserializer(),
new ByteArrayDeserializer(), Integer.MAX_VALUE, IsolationLevel.READ_UNCOMMITTED);
- assignFromUser(Utils.mkSet(tp0, tp1, tp2, tp3));
+ assignFromUser(mkSet(tp0, tp1, tp2, tp3));
subscriptions.seek(tp0, 1);
subscriptions.seek(tp1, 1);
@@ -1408,28 +1701,28 @@ public void testCompletedFetchRemoval() {
assertEquals(1, fetcher.sendFetches());
- Map partitions = new LinkedHashMap<>();
- partitions.put(tp1, new FetchResponseData.PartitionData()
+ Map partitions = new LinkedHashMap<>();
+ partitions.put(tidp1, new FetchResponseData.PartitionData()
.setPartitionIndex(tp1.partition())
.setHighWatermark(100)
.setRecords(records));
- partitions.put(tp0, new FetchResponseData.PartitionData()
+ partitions.put(tidp0, new FetchResponseData.PartitionData()
.setPartitionIndex(tp0.partition())
.setErrorCode(Errors.OFFSET_OUT_OF_RANGE.code())
.setHighWatermark(100));
- partitions.put(tp2, new FetchResponseData.PartitionData()
+ partitions.put(tidp2, new FetchResponseData.PartitionData()
.setPartitionIndex(tp2.partition())
.setHighWatermark(100)
.setLastStableOffset(4)
.setLogStartOffset(0)
.setRecords(nextRecords));
- partitions.put(tp3, new FetchResponseData.PartitionData()
+ partitions.put(tidp3, new FetchResponseData.PartitionData()
.setPartitionIndex(tp3.partition())
.setHighWatermark(100)
.setLastStableOffset(4)
.setLogStartOffset(0)
.setRecords(partialRecords));
- client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, new LinkedHashMap<>(partitions), topicIds));
+ client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, new LinkedHashMap<>(partitions)));
consumerClient.poll(time.timer(0));
List> fetchedRecords = new ArrayList<>();
@@ -1484,29 +1777,29 @@ public void testSeekBeforeException() {
buildFetcher(OffsetResetStrategy.NONE, new ByteArrayDeserializer(),
new ByteArrayDeserializer(), 2, IsolationLevel.READ_UNCOMMITTED);
- assignFromUser(Utils.mkSet(tp0));
+ assignFromUser(mkSet(tp0));
subscriptions.seek(tp0, 1);
assertEquals(1, fetcher.sendFetches());
- Map partitions = new HashMap<>();
- partitions.put(tp0, new FetchResponseData.PartitionData()
+ Map partitions = new HashMap<>();
+ partitions.put(tidp0, new FetchResponseData.PartitionData()
.setPartitionIndex(tp0.partition())
.setHighWatermark(100)
.setRecords(records));
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertEquals(2, fetcher.fetchedRecords().get(tp0).size());
- subscriptions.assignFromUser(Utils.mkSet(tp0, tp1));
+ subscriptions.assignFromUser(mkSet(tp0, tp1));
subscriptions.seekUnvalidated(tp1, new SubscriptionState.FetchPosition(1, Optional.empty(), metadata.currentLeader(tp1)));
assertEquals(1, fetcher.sendFetches());
partitions = new HashMap<>();
- partitions.put(tp1, new FetchResponseData.PartitionData()
+ partitions.put(tidp1, new FetchResponseData.PartitionData()
.setPartitionIndex(tp1.partition())
.setErrorCode(Errors.OFFSET_OUT_OF_RANGE.code())
.setHighWatermark(100));
- client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, new LinkedHashMap<>(partitions), topicIds));
+ client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, new LinkedHashMap<>(partitions)));
consumerClient.poll(time.timer(0));
assertEquals(1, fetcher.fetchedRecords().get(tp0).size());
@@ -1523,7 +1816,7 @@ public void testFetchDisconnected() {
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0), true);
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0), true);
consumerClient.poll(time.timer(0));
assertEquals(0, fetcher.fetchedRecords().size());
@@ -2196,12 +2489,12 @@ public void testQuotaMetrics() {
for (int i = 1; i <= 3; i++) {
int throttleTimeMs = 100 * i;
- FetchRequest.Builder builder = FetchRequest.Builder.forConsumer(ApiKeys.FETCH.latestVersion(), 100, 100, new LinkedHashMap<>(), topicIds);
+ FetchRequest.Builder builder = FetchRequest.Builder.forConsumer(ApiKeys.FETCH.latestVersion(), 100, 100, new LinkedHashMap<>());
builder.rackId("");
ClientRequest request = client.newClientRequest(node.idString(), builder, time.milliseconds(), true);
client.send(request, time.milliseconds());
client.poll(1, time.milliseconds());
- FetchResponse response = fullFetchResponse(tp0, nextRecords, Errors.NONE, i, throttleTimeMs);
+ FetchResponse response = fullFetchResponse(tidp0, nextRecords, Errors.NONE, i, throttleTimeMs);
buffer = RequestTestUtils.serializeResponseWithHeader(response, ApiKeys.FETCH.latestVersion(), request.correlationId());
selector.completeReceive(new NetworkReceive(node.idString(), buffer));
client.poll(1, time.milliseconds());
@@ -2240,7 +2533,7 @@ public void testFetcherMetrics() {
assertEquals(Double.NaN, (Double) recordsFetchLagMax.metricValue(), EPSILON);
// recordsFetchLagMax should be hw - fetchOffset after receiving an empty FetchResponse
- fetchRecords(tp0, MemoryRecords.EMPTY, Errors.NONE, 100L, 0);
+ fetchRecords(tidp0, MemoryRecords.EMPTY, Errors.NONE, 100L, 0);
assertEquals(100, (Double) recordsFetchLagMax.metricValue(), EPSILON);
KafkaMetric partitionLag = allMetrics.get(partitionLagMetric);
@@ -2251,7 +2544,7 @@ public void testFetcherMetrics() {
TimestampType.CREATE_TIME, 0L);
for (int v = 0; v < 3; v++)
builder.appendWithOffset(v, RecordBatch.NO_TIMESTAMP, "key".getBytes(), ("value-" + v).getBytes());
- fetchRecords(tp0, builder.build(), Errors.NONE, 200L, 0);
+ fetchRecords(tidp0, builder.build(), Errors.NONE, 200L, 0);
assertEquals(197, (Double) recordsFetchLagMax.metricValue(), EPSILON);
assertEquals(197, (Double) partitionLag.metricValue(), EPSILON);
@@ -2281,7 +2574,7 @@ public void testFetcherLeadMetric() {
assertEquals(Double.NaN, (Double) recordsFetchLeadMin.metricValue(), EPSILON);
// recordsFetchLeadMin should be position - logStartOffset after receiving an empty FetchResponse
- fetchRecords(tp0, MemoryRecords.EMPTY, Errors.NONE, 100L, -1L, 0L, 0);
+ fetchRecords(tidp0, MemoryRecords.EMPTY, Errors.NONE, 100L, -1L, 0L, 0);
assertEquals(0L, (Double) recordsFetchLeadMin.metricValue(), EPSILON);
KafkaMetric partitionLead = allMetrics.get(partitionLeadMetric);
@@ -2293,7 +2586,7 @@ public void testFetcherLeadMetric() {
for (int v = 0; v < 3; v++) {
builder.appendWithOffset(v, RecordBatch.NO_TIMESTAMP, "key".getBytes(), ("value-" + v).getBytes());
}
- fetchRecords(tp0, builder.build(), Errors.NONE, 200L, -1L, 0L, 0);
+ fetchRecords(tidp0, builder.build(), Errors.NONE, 200L, -1L, 0L, 0);
assertEquals(0L, (Double) recordsFetchLeadMin.metricValue(), EPSILON);
assertEquals(3L, (Double) partitionLead.metricValue(), EPSILON);
@@ -2325,7 +2618,7 @@ public void testReadCommittedLagMetric() {
assertEquals(Double.NaN, (Double) recordsFetchLagMax.metricValue(), EPSILON);
// recordsFetchLagMax should be lso - fetchOffset after receiving an empty FetchResponse
- fetchRecords(tp0, MemoryRecords.EMPTY, Errors.NONE, 100L, 50L, 0);
+ fetchRecords(tidp0, MemoryRecords.EMPTY, Errors.NONE, 100L, 50L, 0);
assertEquals(50, (Double) recordsFetchLagMax.metricValue(), EPSILON);
KafkaMetric partitionLag = allMetrics.get(partitionLagMetric);
@@ -2336,7 +2629,7 @@ public void testReadCommittedLagMetric() {
TimestampType.CREATE_TIME, 0L);
for (int v = 0; v < 3; v++)
builder.appendWithOffset(v, RecordBatch.NO_TIMESTAMP, "key".getBytes(), ("value-" + v).getBytes());
- fetchRecords(tp0, builder.build(), Errors.NONE, 200L, 150L, 0);
+ fetchRecords(tidp0, builder.build(), Errors.NONE, 200L, 150L, 0);
assertEquals(147, (Double) recordsFetchLagMax.metricValue(), EPSILON);
assertEquals(147, (Double) partitionLag.metricValue(), EPSILON);
@@ -2355,20 +2648,22 @@ public void testFetchResponseMetrics() {
TopicPartition tp1 = new TopicPartition(topic1, 0);
TopicPartition tp2 = new TopicPartition(topic2, 0);
- subscriptions.assignFromUser(Utils.mkSet(tp1, tp2));
+ subscriptions.assignFromUser(mkSet(tp1, tp2));
Map partitionCounts = new HashMap<>();
partitionCounts.put(topic1, 1);
partitionCounts.put(topic2, 1);
topicIds.put(topic1, Uuid.randomUuid());
topicIds.put(topic2, Uuid.randomUuid());
+ TopicIdPartition tidp1 = new TopicIdPartition(topicIds.get(topic1), tp1);
+ TopicIdPartition tidp2 = new TopicIdPartition(topicIds.get(topic2), tp2);
client.updateMetadata(RequestTestUtils.metadataUpdateWithIds(1, partitionCounts, tp -> validLeaderEpoch, topicIds));
int expectedBytes = 0;
- LinkedHashMap fetchPartitionData = new LinkedHashMap<>();
+ LinkedHashMap fetchPartitionData = new LinkedHashMap<>();
- for (TopicPartition tp : Utils.mkSet(tp1, tp2)) {
- subscriptions.seek(tp, 0);
+ for (TopicIdPartition tp : mkSet(tidp1, tidp2)) {
+ subscriptions.seek(tp.topicPartition(), 0);
MemoryRecordsBuilder builder = MemoryRecords.builder(ByteBuffer.allocate(1024), CompressionType.NONE,
TimestampType.CREATE_TIME, 0L);
@@ -2379,14 +2674,14 @@ public void testFetchResponseMetrics() {
expectedBytes += record.sizeInBytes();
fetchPartitionData.put(tp, new FetchResponseData.PartitionData()
- .setPartitionIndex(tp.partition())
+ .setPartitionIndex(tp.topicPartition().partition())
.setHighWatermark(15)
.setLogStartOffset(0)
.setRecords(records));
}
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, fetchPartitionData, topicIds));
+ client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, fetchPartitionData));
consumerClient.poll(time.timer(0));
Map>> fetchedRecords = fetchedRecords();
@@ -2423,7 +2718,7 @@ public void testFetchResponseMetricsPartialResponse() {
expectedBytes += record.sizeInBytes();
}
- fetchRecords(tp0, records, Errors.NONE, 100L, 0);
+ fetchRecords(tidp0, records, Errors.NONE, 100L, 0);
assertEquals(expectedBytes, (Double) fetchSizeAverage.metricValue(), EPSILON);
assertEquals(2, (Double) recordsCountAverage.metricValue(), EPSILON);
}
@@ -2431,7 +2726,7 @@ public void testFetchResponseMetricsPartialResponse() {
@Test
public void testFetchResponseMetricsWithOnePartitionError() {
buildFetcher();
- assignFromUser(Utils.mkSet(tp0, tp1));
+ assignFromUser(mkSet(tp0, tp1));
subscriptions.seek(tp0, 0);
subscriptions.seek(tp1, 0);
@@ -2445,20 +2740,20 @@ public void testFetchResponseMetricsWithOnePartitionError() {
builder.appendWithOffset(v, RecordBatch.NO_TIMESTAMP, "key".getBytes(), ("value-" + v).getBytes());
MemoryRecords records = builder.build();
- Map partitions = new HashMap<>();
- partitions.put(tp0, new FetchResponseData.PartitionData()
+ Map partitions = new HashMap<>();
+ partitions.put(tidp0, new FetchResponseData.PartitionData()
.setPartitionIndex(tp0.partition())
.setHighWatermark(100)
.setLogStartOffset(0)
.setRecords(records));
- partitions.put(tp1, new FetchResponseData.PartitionData()
+ partitions.put(tidp1, new FetchResponseData.PartitionData()
.setPartitionIndex(tp1.partition())
.setErrorCode(Errors.OFFSET_OUT_OF_RANGE.code())
.setHighWatermark(100)
.setLogStartOffset(0));
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, new LinkedHashMap<>(partitions), topicIds));
+ client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, new LinkedHashMap<>(partitions)));
consumerClient.poll(time.timer(0));
fetcher.fetchedRecords();
@@ -2474,7 +2769,7 @@ public void testFetchResponseMetricsWithOnePartitionError() {
public void testFetchResponseMetricsWithOnePartitionAtTheWrongOffset() {
buildFetcher();
- assignFromUser(Utils.mkSet(tp0, tp1));
+ assignFromUser(mkSet(tp0, tp1));
subscriptions.seek(tp0, 0);
subscriptions.seek(tp1, 0);
@@ -2492,19 +2787,19 @@ public void testFetchResponseMetricsWithOnePartitionAtTheWrongOffset() {
builder.appendWithOffset(v, RecordBatch.NO_TIMESTAMP, "key".getBytes(), ("value-" + v).getBytes());
MemoryRecords records = builder.build();
- Map partitions = new HashMap<>();
- partitions.put(tp0, new FetchResponseData.PartitionData()
+ Map partitions = new HashMap<>();
+ partitions.put(tidp0, new FetchResponseData.PartitionData()
.setPartitionIndex(tp0.partition())
.setHighWatermark(100)
.setLogStartOffset(0)
.setRecords(records));
- partitions.put(tp1, new FetchResponseData.PartitionData()
+ partitions.put(tidp1, new FetchResponseData.PartitionData()
.setPartitionIndex(tp1.partition())
.setHighWatermark(100)
.setLogStartOffset(0)
.setRecords(MemoryRecords.withRecords(CompressionType.NONE, new SimpleRecord("val".getBytes()))));
- client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, new LinkedHashMap<>(partitions), topicIds));
+ client.prepareResponse(FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, new LinkedHashMap<>(partitions)));
consumerClient.poll(time.timer(0));
fetcher.fetchedRecords();
@@ -2527,7 +2822,7 @@ public void testFetcherMetricsTemplates() {
assignFromUser(singleton(tp0));
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
Map>> partitionRecords = fetchedRecords();
@@ -2547,12 +2842,12 @@ public void testFetcherMetricsTemplates() {
}
private Map>> fetchRecords(
- TopicPartition tp, MemoryRecords records, Errors error, long hw, int throttleTime) {
+ TopicIdPartition tp, MemoryRecords records, Errors error, long hw, int throttleTime) {
return fetchRecords(tp, records, error, hw, FetchResponse.INVALID_LAST_STABLE_OFFSET, throttleTime);
}
private Map>> fetchRecords(
- TopicPartition tp, MemoryRecords records, Errors error, long hw, long lastStableOffset, int throttleTime) {
+ TopicIdPartition tp, MemoryRecords records, Errors error, long hw, long lastStableOffset, int throttleTime) {
assertEquals(1, fetcher.sendFetches());
client.prepareResponse(fullFetchResponse(tp, records, error, hw, lastStableOffset, throttleTime));
consumerClient.poll(time.timer(0));
@@ -2560,7 +2855,7 @@ private Map>> fetchRecords(
}
private Map>> fetchRecords(
- TopicPartition tp, MemoryRecords records, Errors error, long hw, long lastStableOffset, long logStartOffset, int throttleTime) {
+ TopicIdPartition tp, MemoryRecords records, Errors error, long hw, long lastStableOffset, long logStartOffset, int throttleTime) {
assertEquals(1, fetcher.sendFetches());
client.prepareResponse(fetchResponse(tp, records, error, hw, lastStableOffset, logStartOffset, throttleTime));
consumerClient.poll(time.timer(0));
@@ -2632,7 +2927,7 @@ public void testGetOffsetByTimeWithPartitionsRetryCouldTriggerMetadataUpdate() {
for (Errors retriableError : retriableErrors) {
buildFetcher();
- subscriptions.assignFromUser(Utils.mkSet(tp0, tp1));
+ subscriptions.assignFromUser(mkSet(tp0, tp1));
client.updateMetadata(initialUpdateResponse);
final long fetchTimestamp = 10L;
@@ -2784,7 +3079,7 @@ public void testGetOffsetsIncludesLeaderEpoch() {
public void testGetOffsetsForTimesWhenSomeTopicPartitionLeadersNotKnownInitially() {
buildFetcher();
- subscriptions.assignFromUser(Utils.mkSet(tp0, tp1));
+ subscriptions.assignFromUser(mkSet(tp0, tp1));
final String anotherTopic = "another-topic";
final TopicPartition t2p0 = new TopicPartition(anotherTopic, 0);
@@ -2831,7 +3126,7 @@ public void testGetOffsetsForTimesWhenSomeTopicPartitionLeadersDisconnectExcepti
buildFetcher();
final String anotherTopic = "another-topic";
final TopicPartition t2p0 = new TopicPartition(anotherTopic, 0);
- subscriptions.assignFromUser(Utils.mkSet(tp0, t2p0));
+ subscriptions.assignFromUser(mkSet(tp0, t2p0));
client.reset();
@@ -3044,7 +3339,7 @@ public void testReadCommittedWithCommittedAndAbortedTransactions() {
for (ConsumerRecord consumerRecord : fetchedConsumerRecords) {
fetchedKeys.add(new String(consumerRecord.key(), StandardCharsets.UTF_8));
}
- assertEquals(Utils.mkSet("commit1-1", "commit1-2", "commit2-1"), fetchedKeys);
+ assertEquals(mkSet("commit1-1", "commit1-2", "commit2-1"), fetchedKeys);
}
@Test
@@ -3164,7 +3459,7 @@ protected boolean shouldRetainRecord(RecordBatch recordBatch, Record record) {
assignFromUser(singleton(tp0));
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, compactedRecords, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, compactedRecords, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -3201,7 +3496,7 @@ public void testUpdatePositionOnEmptyBatch() {
assignFromUser(singleton(tp0));
subscriptions.seek(tp0, 0);
assertEquals(1, fetcher.sendFetches());
- client.prepareResponse(fullFetchResponse(tp0, recordsWithEmptyBatch, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, recordsWithEmptyBatch, Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -3355,19 +3650,19 @@ public void testConsumingViaIncrementalFetchRequests() {
subscriptions.seekValidated(tp1, new SubscriptionState.FetchPosition(1, Optional.empty(), metadata.currentLeader(tp1)));
// Fetch some records and establish an incremental fetch session.
- LinkedHashMap partitions1 = new LinkedHashMap<>();
- partitions1.put(tp0, new FetchResponseData.PartitionData()
+ LinkedHashMap partitions1 = new LinkedHashMap<>();
+ partitions1.put(tidp0, new FetchResponseData.PartitionData()
.setPartitionIndex(tp0.partition())
.setHighWatermark(2)
.setLastStableOffset(2)
.setLogStartOffset(0)
.setRecords(this.records));
- partitions1.put(tp1, new FetchResponseData.PartitionData()
+ partitions1.put(tidp1, new FetchResponseData.PartitionData()
.setPartitionIndex(tp1.partition())
.setHighWatermark(100)
.setLogStartOffset(0)
.setRecords(emptyRecords));
- FetchResponse resp1 = FetchResponse.of(Errors.NONE, 0, 123, partitions1, topicIds);
+ FetchResponse resp1 = FetchResponse.of(Errors.NONE, 0, 123, partitions1);
client.prepareResponse(resp1);
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
@@ -3392,8 +3687,8 @@ public void testConsumingViaIncrementalFetchRequests() {
assertEquals(4L, subscriptions.position(tp0).offset);
// The second response contains no new records.
- LinkedHashMap partitions2 = new LinkedHashMap<>();
- FetchResponse resp2 = FetchResponse.of(Errors.NONE, 0, 123, partitions2, topicIds);
+ LinkedHashMap partitions2 = new LinkedHashMap<>();
+ FetchResponse resp2 = FetchResponse.of(Errors.NONE, 0, 123, partitions2);
client.prepareResponse(resp2);
assertEquals(1, fetcher.sendFetches());
consumerClient.poll(time.timer(0));
@@ -3403,14 +3698,14 @@ public void testConsumingViaIncrementalFetchRequests() {
assertEquals(1L, subscriptions.position(tp1).offset);
// The third response contains some new records for tp0.
- LinkedHashMap partitions3 = new LinkedHashMap<>();
- partitions3.put(tp0, new FetchResponseData.PartitionData()
+ LinkedHashMap partitions3 = new LinkedHashMap<>();
+ partitions3.put(tidp0, new FetchResponseData.PartitionData()
.setPartitionIndex(tp0.partition())
.setHighWatermark(100)
.setLastStableOffset(4)
.setLogStartOffset(0)
.setRecords(this.nextRecords));
- FetchResponse resp3 = FetchResponse.of(Errors.NONE, 0, 123, partitions3, topicIds);
+ FetchResponse resp3 = FetchResponse.of(Errors.NONE, 0, 123, partitions3);
client.prepareResponse(resp3);
assertEquals(1, fetcher.sendFetches());
consumerClient.poll(time.timer(0));
@@ -3517,18 +3812,18 @@ private void verifySessionPartitions() {
if (!client.requests().isEmpty()) {
ClientRequest request = client.requests().peek();
FetchRequest fetchRequest = (FetchRequest) request.requestBuilder().build();
- LinkedHashMap responseMap = new LinkedHashMap<>();
- for (Map.Entry entry : fetchRequest.fetchData(topicNames).entrySet()) {
- TopicPartition tp = entry.getKey();
+ LinkedHashMap responseMap = new LinkedHashMap<>();
+ for (Map.Entry entry : fetchRequest.fetchData(topicNames).entrySet()) {
+ TopicIdPartition tp = entry.getKey();
long offset = entry.getValue().fetchOffset;
responseMap.put(tp, new FetchResponseData.PartitionData()
- .setPartitionIndex(tp.partition())
+ .setPartitionIndex(tp.topicPartition().partition())
.setHighWatermark(offset + 2)
.setLastStableOffset(offset + 2)
.setLogStartOffset(0)
.setRecords(buildRecords(offset, 2, offset)));
}
- client.respondToRequest(request, FetchResponse.of(Errors.NONE, 0, 123, responseMap, topicIds));
+ client.respondToRequest(request, FetchResponse.of(Errors.NONE, 0, 123, responseMap));
consumerClient.poll(time.timer(0));
}
}
@@ -3583,15 +3878,15 @@ public void testFetcherSessionEpochUpdate() throws Exception {
assertTrue(epoch == 0 || epoch == nextEpoch,
String.format("Unexpected epoch expected %d got %d", nextEpoch, epoch));
nextEpoch++;
- LinkedHashMap responseMap = new LinkedHashMap<>();
- responseMap.put(tp0, new FetchResponseData.PartitionData()
+ LinkedHashMap responseMap = new LinkedHashMap<>();
+ responseMap.put(tidp0, new FetchResponseData.PartitionData()
.setPartitionIndex(tp0.partition())
.setHighWatermark(nextOffset + 2)
.setLastStableOffset(nextOffset + 2)
.setLogStartOffset(0)
.setRecords(buildRecords(nextOffset, 2, nextOffset)));
nextOffset += 2;
- client.respondToRequest(request, FetchResponse.of(Errors.NONE, 0, 123, responseMap, topicIds));
+ client.respondToRequest(request, FetchResponse.of(Errors.NONE, 0, 123, responseMap));
consumerClient.poll(time.timer(0));
}
}
@@ -3857,7 +4152,7 @@ public void testSubscriptionPositionUpdatedWithEpoch() {
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
- client.prepareResponse(fullFetchResponse(tp0, records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, records, Errors.NONE, 100L, 0));
consumerClient.pollNoWakeup();
assertTrue(fetcher.hasCompletedFetches());
@@ -3871,7 +4166,7 @@ public void testSubscriptionPositionUpdatedWithEpoch() {
@Test
public void testOffsetValidationRequestGrouping() {
buildFetcher();
- assignFromUser(Utils.mkSet(tp0, tp1, tp2, tp3));
+ assignFromUser(mkSet(tp0, tp1, tp2, tp3));
metadata.updateWithCurrentRequestVersion(RequestTestUtils.metadataUpdateWithIds("dummy", 3,
Collections.emptyMap(), singletonMap(topicName, 4),
@@ -4314,7 +4609,7 @@ public void testTruncationDetected() {
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
- client.prepareResponse(fullFetchResponse(tp0, records, Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, records, Errors.NONE, 100L, 0));
consumerClient.pollNoWakeup();
assertTrue(fetcher.hasCompletedFetches());
@@ -4342,7 +4637,7 @@ public void testPreferredReadReplica() {
assertFalse(fetcher.hasCompletedFetches());
// Set preferred read replica to node=1
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L,
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L,
FetchResponse.INVALID_LAST_STABLE_OFFSET, 0, Optional.of(1)));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -4359,7 +4654,7 @@ public void testPreferredReadReplica() {
assertFalse(fetcher.hasCompletedFetches());
// Set preferred read replica to node=2, which isn't in our metadata, should revert to leader
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L,
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L,
FetchResponse.INVALID_LAST_STABLE_OFFSET, 0, Optional.of(2)));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -4381,7 +4676,7 @@ public void testPreferredReadReplicaOffsetError() {
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.NONE, 100L,
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.NONE, 100L,
FetchResponse.INVALID_LAST_STABLE_OFFSET, 0, Optional.of(1)));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -4395,7 +4690,7 @@ public void testPreferredReadReplicaOffsetError() {
assertEquals(1, fetcher.sendFetches());
assertFalse(fetcher.hasCompletedFetches());
- client.prepareResponse(fullFetchResponse(tp0, this.records, Errors.OFFSET_OUT_OF_RANGE, 100L,
+ client.prepareResponse(fullFetchResponse(tidp0, this.records, Errors.OFFSET_OUT_OF_RANGE, 100L,
FetchResponse.INVALID_LAST_STABLE_OFFSET, 0, Optional.empty()));
consumerClient.poll(time.timer(0));
assertTrue(fetcher.hasCompletedFetches());
@@ -4412,7 +4707,7 @@ public void testFetchCompletedBeforeHandlerAdded() {
assignFromUser(singleton(tp0));
subscriptions.seek(tp0, 0);
fetcher.sendFetches();
- client.prepareResponse(fullFetchResponse(tp0, buildRecords(1L, 1, 1), Errors.NONE, 100L, 0));
+ client.prepareResponse(fullFetchResponse(tidp0, buildRecords(1L, 1, 1), Errors.NONE, 100L, 0));
consumerClient.poll(time.timer(0));
fetchedRecords();
@@ -4447,7 +4742,7 @@ public void testCorruptMessageError() {
// Prepare a response with the CORRUPT_MESSAGE error.
client.prepareResponse(fullFetchResponse(
- tp0,
+ tidp0,
buildRecords(1L, 1, 1),
Errors.CORRUPT_MESSAGE,
100L, 0));
@@ -4621,22 +4916,22 @@ private ListOffsetsResponse listOffsetResponse(Map offsets
return new ListOffsetsResponse(data);
}
- private FetchResponse fetchResponseWithTopLevelError(TopicPartition tp, Errors error, int throttleTime) {
- Map partitions = Collections.singletonMap(tp,
+ private FetchResponse fetchResponseWithTopLevelError(TopicIdPartition tp, Errors error, int throttleTime) {
+ Map partitions = Collections.singletonMap(tp,
new FetchResponseData.PartitionData()
- .setPartitionIndex(tp.partition())
+ .setPartitionIndex(tp.topicPartition().partition())
.setErrorCode(error.code())
.setHighWatermark(FetchResponse.INVALID_HIGH_WATERMARK));
- return FetchResponse.of(error, throttleTime, INVALID_SESSION_ID, new LinkedHashMap<>(partitions), topicIds);
+ return FetchResponse.of(error, throttleTime, INVALID_SESSION_ID, new LinkedHashMap<>(partitions));
}
private FetchResponse fullFetchResponseWithAbortedTransactions(MemoryRecords records,
- List abortedTransactions,
- Errors error,
- long lastStableOffset,
- long hw,
- int throttleTime) {
- Map partitions = Collections.singletonMap(tp0,
+ List abortedTransactions,
+ Errors error,
+ long lastStableOffset,
+ long hw,
+ int throttleTime) {
+ Map partitions = Collections.singletonMap(tidp0,
new FetchResponseData.PartitionData()
.setPartitionIndex(tp0.partition())
.setErrorCode(error.code())
@@ -4645,51 +4940,60 @@ private FetchResponse fullFetchResponseWithAbortedTransactions(MemoryRecords rec
.setLogStartOffset(0)
.setAbortedTransactions(abortedTransactions)
.setRecords(records));
- return FetchResponse.of(Errors.NONE, throttleTime, INVALID_SESSION_ID, new LinkedHashMap<>(partitions), topicIds);
+ return FetchResponse.of(Errors.NONE, throttleTime, INVALID_SESSION_ID, new LinkedHashMap<>(partitions));
+ }
+
+ private FetchResponse fullFetchResponse(int sessionId, TopicIdPartition tp, MemoryRecords records, Errors error, long hw, int throttleTime) {
+ return fullFetchResponse(sessionId, tp, records, error, hw, FetchResponse.INVALID_LAST_STABLE_OFFSET, throttleTime);
}
- private FetchResponse fullFetchResponse(TopicPartition tp, MemoryRecords records, Errors error, long hw, int throttleTime) {
+ private FetchResponse fullFetchResponse(TopicIdPartition tp, MemoryRecords records, Errors error, long hw, int throttleTime) {
return fullFetchResponse(tp, records, error, hw, FetchResponse.INVALID_LAST_STABLE_OFFSET, throttleTime);
}
- private FetchResponse fullFetchResponse(TopicPartition tp, MemoryRecords records, Errors error, long hw,
+ private FetchResponse fullFetchResponse(TopicIdPartition tp, MemoryRecords records, Errors error, long hw,
+ long lastStableOffset, int throttleTime) {
+ return fullFetchResponse(INVALID_SESSION_ID, tp, records, error, hw, lastStableOffset, throttleTime);
+ }
+
+ private FetchResponse fullFetchResponse(int sessionId, TopicIdPartition tp, MemoryRecords records, Errors error, long hw,
long lastStableOffset, int throttleTime) {
- Map partitions = Collections.singletonMap(tp,
+ Map partitions = Collections.singletonMap(tp,
new FetchResponseData.PartitionData()
- .setPartitionIndex(tp.partition())
+ .setPartitionIndex(tp.topicPartition().partition())
.setErrorCode(error.code())
.setHighWatermark(hw)
.setLastStableOffset(lastStableOffset)
.setLogStartOffset(0)
.setRecords(records));
- return FetchResponse.of(Errors.NONE, throttleTime, INVALID_SESSION_ID, new LinkedHashMap<>(partitions), topicIds);
+ return FetchResponse.of(Errors.NONE, throttleTime, sessionId, new LinkedHashMap<>(partitions));
}
- private FetchResponse fullFetchResponse(TopicPartition tp, MemoryRecords records, Errors error, long hw,
- long lastStableOffset, int throttleTime, Optional preferredReplicaId) {
- Map partitions = Collections.singletonMap(tp,
+ private FetchResponse fullFetchResponse(TopicIdPartition tp, MemoryRecords records, Errors error, long hw,
+ long lastStableOffset, int throttleTime, Optional preferredReplicaId) {
+ Map partitions = Collections.singletonMap(tp,
new FetchResponseData.PartitionData()
- .setPartitionIndex(tp.partition())
+ .setPartitionIndex(tp.topicPartition().partition())
.setErrorCode(error.code())
.setHighWatermark(hw)
.setLastStableOffset(lastStableOffset)
.setLogStartOffset(0)
.setRecords(records)
.setPreferredReadReplica(preferredReplicaId.orElse(FetchResponse.INVALID_PREFERRED_REPLICA_ID)));
- return FetchResponse.of(Errors.NONE, throttleTime, INVALID_SESSION_ID, new LinkedHashMap<>(partitions), topicIds);
+ return FetchResponse.of(Errors.NONE, throttleTime, INVALID_SESSION_ID, new LinkedHashMap<>(partitions));
}
- private FetchResponse fetchResponse(TopicPartition tp, MemoryRecords records, Errors error, long hw,
+ private FetchResponse fetchResponse(TopicIdPartition tp, MemoryRecords records, Errors error, long hw,
long lastStableOffset, long logStartOffset, int throttleTime) {
- Map partitions = Collections.singletonMap(tp,
+ Map partitions = Collections.singletonMap(tp,
new FetchResponseData.PartitionData()
- .setPartitionIndex(tp.partition())
+ .setPartitionIndex(tp.topicPartition().partition())
.setErrorCode(error.code())
.setHighWatermark(hw)
.setLastStableOffset(lastStableOffset)
.setLogStartOffset(logStartOffset)
.setRecords(records));
- return FetchResponse.of(Errors.NONE, throttleTime, INVALID_SESSION_ID, new LinkedHashMap<>(partitions), topicIds);
+ return FetchResponse.of(Errors.NONE, throttleTime, INVALID_SESSION_ID, new LinkedHashMap<>(partitions));
}
private MetadataResponse newMetadataResponse(String topic, Errors error) {
diff --git a/clients/src/test/java/org/apache/kafka/common/requests/FetchRequestTest.java b/clients/src/test/java/org/apache/kafka/common/requests/FetchRequestTest.java
new file mode 100644
index 0000000000000..a567d43dc6347
--- /dev/null
+++ b/clients/src/test/java/org/apache/kafka/common/requests/FetchRequestTest.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kafka.common.requests;
+
+import org.apache.kafka.common.TopicIdPartition;
+import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.Uuid;
+import org.apache.kafka.common.protocol.ApiKeys;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+public class FetchRequestTest {
+
+ private static Stream fetchVersions() {
+ return ApiKeys.FETCH.allVersions().stream().map(version -> Arguments.of(version));
+ }
+
+ @ParameterizedTest
+ @MethodSource("fetchVersions")
+ public void testToReplaceWithDifferentVersions(short version) {
+ boolean fetchRequestUsesTopicIds = version >= 13;
+ Uuid topicId = Uuid.randomUuid();
+ TopicIdPartition tp = new TopicIdPartition(topicId, 0, "topic");
+
+ Map partitionData = Collections.singletonMap(tp.topicPartition(),
+ new FetchRequest.PartitionData(topicId, 0, 0, 0, Optional.empty()));
+ List toReplace = Collections.singletonList(tp);
+
+ FetchRequest fetchRequest = FetchRequest.Builder
+ .forReplica(version, 0, 1, 1, partitionData)
+ .removed(Collections.emptyList())
+ .replaced(toReplace)
+ .metadata(FetchMetadata.newIncremental(123)).build(version);
+
+ // If version < 13, we should not see any partitions in forgottenTopics. This is because we can not
+ // distinguish different topic IDs on versions earlier than 13.
+ assertEquals(fetchRequestUsesTopicIds, fetchRequest.data().forgottenTopicsData().size() > 0);
+ fetchRequest.data().forgottenTopicsData().forEach(forgottenTopic -> {
+ // Since we didn't serialize, we should see the topic name and ID regardless of the version.
+ assertEquals(tp.topic(), forgottenTopic.topic());
+ assertEquals(topicId, forgottenTopic.topicId());
+ });
+
+ assertEquals(1, fetchRequest.data().topics().size());
+ fetchRequest.data().topics().forEach(topic -> {
+ // Since we didn't serialize, we should see the topic name and ID regardless of the version.
+ assertEquals(tp.topic(), topic.topic());
+ assertEquals(topicId, topic.topicId());
+ });
+ }
+
+ @ParameterizedTest
+ @MethodSource("fetchVersions")
+ public void testFetchData(short version) {
+ TopicPartition topicPartition0 = new TopicPartition("topic", 0);
+ TopicPartition topicPartition1 = new TopicPartition("unknownIdTopic", 0);
+ Uuid topicId0 = Uuid.randomUuid();
+ Uuid topicId1 = Uuid.randomUuid();
+
+ // Only include topic IDs for the first topic partition.
+ Map topicNames = Collections.singletonMap(topicId0, topicPartition0.topic());
+ List topicIdPartitions = new LinkedList<>();
+ topicIdPartitions.add(new TopicIdPartition(topicId0, topicPartition0));
+ topicIdPartitions.add(new TopicIdPartition(topicId1, topicPartition1));
+
+ // Include one topic with topic IDs in the topic names map and one without.
+ Map partitionData = new LinkedHashMap<>();
+ partitionData.put(topicPartition0, new FetchRequest.PartitionData(topicId0, 0, 0, 0, Optional.empty()));
+ partitionData.put(topicPartition1, new FetchRequest.PartitionData(topicId1, 0, 0, 0, Optional.empty()));
+ boolean fetchRequestUsesTopicIds = version >= 13;
+
+ FetchRequest fetchRequest = FetchRequest.parse(FetchRequest.Builder
+ .forReplica(version, 0, 1, 1, partitionData)
+ .removed(Collections.emptyList())
+ .replaced(Collections.emptyList())
+ .metadata(FetchMetadata.newIncremental(123)).build(version).serialize(), version);
+
+ // For versions < 13, we will be provided a topic name and a zero UUID in FetchRequestData.
+ // Versions 13+ will contain a valid topic ID but an empty topic name.
+ List expectedData = new LinkedList<>();
+ topicIdPartitions.forEach(tidp -> {
+ String expectedName = fetchRequestUsesTopicIds ? "" : tidp.topic();
+ Uuid expectedTopicId = fetchRequestUsesTopicIds ? tidp.topicId() : Uuid.ZERO_UUID;
+ expectedData.add(new TopicIdPartition(expectedTopicId, tidp.partition(), expectedName));
+ });
+
+ // Build the list of TopicIdPartitions based on the FetchRequestData that was serialized and parsed.
+ List convertedFetchData = new LinkedList<>();
+ fetchRequest.data().topics().forEach(topic ->
+ topic.partitions().forEach(partition ->
+ convertedFetchData.add(new TopicIdPartition(topic.topicId(), partition.partition(), topic.topic()))
+ )
+ );
+ // The TopicIdPartitions built from the request data should match what we expect.
+ assertEquals(expectedData, convertedFetchData);
+
+ // For fetch request version 13+ we expect topic names to be filled in for all topics in the topicNames map.
+ // Otherwise, the topic name should be null.
+ // For earlier request versions, we expect topic names and zero Uuids.
+ Map expectedFetchData = new LinkedHashMap<>();
+ // Build the expected map based on fetchRequestUsesTopicIds.
+ expectedData.forEach(tidp -> {
+ String expectedName = fetchRequestUsesTopicIds ? topicNames.get(tidp.topicId()) : tidp.topic();
+ TopicIdPartition tpKey = new TopicIdPartition(tidp.topicId(), new TopicPartition(expectedName, tidp.partition()));
+ // logStartOffset was not a valid field in versions 4 and earlier.
+ int logStartOffset = version > 4 ? 0 : -1;
+ expectedFetchData.put(tpKey, new FetchRequest.PartitionData(tidp.topicId(), 0, logStartOffset, 0, Optional.empty()));
+ });
+ assertEquals(expectedFetchData, fetchRequest.fetchData(topicNames));
+ }
+
+ @ParameterizedTest
+ @MethodSource("fetchVersions")
+ public void testForgottenTopics(short version) {
+ // Forgotten topics are not allowed prior to version 7
+ if (version >= 7) {
+ TopicPartition topicPartition0 = new TopicPartition("topic", 0);
+ TopicPartition topicPartition1 = new TopicPartition("unknownIdTopic", 0);
+ Uuid topicId0 = Uuid.randomUuid();
+ Uuid topicId1 = Uuid.randomUuid();
+ // Only include topic IDs for the first topic partition.
+ Map topicNames = Collections.singletonMap(topicId0, topicPartition0.topic());
+
+ // Include one topic with topic IDs in the topic names map and one without.
+ List toForgetTopics = new LinkedList<>();
+ toForgetTopics.add(new TopicIdPartition(topicId0, topicPartition0));
+ toForgetTopics.add(new TopicIdPartition(topicId1, topicPartition1));
+
+ boolean fetchRequestUsesTopicIds = version >= 13;
+
+ FetchRequest fetchRequest = FetchRequest.parse(FetchRequest.Builder
+ .forReplica(version, 0, 1, 1, Collections.emptyMap())
+ .removed(toForgetTopics)
+ .replaced(Collections.emptyList())
+ .metadata(FetchMetadata.newIncremental(123)).build(version).serialize(), version);
+
+ // For versions < 13, we will be provided a topic name and a zero Uuid in FetchRequestData.
+ // Versions 13+ will contain a valid topic ID but an empty topic name.
+ List expectedForgottenTopicData = new LinkedList<>();
+ toForgetTopics.forEach(tidp -> {
+ String expectedName = fetchRequestUsesTopicIds ? "" : tidp.topic();
+ Uuid expectedTopicId = fetchRequestUsesTopicIds ? tidp.topicId() : Uuid.ZERO_UUID;
+ expectedForgottenTopicData.add(new TopicIdPartition(expectedTopicId, tidp.partition(), expectedName));
+ });
+
+ // Build the list of TopicIdPartitions based on the FetchRequestData that was serialized and parsed.
+ List convertedForgottenTopicData = new LinkedList<>();
+ fetchRequest.data().forgottenTopicsData().forEach(forgottenTopic ->
+ forgottenTopic.partitions().forEach(partition ->
+ convertedForgottenTopicData.add(new TopicIdPartition(forgottenTopic.topicId(), partition, forgottenTopic.topic()))
+ )
+ );
+ // The TopicIdPartitions built from the request data should match what we expect.
+ assertEquals(expectedForgottenTopicData, convertedForgottenTopicData);
+
+ // Get the forgottenTopics from the request data.
+ List forgottenTopics = fetchRequest.forgottenTopics(topicNames);
+
+ // For fetch request version 13+ we expect topic names to be filled in for all topics in the topicNames map.
+ // Otherwise, the topic name should be null.
+ // For earlier request versions, we expect topic names and zero Uuids.
+ // Build the list of expected TopicIdPartitions. These are different from the earlier expected topicIdPartitions
+ // as empty strings are converted to nulls.
+ assertEquals(expectedForgottenTopicData.size(), forgottenTopics.size());
+ List expectedForgottenTopics = new LinkedList<>();
+ expectedForgottenTopicData.forEach(tidp -> {
+ String expectedName = fetchRequestUsesTopicIds ? topicNames.get(tidp.topicId()) : tidp.topic();
+ expectedForgottenTopics.add(new TopicIdPartition(tidp.topicId(), new TopicPartition(expectedName, tidp.partition())));
+ });
+ assertEquals(expectedForgottenTopics, forgottenTopics);
+ }
+ }
+
+ @Test
+ public void testPartitionDataEquals() {
+ assertEquals(new FetchRequest.PartitionData(Uuid.ZERO_UUID, 300, 0L, 300, Optional.of(300)),
+ new FetchRequest.PartitionData(Uuid.ZERO_UUID, 300, 0L, 300, Optional.of(300)));
+
+ assertNotEquals(new FetchRequest.PartitionData(Uuid.randomUuid(), 300, 0L, 300, Optional.of(300)),
+ new FetchRequest.PartitionData(Uuid.randomUuid(), 300, 0L, 300, Optional.of(300)));
+ }
+
+}
diff --git a/clients/src/test/java/org/apache/kafka/common/requests/RequestResponseTest.java b/clients/src/test/java/org/apache/kafka/common/requests/RequestResponseTest.java
index 0f524780f92de..70b1e0597dca4 100644
--- a/clients/src/test/java/org/apache/kafka/common/requests/RequestResponseTest.java
+++ b/clients/src/test/java/org/apache/kafka/common/requests/RequestResponseTest.java
@@ -21,6 +21,7 @@
import org.apache.kafka.common.IsolationLevel;
import org.apache.kafka.common.Node;
import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.TopicIdPartition;
import org.apache.kafka.common.Uuid;
import org.apache.kafka.common.acl.AccessControlEntry;
import org.apache.kafka.common.acl.AccessControlEntryFilter;
@@ -219,6 +220,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
@@ -258,10 +260,10 @@ public void testSerialization() throws Exception {
checkErrorResponse(createControlledShutdownRequest(0), unknownServerException, true);
checkRequest(createFetchRequest(4), true);
checkResponse(createFetchResponse(true), 4, true);
- List toForgetTopics = new ArrayList<>();
- toForgetTopics.add(new TopicPartition("foo", 0));
- toForgetTopics.add(new TopicPartition("foo", 2));
- toForgetTopics.add(new TopicPartition("bar", 0));
+ List toForgetTopics = new ArrayList<>();
+ toForgetTopics.add(new TopicIdPartition(Uuid.ZERO_UUID, new TopicPartition("foo", 0)));
+ toForgetTopics.add(new TopicIdPartition(Uuid.ZERO_UUID, new TopicPartition("foo", 2)));
+ toForgetTopics.add(new TopicIdPartition(Uuid.ZERO_UUID, new TopicPartition("bar", 0)));
checkRequest(createFetchRequest(7, new FetchMetadata(123, 456), toForgetTopics), true);
checkResponse(createFetchResponse(123), 7, true);
checkResponse(createFetchResponse(Errors.FETCH_SESSION_ID_NOT_FOUND, 123), 7, true);
@@ -834,32 +836,41 @@ public void produceRequestGetErrorResponseTest() {
@Test
public void fetchResponseVersionTest() {
- LinkedHashMap responseData = new LinkedHashMap<>();
- Map topicNames = new HashMap<>();
- Map topicIds = new HashMap<>();
+ LinkedHashMap responseData = new LinkedHashMap<>();
Uuid id = Uuid.randomUuid();
- topicNames.put(id, "test");
- topicIds.put("test", id);
-
+ Map topicNames = Collections.singletonMap(id, "test");
+ TopicPartition tp = new TopicPartition("test", 0);
MemoryRecords records = MemoryRecords.readableRecords(ByteBuffer.allocate(10));
- responseData.put(new TopicPartition("test", 0),
- new FetchResponseData.PartitionData()
- .setPartitionIndex(0)
- .setHighWatermark(1000000)
- .setLogStartOffset(-1)
- .setRecords(records));
+ FetchResponseData.PartitionData partitionData = new FetchResponseData.PartitionData()
+ .setPartitionIndex(0)
+ .setHighWatermark(1000000)
+ .setLogStartOffset(-1)
+ .setRecords(records);
+
+ // Use zero UUID since we are comparing with old request versions
+ responseData.put(new TopicIdPartition(Uuid.ZERO_UUID, tp), partitionData);
+
+ LinkedHashMap tpResponseData = new LinkedHashMap<>();
+ tpResponseData.put(tp, partitionData);
- FetchResponse v0Response = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, responseData, topicIds);
- FetchResponse v1Response = FetchResponse.of(Errors.NONE, 10, INVALID_SESSION_ID, responseData, topicIds);
+ FetchResponse v0Response = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, responseData);
+ FetchResponse v1Response = FetchResponse.of(Errors.NONE, 10, INVALID_SESSION_ID, responseData);
FetchResponse v0Deserialized = FetchResponse.parse(v0Response.serialize((short) 0), (short) 0);
FetchResponse v1Deserialized = FetchResponse.parse(v1Response.serialize((short) 1), (short) 1);
assertEquals(0, v0Deserialized.throttleTimeMs(), "Throttle time must be zero");
assertEquals(10, v1Deserialized.throttleTimeMs(), "Throttle time must be 10");
- assertEquals(responseData, v0Deserialized.responseData(topicNames, (short) 0), "Response data does not match");
- assertEquals(responseData, v1Deserialized.responseData(topicNames, (short) 1), "Response data does not match");
+ assertEquals(tpResponseData, v0Deserialized.responseData(topicNames, (short) 0), "Response data does not match");
+ assertEquals(tpResponseData, v1Deserialized.responseData(topicNames, (short) 1), "Response data does not match");
- FetchResponse idTestResponse = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, responseData, topicIds);
+ LinkedHashMap idResponseData = new LinkedHashMap<>();
+ idResponseData.put(new TopicIdPartition(id, new TopicPartition("test", 0)),
+ new FetchResponseData.PartitionData()
+ .setPartitionIndex(0)
+ .setHighWatermark(1000000)
+ .setLogStartOffset(-1)
+ .setRecords(records));
+ FetchResponse idTestResponse = FetchResponse.of(Errors.NONE, 0, INVALID_SESSION_ID, idResponseData);
FetchResponse v12Deserialized = FetchResponse.parse(idTestResponse.serialize((short) 12), (short) 12);
FetchResponse newestDeserialized = FetchResponse.parse(idTestResponse.serialize(FETCH.latestVersion()), FETCH.latestVersion());
assertTrue(v12Deserialized.topicIds().isEmpty());
@@ -869,40 +880,41 @@ public void fetchResponseVersionTest() {
@Test
public void testFetchResponseV4() {
- LinkedHashMap responseData = new LinkedHashMap<>();
+ LinkedHashMap responseData = new LinkedHashMap<>();
Map topicNames = new HashMap<>();
- Map topicIds = new HashMap<>();
topicNames.put(Uuid.randomUuid(), "bar");
topicNames.put(Uuid.randomUuid(), "foo");
- topicNames.forEach((id, name) -> topicIds.put(name, id));
MemoryRecords records = MemoryRecords.readableRecords(ByteBuffer.allocate(10));
List abortedTransactions = asList(
new FetchResponseData.AbortedTransaction().setProducerId(10).setFirstOffset(100),
new FetchResponseData.AbortedTransaction().setProducerId(15).setFirstOffset(50)
);
- responseData.put(new TopicPartition("bar", 0),
+
+ // Use zero UUID since this is an old request version.
+ responseData.put(new TopicIdPartition(Uuid.ZERO_UUID, new TopicPartition("bar", 0)),
new FetchResponseData.PartitionData()
.setPartitionIndex(0)
.setHighWatermark(1000000)
.setAbortedTransactions(abortedTransactions)
.setRecords(records));
- responseData.put(new TopicPartition("bar", 1),
+ responseData.put(new TopicIdPartition(Uuid.ZERO_UUID, new TopicPartition("bar", 1)),
new FetchResponseData.PartitionData()
.setPartitionIndex(1)
.setHighWatermark(900000)
.setLastStableOffset(5)
.setRecords(records));
- responseData.put(new TopicPartition("foo", 0),
+ responseData.put(new TopicIdPartition(Uuid.ZERO_UUID, new TopicPartition("foo", 0)),
new FetchResponseData.PartitionData()
.setPartitionIndex(0)
.setHighWatermark(70000)
.setLastStableOffset(6)
.setRecords(records));
- FetchResponse response = FetchResponse.of(Errors.NONE, 10, INVALID_SESSION_ID, responseData, topicIds);
+ FetchResponse response = FetchResponse.of(Errors.NONE, 10, INVALID_SESSION_ID, responseData);
FetchResponse deserialized = FetchResponse.parse(response.serialize((short) 4), (short) 4);
- assertEquals(responseData, deserialized.responseData(topicNames, (short) 4));
+ assertEquals(responseData.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().topicPartition(), Map.Entry::getValue)),
+ deserialized.responseData(topicNames, (short) 4));
}
@Test
@@ -1015,10 +1027,9 @@ public void testFetchRequestWithMetadata() throws Exception {
@Test
public void testFetchRequestCompat() {
Map fetchData = new HashMap<>();
- fetchData.put(new TopicPartition("test", 0), new FetchRequest.PartitionData(100, 2, 100, Optional.of(42)));
- Map topicIds = Collections.singletonMap("test1", Uuid.randomUuid());
+ fetchData.put(new TopicPartition("test", 0), new FetchRequest.PartitionData(Uuid.ZERO_UUID, 100, 2, 100, Optional.of(42)));
FetchRequest req = FetchRequest.Builder
- .forConsumer((short) 2, 100, 100, fetchData, topicIds)
+ .forConsumer((short) 2, 100, 100, fetchData)
.metadata(new FetchMetadata(10, 20))
.isolationLevel(IsolationLevel.READ_COMMITTED)
.build((short) 2);
@@ -1286,75 +1297,66 @@ private FindCoordinatorResponse createFindCoordinatorResponse(short version) {
return FindCoordinatorResponse.prepareResponse(Errors.NONE, "group", node);
}
- private FetchRequest createFetchRequest(int version, FetchMetadata metadata, List toForget) {
+ private FetchRequest createFetchRequest(int version, FetchMetadata metadata, List toForget) {
LinkedHashMap fetchData = new LinkedHashMap<>();
- fetchData.put(new TopicPartition("test1", 0), new FetchRequest.PartitionData(100, -1L,
- 1000000, Optional.empty()));
- fetchData.put(new TopicPartition("test2", 0), new FetchRequest.PartitionData(200, -1L,
- 1000000, Optional.empty()));
- Map topicIds = new HashMap<>();
- topicIds.put("test1", Uuid.randomUuid());
- topicIds.put("test2", Uuid.randomUuid());
- return FetchRequest.Builder.forConsumer((short) version, 100, 100000, fetchData, topicIds).
- metadata(metadata).setMaxBytes(1000).toForget(toForget).build((short) version);
+ fetchData.put(new TopicPartition("test1", 0),
+ new FetchRequest.PartitionData(Uuid.randomUuid(), 100, -1L, 1000000, Optional.empty()));
+ fetchData.put(new TopicPartition("test2", 0),
+ new FetchRequest.PartitionData(Uuid.randomUuid(), 200, -1L, 1000000, Optional.empty()));
+ return FetchRequest.Builder.forConsumer((short) version, 100, 100000, fetchData).
+ metadata(metadata).setMaxBytes(1000).removed(toForget).build((short) version);
}
private FetchRequest createFetchRequest(int version, IsolationLevel isolationLevel) {
LinkedHashMap fetchData = new LinkedHashMap<>();
- fetchData.put(new TopicPartition("test1", 0), new FetchRequest.PartitionData(100, -1L,
- 1000000, Optional.empty()));
- fetchData.put(new TopicPartition("test2", 0), new FetchRequest.PartitionData(200, -1L,
- 1000000, Optional.empty()));
- Map topicIds = new HashMap<>();
- topicIds.put("test1", Uuid.randomUuid());
- topicIds.put("test2", Uuid.randomUuid());
- return FetchRequest.Builder.forConsumer((short) version, 100, 100000, fetchData, topicIds).
+ fetchData.put(new TopicPartition("test1", 0),
+ new FetchRequest.PartitionData(Uuid.randomUuid(), 100, -1L, 1000000, Optional.empty()));
+ fetchData.put(new TopicPartition("test2", 0),
+ new FetchRequest.PartitionData(Uuid.randomUuid(), 200, -1L, 1000000, Optional.empty()));
+ return FetchRequest.Builder.forConsumer((short) version, 100, 100000, fetchData).
isolationLevel(isolationLevel).setMaxBytes(1000).build((short) version);
}
private FetchRequest createFetchRequest(int version) {
LinkedHashMap fetchData = new LinkedHashMap<>();
- fetchData.put(new TopicPartition("test1", 0), new FetchRequest.PartitionData(100, -1L,
- 1000000, Optional.empty()));
- fetchData.put(new TopicPartition("test2", 0), new FetchRequest.PartitionData(200, -1L,
- 1000000, Optional.empty()));
- Map topicIds = new HashMap<>();
- topicIds.put("test1", Uuid.randomUuid());
- topicIds.put("test2", Uuid.randomUuid());
- return FetchRequest.Builder.forConsumer((short) version, 100, 100000, fetchData, topicIds).setMaxBytes(1000).build((short) version);
+ fetchData.put(new TopicPartition("test1", 0),
+ new FetchRequest.PartitionData(Uuid.randomUuid(), 100, -1L, 1000000, Optional.empty()));
+ fetchData.put(new TopicPartition("test2", 0),
+ new FetchRequest.PartitionData(Uuid.randomUuid(), 200, -1L, 1000000, Optional.empty()));
+ return FetchRequest.Builder.forConsumer((short) version, 100, 100000, fetchData).setMaxBytes(1000).build((short) version);
}
private FetchResponse createFetchResponse(Errors error, int sessionId) {
return FetchResponse.parse(
- FetchResponse.of(error, 25, sessionId, new LinkedHashMap<>(),
- new HashMap<>()).serialize(FETCH.latestVersion()), FETCH.latestVersion());
+ FetchResponse.of(error, 25, sessionId, new LinkedHashMap<>()).serialize(FETCH.latestVersion()), FETCH.latestVersion());
}
private FetchResponse createFetchResponse(int sessionId) {
- LinkedHashMap responseData = new LinkedHashMap<>();
+ LinkedHashMap responseData = new LinkedHashMap<>();
Map topicIds = new HashMap<>();
topicIds.put("test", Uuid.randomUuid());
MemoryRecords records = MemoryRecords.withRecords(CompressionType.NONE, new SimpleRecord("blah".getBytes()));
- responseData.put(new TopicPartition("test", 0), new FetchResponseData.PartitionData()
+ responseData.put(new TopicIdPartition(topicIds.get("test"), new TopicPartition("test", 0)), new FetchResponseData.PartitionData()
.setPartitionIndex(0)
.setHighWatermark(1000000)
.setLogStartOffset(0)
.setRecords(records));
List