use of org.apache.kafka.common.message.VoteResponseData in project kafka by apache.
the class KafkaRaftClientTest method testVoteResponseIgnoredAfterBecomingFollower.
@Test
public void testVoteResponseIgnoredAfterBecomingFollower() throws Exception {
int localId = 0;
int voter1 = localId;
int voter2 = localId + 1;
int voter3 = localId + 2;
int epoch = 5;
Set<Integer> voters = Utils.mkSet(voter1, voter2, voter3);
RaftClientTestContext context = new RaftClientTestContext.Builder(localId, voters).withUnknownLeader(epoch - 1).build();
context.assertUnknownLeader(epoch - 1);
// Sleep a little to ensure that we become a candidate
context.time.sleep(context.electionTimeoutMs() * 2);
// Wait until the vote requests are inflight
context.pollUntilRequest();
context.assertVotedCandidate(epoch, localId);
List<RaftRequest.Outbound> voteRequests = context.collectVoteRequests(epoch, 0, 0);
assertEquals(2, voteRequests.size());
// While the vote requests are still inflight, we receive a BeginEpoch for the same epoch
context.deliverRequest(context.beginEpochRequest(epoch, voter3));
context.client.poll();
context.assertElectedLeader(epoch, voter3);
// The vote requests now return and should be ignored
VoteResponseData voteResponse1 = context.voteResponse(false, Optional.empty(), epoch);
context.deliverResponse(voteRequests.get(0).correlationId, voter2, voteResponse1);
VoteResponseData voteResponse2 = context.voteResponse(false, Optional.of(voter3), epoch);
context.deliverResponse(voteRequests.get(1).correlationId, voter3, voteResponse2);
context.client.poll();
context.assertElectedLeader(epoch, voter3);
}
use of org.apache.kafka.common.message.VoteResponseData in project kafka by apache.
the class RaftClientTestContext method assertSentVoteResponse.
void assertSentVoteResponse(Errors error, int epoch, OptionalInt leaderId, boolean voteGranted) {
List<RaftResponse.Outbound> sentMessages = drainSentResponses(ApiKeys.VOTE);
assertEquals(1, sentMessages.size());
RaftMessage raftMessage = sentMessages.get(0);
assertTrue(raftMessage.data() instanceof VoteResponseData);
VoteResponseData response = (VoteResponseData) raftMessage.data();
assertTrue(hasValidTopicPartition(response, metadataPartition));
VoteResponseData.PartitionData partitionResponse = response.topics().get(0).partitions().get(0);
assertEquals(voteGranted, partitionResponse.voteGranted());
assertEquals(error, Errors.forCode(partitionResponse.errorCode()));
assertEquals(epoch, partitionResponse.leaderEpoch());
assertEquals(leaderId.orElse(-1), partitionResponse.leaderId());
}
use of org.apache.kafka.common.message.VoteResponseData in project kafka by apache.
the class KafkaRaftClient method handleVoteRequest.
/**
* Handle a Vote request. This API may return the following errors:
*
* - {@link Errors#INCONSISTENT_CLUSTER_ID} if the cluster id is presented in request
* but different from this node
* - {@link Errors#BROKER_NOT_AVAILABLE} if this node is currently shutting down
* - {@link Errors#FENCED_LEADER_EPOCH} if the epoch is smaller than this node's epoch
* - {@link Errors#INCONSISTENT_VOTER_SET} if the request suggests inconsistent voter membership (e.g.
* if this node or the sender is not one of the current known voters)
* - {@link Errors#INVALID_REQUEST} if the last epoch or offset are invalid
*/
private VoteResponseData handleVoteRequest(RaftRequest.Inbound requestMetadata) {
VoteRequestData request = (VoteRequestData) requestMetadata.data;
if (!hasValidClusterId(request.clusterId())) {
return new VoteResponseData().setErrorCode(Errors.INCONSISTENT_CLUSTER_ID.code());
}
if (!hasValidTopicPartition(request, log.topicPartition())) {
// Until we support multi-raft, we treat individual topic partition mismatches as invalid requests
return new VoteResponseData().setErrorCode(Errors.INVALID_REQUEST.code());
}
VoteRequestData.PartitionData partitionRequest = request.topics().get(0).partitions().get(0);
int candidateId = partitionRequest.candidateId();
int candidateEpoch = partitionRequest.candidateEpoch();
int lastEpoch = partitionRequest.lastOffsetEpoch();
long lastEpochEndOffset = partitionRequest.lastOffset();
if (lastEpochEndOffset < 0 || lastEpoch < 0 || lastEpoch >= candidateEpoch) {
return buildVoteResponse(Errors.INVALID_REQUEST, false);
}
Optional<Errors> errorOpt = validateVoterOnlyRequest(candidateId, candidateEpoch);
if (errorOpt.isPresent()) {
return buildVoteResponse(errorOpt.get(), false);
}
if (candidateEpoch > quorum.epoch()) {
transitionToUnattached(candidateEpoch);
}
OffsetAndEpoch lastEpochEndOffsetAndEpoch = new OffsetAndEpoch(lastEpochEndOffset, lastEpoch);
boolean voteGranted = quorum.canGrantVote(candidateId, lastEpochEndOffsetAndEpoch.compareTo(endOffset()) >= 0);
if (voteGranted && quorum.isUnattached()) {
transitionToVoted(candidateId, candidateEpoch);
}
logger.info("Vote request {} with epoch {} is {}", request, candidateEpoch, voteGranted ? "granted" : "rejected");
return buildVoteResponse(Errors.NONE, voteGranted);
}
use of org.apache.kafka.common.message.VoteResponseData in project kafka by apache.
the class KafkaRaftClient method handleVoteResponse.
private boolean handleVoteResponse(RaftResponse.Inbound responseMetadata, long currentTimeMs) {
int remoteNodeId = responseMetadata.sourceId();
VoteResponseData response = (VoteResponseData) responseMetadata.data;
Errors topLevelError = Errors.forCode(response.errorCode());
if (topLevelError != Errors.NONE) {
return handleTopLevelError(topLevelError, responseMetadata);
}
if (!hasValidTopicPartition(response, log.topicPartition())) {
return false;
}
VoteResponseData.PartitionData partitionResponse = response.topics().get(0).partitions().get(0);
Errors error = Errors.forCode(partitionResponse.errorCode());
OptionalInt responseLeaderId = optionalLeaderId(partitionResponse.leaderId());
int responseEpoch = partitionResponse.leaderEpoch();
Optional<Boolean> handled = maybeHandleCommonResponse(error, responseLeaderId, responseEpoch, currentTimeMs);
if (handled.isPresent()) {
return handled.get();
} else if (error == Errors.NONE) {
if (quorum.isLeader()) {
logger.debug("Ignoring vote response {} since we already became leader for epoch {}", partitionResponse, quorum.epoch());
} else if (quorum.isCandidate()) {
CandidateState state = quorum.candidateStateOrThrow();
if (partitionResponse.voteGranted()) {
state.recordGrantedVote(remoteNodeId);
maybeTransitionToLeader(state, currentTimeMs);
} else {
state.recordRejectedVote(remoteNodeId);
// vote has become gridlocked.
if (state.isVoteRejected() && !state.isBackingOff()) {
logger.info("Insufficient remaining votes to become leader (rejected by {}). " + "We will backoff before retrying election again", state.rejectingVoters());
state.startBackingOff(currentTimeMs, binaryExponentialElectionBackoffMs(state.retries()));
}
}
} else {
logger.debug("Ignoring vote response {} since we are no longer a candidate in epoch {}", partitionResponse, quorum.epoch());
}
return true;
} else {
return handleUnexpectedError(error, responseMetadata);
}
}
use of org.apache.kafka.common.message.VoteResponseData in project kafka by apache.
the class KafkaRaftClientTest method testClusterAuthorizationFailedInVote.
@Test
public void testClusterAuthorizationFailedInVote() throws Exception {
int localId = 0;
int otherNodeId = 1;
int epoch = 5;
Set<Integer> voters = Utils.mkSet(localId, otherNodeId);
RaftClientTestContext context = new RaftClientTestContext.Builder(localId, voters).withUnknownLeader(epoch - 1).build();
// Sleep a little to ensure that we become a candidate
context.time.sleep(context.electionTimeoutMs() * 2);
context.pollUntilRequest();
context.assertVotedCandidate(epoch, localId);
int correlationId = context.assertSentVoteRequest(epoch, 0, 0L, 1);
VoteResponseData response = new VoteResponseData().setErrorCode(Errors.CLUSTER_AUTHORIZATION_FAILED.code());
context.deliverResponse(correlationId, otherNodeId, response);
assertThrows(ClusterAuthorizationException.class, context.client::poll);
}
Aggregations