-
Notifications
You must be signed in to change notification settings - Fork 15k
KAFKA-16563: retry pollEvent in KRaftMigrationDriver for retriable errors #15732
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 all commits
b5b796f
88adc82
617bc1c
e800634
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 |
|---|---|---|
|
|
@@ -165,19 +165,6 @@ public CompletableFuture<MigrationDriverState> migrationState() { | |
| return stateFuture; | ||
| } | ||
|
|
||
| private void recoverMigrationStateFromZK() { | ||
| applyMigrationOperation("Recovering migration state from ZK", zkMigrationClient::getOrCreateMigrationRecoveryState); | ||
| String maybeDone = migrationLeadershipState.initialZkMigrationComplete() ? "done" : "not done"; | ||
| log.info("Initial migration of ZK metadata is {}.", maybeDone); | ||
|
|
||
| // Once we've recovered the migration state from ZK, install this class as a metadata publisher | ||
| // by calling the initialZkLoadHandler. | ||
| initialZkLoadHandler.accept(this); | ||
|
|
||
| // Transition to INACTIVE state and wait for leadership events. | ||
| transitionTo(MigrationDriverState.INACTIVE); | ||
| } | ||
|
|
||
| private boolean isControllerQuorumReadyForMigration() { | ||
| Optional<String> notReadyMsg = this.quorumFeatures.reasonAllControllersZkMigrationNotReady( | ||
| image.features().metadataVersion(), image.cluster().controllers()); | ||
|
|
@@ -414,7 +401,7 @@ public String toString() { | |
| /** | ||
| * An event generated by a call to {@link MetadataPublisher#onControllerChange}. This will not be called until | ||
| * this class is registered with {@link org.apache.kafka.image.loader.MetadataLoader}. The registration happens | ||
| * after the migration state is loaded from ZooKeeper in {@link #recoverMigrationStateFromZK}. | ||
| * after the migration state is loaded from ZooKeeper in {@link RecoverMigrationStateFromZKEvent}. | ||
| */ | ||
| class KRaftLeaderEvent extends MigrationEvent { | ||
| private final LeaderAndEpoch leaderAndEpoch; | ||
|
|
@@ -786,12 +773,31 @@ public void run() throws Exception { | |
| } | ||
| } | ||
|
|
||
| class RecoverMigrationStateFromZKEvent extends MigrationEvent { | ||
| @Override | ||
| public void run() throws Exception { | ||
| if (checkDriverState(MigrationDriverState.UNINITIALIZED, this)) { | ||
| applyMigrationOperation("Recovering migration state from ZK", zkMigrationClient::getOrCreateMigrationRecoveryState); | ||
| String maybeDone = migrationLeadershipState.initialZkMigrationComplete() ? "done" : "not done"; | ||
| log.info("Initial migration of ZK metadata is {}.", maybeDone); | ||
|
|
||
| // Once we've recovered the migration state from ZK, install this class as a metadata publisher | ||
| // by calling the initialZkLoadHandler. | ||
| initialZkLoadHandler.accept(KRaftMigrationDriver.this); | ||
|
|
||
| // Transition to INACTIVE state and wait for leadership events. | ||
| transitionTo(MigrationDriverState.INACTIVE); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| class PollEvent extends MigrationEvent { | ||
|
|
||
| @Override | ||
| public void run() throws Exception { | ||
| switch (migrationState) { | ||
| case UNINITIALIZED: | ||
| recoverMigrationStateFromZK(); | ||
| eventQueue.append(new RecoverMigrationStateFromZKEvent()); | ||
|
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. Should we use
Member
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. No need. Like I said in this comment, in the
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. Are we allowing a race between the
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. On second thought, I don't think my question makes sense. The following |
||
| break; | ||
| case INACTIVE: | ||
| // Nothing to do when the driver is inactive. We must wait until a KRaftLeaderEvent | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -253,7 +253,7 @@ public void testOnlySendNeededRPCsToBrokers(boolean registerControllers) throws | |
| MetadataImage image = MetadataImage.EMPTY; | ||
| MetadataDelta delta = new MetadataDelta(image); | ||
|
|
||
| driver.start(); | ||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
| setupDeltaForMigration(delta, registerControllers); | ||
| delta.replay(ZkMigrationState.PRE_MIGRATION.toRecord().message()); | ||
| delta.replay(zkBrokerRecord(1)); | ||
|
|
@@ -338,7 +338,7 @@ public ZkMigrationLeadershipState claimControllerLeadership(ZkMigrationLeadershi | |
| MetadataDelta delta = new MetadataDelta(image); | ||
| setupDeltaForMigration(delta, true); | ||
|
|
||
| driver.start(); | ||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
| delta.replay(ZkMigrationState.PRE_MIGRATION.toRecord().message()); | ||
| delta.replay(zkBrokerRecord(1)); | ||
| delta.replay(zkBrokerRecord(2)); | ||
|
|
@@ -363,6 +363,62 @@ public ZkMigrationLeadershipState claimControllerLeadership(ZkMigrationLeadershi | |
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testMigrationWithClientExceptionWhileMigratingZnodeCreation() throws Exception { | ||
| CountingMetadataPropagator metadataPropagator = new CountingMetadataPropagator(); | ||
| // suppose the ZNode creation failed 3 times | ||
| CountDownLatch createZnodeAttempts = new CountDownLatch(3); | ||
| CapturingMigrationClient migrationClient = new CapturingMigrationClient(new HashSet<>(Arrays.asList(1, 2, 3)), | ||
| new CapturingTopicMigrationClient(), | ||
| new CapturingConfigMigrationClient(), | ||
| new CapturingAclMigrationClient(), | ||
| new CapturingDelegationTokenMigrationClient(), | ||
| CapturingMigrationClient.EMPTY_BATCH_SUPPLIER) { | ||
| @Override | ||
| public ZkMigrationLeadershipState getOrCreateMigrationRecoveryState(ZkMigrationLeadershipState initialState) { | ||
| if (createZnodeAttempts.getCount() == 0) { | ||
| this.setMigrationRecoveryState(initialState); | ||
| return initialState; | ||
| } else { | ||
| createZnodeAttempts.countDown(); | ||
| throw new MigrationClientException("Some kind of ZK error!"); | ||
| } | ||
| } | ||
| }; | ||
| MockFaultHandler faultHandler = new MockFaultHandler("testMigrationClientExpiration"); | ||
| KRaftMigrationDriver.Builder builder = defaultTestBuilder() | ||
| .setZkMigrationClient(migrationClient) | ||
| .setFaultHandler(faultHandler) | ||
| .setPropagator(metadataPropagator); | ||
| try (KRaftMigrationDriver driver = builder.build()) { | ||
| MetadataImage image = MetadataImage.EMPTY; | ||
| MetadataDelta delta = new MetadataDelta(image); | ||
| setupDeltaForMigration(delta, true); | ||
|
|
||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
|
|
||
| delta.replay(ZkMigrationState.PRE_MIGRATION.toRecord().message()); | ||
| delta.replay(zkBrokerRecord(1)); | ||
| delta.replay(zkBrokerRecord(2)); | ||
| delta.replay(zkBrokerRecord(3)); | ||
| MetadataProvenance provenance = new MetadataProvenance(100, 1, 1); | ||
| image = delta.apply(provenance); | ||
| // Before leadership claiming, the getOrCreateMigrationRecoveryState should be able to get correct state | ||
| assertTrue(createZnodeAttempts.await(1, TimeUnit.MINUTES)); | ||
|
|
||
| // Notify the driver that it is the leader | ||
| driver.onControllerChange(new LeaderAndEpoch(OptionalInt.of(3000), 1)); | ||
| // Publish metadata of all the ZK brokers being ready | ||
| driver.onMetadataUpdate(delta, image, logDeltaManifestBuilder(provenance, | ||
| new LeaderAndEpoch(OptionalInt.of(3000), 1)).build()); | ||
|
|
||
| TestUtils.waitForCondition(() -> driver.migrationState().get(1, TimeUnit.MINUTES).equals(MigrationDriverState.DUAL_WRITE), | ||
| "Waiting for KRaftMigrationDriver to enter DUAL_WRITE state"); | ||
|
|
||
| Assertions.assertNull(faultHandler.firstException()); | ||
| } | ||
| } | ||
|
|
||
| private void setupDeltaForMigration( | ||
| MetadataDelta delta, | ||
| boolean registerControllers | ||
|
|
@@ -413,7 +469,7 @@ public void testShouldNotMoveToNextStateIfControllerNodesAreNotReadyToMigrate( | |
| MetadataImage image = MetadataImage.EMPTY; | ||
| MetadataDelta delta = new MetadataDelta(image); | ||
|
|
||
| driver.start(); | ||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
| if (allNodePresent) { | ||
| setupDeltaWithControllerRegistrations(delta, Arrays.asList(4, 5, 6), Arrays.asList()); | ||
| } else { | ||
|
|
@@ -469,7 +525,7 @@ public void testSkipWaitForBrokersInDualWrite() throws Exception { | |
| migrationClient.setMigrationRecoveryState( | ||
| ZkMigrationLeadershipState.EMPTY.withKRaftMetadataOffsetAndEpoch(100, 1)); | ||
|
|
||
| driver.start(); | ||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
| delta.replay(ZkMigrationState.PRE_MIGRATION.toRecord().message()); | ||
| delta.replay(zkBrokerRecord(1)); | ||
| delta.replay(zkBrokerRecord(2)); | ||
|
|
@@ -483,7 +539,7 @@ public void testSkipWaitForBrokersInDualWrite() throws Exception { | |
| new LeaderAndEpoch(OptionalInt.of(3000), 1)).build()); | ||
|
|
||
| TestUtils.waitForCondition(() -> driver.migrationState().get(1, TimeUnit.MINUTES).equals(MigrationDriverState.DUAL_WRITE), | ||
| "Waiting for KRaftMigrationDriver to enter ZK_MIGRATION state"); | ||
| "Waiting for KRaftMigrationDriver to enter DUAL_WRITE state"); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -546,7 +602,7 @@ public void testTopicDualWriteSnapshot() throws Exception { | |
| DelegationTokenImage.EMPTY); | ||
| MetadataDelta delta = new MetadataDelta(image); | ||
|
|
||
| driver.start(); | ||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
| setupDeltaForMigration(delta, true); | ||
| delta.replay(ZkMigrationState.PRE_MIGRATION.toRecord().message()); | ||
| delta.replay(zkBrokerRecord(0)); | ||
|
|
@@ -565,7 +621,7 @@ public void testTopicDualWriteSnapshot() throws Exception { | |
|
|
||
| // Wait for migration | ||
| TestUtils.waitForCondition(() -> driver.migrationState().get(1, TimeUnit.MINUTES).equals(MigrationDriverState.DUAL_WRITE), | ||
| "Waiting for KRaftMigrationDriver to enter ZK_MIGRATION state"); | ||
| "Waiting for KRaftMigrationDriver to enter DUAL_WRITE state"); | ||
|
|
||
| // Modify topics in a KRaft snapshot -- delete foo, modify bar, add baz, add new foo, add bam, delete bam | ||
| provenance = new MetadataProvenance(200, 1, 1); | ||
|
|
@@ -601,7 +657,7 @@ public void testTopicDualWriteDelta() throws Exception { | |
| DelegationTokenImage.EMPTY); | ||
| MetadataDelta delta = new MetadataDelta(image); | ||
|
|
||
| driver.start(); | ||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
| setupDeltaForMigration(delta, true); | ||
| delta.replay(ZkMigrationState.PRE_MIGRATION.toRecord().message()); | ||
| delta.replay(zkBrokerRecord(0)); | ||
|
|
@@ -656,7 +712,7 @@ public void testNoDualWriteBeforeMigration() throws Exception { | |
| DelegationTokenImage.EMPTY); | ||
| MetadataDelta delta = new MetadataDelta(image); | ||
|
|
||
| driver.start(); | ||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
| setupDeltaForMigration(delta, true); | ||
| delta.replay(ZkMigrationState.PRE_MIGRATION.toRecord().message()); | ||
| delta.replay(zkBrokerRecord(0)); | ||
|
|
@@ -673,7 +729,7 @@ public void testNoDualWriteBeforeMigration() throws Exception { | |
| driver.onControllerChange(newLeader); | ||
|
|
||
| TestUtils.waitForCondition(() -> driver.migrationState().get(1, TimeUnit.MINUTES).equals(MigrationDriverState.WAIT_FOR_CONTROLLER_QUORUM), | ||
| "Waiting for KRaftMigrationDriver to enter DUAL_WRITE state"); | ||
| "Waiting for KRaftMigrationDriver to enter WAIT_FOR_CONTROLLER_QUORUM state"); | ||
|
|
||
| driver.onMetadataUpdate(delta, image, logDeltaManifestBuilder(provenance, newLeader).build()); | ||
|
|
||
|
|
@@ -711,7 +767,7 @@ public void testControllerFailover() throws Exception { | |
| DelegationTokenImage.EMPTY); | ||
| MetadataDelta delta = new MetadataDelta(image); | ||
|
|
||
| driver.start(); | ||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
| setupDeltaForMigration(delta, true); | ||
| delta.replay(ZkMigrationState.PRE_MIGRATION.toRecord().message()); | ||
| delta.replay(zkBrokerRecord(0)); | ||
|
|
@@ -778,7 +834,7 @@ public CompletableFuture<?> beginMigration() { | |
| MetadataImage image = MetadataImage.EMPTY; | ||
| MetadataDelta delta = new MetadataDelta(image); | ||
|
|
||
| driver.start(); | ||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
| setupDeltaForMigration(delta, true); | ||
| delta.replay(ZkMigrationState.PRE_MIGRATION.toRecord().message()); | ||
| delta.replay(zkBrokerRecord(1)); | ||
|
|
@@ -798,7 +854,7 @@ public CompletableFuture<?> beginMigration() { | |
| new LeaderAndEpoch(OptionalInt.of(3000), 1)).build()); | ||
|
|
||
| TestUtils.waitForCondition(() -> driver.migrationState().get(1, TimeUnit.MINUTES).equals(MigrationDriverState.DUAL_WRITE), | ||
| "Waiting for KRaftMigrationDriver to enter ZK_MIGRATION state"); | ||
| "Waiting for KRaftMigrationDriver to enter DUAL_WRITE state"); | ||
| assertEquals(1, migrationBeginCalls.get()); | ||
| } | ||
| } | ||
|
|
@@ -864,7 +920,7 @@ public List<List<ApiMessageAndVersion>> recordBatches() { | |
| MetadataImage image = MetadataImage.EMPTY; | ||
| MetadataDelta delta = new MetadataDelta(image); | ||
|
|
||
| driver.start(); | ||
| startAndWaitForRecoveringMigrationStateFromZK(driver); | ||
| setupDeltaForMigration(delta, true); | ||
| delta.replay(ZkMigrationState.PRE_MIGRATION.toRecord().message()); | ||
| delta.replay(zkBrokerRecord(1)); | ||
|
|
@@ -881,10 +937,18 @@ public List<List<ApiMessageAndVersion>> recordBatches() { | |
| new LeaderAndEpoch(OptionalInt.of(3000), 1)).build()); | ||
|
|
||
| TestUtils.waitForCondition(() -> driver.migrationState().get(1, TimeUnit.MINUTES).equals(MigrationDriverState.DUAL_WRITE), | ||
| "Waiting for KRaftMigrationDriver to enter ZK_MIGRATION state"); | ||
| "Waiting for KRaftMigrationDriver to enter DUAL_WRITE state"); | ||
|
Comment on lines
939
to
+940
Member
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. Side fix. |
||
|
|
||
| assertEquals(expectedBatchCount, batchesPassedToController.size()); | ||
| assertEquals(expectedRecordCount, batchesPassedToController.stream().mapToInt(List::size).sum()); | ||
| } | ||
| } | ||
|
|
||
| // Wait until the driver has recovered MigrationState From ZK. This is to simulate the driver needs to be installed as the metadata publisher | ||
| // so that it can receive onControllerChange (KRaftLeaderEvent) and onMetadataUpdate (MetadataChangeEvent) events. | ||
| private void startAndWaitForRecoveringMigrationStateFromZK(KRaftMigrationDriver driver) throws InterruptedException { | ||
| driver.start(); | ||
| TestUtils.waitForCondition(() -> driver.migrationState().get(1, TimeUnit.MINUTES).equals(MigrationDriverState.INACTIVE), | ||
| "Waiting for KRaftMigrationDriver to enter INACTIVE state"); | ||
|
Comment on lines
+947
to
+952
Member
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. This is necessary now because in the test suite, we might invoke |
||
| } | ||
| } | ||
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.
For my understanding, was this the line where uncaught exception is thrown? Can we handle the exception more gracefully and log and error?
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.
Yes, this is where the uncaught exception thrown. The exception will be handled by its parent
MigrationEvent#handleException, and we'll log error there, and even call thefaultHandler.handleFaultto handle fatal errors.kafka/metadata/src/main/java/org/apache/kafka/metadata/migration/KRaftMigrationDriver.java
Lines 396 to 404 in 994077e
Thanks.