Skip to content

Commit adee6f0

Browse files
authored
KAFKA-16527; Implement request handling for updated KRaft RPCs (#16235)
Implement request handling for the updated versions of the KRaft RPCs (Fetch, FetchSnapshot, Vote, BeginQuorumEpoch and EndQuorumEpoch). This doesn't add support for KRaft replicas to send the new version of the KRaft RPCs. That will be implemented in KAFKA-16529. All of the RPCs responses were extended to include the leader's endpoint for the listener of the channel used in the request. EpochState was extended to include the leader's endpoint information but only the FollowerState and LeaderState know the leader id and its endpoint(s). For the Fetch request, the replica directory id was added. The leader now tracks the follower's log end offset using both the replica id and replica directory id. For the FetchSnapshot request, the replica directory id was added. This is not used by the KRaft leader and it is there for consistency with Fetch and for help debugging. For the Vote request, the replica key for both the voter (destination) and the candidate (source) were added. The voter key is checked for consistency. The candidate key is persisted when the vote is granted. For the BeginQuorumEpoch request, all of the leader's endpoints are included. This is needed so that the voters can return the leader's endpoint for all of the supported listeners. For the EndQuorumEpoch request, all of the leader's endpoints are included. This is needed so that the voters can return the leader's endpoint for all of the supported listeners. The successor list has been extended to include the directory id. Receiving voters can use the entire replica key when searching their position in the successor list. Updated the existing test in KafkaRaftClientTest and KafkaRaftClientSnapshotTest to execute using both the old version and new version of the RPCs. Reviewers: Luke Chen <[email protected]>, Colin P. McCabe <[email protected]>
1 parent 5b0e96d commit adee6f0

File tree

72 files changed

+4095
-1883
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+4095
-1883
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.kafka.common.errors;
18+
19+
public class InvalidVoterKeyException extends ApiException {
20+
21+
private static final long serialVersionUID = 1;
22+
23+
public InvalidVoterKeyException(String s) {
24+
super(s);
25+
}
26+
27+
public InvalidVoterKeyException(String message, Throwable cause) {
28+
super(message, cause);
29+
}
30+
31+
}

clients/src/main/java/org/apache/kafka/common/protocol/Errors.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import org.apache.kafka.common.errors.InvalidTxnStateException;
7878
import org.apache.kafka.common.errors.InvalidTxnTimeoutException;
7979
import org.apache.kafka.common.errors.InvalidUpdateVersionException;
80+
import org.apache.kafka.common.errors.InvalidVoterKeyException;
8081
import org.apache.kafka.common.errors.KafkaStorageException;
8182
import org.apache.kafka.common.errors.LeaderNotAvailableException;
8283
import org.apache.kafka.common.errors.ListenerNotFoundException;
@@ -403,7 +404,8 @@ public enum Errors {
403404
INVALID_RECORD_STATE(121, "The record state is invalid. The acknowledgement of delivery could not be completed.", InvalidRecordStateException::new),
404405
SHARE_SESSION_NOT_FOUND(122, "The share session was not found.", ShareSessionNotFoundException::new),
405406
INVALID_SHARE_SESSION_EPOCH(123, "The share session epoch is invalid.", InvalidShareSessionEpochException::new),
406-
FENCED_STATE_EPOCH(124, "The share coordinator rejected the request because the share-group state epoch did not match.", FencedStateEpochException::new);
407+
FENCED_STATE_EPOCH(124, "The share coordinator rejected the request because the share-group state epoch did not match.", FencedStateEpochException::new),
408+
INVALID_VOTER_KEY(125, "The voter key doesn't match the receiving replica's key.", InvalidVoterKeyException::new);
407409

408410
private static final Logger log = LoggerFactory.getLogger(Errors.class);
409411

clients/src/main/java/org/apache/kafka/common/requests/BeginQuorumEpochRequest.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,12 @@ public static BeginQuorumEpochRequest parse(ByteBuffer buffer, short version) {
6868
return new BeginQuorumEpochRequest(new BeginQuorumEpochRequestData(new ByteBufferAccessor(buffer), version), version);
6969
}
7070

71-
public static BeginQuorumEpochRequestData singletonRequest(TopicPartition topicPartition,
72-
int leaderEpoch,
73-
int leaderId) {
74-
return singletonRequest(topicPartition, null, leaderEpoch, leaderId);
75-
}
76-
77-
public static BeginQuorumEpochRequestData singletonRequest(TopicPartition topicPartition,
78-
String clusterId,
79-
int leaderEpoch,
80-
int leaderId) {
71+
public static BeginQuorumEpochRequestData singletonRequest(
72+
TopicPartition topicPartition,
73+
String clusterId,
74+
int leaderEpoch,
75+
int leaderId
76+
) {
8177
return new BeginQuorumEpochRequestData()
8278
.setClusterId(clusterId)
8379
.setTopics(Collections.singletonList(
@@ -90,5 +86,4 @@ public static BeginQuorumEpochRequestData singletonRequest(TopicPartition topicP
9086
.setLeaderId(leaderId))))
9187
);
9288
}
93-
9489
}

clients/src/main/java/org/apache/kafka/common/requests/BeginQuorumEpochResponse.java

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@
1717

1818
package org.apache.kafka.common.requests;
1919

20-
import org.apache.kafka.common.TopicPartition;
2120
import org.apache.kafka.common.message.BeginQuorumEpochResponseData;
2221
import org.apache.kafka.common.protocol.ApiKeys;
2322
import org.apache.kafka.common.protocol.ByteBufferAccessor;
2423
import org.apache.kafka.common.protocol.Errors;
2524

2625
import java.nio.ByteBuffer;
27-
import java.util.Collections;
2826
import java.util.HashMap;
2927
import java.util.Map;
3028

@@ -49,27 +47,6 @@ public BeginQuorumEpochResponse(BeginQuorumEpochResponseData data) {
4947
this.data = data;
5048
}
5149

52-
public static BeginQuorumEpochResponseData singletonResponse(
53-
Errors topLevelError,
54-
TopicPartition topicPartition,
55-
Errors partitionLevelError,
56-
int leaderEpoch,
57-
int leaderId
58-
) {
59-
return new BeginQuorumEpochResponseData()
60-
.setErrorCode(topLevelError.code())
61-
.setTopics(Collections.singletonList(
62-
new BeginQuorumEpochResponseData.TopicData()
63-
.setTopicName(topicPartition.topic())
64-
.setPartitions(Collections.singletonList(
65-
new BeginQuorumEpochResponseData.PartitionData()
66-
.setErrorCode(partitionLevelError.code())
67-
.setLeaderId(leaderId)
68-
.setLeaderEpoch(leaderEpoch)
69-
)))
70-
);
71-
}
72-
7350
@Override
7451
public Map<Errors, Integer> errorCounts() {
7552
Map<Errors, Integer> errors = new HashMap<>();

clients/src/main/java/org/apache/kafka/common/requests/EndQuorumEpochRequest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.kafka.common.requests;
1818

1919
import org.apache.kafka.common.TopicPartition;
20+
import org.apache.kafka.common.Uuid;
2021
import org.apache.kafka.common.message.EndQuorumEpochRequestData;
2122
import org.apache.kafka.common.message.EndQuorumEpochResponseData;
2223
import org.apache.kafka.common.protocol.ApiKeys;
@@ -26,6 +27,7 @@
2627
import java.nio.ByteBuffer;
2728
import java.util.Collections;
2829
import java.util.List;
30+
import java.util.stream.Collectors;
2931

3032
public class EndQuorumEpochRequest extends AbstractRequest {
3133
public static class Builder extends AbstractRequest.Builder<EndQuorumEpochRequest> {
@@ -95,4 +97,18 @@ public static EndQuorumEpochRequestData singletonRequest(TopicPartition topicPar
9597
);
9698
}
9799

100+
public static List<EndQuorumEpochRequestData.ReplicaInfo> preferredCandidates(EndQuorumEpochRequestData.PartitionData partition) {
101+
if (partition.preferredCandidates().isEmpty()) {
102+
return partition
103+
.preferredSuccessors()
104+
.stream()
105+
.map(id -> new EndQuorumEpochRequestData.ReplicaInfo()
106+
.setCandidateId(id)
107+
.setCandidateDirectoryId(Uuid.ZERO_UUID)
108+
)
109+
.collect(Collectors.toList());
110+
} else {
111+
return partition.preferredCandidates();
112+
}
113+
}
98114
}

clients/src/main/java/org/apache/kafka/common/requests/EndQuorumEpochResponse.java

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@
1717

1818
package org.apache.kafka.common.requests;
1919

20-
import org.apache.kafka.common.TopicPartition;
2120
import org.apache.kafka.common.message.EndQuorumEpochResponseData;
2221
import org.apache.kafka.common.protocol.ApiKeys;
2322
import org.apache.kafka.common.protocol.ByteBufferAccessor;
2423
import org.apache.kafka.common.protocol.Errors;
2524

2625
import java.nio.ByteBuffer;
27-
import java.util.Collections;
2826
import java.util.HashMap;
2927
import java.util.Map;
3028

@@ -78,27 +76,6 @@ public void maybeSetThrottleTimeMs(int throttleTimeMs) {
7876
// Not supported by the response schema
7977
}
8078

81-
public static EndQuorumEpochResponseData singletonResponse(
82-
Errors topLevelError,
83-
TopicPartition topicPartition,
84-
Errors partitionLevelError,
85-
int leaderEpoch,
86-
int leaderId
87-
) {
88-
return new EndQuorumEpochResponseData()
89-
.setErrorCode(topLevelError.code())
90-
.setTopics(Collections.singletonList(
91-
new EndQuorumEpochResponseData.TopicData()
92-
.setTopicName(topicPartition.topic())
93-
.setPartitions(Collections.singletonList(
94-
new EndQuorumEpochResponseData.PartitionData()
95-
.setErrorCode(partitionLevelError.code())
96-
.setLeaderId(leaderId)
97-
.setLeaderEpoch(leaderEpoch)
98-
)))
99-
);
100-
}
101-
10279
public static EndQuorumEpochResponse parse(ByteBuffer buffer, short version) {
10380
return new EndQuorumEpochResponse(new EndQuorumEpochResponseData(new ByteBufferAccessor(buffer), version));
10481
}

clients/src/main/java/org/apache/kafka/common/requests/FetchSnapshotResponse.java

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,9 @@
2323
import org.apache.kafka.common.protocol.Errors;
2424

2525
import java.nio.ByteBuffer;
26-
import java.util.Collections;
2726
import java.util.HashMap;
2827
import java.util.Map;
2928
import java.util.Optional;
30-
import java.util.function.UnaryOperator;
3129

3230
public final class FetchSnapshotResponse extends AbstractResponse {
3331
private final FetchSnapshotResponseData data;
@@ -81,33 +79,6 @@ public static FetchSnapshotResponseData withTopLevelError(Errors error) {
8179
return new FetchSnapshotResponseData().setErrorCode(error.code());
8280
}
8381

84-
/**
85-
* Creates a FetchSnapshotResponseData with a single PartitionSnapshot for the topic partition.
86-
*
87-
* The partition index will already be populated when calling operator.
88-
*
89-
* @param topicPartition the topic partition to include
90-
* @param operator unary operator responsible for populating all of the appropriate fields
91-
* @return the created fetch snapshot response data
92-
*/
93-
public static FetchSnapshotResponseData singleton(
94-
TopicPartition topicPartition,
95-
UnaryOperator<FetchSnapshotResponseData.PartitionSnapshot> operator
96-
) {
97-
FetchSnapshotResponseData.PartitionSnapshot partitionSnapshot = operator.apply(
98-
new FetchSnapshotResponseData.PartitionSnapshot().setIndex(topicPartition.partition())
99-
);
100-
101-
return new FetchSnapshotResponseData()
102-
.setTopics(
103-
Collections.singletonList(
104-
new FetchSnapshotResponseData.TopicSnapshot()
105-
.setName(topicPartition.topic())
106-
.setPartitions(Collections.singletonList(partitionSnapshot))
107-
)
108-
);
109-
}
110-
11182
/**
11283
* Finds the PartitionSnapshot for a given topic partition.
11384
*

clients/src/main/java/org/apache/kafka/common/requests/VoteRequest.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,6 @@ public static VoteRequest parse(ByteBuffer buffer, short version) {
6969
return new VoteRequest(new VoteRequestData(new ByteBufferAccessor(buffer), version), version);
7070
}
7171

72-
public static VoteRequestData singletonRequest(TopicPartition topicPartition,
73-
int candidateEpoch,
74-
int candidateId,
75-
int lastEpoch,
76-
long lastEpochEndOffset) {
77-
return singletonRequest(topicPartition,
78-
null,
79-
candidateEpoch,
80-
candidateId,
81-
lastEpoch,
82-
lastEpochEndOffset);
83-
}
84-
8572
public static VoteRequestData singletonRequest(TopicPartition topicPartition,
8673
String clusterId,
8774
int candidateEpoch,
@@ -102,5 +89,4 @@ public static VoteRequestData singletonRequest(TopicPartition topicPartition,
10289
.setLastOffset(lastEpochEndOffset))
10390
)));
10491
}
105-
10692
}

clients/src/main/java/org/apache/kafka/common/requests/VoteResponse.java

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@
1717

1818
package org.apache.kafka.common.requests;
1919

20-
import org.apache.kafka.common.TopicPartition;
2120
import org.apache.kafka.common.message.VoteResponseData;
2221
import org.apache.kafka.common.protocol.ApiKeys;
2322
import org.apache.kafka.common.protocol.ByteBufferAccessor;
2423
import org.apache.kafka.common.protocol.Errors;
2524

2625
import java.nio.ByteBuffer;
27-
import java.util.Collections;
2826
import java.util.HashMap;
2927
import java.util.Map;
3028

@@ -49,25 +47,6 @@ public VoteResponse(VoteResponseData data) {
4947
this.data = data;
5048
}
5149

52-
public static VoteResponseData singletonResponse(Errors topLevelError,
53-
TopicPartition topicPartition,
54-
Errors partitionLevelError,
55-
int leaderEpoch,
56-
int leaderId,
57-
boolean voteGranted) {
58-
return new VoteResponseData()
59-
.setErrorCode(topLevelError.code())
60-
.setTopics(Collections.singletonList(
61-
new VoteResponseData.TopicData()
62-
.setTopicName(topicPartition.topic())
63-
.setPartitions(Collections.singletonList(
64-
new VoteResponseData.PartitionData()
65-
.setErrorCode(partitionLevelError.code())
66-
.setLeaderId(leaderId)
67-
.setLeaderEpoch(leaderEpoch)
68-
.setVoteGranted(voteGranted)))));
69-
}
70-
7150
@Override
7251
public Map<Errors, Integer> errorCounts() {
7352
Map<Errors, Integer> errors = new HashMap<>();

clients/src/main/resources/common/message/BeginQuorumEpochRequest.json

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,38 @@
1818
"type": "request",
1919
"listeners": ["controller"],
2020
"name": "BeginQuorumEpochRequest",
21-
"validVersions": "0",
22-
"flexibleVersions": "none",
21+
// Version 1 adds flexible versions, voter key and leader endpoints (KIP-853)
22+
"validVersions": "0-1",
23+
"flexibleVersions": "1+",
2324
"fields": [
2425
{ "name": "ClusterId", "type": "string", "versions": "0+",
2526
"nullableVersions": "0+", "default": "null"},
27+
{ "name": "VoterId", "type": "int32", "versions": "1+", "entityType": "brokerId", "ignorable": true,
28+
"about": "The voter ID of the receiving replica" },
2629
{ "name": "Topics", "type": "[]TopicData",
2730
"versions": "0+", "fields": [
28-
{ "name": "TopicName", "type": "string", "versions": "0+", "entityType": "topicName",
29-
"about": "The topic name." },
30-
{ "name": "Partitions", "type": "[]PartitionData",
31-
"versions": "0+", "fields": [
32-
{ "name": "PartitionIndex", "type": "int32", "versions": "0+",
33-
"about": "The partition index." },
34-
{ "name": "LeaderId", "type": "int32", "versions": "0+", "entityType": "brokerId",
35-
"about": "The ID of the newly elected leader"},
36-
{ "name": "LeaderEpoch", "type": "int32", "versions": "0+",
37-
"about": "The epoch of the newly elected leader"}
38-
]}
39-
]}
31+
{ "name": "TopicName", "type": "string", "versions": "0+", "entityType": "topicName",
32+
"about": "The topic name" },
33+
{ "name": "Partitions", "type": "[]PartitionData",
34+
"versions": "0+", "fields": [
35+
{ "name": "PartitionIndex", "type": "int32", "versions": "0+",
36+
"about": "The partition index" },
37+
{ "name": "VoterDirectoryId", "type": "uuid", "versions": "1+", "ignorable": true,
38+
"about": "The directory id of the receiving replica" },
39+
{ "name": "LeaderId", "type": "int32", "versions": "0+", "entityType": "brokerId",
40+
"about": "The ID of the newly elected leader"},
41+
{ "name": "LeaderEpoch", "type": "int32", "versions": "0+",
42+
"about": "The epoch of the newly elected leader"}
43+
]
44+
}
45+
]
46+
},
47+
{ "name": "LeaderEndpoints", "type": "[]LeaderEndpoint", "versions": "1+", "ignorable": true,
48+
"about": "Endpoints for the leader", "fields": [
49+
{ "name": "Name", "type": "string", "versions": "1+", "mapKey": true, "about": "The name of the endpoint" },
50+
{ "name": "Host", "type": "string", "versions": "1+", "about": "The node's hostname" },
51+
{ "name": "Port", "type": "uint16", "versions": "1+", "about": "The node's port" }
52+
]
53+
}
4054
]
4155
}

0 commit comments

Comments
 (0)