-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-9839: Broker should accept control requests with newer broker epoch #8509
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
0c9d015
d8604ed
062c787
d952854
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,8 +21,7 @@ import java.net.InetAddress | |
| import java.nio.charset.StandardCharsets | ||
| import java.util | ||
| import java.util.Arrays.asList | ||
| import java.util.Random | ||
| import java.util.{Collections, Optional} | ||
| import java.util.{Collections, Optional, Random} | ||
| import java.util.concurrent.TimeUnit | ||
|
|
||
| import kafka.api.LeaderAndIsr | ||
|
|
@@ -42,7 +41,7 @@ import kafka.server.QuotaFactory.QuotaManagers | |
| import kafka.utils.{MockTime, TestUtils} | ||
| import kafka.zk.KafkaZkClient | ||
| import org.apache.kafka.common.acl.AclOperation | ||
| import org.apache.kafka.common.{IsolationLevel, TopicPartition} | ||
| import org.apache.kafka.common.{IsolationLevel, Node, TopicPartition} | ||
| import org.apache.kafka.common.errors.UnsupportedVersionException | ||
| import org.apache.kafka.common.internals.Topic | ||
| import org.apache.kafka.common.memory.MemoryPool | ||
|
|
@@ -301,7 +300,7 @@ class KafkaApisTest { | |
|
|
||
| val topicPartition = new TopicPartition(topic, 1) | ||
| val capturedResponse: Capture[RequestChannel.Response] = EasyMock.newCapture() | ||
| val responseCallback: Capture[Map[TopicPartition, Errors] => Unit] = EasyMock.newCapture() | ||
| val responseCallback: Capture[Map[TopicPartition, Errors] => Unit] = EasyMock.newCapture() | ||
|
|
||
| val partitionOffsetCommitData = new TxnOffsetCommitRequest.CommittedOffset(15L, "", Optional.empty()) | ||
| val groupId = "groupId" | ||
|
|
@@ -328,7 +327,7 @@ class KafkaApisTest { | |
| )).andAnswer( | ||
| () => responseCallback.getValue.apply(Map(topicPartition -> Errors.COORDINATOR_LOAD_IN_PROGRESS))) | ||
|
|
||
| EasyMock.expect(requestChannel.sendResponse(EasyMock.capture(capturedResponse))) | ||
| EasyMock.expect(requestChannel.sendResponse(EasyMock.capture(capturedResponse))) | ||
|
|
||
| EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel, groupCoordinator) | ||
|
|
||
|
|
@@ -437,7 +436,7 @@ class KafkaApisTest { | |
| val expectedErrors = Map(tp1 -> Errors.UNSUPPORTED_FOR_MESSAGE_FORMAT, tp2 -> Errors.NONE).asJava | ||
|
|
||
| val capturedResponse: Capture[RequestChannel.Response] = EasyMock.newCapture() | ||
| val responseCallback: Capture[Map[TopicPartition, PartitionResponse] => Unit] = EasyMock.newCapture() | ||
| val responseCallback: Capture[Map[TopicPartition, PartitionResponse] => Unit] = EasyMock.newCapture() | ||
|
|
||
| EasyMock.expect(replicaManager.getMagic(tp1)) | ||
| .andReturn(Some(RecordBatch.MAGIC_VALUE_V1)) | ||
|
|
@@ -1466,7 +1465,7 @@ class KafkaApisTest { | |
| val leaderEpoch = 0 | ||
| val tp0 = new TopicPartition("tp", 0) | ||
|
|
||
| val fetchData = Collections.singletonMap(tp0, new FetchRequest.PartitionData(0,0, Int.MaxValue, Optional.of(leaderEpoch))) | ||
| val fetchData = Collections.singletonMap(tp0, new FetchRequest.PartitionData(0, 0, Int.MaxValue, Optional.of(leaderEpoch))) | ||
| val fetchFromFollower = buildRequest(new FetchRequest.Builder( | ||
| ApiKeys.FETCH.oldestVersion(), ApiKeys.FETCH.latestVersion(), 1, 1000, 0, fetchData | ||
| ).build()) | ||
|
|
@@ -1553,6 +1552,179 @@ class KafkaApisTest { | |
| assertEquals(Errors.INVALID_REQUEST, response.error) | ||
| } | ||
|
|
||
| @Test | ||
| def testUpdateMetadataRequestWithCurrentBrokerEpoch(): Unit = { | ||
| val currentBrokerEpoch = 1239875L | ||
| testUpdateMetadataRequest(currentBrokerEpoch, currentBrokerEpoch, Errors.NONE) | ||
| } | ||
|
|
||
| @Test | ||
| def testUpdateMetadataRequestWithNewerBrokerEpochIsValid(): Unit = { | ||
| val currentBrokerEpoch = 1239875L | ||
| testUpdateMetadataRequest(currentBrokerEpoch, currentBrokerEpoch + 1, Errors.NONE) | ||
| } | ||
|
|
||
| @Test | ||
| def testUpdateMetadataRequestWithStaleBrokerEpochIsRejected(): Unit = { | ||
| val currentBrokerEpoch = 1239875L | ||
| testUpdateMetadataRequest(currentBrokerEpoch, currentBrokerEpoch - 1, Errors.STALE_BROKER_EPOCH) | ||
| } | ||
|
|
||
| def testUpdateMetadataRequest(currentBrokerEpoch: Long, brokerEpochInRequest: Long, expectedError: Errors): Unit = { | ||
| val updateMetadataRequest = createBasicMetadataRequest("topicA", 1, brokerEpochInRequest) | ||
| val request = buildRequest(updateMetadataRequest) | ||
|
|
||
| val capturedResponse: Capture[RequestChannel.Response] = EasyMock.newCapture() | ||
|
|
||
| EasyMock.expect(controller.brokerEpoch).andStubReturn(currentBrokerEpoch) | ||
| EasyMock.expect(replicaManager.maybeUpdateMetadataCache( | ||
| EasyMock.eq(request.context.correlationId), | ||
| EasyMock.anyObject() | ||
| )).andStubReturn( | ||
| Seq() | ||
| ) | ||
|
|
||
| EasyMock.expect(requestChannel.sendResponse(EasyMock.capture(capturedResponse))) | ||
| EasyMock.replay(replicaManager, controller, requestChannel) | ||
|
|
||
| createKafkaApis().handleUpdateMetadataRequest(request) | ||
| val updateMetadataResponse = readResponse(ApiKeys.UPDATE_METADATA, updateMetadataRequest, capturedResponse) | ||
| .asInstanceOf[UpdateMetadataResponse] | ||
| assertEquals(expectedError, updateMetadataResponse.error()) | ||
| EasyMock.verify(replicaManager) | ||
| } | ||
|
|
||
| @Test | ||
| def testLeaderAndIsrRequestWithCurrentBrokerEpoch(): Unit = { | ||
| val currentBrokerEpoch = 1239875L | ||
| testLeaderAndIsrRequest(currentBrokerEpoch, currentBrokerEpoch, Errors.NONE) | ||
| } | ||
|
|
||
| @Test | ||
| def testLeaderAndIsrRequestWithNewerBrokerEpochIsValid(): Unit = { | ||
| val currentBrokerEpoch = 1239875L | ||
| testLeaderAndIsrRequest(currentBrokerEpoch, currentBrokerEpoch + 1, Errors.NONE) | ||
| } | ||
|
|
||
| @Test | ||
| def testLeaderAndIsrRequestWithStaleBrokerEpochIsRejected(): Unit = { | ||
| val currentBrokerEpoch = 1239875L | ||
| testLeaderAndIsrRequest(currentBrokerEpoch, currentBrokerEpoch - 1, Errors.STALE_BROKER_EPOCH) | ||
| } | ||
|
|
||
| def testLeaderAndIsrRequest(currentBrokerEpoch: Long, brokerEpochInRequest: Long, expectedError: Errors): Unit = { | ||
| val controllerId = 2 | ||
| val controllerEpoch = 6 | ||
| val capturedResponse: Capture[RequestChannel.Response] = EasyMock.newCapture() | ||
| val partitionStates = Seq( | ||
| new LeaderAndIsrRequestData.LeaderAndIsrPartitionState() | ||
| .setTopicName("topicW") | ||
| .setPartitionIndex(1) | ||
| .setControllerEpoch(1) | ||
| .setLeader(0) | ||
| .setLeaderEpoch(1) | ||
| .setIsr(asList(0, 1)) | ||
| .setZkVersion(2) | ||
| .setReplicas(asList(0, 1, 2)) | ||
| .setIsNew(false) | ||
| ).asJava | ||
| val leaderAndIsrRequest = new LeaderAndIsrRequest.Builder( | ||
| ApiKeys.LEADER_AND_ISR.latestVersion, | ||
| controllerId, | ||
| controllerEpoch, | ||
| brokerEpochInRequest, | ||
| partitionStates, | ||
| asList(new Node(0, "host0", 9090), new Node(1, "host1", 9091)) | ||
| ).build() | ||
| val request = buildRequest(leaderAndIsrRequest) | ||
| val response = new LeaderAndIsrResponse(new LeaderAndIsrResponseData() | ||
| .setErrorCode(Errors.NONE.code) | ||
| .setPartitionErrors(asList())) | ||
|
|
||
| EasyMock.expect(controller.brokerEpoch).andStubReturn(currentBrokerEpoch) | ||
| EasyMock.expect(replicaManager.becomeLeaderOrFollower( | ||
| EasyMock.eq(request.context.correlationId), | ||
| EasyMock.anyObject(), | ||
| EasyMock.anyObject() | ||
| )).andStubReturn( | ||
| response | ||
| ) | ||
|
|
||
| EasyMock.expect(requestChannel.sendResponse(EasyMock.capture(capturedResponse))) | ||
| EasyMock.replay(replicaManager, controller, requestChannel) | ||
|
|
||
| createKafkaApis().handleLeaderAndIsrRequest(request) | ||
| val leaderAndIsrResponse = readResponse(ApiKeys.LEADER_AND_ISR, leaderAndIsrRequest, capturedResponse) | ||
| .asInstanceOf[LeaderAndIsrResponse] | ||
| assertEquals(expectedError, leaderAndIsrResponse.error()) | ||
| EasyMock.verify(replicaManager) | ||
| } | ||
|
|
||
| @Test | ||
| def testStopReplicaRequestWithCurrentBrokerEpoch(): Unit = { | ||
| val currentBrokerEpoch = 1239875L | ||
| testUpdateMetadataRequest(currentBrokerEpoch, currentBrokerEpoch, Errors.NONE) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This and the two above should use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for catching this and others below! |
||
| } | ||
|
|
||
| @Test | ||
| def testStopReplicaRequestWithNewerBrokerEpochIsValid(): Unit = { | ||
| val currentBrokerEpoch = 1239875L | ||
| testUpdateMetadataRequest(currentBrokerEpoch, currentBrokerEpoch + 1, Errors.NONE) | ||
| } | ||
|
|
||
| @Test | ||
| def testStopReplicaRequestWithStaleBrokerEpochIsRejected(): Unit = { | ||
| val currentBrokerEpoch = 1239875L | ||
| testUpdateMetadataRequest(currentBrokerEpoch, currentBrokerEpoch - 1, Errors.STALE_BROKER_EPOCH) | ||
| } | ||
|
|
||
| def testStopReplicaRequest(currentBrokerEpoch: Long, brokerEpochInRequest: Long, expectedError: Errors): Unit = { | ||
| val controllerId = 0 | ||
| val controllerEpoch = 5 | ||
| val brokerEpoch = 230498320L | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| val capturedResponse: Capture[RequestChannel.Response] = EasyMock.newCapture() | ||
| val fooPartition = new TopicPartition("foo", 0) | ||
| val topicStates = Seq( | ||
| new StopReplicaTopicState() | ||
| .setTopicName(fooPartition.topic()) | ||
| .setPartitionStates(Seq(new StopReplicaPartitionState() | ||
| .setPartitionIndex(fooPartition.partition()) | ||
| .setLeaderEpoch(1) | ||
| .setDeletePartition(false)).asJava) | ||
| ).asJava | ||
| val stopReplicaRequest = new StopReplicaRequest.Builder( | ||
| ApiKeys.STOP_REPLICA.latestVersion, | ||
| controllerId, | ||
| controllerEpoch, | ||
| brokerEpochInRequest, | ||
| false, | ||
| topicStates | ||
| ).build() | ||
| val request = buildRequest(stopReplicaRequest) | ||
|
|
||
| EasyMock.expect(controller.brokerEpoch).andStubReturn(currentBrokerEpoch) | ||
| EasyMock.expect(replicaManager.stopReplicas( | ||
| EasyMock.eq(request.context.correlationId), | ||
| EasyMock.eq(controllerId), | ||
| EasyMock.eq(controllerEpoch), | ||
| EasyMock.eq(brokerEpoch + 1), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should rely on |
||
| EasyMock.eq(stopReplicaRequest.partitionStates().asScala) | ||
| )).andStubReturn( | ||
| (mutable.Map( | ||
| fooPartition -> Errors.NONE | ||
| ), Errors.NONE) | ||
| ) | ||
| EasyMock.expect(requestChannel.sendResponse(EasyMock.capture(capturedResponse))) | ||
|
|
||
| EasyMock.replay(controller, replicaManager, requestChannel) | ||
|
|
||
| createKafkaApis().handleStopReplicaRequest(request) | ||
| val stopReplicaResponse = readResponse(ApiKeys.STOP_REPLICA, stopReplicaRequest, capturedResponse) | ||
| .asInstanceOf[StopReplicaResponse] | ||
| assertEquals(expectedError, stopReplicaResponse.error()) | ||
| EasyMock.verify(replicaManager) | ||
| } | ||
|
|
||
| /** | ||
| * Return pair of listener names in the metadataCache: PLAINTEXT and LISTENER2 respectively. | ||
| */ | ||
|
|
@@ -1680,7 +1852,7 @@ class KafkaApisTest { | |
| capturedResponse | ||
| } | ||
|
|
||
| private def setupBasicMetadataCache(topic: String, numPartitions: Int): Unit = { | ||
| private def createBasicMetadataRequest(topic: String, numPartitions: Int, brokerEpoch: Long): UpdateMetadataRequest = { | ||
| val replicas = List(0.asInstanceOf[Integer]).asJava | ||
|
|
||
| def createPartitionState(partition: Int) = new UpdateMetadataPartitionState() | ||
|
|
@@ -1703,8 +1875,12 @@ class KafkaApisTest { | |
| .setSecurityProtocol(SecurityProtocol.PLAINTEXT.id) | ||
| .setListener(plaintextListener.value)).asJava) | ||
| val partitionStates = (0 until numPartitions).map(createPartitionState) | ||
| val updateMetadataRequest = new UpdateMetadataRequest.Builder(ApiKeys.UPDATE_METADATA.latestVersion, 0, | ||
| 0, 0, partitionStates.asJava, Seq(broker).asJava).build() | ||
| new UpdateMetadataRequest.Builder(ApiKeys.UPDATE_METADATA.latestVersion, 0, | ||
| 0, brokerEpoch, partitionStates.asJava, Seq(broker).asJava).build() | ||
| } | ||
|
|
||
| private def setupBasicMetadataCache(topic: String, numPartitions: Int): Unit = { | ||
| val updateMetadataRequest = createBasicMetadataRequest(topic, numPartitions, 0) | ||
| metadataCache.updateMetadata(correlationId = 0, updateMetadataRequest) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Short comment here may be helpful about the case where the controller sees the epoch bump first.