Search in sources :

Example 1 with VoteResponseData

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);
}
Also used : VoteResponseData(org.apache.kafka.common.message.VoteResponseData) Test(org.junit.jupiter.api.Test)

Example 2 with VoteResponseData

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());
}
Also used : VoteResponseData(org.apache.kafka.common.message.VoteResponseData)

Example 3 with VoteResponseData

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);
}
Also used : Errors(org.apache.kafka.common.protocol.Errors) VoteResponseData(org.apache.kafka.common.message.VoteResponseData) VoteRequestData(org.apache.kafka.common.message.VoteRequestData)

Example 4 with VoteResponseData

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);
    }
}
Also used : Errors(org.apache.kafka.common.protocol.Errors) VoteResponseData(org.apache.kafka.common.message.VoteResponseData) OptionalInt(java.util.OptionalInt)

Example 5 with VoteResponseData

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);
}
Also used : VoteResponseData(org.apache.kafka.common.message.VoteResponseData) Test(org.junit.jupiter.api.Test)

Aggregations

VoteResponseData (org.apache.kafka.common.message.VoteResponseData)7 Errors (org.apache.kafka.common.protocol.Errors)2 Test (org.junit.jupiter.api.Test)2 OptionalInt (java.util.OptionalInt)1 VoteRequestData (org.apache.kafka.common.message.VoteRequestData)1