You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jira@kafka.apache.org by "yashmayya (via GitHub)" <gi...@apache.org> on 2023/07/26 13:40:22 UTC
[GitHub] [kafka] yashmayya opened a new pull request, #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
yashmayya opened a new pull request, #14102:
URL: https://github.com/apache/kafka/pull/14102
- https://issues.apache.org/jira/browse/KAFKA-13187
- Original / abandoned PR for this issue is - https://github.com/apache/kafka/pull/11792.
- https://github.com/mockito/mockito/issues/2601 is resolved in the current version of Mockito that we're using (`4.11.0`) so the workaround hack is no longer needed.
- The current / new patch takes a slightly different approach from the original one. Using a combination of Mockito's [strict stubs](https://www.javadoc.io/doc/org.mockito/mockito-core/4.11.0/org/mockito/quality/Strictness.html#STRICT_STUBS) functionality and [verifyNoMoreInteractions](https://javadoc.io/static/org.mockito/mockito-core/4.11.0/org/mockito/Mockito.html#verifyNoMoreInteractions(java.lang.Object...)), we get some nice properties -
- Any unused stubbings will lead to test failures
- Tests fail early when stubbed methods are invoked with different arguments
- Stubbed invocations are automatically verified and don't need explicit verification.
- The above properties allow us to make the test code significantly leaner and cleaner (as can be seen from the + / - SLOC in this diff).
- One big caveat with this approach is that the number of invocations for stubbed methods isn't automatically verified for us since `Mockito` stubbings are applicable any number of times by default (unlike in `EasyMock` where the default is a single invocation being stubbed).
- Hence, the older `EasyMock` way of stacking up expectations and then verifying them automatically took care of verifying the number of invocations for stubbed methods (assuming `anyTimes()` wasn't used in the expect calls).
- While for a lot of cases this really isn't required (for instance, verifying the number of calls to `WorkerGroupMember::wakeup` in large tests that involve multiple calls to `DistributedHerder::tick`), there are some cases where it is crucial to the test's coverage and this patch attempts to retain that coverage wherever it seemed important by using the [times() verification mode](https://javadoc.io/doc/org.mockito/mockito-core/4.11.0/org/mockito/verification/VerificationMode.html) explicitly.
- There was no refactoring required for the `DistributedHerder` class itself apart from bumping up the visibility of the `herderExecutor` for this specific test - https://github.com/apache/kafka/blob/ff390ab60a57100cc829be243ce525ac31523000/connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java#L3906
### Committer Checklist (excluded from commit message)
- [ ] Verify design and implementation
- [ ] Verify test coverage and CI build status
- [ ] Verify documentation (including upgrade notes)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [kafka] C0urante commented on a diff in pull request #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
Posted by "C0urante (via GitHub)" <gi...@apache.org>.
C0urante commented on code in PR #14102:
URL: https://github.com/apache/kafka/pull/14102#discussion_r1290658873
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3872,7 +2952,7 @@ public void testVerifyTaskGeneration() {
assertThrows(ConnectException.class, () -> herder.verifyTaskGenerationAndOwnership(unassignedTask, 1, verifyCallback));
assertThrows(ConnectException.class, () -> herder.verifyTaskGenerationAndOwnership(unassignedTask, 2, verifyCallback));
- PowerMock.verifyAll();
+ verify(verifyCallback, times(3)).onCompletion(isNull(), isNull());
Review Comment:
Whoops!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [kafka] C0urante commented on pull request #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
Posted by "C0urante (via GitHub)" <gi...@apache.org>.
C0urante commented on PR #14102:
URL: https://github.com/apache/kafka/pull/14102#issuecomment-1673896029
I should note that because this test makes heavy use of Mockito's strict stubbing feature, I've opened https://github.com/apache/kafka/pull/14186 to enable reporting of unused stubbings in our CI builds. I've verified locally that there are no unused stubbings in this PR (`./gradlew :connect:runtime:test --tests DistributedHerderTest` does the trick), and any local build that uses the `test` task instead of `unitTest` should also report unused stubbings, so I don't believe it's necessary to block on https://github.com/apache/kafka/pull/14186 before merging this.
Test failures in CI also appear unrelated. Merging...
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [kafka] yashmayya commented on a diff in pull request #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
Posted by "yashmayya (via GitHub)" <gi...@apache.org>.
yashmayya commented on code in PR #14102:
URL: https://github.com/apache/kafka/pull/14102#discussion_r1287941830
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -687,101 +617,72 @@ public void revokeAndReassign(boolean incompleteRebalance) throws TimeoutExcepti
);
expectConfigRefreshAndSnapshot(secondSnapshot);
}
- member.requestRejoin();
- PowerMock.expectLastCall();
+
+ doNothing().when(member).requestRejoin();
+
+ herder.tick();
// re-assign the connector back to the same worker to ensure state was cleaned up
expectRebalance(configOffset, Arrays.asList(CONN1), Arrays.asList());
- Capture<Callback<TargetState>> onSecondStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(),
- EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onSecondStart));
- PowerMock.expectLastCall().andAnswer(() -> {
- onSecondStart.getValue().onCompletion(null, TargetState.STARTED);
- return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
- if (incompleteRebalance) {
- herder.tick();
- }
- herder.tick();
- herder.tick();
- PowerMock.verifyAll();
+ verify(worker, times(2)).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), any());
+ verify(worker, times(2)).connectorTaskConfigs(eq(CONN1), eq(conn1SinkConfig));
+ verify(worker).startSourceTask(eq(TASK1), any(), any(), any(), eq(herder), eq(TargetState.STARTED));
+
Review Comment:
Wow, good catch, and thanks for the additional context!
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -844,73 +737,57 @@ public void testCreateConnectorConfigBackingStoreError() {
time.sleep(1000L);
assertStatistics(3, 1, 100, 1000L);
- PowerMock.verifyAll();
+ // Verify that the exception thrown during the config backing store write is propagated via the callback
+ verify(putConnectorCallback).onCompletion(any(ConnectException.class), isNull());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore, putConnectorCallback);
}
@Test
public void testCreateConnectorFailedValidation() throws Exception {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ // Initial rebalance where this member becomes the leader
+ herder.tick();
Review Comment:
Hm sorry, I don't really remember the context as it's actually been a few months since I worked on this test. Playing around it with it a bit though, it looks like we don't really need the extra tick and I've modified the test accordingly. My guess is that I added the new tick to ensure that the number of rebalances is as expected. The number depends on the ordering of tick calls and `WorkerGroupMember::ensureActive` stubs, but the result can also be achieved by some simpler re-ordering instead (which is what I've done now).
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
stateCallback.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
-
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
-
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyTasks() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
// The connector has three tasks
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Arrays.asList(TASK0, TASK1, TASK2)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ when(restartPlan.taskIdsToRestart()).thenReturn(Arrays.asList(TASK0, TASK1, TASK2));
+ when(restartPlan.totalTaskCount()).thenReturn(3);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.emptyList());
// But only one task is assigned to this worker
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(TASK0)).anyTimes();
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(TASK0));
herder.configState = SNAPSHOT;
- worker.stopAndAwaitTasks(Collections.singletonList(TASK0));
- PowerMock.expectLastCall();
-
- herder.onRestart(TASK0);
- EasyMock.expectLastCall();
-
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(TASK0));
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksBoth() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(taskId)).anyTimes();
-
- herder.configState = SNAPSHOT;
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
+ when(restartPlan.totalTaskCount()).thenReturn(1);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
-
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(taskId));
+ herder.configState = SNAPSHOT;
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- worker.stopAndAwaitTasks(Collections.singletonList(taskId));
- PowerMock.expectLastCall();
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ stateCallback.getValue().onCompletion(null, TargetState.STARTED);
+ return true;
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
- herder.onRestart(taskId);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(taskId));
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTask() throws Exception {
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, conn1SinkConfig)).andStubReturn(TASK_CONFIGS);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), singletonList(TASK0), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.stopAndAwaitTask(TASK0);
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
+ doNothing().when(worker).stopAndAwaitTask(TASK0);
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
callback.get(1000L, TimeUnit.MILLISECONDS);
- PowerMock.verifyAll();
+ verify(worker, times(2)).startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartUnknownTask() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ herder.tick();
+ doNothing().when(member).ensureActive();
FutureCallback<Void> callback = new FutureCallback<>();
- herder.tick();
herder.restartTask(new ConnectorTaskId("blah", 0), callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotFoundException);
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotFoundException.class, e.getCause().getClass());
- PowerMock.verifyAll();
- }
-
- @Test
- public void testRequestProcessingOrder() {
- final DistributedHerder.DistributedHerderRequest req1 = herder.addRequest(100, null, null);
- final DistributedHerder.DistributedHerderRequest req2 = herder.addRequest(10, null, null);
- final DistributedHerder.DistributedHerderRequest req3 = herder.addRequest(200, null, null);
- final DistributedHerder.DistributedHerderRequest req4 = herder.addRequest(200, null, null);
-
- assertEquals(req2, herder.requests.pollFirst()); // lowest delay
- assertEquals(req1, herder.requests.pollFirst()); // next lowest delay
- assertEquals(req3, herder.requests.pollFirst()); // same delay as req4, but added first
- assertEquals(req4, herder.requests.pollFirst());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTaskRedirectToLeader() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotLeaderException);
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotLeaderException.class, e.getCause().getClass());
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTaskRedirectToOwner() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the task restart
String ownerUrl = "ownerUrl";
- EasyMock.expect(member.ownerUrl(TASK0)).andReturn(ownerUrl);
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ when(member.ownerUrl(TASK0)).thenReturn(ownerUrl);
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotAssignedException);
- NotAssignedException notAssignedException = (NotAssignedException) e.getCause();
- assertEquals(ownerUrl, notAssignedException.forwardUrl());
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotAssignedException.class, e.getCause().getClass());
+ assertEquals(ownerUrl, ((NotAssignedException) e.getCause()).forwardUrl());
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
- public void testConnectorConfigAdded() {
+ public void testRequestProcessingOrder() {
+ final DistributedHerder.DistributedHerderRequest req1 = herder.addRequest(100, null, null);
+ final DistributedHerder.DistributedHerderRequest req2 = herder.addRequest(10, null, null);
+ final DistributedHerder.DistributedHerderRequest req3 = herder.addRequest(200, null, null);
+ final DistributedHerder.DistributedHerderRequest req4 = herder.addRequest(200, null, null);
+
+ assertEquals(req2, herder.requests.pollFirst()); // lowest delay
+ assertEquals(req1, herder.requests.pollFirst()); // next lowest delay
+ assertEquals(req3, herder.requests.pollFirst()); // same delay as req4, but added first
+ assertEquals(req4, herder.requests.pollFirst());
+ }
+
+ @Test
+ public void testConnectorConfigAdded() throws Exception {
// If a connector was added, we need to rebalance
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join, no configs so no need to catch up on config topic
expectRebalance(-1, Collections.emptyList(), Collections.emptyList());
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick(); // join
- // apply config
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
// Checks for config updates and starts rebalance
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
- member.requestRejoin();
- PowerMock.expectLastCall();
+ doNothing().when(configBackingStore).refresh(anyLong(), any());
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
Review Comment:
Whoops 🤦
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3724,62 +2818,54 @@ public void testExternalZombieFencingRequestDelayedCompletion() throws Exception
pendingFencing,
tasksPerConnector
);
- tasksPerConnector.keySet().forEach(c -> expectConfigRefreshAndSnapshot(configState));
+ expectConfigRefreshAndSnapshot(configState);
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
// The callbacks that the herder has accrued for outstanding fencing futures, which will be completed after
// a successful round of fencing and a task record write to the config topic
- Map<String, Capture<KafkaFuture.BiConsumer<Void, Throwable>>> herderFencingCallbacks = new HashMap<>();
+ Map<String, ArgumentCaptor<KafkaFuture.BiConsumer<Void, Throwable>>> herderFencingCallbacks = new HashMap<>();
// The callbacks that the herder has installed for after a successful round of zombie fencing, but before writing
// a task record to the config topic
- Map<String, Capture<KafkaFuture.BaseFunction<Void, Void>>> workerFencingFollowups = new HashMap<>();
+ Map<String, ArgumentCaptor<KafkaFuture.BaseFunction<Void, Void>>> workerFencingFollowups = new HashMap<>();
Map<String, CountDownLatch> callbacksInstalled = new HashMap<>();
tasksPerConnector.forEach((connector, numStackedRequests) -> {
// The future returned by Worker::fenceZombies
- KafkaFuture<Void> workerFencingFuture = EasyMock.mock(KafkaFuture.class);
- // The future tracked by the herder (which tracks the fencing performed by the worker and the possible followup write to the config topic)
- KafkaFuture<Void> herderFencingFuture = EasyMock.mock(KafkaFuture.class);
+ KafkaFuture<Void> workerFencingFuture = mock(KafkaFuture.class);
+ // The future tracked by the herder (which tracks the fencing performed by the worker and the possible followup write to the config topic)
+ KafkaFuture<Void> herderFencingFuture = mock(KafkaFuture.class);
- Capture<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = EasyMock.newCapture(CaptureType.ALL);
+ ArgumentCaptor<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = ArgumentCaptor.forClass(KafkaFuture.BiConsumer.class);
herderFencingCallbacks.put(connector, herderFencingCallback);
// Don't immediately invoke callbacks that the herder sets up for when the worker fencing and writes to the config topic have completed
// Instead, wait for them to be installed, then invoke them explicitly after the fact on a thread separate from the herder's tick thread
- EasyMock.expect(herderFencingFuture.whenComplete(EasyMock.capture(herderFencingCallback)))
- .andReturn(null)
- .times(numStackedRequests + 1);
+ when(herderFencingFuture.whenComplete(herderFencingCallback.capture())).thenReturn(null);
- Capture<KafkaFuture.BaseFunction<Void, Void>> fencingFollowup = EasyMock.newCapture();
+ ArgumentCaptor<KafkaFuture.BaseFunction<Void, Void>> fencingFollowup = ArgumentCaptor.forClass(KafkaFuture.BaseFunction.class);
CountDownLatch callbackInstalled = new CountDownLatch(1);
workerFencingFollowups.put(connector, fencingFollowup);
callbacksInstalled.put(connector, callbackInstalled);
- EasyMock.expect(workerFencingFuture.thenApply(EasyMock.capture(fencingFollowup))).andAnswer(() -> {
+ when(workerFencingFuture.thenApply(fencingFollowup.capture())).thenAnswer(invocation -> {
callbackInstalled.countDown();
return herderFencingFuture;
});
// We should only perform a single physical zombie fencing; all the subsequent requests should be stacked onto the first one
- EasyMock.expect(worker.fenceZombies(
- EasyMock.eq(connector), EasyMock.eq(taskCountRecords.get(connector)), EasyMock.anyObject())
- ).andReturn(workerFencingFuture);
-
- for (int i = 0; i < numStackedRequests; i++) {
- expectConfigRefreshAndSnapshot(configState);
- }
-
- PowerMock.replay(workerFencingFuture, herderFencingFuture);
+ when(worker.fenceZombies(eq(connector), eq(taskCountRecords.get(connector)), any()))
+ .thenReturn(workerFencingFuture)
+ .thenAnswer(invocation -> {
+ fail("Expected only a single zombie fencing per connector");
Review Comment:
Oh, that's a good point, it would not be bubbled up correctly in fact.
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
stateCallback.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
Review Comment:
> One could be to set up a mock expectation with the exact status we expect to see reported to the status store
Makes sense, I've made this change here and in the subsequent tests as well. I guess I just put in the lowest impedance change initially since it's additional coverage (but was required to satisfy the new stricter verifications) 😅
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3003,41 +2192,39 @@ public void testKeyRotationWhenWorkerBecomesLeader() throws Exception {
Collections.emptySet(),
Collections.emptySet());
expectConfigRefreshAndSnapshot(snapshotWithKey);
- // Second rebalance: poll indefinitely as worker is follower, so expiration still doesn't come into play
- member.poll(Long.MAX_VALUE);
- EasyMock.expectLastCall();
+
+ configUpdateListener.onSessionKeyUpdate(initialKey);
+ herder.tick();
expectRebalance(2, Collections.emptyList(), Collections.emptyList(), "member", MEMBER_URL, true);
- Capture<SessionKey> updatedKey = EasyMock.newCapture();
- configBackingStore.putSessionKey(EasyMock.capture(updatedKey));
- EasyMock.expectLastCall().andAnswer(() -> {
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ ArgumentCaptor<SessionKey> updatedKey = ArgumentCaptor.forClass(SessionKey.class);
+ doAnswer(invocation -> {
configUpdateListener.onSessionKeyUpdate(updatedKey.getValue());
return null;
- });
- // Third rebalance: poll for a limited time as worker has become leader and must wake up for key expiration
- member.poll(leq(rotationTtlDelay));
- EasyMock.expectLastCall();
+ }).when(configBackingStore).putSessionKey(updatedKey.capture());
- PowerMock.replayAll();
-
- herder.tick();
- configUpdateListener.onSessionKeyUpdate(initialKey);
- herder.tick();
herder.tick();
- PowerMock.verifyAll();
+ // First rebalance: poll indefinitely as no key has been read yet, so expiration doesn't come into play
+ // Second rebalance: poll indefinitely as worker is follower, so expiration still doesn't come into play
+ verify(member, times(2)).poll(eq(Long.MAX_VALUE));
Review Comment:
Good idea, done
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3872,7 +2952,7 @@ public void testVerifyTaskGeneration() {
assertThrows(ConnectException.class, () -> herder.verifyTaskGenerationAndOwnership(unassignedTask, 1, verifyCallback));
assertThrows(ConnectException.class, () -> herder.verifyTaskGenerationAndOwnership(unassignedTask, 2, verifyCallback));
- PowerMock.verifyAll();
+ verify(verifyCallback, times(3)).onCompletion(isNull(), isNull());
Review Comment:
> We expect this to be invoked five times in the original test
There are only 3 calls to `herder::verifyTaskGenerationAndOwnership` in this test that aren't expected to throw an exception.
> Were we never properly verifying the number of invocations in the existing test?
Yep, the `verifyCallback` mock isn't passed into `PowerMock::replayAll` which means that it isn't "known" to `PowerMock` and the `PowerMock::verifyAll` will not make any verifications on it. If this line -
https://github.com/apache/kafka/blob/f23394336a7741bf4eb23fcde951af0a23a69bd0/connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java#L3838
is changed to `PowerMock.replayAll(verifyCallback);` then the test fails and only passes when we change this loop from 5 iterations to 3 -
https://github.com/apache/kafka/blob/f23394336a7741bf4eb23fcde951af0a23a69bd0/connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java#L3833-L3836
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
Review Comment:
Good point, done.
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
stateCallback.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
Review Comment:
> One could be to set up a mock expectation with the exact status we expect to see reported to the status store
Makes sense, I've made this change here and in the subsequent tests as well. I guess I just put in the lowest impedance change initially since it's additional coverage (but was required to satisfy the new stricter verifications) 😅
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
stateCallback.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
-
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
-
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyTasks() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
// The connector has three tasks
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Arrays.asList(TASK0, TASK1, TASK2)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ when(restartPlan.taskIdsToRestart()).thenReturn(Arrays.asList(TASK0, TASK1, TASK2));
+ when(restartPlan.totalTaskCount()).thenReturn(3);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.emptyList());
// But only one task is assigned to this worker
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(TASK0)).anyTimes();
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(TASK0));
herder.configState = SNAPSHOT;
- worker.stopAndAwaitTasks(Collections.singletonList(TASK0));
- PowerMock.expectLastCall();
-
- herder.onRestart(TASK0);
- EasyMock.expectLastCall();
-
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(TASK0));
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksBoth() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(taskId)).anyTimes();
-
- herder.configState = SNAPSHOT;
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
+ when(restartPlan.totalTaskCount()).thenReturn(1);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
-
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(taskId));
+ herder.configState = SNAPSHOT;
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- worker.stopAndAwaitTasks(Collections.singletonList(taskId));
- PowerMock.expectLastCall();
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ stateCallback.getValue().onCompletion(null, TargetState.STARTED);
+ return true;
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
- herder.onRestart(taskId);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(taskId));
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTask() throws Exception {
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, conn1SinkConfig)).andStubReturn(TASK_CONFIGS);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), singletonList(TASK0), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.stopAndAwaitTask(TASK0);
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
+ doNothing().when(worker).stopAndAwaitTask(TASK0);
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
callback.get(1000L, TimeUnit.MILLISECONDS);
- PowerMock.verifyAll();
+ verify(worker, times(2)).startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartUnknownTask() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ herder.tick();
+ doNothing().when(member).ensureActive();
FutureCallback<Void> callback = new FutureCallback<>();
- herder.tick();
herder.restartTask(new ConnectorTaskId("blah", 0), callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotFoundException);
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotFoundException.class, e.getCause().getClass());
- PowerMock.verifyAll();
- }
-
- @Test
- public void testRequestProcessingOrder() {
- final DistributedHerder.DistributedHerderRequest req1 = herder.addRequest(100, null, null);
- final DistributedHerder.DistributedHerderRequest req2 = herder.addRequest(10, null, null);
- final DistributedHerder.DistributedHerderRequest req3 = herder.addRequest(200, null, null);
- final DistributedHerder.DistributedHerderRequest req4 = herder.addRequest(200, null, null);
-
- assertEquals(req2, herder.requests.pollFirst()); // lowest delay
- assertEquals(req1, herder.requests.pollFirst()); // next lowest delay
- assertEquals(req3, herder.requests.pollFirst()); // same delay as req4, but added first
- assertEquals(req4, herder.requests.pollFirst());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTaskRedirectToLeader() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotLeaderException);
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotLeaderException.class, e.getCause().getClass());
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTaskRedirectToOwner() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the task restart
String ownerUrl = "ownerUrl";
- EasyMock.expect(member.ownerUrl(TASK0)).andReturn(ownerUrl);
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ when(member.ownerUrl(TASK0)).thenReturn(ownerUrl);
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotAssignedException);
- NotAssignedException notAssignedException = (NotAssignedException) e.getCause();
- assertEquals(ownerUrl, notAssignedException.forwardUrl());
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotAssignedException.class, e.getCause().getClass());
+ assertEquals(ownerUrl, ((NotAssignedException) e.getCause()).forwardUrl());
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
- public void testConnectorConfigAdded() {
+ public void testRequestProcessingOrder() {
+ final DistributedHerder.DistributedHerderRequest req1 = herder.addRequest(100, null, null);
+ final DistributedHerder.DistributedHerderRequest req2 = herder.addRequest(10, null, null);
+ final DistributedHerder.DistributedHerderRequest req3 = herder.addRequest(200, null, null);
+ final DistributedHerder.DistributedHerderRequest req4 = herder.addRequest(200, null, null);
+
+ assertEquals(req2, herder.requests.pollFirst()); // lowest delay
+ assertEquals(req1, herder.requests.pollFirst()); // next lowest delay
+ assertEquals(req3, herder.requests.pollFirst()); // same delay as req4, but added first
+ assertEquals(req4, herder.requests.pollFirst());
+ }
+
+ @Test
+ public void testConnectorConfigAdded() throws Exception {
// If a connector was added, we need to rebalance
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join, no configs so no need to catch up on config topic
expectRebalance(-1, Collections.emptyList(), Collections.emptyList());
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick(); // join
- // apply config
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
// Checks for config updates and starts rebalance
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
- member.requestRejoin();
- PowerMock.expectLastCall();
+ doNothing().when(configBackingStore).refresh(anyLong(), any());
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
Review Comment:
Whoops 🤦
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3929,543 +3008,636 @@ public void testPollDurationOnSlowConnectorOperations() {
final int rebalanceDelayMs = 20000;
final long operationDelayMs = 10000;
final long maxPollWaitMs = rebalanceDelayMs - operationDelayMs;
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(connectProtocolVersion);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(connectProtocolVersion);
// Assign the connector to this worker, and have it start
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- Capture<Callback<TargetState>> onFirstStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onFirstStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> onFirstStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
time.sleep(operationDelayMs);
onFirstStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- // We should poll for less than the delay - time to start the connector, meaning that a long connector start
- // does not delay the poll timeout
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), eq(CONN1_CONFIG), any(), eq(herder), eq(TargetState.STARTED), onFirstStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
+
+ herder.tick();
// Rebalance again due to config update
- member.wakeup();
- PowerMock.expectLastCall();
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT_UPDATED_CONN1_CONFIG);
-
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(connectProtocolVersion);
- Capture<Callback<TargetState>> onSecondStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onSecondStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT_UPDATED_CONN1_CONFIG);
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
+
+ ArgumentCaptor<Callback<TargetState>> onSecondStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
time.sleep(operationDelayMs);
onSecondStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), eq(CONN1_CONFIG_UPDATED), any(), eq(herder), eq(TargetState.STARTED), onSecondStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, invocation -> TASK_CONFIGS);
+
+ configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
+ herder.tick();
// Third tick should resolve all outstanding requests
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
// which includes querying the connector task configs after the update
- expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, () -> {
+ expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, invocation -> {
time.sleep(operationDelayMs);
return TASK_CONFIGS;
});
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
- herder.tick();
- configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
- herder.tick();
herder.tick();
- PowerMock.verifyAll();
+
+ // We should poll for less than the delay - time to start the connector, meaning that a long connector start
+ // does not delay the poll timeout
+ verify(member, times(3)).poll(leq(maxPollWaitMs));
+ verify(worker, times(2)).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), any());
+ verifyNoMoreInteractions(member, worker, configBackingStore);
}
@Test
public void shouldThrowWhenStartAndStopExecutorThrowsRejectedExecutionExceptionAndHerderNotStopping() {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Arrays.asList(CONN1), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
-
- PowerMock.replayAll();
herder.startAndStopExecutor.shutdown();
assertThrows(RejectedExecutionException.class, herder::tick);
+ }
+
+ @Test
+ public void testTaskReconfigurationRetriesWithConnectorTaskConfigsException() {
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
+
+ when(worker.isRunning(CONN1)).thenReturn(true);
+ when(worker.getPlugins()).thenReturn(plugins);
+
+ SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+
+ when(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig))
+ .thenThrow(new ConnectException("Failed to generate task configs"))
+ .thenThrow(new ConnectException("Failed to generate task configs"))
+ .thenReturn(TASK_CONFIGS);
+
+ expectAndVerifyTaskReconfigurationRetries();
+ }
+
+ @Test
+ public void testTaskReconfigurationRetriesWithLeaderRequestForwardingException() {
+ herder = mock(DistributedHerder.class, withSettings().defaultAnswer(CALLS_REAL_METHODS).useConstructor(new DistributedConfig(HERDER_CONFIG),
+ worker, WORKER_ID, KAFKA_CLUSTER_ID, statusBackingStore, configBackingStore, member, MEMBER_URL, restClient, metrics, time,
+ noneConnectorClientConfigOverridePolicy, Collections.emptyList(), new MockSynchronousExecutor(), new AutoCloseable[]{}));
+
+ rebalanceListener = herder.new RebalanceListener(time);
+
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList(), false);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
+
+ when(worker.isRunning(CONN1)).thenReturn(true);
+ when(worker.getPlugins()).thenReturn(plugins);
+
+ SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+
+ List<Map<String, String>> changedTaskConfigs = new ArrayList<>(TASK_CONFIGS);
+ changedTaskConfigs.add(TASK_CONFIG);
+ when(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig)).thenReturn(changedTaskConfigs);
+
+ when(restClient.httpRequest(any(), eq("POST"), any(), any(), any(), any(), any()))
+ .thenThrow(new ConnectException("Request to leader to reconfigure connector tasks failed"))
+ .thenThrow(new ConnectException("Request to leader to reconfigure connector tasks failed"))
+ .thenReturn(null);
+
+ expectAndVerifyTaskReconfigurationRetries();
+ }
+
+ private void expectAndVerifyTaskReconfigurationRetries() {
+ // initial tick
+ herder.tick();
+ herder.requestTaskReconfiguration(CONN1);
+ // process the task reconfiguration request in this tick
+ herder.tick();
+ // advance the time by 250ms so that the task reconfiguration request with initial retry backoff is processed
+ time.sleep(250);
+ herder.tick();
+ // advance the time by 500ms so that the task reconfiguration request with double the initial retry backoff is processed
+ time.sleep(500);
+ herder.tick();
+
+ // 1. end of initial tick when no request has been added to the herder queue yet
+ // 2. the third task reconfiguration request is expected to pass; so expect no more retries (a Long.MAX_VALUE poll
+ // timeout indicates that there is no herder request currently in the queue)
+ verify(member, times(2)).poll(eq(Long.MAX_VALUE));
+
+ // task reconfiguration herder request with initial retry backoff
+ verify(member).poll(eq(250L));
+
+ // task reconfiguration herder request with double the initial retry backoff
+ verify(member).poll(eq(500L));
+
+ verifyNoMoreInteractions(member, worker);
+ }
+
+ @Test
+ public void processRestartRequestsFailureSuppression() {
+ doNothing().when(member).wakeup();
+
+ final String connectorName = "foo";
+ RestartRequest restartRequest = new RestartRequest(connectorName, false, false);
+ doThrow(new RuntimeException()).when(herder).buildRestartPlan(restartRequest);
+
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(1, herder.pendingRestartRequests.size());
+ herder.processRestartRequests();
+ assertTrue(herder.pendingRestartRequests.isEmpty());
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
+ }
+
+ @Test
+ public void processRestartRequestsDequeue() {
+ doNothing().when(member).wakeup();
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(any(RestartRequest.class));
+
+ RestartRequest restartRequest = new RestartRequest("foo", false, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+ restartRequest = new RestartRequest("bar", false, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(2, herder.pendingRestartRequests.size());
+ herder.processRestartRequests();
+ assertTrue(herder.pendingRestartRequests.isEmpty());
+ }
+
+ @Test
+ public void preserveHighestImpactRestartRequest() {
+ doNothing().when(member).wakeup();
+
+ final String connectorName = "foo";
+ RestartRequest restartRequest = new RestartRequest(connectorName, false, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+
+ // Will overwrite as this is higher impact
+ restartRequest = new RestartRequest(connectorName, false, true);
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(1, herder.pendingRestartRequests.size());
+ assertFalse(herder.pendingRestartRequests.get(connectorName).onlyFailed());
+ assertTrue(herder.pendingRestartRequests.get(connectorName).includeTasks());
+
+ // Will be ignored as the existing request has higher impact
+ restartRequest = new RestartRequest(connectorName, true, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(1, herder.pendingRestartRequests.size());
+ // Compare against existing request
+ assertFalse(herder.pendingRestartRequests.get(connectorName).onlyFailed());
+ assertTrue(herder.pendingRestartRequests.get(connectorName).includeTasks());
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidation() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(ExactlyOnceSupport.SUPPORTED);
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
+
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertEquals(Collections.emptyList(), errors);
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidationOnUnsupportedConnector() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(ExactlyOnceSupport.UNSUPPORTED);
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
+
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertEquals(
+ Collections.singletonList("The connector does not support exactly-once semantics with the provided configuration."),
+ errors);
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidationOnUnknownConnector() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(null);
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
+
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("The connector does not implement the API required for preflight validation of exactly-once source support."));
+ assertEquals(1, errors.size());
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidationHandlesConnectorErrorsGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ String errorMessage = "time to add a new unit test :)";
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenThrow(new NullPointerException(errorMessage));
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- PowerMock.verifyAll();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains(errorMessage));
+ assertEquals(1, errors.size());
}
@Test
- public void testTaskReconfigurationRetriesWithConnectorTaskConfigsException() {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
- expectConfigRefreshAndSnapshot(SNAPSHOT);
+ public void testExactlyOnceSourceSupportValidationWhenExactlyOnceNotEnabledOnWorker() {
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
- // end of initial tick
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(ExactlyOnceSupport.SUPPORTED);
- member.wakeup();
- PowerMock.expectLastCall().anyTimes();
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- member.ensureActive();
- PowerMock.expectLastCall().anyTimes();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertEquals(
+ Collections.singletonList("This worker does not have exactly-once source support enabled."),
+ errors);
+ }
- EasyMock.expect(worker.isRunning(CONN1)).andReturn(true).anyTimes();
- EasyMock.expect(worker.getPlugins()).andReturn(plugins).anyTimes();
+ @Test
+ public void testExactlyOnceSourceSupportValidationHandlesInvalidValuesGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, "invalid");
- SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig))
- .andThrow(new ConnectException("Failed to generate task configs")).times(2);
+ SourceConnector connectorMock = mock(SourceConnector.class);
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig)).andReturn(TASK_CONFIGS);
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- expectAndVerifyTaskReconfigurationRetries();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("String must be one of (case insensitive): "));
+ assertEquals(1, errors.size());
}
@Test
- public void testTaskReconfigurationRetriesWithLeaderRequestForwardingException() {
- herder = PowerMock.createPartialMock(DistributedHerder.class,
- new String[]{"connectorType", "updateDeletedConnectorStatus", "updateDeletedTaskStatus", "validateConnectorConfig", "buildRestartPlan", "recordRestarting"},
- new DistributedConfig(HERDER_CONFIG), worker, WORKER_ID, KAFKA_CLUSTER_ID,
- statusBackingStore, configBackingStore, member, MEMBER_URL, restClient, metrics, time, noneConnectorClientConfigOverridePolicy,
- Collections.emptyList(), new MockSynchronousExecutor(), new AutoCloseable[]{});
+ public void testConnectorTransactionBoundaryValidation() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, CONNECTOR.toString());
- rebalanceListener = herder.new RebalanceListener(time);
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.canDefineTransactionBoundaries(eq(config)))
+ .thenReturn(ConnectorTransactionBoundaries.SUPPORTED);
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- expectRebalance(1, Collections.emptyList(), Collections.emptyList(), false);
- expectConfigRefreshAndSnapshot(SNAPSHOT);
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- // end of initial tick
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertEquals(Collections.emptyList(), errors);
+ }
- member.wakeup();
- PowerMock.expectLastCall().anyTimes();
+ @Test
+ public void testConnectorTransactionBoundaryValidationOnUnsupportedConnector() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, CONNECTOR.toString());
- member.ensureActive();
- PowerMock.expectLastCall().anyTimes();
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.canDefineTransactionBoundaries(eq(config)))
+ .thenReturn(ConnectorTransactionBoundaries.UNSUPPORTED);
- EasyMock.expect(worker.isRunning(CONN1)).andReturn(true).anyTimes();
- EasyMock.expect(worker.getPlugins()).andReturn(plugins).anyTimes();
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("The connector does not support connector-defined transaction boundaries with the given configuration."));
+ assertEquals(1, errors.size());
+ }
- List<Map<String, String>> changedTaskConfigs = new ArrayList<>(TASK_CONFIGS);
- changedTaskConfigs.add(TASK_CONFIG);
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig)).andReturn(changedTaskConfigs).anyTimes();
+ @Test
+ public void testConnectorTransactionBoundaryValidationHandlesConnectorErrorsGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, CONNECTOR.toString());
- EasyMock.expect(restClient.httpRequest(
- EasyMock.anyString(), EasyMock.eq("POST"), EasyMock.anyObject(HttpHeaders.class),
- EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(SecretKey.class), EasyMock.anyString())
- ).andThrow(new ConnectException("Request to leader to reconfigure connector tasks failed")).times(2);
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ String errorMessage = "Wait I thought we tested for this?";
+ when(connectorMock.canDefineTransactionBoundaries(eq(config))).thenThrow(new ConnectException(errorMessage));
- EasyMock.expect(restClient.httpRequest(
- EasyMock.anyString(), EasyMock.eq("POST"), EasyMock.anyObject(HttpHeaders.class),
- EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(SecretKey.class), EasyMock.anyString())
- ).andReturn(null);
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- expectAndVerifyTaskReconfigurationRetries();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains(errorMessage));
+ assertEquals(1, errors.size());
}
- private void expectAndVerifyTaskReconfigurationRetries() {
- // task reconfiguration herder request with initial retry backoff
- member.poll(EasyMock.eq(250L));
- PowerMock.expectLastCall();
-
- // task reconfiguration herder request with double the initial retry backoff
- member.poll(EasyMock.eq(500L));
- PowerMock.expectLastCall();
-
- // the third task reconfiguration request is expected to pass; so expect no more retries (a Long.MAX_VALUE poll
- // timeout indicates that there is no herder request currently in the queue)
- member.poll(EasyMock.eq(Long.MAX_VALUE));
- PowerMock.expectLastCall();
+ @Test
+ public void testConnectorTransactionBoundaryValidationHandlesInvalidValuesGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, "CONNECTOR.toString()");
- PowerMock.replayAll();
+ SourceConnector connectorMock = mock(SourceConnector.class);
- // initial tick
- herder.tick();
- herder.requestTaskReconfiguration(CONN1);
- // process the task reconfiguration request in this tick
- herder.tick();
- // advance the time by 250ms so that the task reconfiguration request with initial retry backoff is processed
- time.sleep(250);
- herder.tick();
- // advance the time by 500ms so that the task reconfiguration request with double the initial retry backoff is processed
- time.sleep(500);
- herder.tick();
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- PowerMock.verifyAll();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("String must be one of (case insensitive): "));
+ assertEquals(1, errors.size());
}
@Test
public void testConnectorOffsets() throws Exception {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- expectAnyTicks();
- member.wakeup();
- PowerMock.expectLastCall();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
+ herder.tick();
+
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
ConnectorOffsets offsets = new ConnectorOffsets(Collections.singletonList(new ConnectorOffset(
Collections.singletonMap("partitionKey", "partitionValue"),
Collections.singletonMap("offsetKey", "offsetValue"))));
- Capture<Callback<ConnectorOffsets>> callbackCapture = newCapture();
- worker.connectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), capture(callbackCapture));
- PowerMock.expectLastCall().andAnswer(() -> {
+
+ ArgumentCaptor<Callback<ConnectorOffsets>> callbackCapture = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
callbackCapture.getValue().onCompletion(null, offsets);
return null;
- });
+ }).when(worker).connectorOffsets(eq(CONN1), eq(CONN1_CONFIG), callbackCapture.capture());
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<ConnectorOffsets> cb = new FutureCallback<>();
herder.connectorOffsets(CONN1, cb);
herder.tick();
assertEquals(offsets, cb.get(1000, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testModifyConnectorOffsetsUnknownConnector() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // Now handle the connector offsets modification request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
+
+ // Now handle the connector offsets modification request
FutureCallback<Message> callback = new FutureCallback<>();
herder.modifyConnectorOffsets("connector-does-not-exist", new HashMap<>(), callback);
herder.tick();
ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(e.getCause() instanceof NotFoundException);
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsConnectorNotInStoppedState() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // Now handle the connector offsets modification request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
+
+ // Now handle the connector offsets modification request
FutureCallback<Message> callback = new FutureCallback<>();
herder.modifyConnectorOffsets(CONN1, null, callback);
herder.tick();
ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(e.getCause() instanceof BadRequestException);
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsNotLeader() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), false);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // Now handle the connector offsets modification request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
+
+ // Now handle the connector offsets modification request
FutureCallback<Message> callback = new FutureCallback<>();
herder.modifyConnectorOffsets(CONN1, new HashMap<>(), callback);
herder.tick();
ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(e.getCause() instanceof NotLeaderException);
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsSinkConnector() throws Exception {
- EasyMock.reset(herder);
- EasyMock.expect(herder.connectorType(EasyMock.anyObject())).andReturn(ConnectorType.SINK).anyTimes();
- PowerMock.expectPrivate(herder, "updateDeletedConnectorStatus").andVoid().anyTimes();
- PowerMock.expectPrivate(herder, "updateDeletedTaskStatus").andVoid().anyTimes();
-
+ when(herder.connectorType(any())).thenReturn(ConnectorType.SINK);
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ herder.tick();
// Now handle the alter connector offsets request
Map<Map<String, ?>, Map<String, ?>> offsets = Collections.singletonMap(
Collections.singletonMap("partitionKey", "partitionValue"),
Collections.singletonMap("offsetKey", "offsetValue"));
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- Capture<Callback<Message>> workerCallbackCapture = Capture.newInstance();
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), EasyMock.eq(offsets), capture(workerCallbackCapture));
+
+ ArgumentCaptor<Callback<Message>> workerCallbackCapture = ArgumentCaptor.forClass(Callback.class);
Message msg = new Message("The offsets for this connector have been altered successfully");
- EasyMock.expectLastCall().andAnswer(() -> {
+ doAnswer(invocation -> {
workerCallbackCapture.getValue().onCompletion(null, msg);
return null;
- });
+ }).when(worker).modifyConnectorOffsets(eq(CONN1), eq(CONN1_CONFIG), eq(offsets), workerCallbackCapture.capture());
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Message> callback = new FutureCallback<>();
herder.alterConnectorOffsets(CONN1, offsets, callback);
herder.tick();
assertEquals(msg, callback.get(1000L, TimeUnit.MILLISECONDS));
assertEquals("The offsets for this connector have been altered successfully", msg.message());
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsSourceConnectorExactlyOnceDisabled() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
// Now handle the reset connector offsets request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- Capture<Callback<Message>> workerCallbackCapture = Capture.newInstance();
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), isNull(), capture(workerCallbackCapture));
+ ArgumentCaptor<Callback<Message>> workerCallbackCapture = ArgumentCaptor.forClass(Callback.class);
Message msg = new Message("The offsets for this connector have been reset successfully");
- EasyMock.expectLastCall().andAnswer(() -> {
+ doAnswer(invocation -> {
workerCallbackCapture.getValue().onCompletion(null, msg);
return null;
- });
+ }).when(worker).modifyConnectorOffsets(eq(CONN1), eq(CONN1_CONFIG), isNull(), workerCallbackCapture.capture());
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Message> callback = new FutureCallback<>();
herder.resetConnectorOffsets(CONN1, callback);
herder.tick();
assertEquals(msg, callback.get(1000L, TimeUnit.MILLISECONDS));
assertEquals("The offsets for this connector have been reset successfully", msg.message());
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsSourceConnectorExactlyOnceEnabled() throws Exception {
// Setup herder with exactly-once support for source connectors enabled
herder = exactlyOnceHerder();
rebalanceListener = herder.new RebalanceListener(time);
- PowerMock.expectPrivate(herder, "updateDeletedConnectorStatus").andVoid().anyTimes();
- PowerMock.expectPrivate(herder, "updateDeletedTaskStatus").andVoid().anyTimes();
-
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall().anyTimes();
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// Now handle the alter connector offsets request
- Map<Map<String, ?>, Map<String, ?>> offsets = Collections.singletonMap(
- Collections.singletonMap("partitionKey", "partitionValue"),
- Collections.singletonMap("offsetKey", "offsetValue"));
- member.wakeup();
- PowerMock.expectLastCall().anyTimes();
- member.ensureActive();
- PowerMock.expectLastCall().anyTimes();
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- EasyMock.expect(herder.connectorType(EasyMock.anyObject())).andReturn(ConnectorType.SOURCE).anyTimes();
+ doNothing().when(member).ensureActive();
+ when(herder.connectorType(any())).thenReturn(ConnectorType.SOURCE);
// Expect a round of zombie fencing to occur
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- KafkaFuture<Void> workerFencingFuture = EasyMock.mock(KafkaFuture.class);
- KafkaFuture<Void> herderFencingFuture = EasyMock.mock(KafkaFuture.class);
- EasyMock.expect(worker.fenceZombies(CONN1, SNAPSHOT_STOPPED_CONN1.taskCountRecord(CONN1), CONN1_CONFIG)).andReturn(workerFencingFuture);
- EasyMock.expect(workerFencingFuture.thenApply(EasyMock.<KafkaFuture.BaseFunction<Void, Void>>anyObject())).andReturn(herderFencingFuture);
-
- // Two fencing callbacks are added - one is in ZombieFencing::start itself to remove the connector from the active
- // fencing list. The other is the callback passed from DistributedHerder::modifyConnectorOffsets in order to
- // queue up the actual alter offsets request if the zombie fencing succeeds.
- for (int i = 0; i < 2; i++) {
- Capture<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = EasyMock.newCapture();
- EasyMock.expect(herderFencingFuture.whenComplete(EasyMock.capture(herderFencingCallback))).andAnswer(() -> {
- herderFencingCallback.getValue().accept(null, null);
- return null;
- });
- }
-
- Capture<Callback<Message>> workerCallbackCapture = Capture.newInstance();
- Message msg = new Message("The offsets for this connector have been altered successfully");
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), EasyMock.eq(offsets), capture(workerCallbackCapture));
- EasyMock.expectLastCall().andAnswer(() -> {
- workerCallbackCapture.getValue().onCompletion(null, msg);
+ KafkaFuture<Void> workerFencingFuture = mock(KafkaFuture.class);
+ KafkaFuture<Void> herderFencingFuture = mock(KafkaFuture.class);
+ when(worker.fenceZombies(CONN1, SNAPSHOT_STOPPED_CONN1.taskCountRecord(CONN1), CONN1_CONFIG)).thenReturn(workerFencingFuture);
+ when(workerFencingFuture.thenApply(any(KafkaFuture.BaseFunction.class))).thenReturn(herderFencingFuture);
+
+ ArgumentCaptor<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = ArgumentCaptor.forClass(KafkaFuture.BiConsumer.class);
+ when(herderFencingFuture.whenComplete(herderFencingCallback.capture())).thenAnswer(invocation -> {
+ herderFencingCallback.getValue().accept(null, null);
return null;
});
- // Handle the second alter connector offsets request. No zombie fencing request to the worker is expected now since we
- // already did a round of zombie fencing last time and no new tasks came up in the meanwhile. The config snapshot is
- // refreshed once at the beginning of the DistributedHerder::modifyConnectorOffsets method, once before checking
- // whether zombie fencing is required, and once before actually proceeding to alter connector offsets.
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1_FENCED);
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1_FENCED);
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1_FENCED);
- Capture<Callback<Message>> workerCallbackCapture2 = Capture.newInstance();
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), EasyMock.eq(offsets), capture(workerCallbackCapture2));
- EasyMock.expectLastCall().andAnswer(() -> {
- workerCallbackCapture2.getValue().onCompletion(null, msg);
- return null;
- });
+ ArgumentCaptor<Callback<Message>> workerCallbackCapture = ArgumentCaptor.forClass(Callback.class);
+ Message msg = new Message("The offsets for this connector have been altered successfully");
- PowerMock.replayAll(workerFencingFuture, herderFencingFuture);
+ Map<Map<String, ?>, Map<String, ?>> offsets = Collections.singletonMap(
+ Collections.singletonMap("partitionKey", "partitionValue"),
+ Collections.singletonMap("offsetKey", "offsetValue"));
+ doAnswer(invocation -> {
+ workerCallbackCapture.getValue().onCompletion(null, msg);
+ return null;
+ }).when(worker).modifyConnectorOffsets(eq(CONN1), eq(CONN1_CONFIG), eq(offsets), workerCallbackCapture.capture());
- herder.tick();
FutureCallback<Message> callback = new FutureCallback<>();
herder.alterConnectorOffsets(CONN1, offsets, callback);
- // Process the zombie fencing request
- herder.tick();
- // Process the alter offsets request
+ // Process the zombie fencing request that is queued up first followed by the actual alter offsets request
Review Comment:
> Ah... is it because we're adding the followup request to alter the connector offsets to the herder's request queue while it's in the middle of handling the initial request to fence zombie source tasks, and so the herder immediately handles the follow-up request once it's done with the first one?
Yep, exactly. This bit will basically run synchronously -
https://github.com/apache/kafka/blob/f23394336a7741bf4eb23fcde951af0a23a69bd0/connect/runtime/src/main/java/org/apache/kafka/connect/runtime/distributed/DistributedHerder.java#L1536-L1547
which will result in the modify connector offsets request being run in the same request processing loop as the zombie fencing request here -
https://github.com/apache/kafka/blob/f23394336a7741bf4eb23fcde951af0a23a69bd0/connect/runtime/src/main/java/org/apache/kafka/connect/runtime/distributed/DistributedHerder.java#L457-L469
In fact the original test also passes without the second tick call.
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
stateCallback.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
-
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
-
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyTasks() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
// The connector has three tasks
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Arrays.asList(TASK0, TASK1, TASK2)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ when(restartPlan.taskIdsToRestart()).thenReturn(Arrays.asList(TASK0, TASK1, TASK2));
+ when(restartPlan.totalTaskCount()).thenReturn(3);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.emptyList());
// But only one task is assigned to this worker
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(TASK0)).anyTimes();
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(TASK0));
herder.configState = SNAPSHOT;
- worker.stopAndAwaitTasks(Collections.singletonList(TASK0));
- PowerMock.expectLastCall();
-
- herder.onRestart(TASK0);
- EasyMock.expectLastCall();
-
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(TASK0));
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksBoth() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(taskId)).anyTimes();
-
- herder.configState = SNAPSHOT;
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
+ when(restartPlan.totalTaskCount()).thenReturn(1);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
-
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(taskId));
+ herder.configState = SNAPSHOT;
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- worker.stopAndAwaitTasks(Collections.singletonList(taskId));
- PowerMock.expectLastCall();
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ stateCallback.getValue().onCompletion(null, TargetState.STARTED);
+ return true;
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
- herder.onRestart(taskId);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(taskId));
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTask() throws Exception {
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, conn1SinkConfig)).andStubReturn(TASK_CONFIGS);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), singletonList(TASK0), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.stopAndAwaitTask(TASK0);
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
+ doNothing().when(worker).stopAndAwaitTask(TASK0);
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
callback.get(1000L, TimeUnit.MILLISECONDS);
- PowerMock.verifyAll();
+ verify(worker, times(2)).startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartUnknownTask() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ herder.tick();
+ doNothing().when(member).ensureActive();
FutureCallback<Void> callback = new FutureCallback<>();
- herder.tick();
herder.restartTask(new ConnectorTaskId("blah", 0), callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotFoundException);
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotFoundException.class, e.getCause().getClass());
- PowerMock.verifyAll();
- }
-
- @Test
- public void testRequestProcessingOrder() {
- final DistributedHerder.DistributedHerderRequest req1 = herder.addRequest(100, null, null);
- final DistributedHerder.DistributedHerderRequest req2 = herder.addRequest(10, null, null);
- final DistributedHerder.DistributedHerderRequest req3 = herder.addRequest(200, null, null);
- final DistributedHerder.DistributedHerderRequest req4 = herder.addRequest(200, null, null);
-
- assertEquals(req2, herder.requests.pollFirst()); // lowest delay
- assertEquals(req1, herder.requests.pollFirst()); // next lowest delay
- assertEquals(req3, herder.requests.pollFirst()); // same delay as req4, but added first
- assertEquals(req4, herder.requests.pollFirst());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTaskRedirectToLeader() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotLeaderException);
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotLeaderException.class, e.getCause().getClass());
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTaskRedirectToOwner() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the task restart
String ownerUrl = "ownerUrl";
- EasyMock.expect(member.ownerUrl(TASK0)).andReturn(ownerUrl);
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ when(member.ownerUrl(TASK0)).thenReturn(ownerUrl);
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotAssignedException);
- NotAssignedException notAssignedException = (NotAssignedException) e.getCause();
- assertEquals(ownerUrl, notAssignedException.forwardUrl());
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotAssignedException.class, e.getCause().getClass());
+ assertEquals(ownerUrl, ((NotAssignedException) e.getCause()).forwardUrl());
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
- public void testConnectorConfigAdded() {
+ public void testRequestProcessingOrder() {
+ final DistributedHerder.DistributedHerderRequest req1 = herder.addRequest(100, null, null);
+ final DistributedHerder.DistributedHerderRequest req2 = herder.addRequest(10, null, null);
+ final DistributedHerder.DistributedHerderRequest req3 = herder.addRequest(200, null, null);
+ final DistributedHerder.DistributedHerderRequest req4 = herder.addRequest(200, null, null);
+
+ assertEquals(req2, herder.requests.pollFirst()); // lowest delay
+ assertEquals(req1, herder.requests.pollFirst()); // next lowest delay
+ assertEquals(req3, herder.requests.pollFirst()); // same delay as req4, but added first
+ assertEquals(req4, herder.requests.pollFirst());
+ }
+
+ @Test
+ public void testConnectorConfigAdded() throws Exception {
// If a connector was added, we need to rebalance
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join, no configs so no need to catch up on config topic
expectRebalance(-1, Collections.emptyList(), Collections.emptyList());
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick(); // join
- // apply config
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
// Checks for config updates and starts rebalance
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
- member.requestRejoin();
- PowerMock.expectLastCall();
+ doNothing().when(configBackingStore).refresh(anyLong(), any());
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
// Performs rebalance and gets new assignment
expectRebalance(Collections.emptyList(), Collections.emptyList(),
ConnectProtocol.Assignment.NO_ERROR, 1, Arrays.asList(CONN1), Collections.emptyList());
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- Capture<Callback<TargetState>> onStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
onStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
- PowerMock.replayAll();
+ herder.tick(); // apply config
- herder.tick(); // join
+ doNothing().when(member).ensureActive();
configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
Review Comment:
Yikes, the semantics of the test were changed completely here, good catch! Your understanding of the original test looks correct to me and I've updated the test to match the original semantics.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [kafka] yashmayya commented on a diff in pull request #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
Posted by "yashmayya (via GitHub)" <gi...@apache.org>.
yashmayya commented on code in PR #14102:
URL: https://github.com/apache/kafka/pull/14102#discussion_r1288239604
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3724,62 +2818,54 @@ public void testExternalZombieFencingRequestDelayedCompletion() throws Exception
pendingFencing,
tasksPerConnector
);
- tasksPerConnector.keySet().forEach(c -> expectConfigRefreshAndSnapshot(configState));
+ expectConfigRefreshAndSnapshot(configState);
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
// The callbacks that the herder has accrued for outstanding fencing futures, which will be completed after
// a successful round of fencing and a task record write to the config topic
- Map<String, Capture<KafkaFuture.BiConsumer<Void, Throwable>>> herderFencingCallbacks = new HashMap<>();
+ Map<String, ArgumentCaptor<KafkaFuture.BiConsumer<Void, Throwable>>> herderFencingCallbacks = new HashMap<>();
// The callbacks that the herder has installed for after a successful round of zombie fencing, but before writing
// a task record to the config topic
- Map<String, Capture<KafkaFuture.BaseFunction<Void, Void>>> workerFencingFollowups = new HashMap<>();
+ Map<String, ArgumentCaptor<KafkaFuture.BaseFunction<Void, Void>>> workerFencingFollowups = new HashMap<>();
Map<String, CountDownLatch> callbacksInstalled = new HashMap<>();
tasksPerConnector.forEach((connector, numStackedRequests) -> {
// The future returned by Worker::fenceZombies
- KafkaFuture<Void> workerFencingFuture = EasyMock.mock(KafkaFuture.class);
- // The future tracked by the herder (which tracks the fencing performed by the worker and the possible followup write to the config topic)
- KafkaFuture<Void> herderFencingFuture = EasyMock.mock(KafkaFuture.class);
+ KafkaFuture<Void> workerFencingFuture = mock(KafkaFuture.class);
+ // The future tracked by the herder (which tracks the fencing performed by the worker and the possible followup write to the config topic)
+ KafkaFuture<Void> herderFencingFuture = mock(KafkaFuture.class);
- Capture<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = EasyMock.newCapture(CaptureType.ALL);
+ ArgumentCaptor<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = ArgumentCaptor.forClass(KafkaFuture.BiConsumer.class);
herderFencingCallbacks.put(connector, herderFencingCallback);
// Don't immediately invoke callbacks that the herder sets up for when the worker fencing and writes to the config topic have completed
// Instead, wait for them to be installed, then invoke them explicitly after the fact on a thread separate from the herder's tick thread
- EasyMock.expect(herderFencingFuture.whenComplete(EasyMock.capture(herderFencingCallback)))
- .andReturn(null)
- .times(numStackedRequests + 1);
+ when(herderFencingFuture.whenComplete(herderFencingCallback.capture())).thenReturn(null);
- Capture<KafkaFuture.BaseFunction<Void, Void>> fencingFollowup = EasyMock.newCapture();
+ ArgumentCaptor<KafkaFuture.BaseFunction<Void, Void>> fencingFollowup = ArgumentCaptor.forClass(KafkaFuture.BaseFunction.class);
CountDownLatch callbackInstalled = new CountDownLatch(1);
workerFencingFollowups.put(connector, fencingFollowup);
callbacksInstalled.put(connector, callbackInstalled);
- EasyMock.expect(workerFencingFuture.thenApply(EasyMock.capture(fencingFollowup))).andAnswer(() -> {
+ when(workerFencingFuture.thenApply(fencingFollowup.capture())).thenAnswer(invocation -> {
callbackInstalled.countDown();
return herderFencingFuture;
});
// We should only perform a single physical zombie fencing; all the subsequent requests should be stacked onto the first one
- EasyMock.expect(worker.fenceZombies(
- EasyMock.eq(connector), EasyMock.eq(taskCountRecords.get(connector)), EasyMock.anyObject())
- ).andReturn(workerFencingFuture);
-
- for (int i = 0; i < numStackedRequests; i++) {
- expectConfigRefreshAndSnapshot(configState);
- }
-
- PowerMock.replay(workerFencingFuture, herderFencingFuture);
+ when(worker.fenceZombies(eq(connector), eq(taskCountRecords.get(connector)), any()))
+ .thenReturn(workerFencingFuture)
+ .thenAnswer(invocation -> {
+ fail("Expected only a single zombie fencing per connector");
Review Comment:
Oh, that's a good point, it would not be bubbled up correctly in fact.
Edit: I guess it'd actually depend on where the extra calls are being made from but either way the explicit verification is better.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [kafka] C0urante commented on a diff in pull request #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
Posted by "C0urante (via GitHub)" <gi...@apache.org>.
C0urante commented on code in PR #14102:
URL: https://github.com/apache/kafka/pull/14102#discussion_r1290659476
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3929,543 +3008,636 @@ public void testPollDurationOnSlowConnectorOperations() {
final int rebalanceDelayMs = 20000;
final long operationDelayMs = 10000;
final long maxPollWaitMs = rebalanceDelayMs - operationDelayMs;
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(connectProtocolVersion);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(connectProtocolVersion);
// Assign the connector to this worker, and have it start
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- Capture<Callback<TargetState>> onFirstStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onFirstStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> onFirstStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
time.sleep(operationDelayMs);
onFirstStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- // We should poll for less than the delay - time to start the connector, meaning that a long connector start
- // does not delay the poll timeout
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), eq(CONN1_CONFIG), any(), eq(herder), eq(TargetState.STARTED), onFirstStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
+
+ herder.tick();
// Rebalance again due to config update
- member.wakeup();
- PowerMock.expectLastCall();
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT_UPDATED_CONN1_CONFIG);
-
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(connectProtocolVersion);
- Capture<Callback<TargetState>> onSecondStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onSecondStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT_UPDATED_CONN1_CONFIG);
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
+
+ ArgumentCaptor<Callback<TargetState>> onSecondStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
time.sleep(operationDelayMs);
onSecondStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), eq(CONN1_CONFIG_UPDATED), any(), eq(herder), eq(TargetState.STARTED), onSecondStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, invocation -> TASK_CONFIGS);
+
+ configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
+ herder.tick();
// Third tick should resolve all outstanding requests
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
// which includes querying the connector task configs after the update
- expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, () -> {
+ expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, invocation -> {
time.sleep(operationDelayMs);
return TASK_CONFIGS;
});
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
- herder.tick();
- configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
- herder.tick();
herder.tick();
- PowerMock.verifyAll();
+
+ // We should poll for less than the delay - time to start the connector, meaning that a long connector start
+ // does not delay the poll timeout
+ verify(member, times(3)).poll(leq(maxPollWaitMs));
+ verify(worker, times(2)).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), any());
+ verifyNoMoreInteractions(member, worker, configBackingStore);
}
@Test
public void shouldThrowWhenStartAndStopExecutorThrowsRejectedExecutionExceptionAndHerderNotStopping() {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Arrays.asList(CONN1), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
-
- PowerMock.replayAll();
herder.startAndStopExecutor.shutdown();
assertThrows(RejectedExecutionException.class, herder::tick);
+ }
+
+ @Test
+ public void testTaskReconfigurationRetriesWithConnectorTaskConfigsException() {
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
+
+ when(worker.isRunning(CONN1)).thenReturn(true);
+ when(worker.getPlugins()).thenReturn(plugins);
+
+ SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+
+ when(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig))
+ .thenThrow(new ConnectException("Failed to generate task configs"))
+ .thenThrow(new ConnectException("Failed to generate task configs"))
+ .thenReturn(TASK_CONFIGS);
+
+ expectAndVerifyTaskReconfigurationRetries();
+ }
+
+ @Test
+ public void testTaskReconfigurationRetriesWithLeaderRequestForwardingException() {
+ herder = mock(DistributedHerder.class, withSettings().defaultAnswer(CALLS_REAL_METHODS).useConstructor(new DistributedConfig(HERDER_CONFIG),
+ worker, WORKER_ID, KAFKA_CLUSTER_ID, statusBackingStore, configBackingStore, member, MEMBER_URL, restClient, metrics, time,
+ noneConnectorClientConfigOverridePolicy, Collections.emptyList(), new MockSynchronousExecutor(), new AutoCloseable[]{}));
+
+ rebalanceListener = herder.new RebalanceListener(time);
+
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList(), false);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
+
+ when(worker.isRunning(CONN1)).thenReturn(true);
+ when(worker.getPlugins()).thenReturn(plugins);
+
+ SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+
+ List<Map<String, String>> changedTaskConfigs = new ArrayList<>(TASK_CONFIGS);
+ changedTaskConfigs.add(TASK_CONFIG);
+ when(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig)).thenReturn(changedTaskConfigs);
+
+ when(restClient.httpRequest(any(), eq("POST"), any(), any(), any(), any(), any()))
+ .thenThrow(new ConnectException("Request to leader to reconfigure connector tasks failed"))
+ .thenThrow(new ConnectException("Request to leader to reconfigure connector tasks failed"))
+ .thenReturn(null);
+
+ expectAndVerifyTaskReconfigurationRetries();
+ }
+
+ private void expectAndVerifyTaskReconfigurationRetries() {
+ // initial tick
+ herder.tick();
+ herder.requestTaskReconfiguration(CONN1);
+ // process the task reconfiguration request in this tick
+ herder.tick();
+ // advance the time by 250ms so that the task reconfiguration request with initial retry backoff is processed
+ time.sleep(250);
+ herder.tick();
+ // advance the time by 500ms so that the task reconfiguration request with double the initial retry backoff is processed
+ time.sleep(500);
+ herder.tick();
+
+ // 1. end of initial tick when no request has been added to the herder queue yet
+ // 2. the third task reconfiguration request is expected to pass; so expect no more retries (a Long.MAX_VALUE poll
+ // timeout indicates that there is no herder request currently in the queue)
+ verify(member, times(2)).poll(eq(Long.MAX_VALUE));
+
+ // task reconfiguration herder request with initial retry backoff
+ verify(member).poll(eq(250L));
+
+ // task reconfiguration herder request with double the initial retry backoff
+ verify(member).poll(eq(500L));
+
+ verifyNoMoreInteractions(member, worker);
+ }
+
+ @Test
+ public void processRestartRequestsFailureSuppression() {
+ doNothing().when(member).wakeup();
+
+ final String connectorName = "foo";
+ RestartRequest restartRequest = new RestartRequest(connectorName, false, false);
+ doThrow(new RuntimeException()).when(herder).buildRestartPlan(restartRequest);
+
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(1, herder.pendingRestartRequests.size());
+ herder.processRestartRequests();
+ assertTrue(herder.pendingRestartRequests.isEmpty());
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
+ }
+
+ @Test
+ public void processRestartRequestsDequeue() {
+ doNothing().when(member).wakeup();
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(any(RestartRequest.class));
+
+ RestartRequest restartRequest = new RestartRequest("foo", false, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+ restartRequest = new RestartRequest("bar", false, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(2, herder.pendingRestartRequests.size());
+ herder.processRestartRequests();
+ assertTrue(herder.pendingRestartRequests.isEmpty());
+ }
+
+ @Test
+ public void preserveHighestImpactRestartRequest() {
+ doNothing().when(member).wakeup();
+
+ final String connectorName = "foo";
+ RestartRequest restartRequest = new RestartRequest(connectorName, false, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+
+ // Will overwrite as this is higher impact
+ restartRequest = new RestartRequest(connectorName, false, true);
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(1, herder.pendingRestartRequests.size());
+ assertFalse(herder.pendingRestartRequests.get(connectorName).onlyFailed());
+ assertTrue(herder.pendingRestartRequests.get(connectorName).includeTasks());
+
+ // Will be ignored as the existing request has higher impact
+ restartRequest = new RestartRequest(connectorName, true, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(1, herder.pendingRestartRequests.size());
+ // Compare against existing request
+ assertFalse(herder.pendingRestartRequests.get(connectorName).onlyFailed());
+ assertTrue(herder.pendingRestartRequests.get(connectorName).includeTasks());
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidation() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(ExactlyOnceSupport.SUPPORTED);
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
+
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertEquals(Collections.emptyList(), errors);
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidationOnUnsupportedConnector() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(ExactlyOnceSupport.UNSUPPORTED);
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
+
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertEquals(
+ Collections.singletonList("The connector does not support exactly-once semantics with the provided configuration."),
+ errors);
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidationOnUnknownConnector() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(null);
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
+
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("The connector does not implement the API required for preflight validation of exactly-once source support."));
+ assertEquals(1, errors.size());
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidationHandlesConnectorErrorsGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ String errorMessage = "time to add a new unit test :)";
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenThrow(new NullPointerException(errorMessage));
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- PowerMock.verifyAll();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains(errorMessage));
+ assertEquals(1, errors.size());
}
@Test
- public void testTaskReconfigurationRetriesWithConnectorTaskConfigsException() {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
- expectConfigRefreshAndSnapshot(SNAPSHOT);
+ public void testExactlyOnceSourceSupportValidationWhenExactlyOnceNotEnabledOnWorker() {
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
- // end of initial tick
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(ExactlyOnceSupport.SUPPORTED);
- member.wakeup();
- PowerMock.expectLastCall().anyTimes();
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- member.ensureActive();
- PowerMock.expectLastCall().anyTimes();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertEquals(
+ Collections.singletonList("This worker does not have exactly-once source support enabled."),
+ errors);
+ }
- EasyMock.expect(worker.isRunning(CONN1)).andReturn(true).anyTimes();
- EasyMock.expect(worker.getPlugins()).andReturn(plugins).anyTimes();
+ @Test
+ public void testExactlyOnceSourceSupportValidationHandlesInvalidValuesGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, "invalid");
- SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig))
- .andThrow(new ConnectException("Failed to generate task configs")).times(2);
+ SourceConnector connectorMock = mock(SourceConnector.class);
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig)).andReturn(TASK_CONFIGS);
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- expectAndVerifyTaskReconfigurationRetries();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("String must be one of (case insensitive): "));
+ assertEquals(1, errors.size());
}
@Test
- public void testTaskReconfigurationRetriesWithLeaderRequestForwardingException() {
- herder = PowerMock.createPartialMock(DistributedHerder.class,
- new String[]{"connectorType", "updateDeletedConnectorStatus", "updateDeletedTaskStatus", "validateConnectorConfig", "buildRestartPlan", "recordRestarting"},
- new DistributedConfig(HERDER_CONFIG), worker, WORKER_ID, KAFKA_CLUSTER_ID,
- statusBackingStore, configBackingStore, member, MEMBER_URL, restClient, metrics, time, noneConnectorClientConfigOverridePolicy,
- Collections.emptyList(), new MockSynchronousExecutor(), new AutoCloseable[]{});
+ public void testConnectorTransactionBoundaryValidation() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, CONNECTOR.toString());
- rebalanceListener = herder.new RebalanceListener(time);
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.canDefineTransactionBoundaries(eq(config)))
+ .thenReturn(ConnectorTransactionBoundaries.SUPPORTED);
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- expectRebalance(1, Collections.emptyList(), Collections.emptyList(), false);
- expectConfigRefreshAndSnapshot(SNAPSHOT);
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- // end of initial tick
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertEquals(Collections.emptyList(), errors);
+ }
- member.wakeup();
- PowerMock.expectLastCall().anyTimes();
+ @Test
+ public void testConnectorTransactionBoundaryValidationOnUnsupportedConnector() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, CONNECTOR.toString());
- member.ensureActive();
- PowerMock.expectLastCall().anyTimes();
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.canDefineTransactionBoundaries(eq(config)))
+ .thenReturn(ConnectorTransactionBoundaries.UNSUPPORTED);
- EasyMock.expect(worker.isRunning(CONN1)).andReturn(true).anyTimes();
- EasyMock.expect(worker.getPlugins()).andReturn(plugins).anyTimes();
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("The connector does not support connector-defined transaction boundaries with the given configuration."));
+ assertEquals(1, errors.size());
+ }
- List<Map<String, String>> changedTaskConfigs = new ArrayList<>(TASK_CONFIGS);
- changedTaskConfigs.add(TASK_CONFIG);
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig)).andReturn(changedTaskConfigs).anyTimes();
+ @Test
+ public void testConnectorTransactionBoundaryValidationHandlesConnectorErrorsGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, CONNECTOR.toString());
- EasyMock.expect(restClient.httpRequest(
- EasyMock.anyString(), EasyMock.eq("POST"), EasyMock.anyObject(HttpHeaders.class),
- EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(SecretKey.class), EasyMock.anyString())
- ).andThrow(new ConnectException("Request to leader to reconfigure connector tasks failed")).times(2);
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ String errorMessage = "Wait I thought we tested for this?";
+ when(connectorMock.canDefineTransactionBoundaries(eq(config))).thenThrow(new ConnectException(errorMessage));
- EasyMock.expect(restClient.httpRequest(
- EasyMock.anyString(), EasyMock.eq("POST"), EasyMock.anyObject(HttpHeaders.class),
- EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(SecretKey.class), EasyMock.anyString())
- ).andReturn(null);
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- expectAndVerifyTaskReconfigurationRetries();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains(errorMessage));
+ assertEquals(1, errors.size());
}
- private void expectAndVerifyTaskReconfigurationRetries() {
- // task reconfiguration herder request with initial retry backoff
- member.poll(EasyMock.eq(250L));
- PowerMock.expectLastCall();
-
- // task reconfiguration herder request with double the initial retry backoff
- member.poll(EasyMock.eq(500L));
- PowerMock.expectLastCall();
-
- // the third task reconfiguration request is expected to pass; so expect no more retries (a Long.MAX_VALUE poll
- // timeout indicates that there is no herder request currently in the queue)
- member.poll(EasyMock.eq(Long.MAX_VALUE));
- PowerMock.expectLastCall();
+ @Test
+ public void testConnectorTransactionBoundaryValidationHandlesInvalidValuesGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, "CONNECTOR.toString()");
- PowerMock.replayAll();
+ SourceConnector connectorMock = mock(SourceConnector.class);
- // initial tick
- herder.tick();
- herder.requestTaskReconfiguration(CONN1);
- // process the task reconfiguration request in this tick
- herder.tick();
- // advance the time by 250ms so that the task reconfiguration request with initial retry backoff is processed
- time.sleep(250);
- herder.tick();
- // advance the time by 500ms so that the task reconfiguration request with double the initial retry backoff is processed
- time.sleep(500);
- herder.tick();
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- PowerMock.verifyAll();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("String must be one of (case insensitive): "));
+ assertEquals(1, errors.size());
}
@Test
public void testConnectorOffsets() throws Exception {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- expectAnyTicks();
- member.wakeup();
- PowerMock.expectLastCall();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
+ herder.tick();
+
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
ConnectorOffsets offsets = new ConnectorOffsets(Collections.singletonList(new ConnectorOffset(
Collections.singletonMap("partitionKey", "partitionValue"),
Collections.singletonMap("offsetKey", "offsetValue"))));
- Capture<Callback<ConnectorOffsets>> callbackCapture = newCapture();
- worker.connectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), capture(callbackCapture));
- PowerMock.expectLastCall().andAnswer(() -> {
+
+ ArgumentCaptor<Callback<ConnectorOffsets>> callbackCapture = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
callbackCapture.getValue().onCompletion(null, offsets);
return null;
- });
+ }).when(worker).connectorOffsets(eq(CONN1), eq(CONN1_CONFIG), callbackCapture.capture());
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<ConnectorOffsets> cb = new FutureCallback<>();
herder.connectorOffsets(CONN1, cb);
herder.tick();
assertEquals(offsets, cb.get(1000, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testModifyConnectorOffsetsUnknownConnector() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // Now handle the connector offsets modification request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
+
+ // Now handle the connector offsets modification request
FutureCallback<Message> callback = new FutureCallback<>();
herder.modifyConnectorOffsets("connector-does-not-exist", new HashMap<>(), callback);
herder.tick();
ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(e.getCause() instanceof NotFoundException);
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsConnectorNotInStoppedState() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // Now handle the connector offsets modification request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
+
+ // Now handle the connector offsets modification request
FutureCallback<Message> callback = new FutureCallback<>();
herder.modifyConnectorOffsets(CONN1, null, callback);
herder.tick();
ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(e.getCause() instanceof BadRequestException);
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsNotLeader() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), false);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // Now handle the connector offsets modification request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
+
+ // Now handle the connector offsets modification request
FutureCallback<Message> callback = new FutureCallback<>();
herder.modifyConnectorOffsets(CONN1, new HashMap<>(), callback);
herder.tick();
ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(e.getCause() instanceof NotLeaderException);
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsSinkConnector() throws Exception {
- EasyMock.reset(herder);
- EasyMock.expect(herder.connectorType(EasyMock.anyObject())).andReturn(ConnectorType.SINK).anyTimes();
- PowerMock.expectPrivate(herder, "updateDeletedConnectorStatus").andVoid().anyTimes();
- PowerMock.expectPrivate(herder, "updateDeletedTaskStatus").andVoid().anyTimes();
-
+ when(herder.connectorType(any())).thenReturn(ConnectorType.SINK);
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ herder.tick();
// Now handle the alter connector offsets request
Map<Map<String, ?>, Map<String, ?>> offsets = Collections.singletonMap(
Collections.singletonMap("partitionKey", "partitionValue"),
Collections.singletonMap("offsetKey", "offsetValue"));
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- Capture<Callback<Message>> workerCallbackCapture = Capture.newInstance();
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), EasyMock.eq(offsets), capture(workerCallbackCapture));
+
+ ArgumentCaptor<Callback<Message>> workerCallbackCapture = ArgumentCaptor.forClass(Callback.class);
Message msg = new Message("The offsets for this connector have been altered successfully");
- EasyMock.expectLastCall().andAnswer(() -> {
+ doAnswer(invocation -> {
workerCallbackCapture.getValue().onCompletion(null, msg);
return null;
- });
+ }).when(worker).modifyConnectorOffsets(eq(CONN1), eq(CONN1_CONFIG), eq(offsets), workerCallbackCapture.capture());
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Message> callback = new FutureCallback<>();
herder.alterConnectorOffsets(CONN1, offsets, callback);
herder.tick();
assertEquals(msg, callback.get(1000L, TimeUnit.MILLISECONDS));
assertEquals("The offsets for this connector have been altered successfully", msg.message());
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsSourceConnectorExactlyOnceDisabled() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
// Now handle the reset connector offsets request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- Capture<Callback<Message>> workerCallbackCapture = Capture.newInstance();
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), isNull(), capture(workerCallbackCapture));
+ ArgumentCaptor<Callback<Message>> workerCallbackCapture = ArgumentCaptor.forClass(Callback.class);
Message msg = new Message("The offsets for this connector have been reset successfully");
- EasyMock.expectLastCall().andAnswer(() -> {
+ doAnswer(invocation -> {
workerCallbackCapture.getValue().onCompletion(null, msg);
return null;
- });
+ }).when(worker).modifyConnectorOffsets(eq(CONN1), eq(CONN1_CONFIG), isNull(), workerCallbackCapture.capture());
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Message> callback = new FutureCallback<>();
herder.resetConnectorOffsets(CONN1, callback);
herder.tick();
assertEquals(msg, callback.get(1000L, TimeUnit.MILLISECONDS));
assertEquals("The offsets for this connector have been reset successfully", msg.message());
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsSourceConnectorExactlyOnceEnabled() throws Exception {
// Setup herder with exactly-once support for source connectors enabled
herder = exactlyOnceHerder();
rebalanceListener = herder.new RebalanceListener(time);
- PowerMock.expectPrivate(herder, "updateDeletedConnectorStatus").andVoid().anyTimes();
- PowerMock.expectPrivate(herder, "updateDeletedTaskStatus").andVoid().anyTimes();
-
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall().anyTimes();
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// Now handle the alter connector offsets request
- Map<Map<String, ?>, Map<String, ?>> offsets = Collections.singletonMap(
- Collections.singletonMap("partitionKey", "partitionValue"),
- Collections.singletonMap("offsetKey", "offsetValue"));
- member.wakeup();
- PowerMock.expectLastCall().anyTimes();
- member.ensureActive();
- PowerMock.expectLastCall().anyTimes();
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- EasyMock.expect(herder.connectorType(EasyMock.anyObject())).andReturn(ConnectorType.SOURCE).anyTimes();
+ doNothing().when(member).ensureActive();
+ when(herder.connectorType(any())).thenReturn(ConnectorType.SOURCE);
// Expect a round of zombie fencing to occur
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- KafkaFuture<Void> workerFencingFuture = EasyMock.mock(KafkaFuture.class);
- KafkaFuture<Void> herderFencingFuture = EasyMock.mock(KafkaFuture.class);
- EasyMock.expect(worker.fenceZombies(CONN1, SNAPSHOT_STOPPED_CONN1.taskCountRecord(CONN1), CONN1_CONFIG)).andReturn(workerFencingFuture);
- EasyMock.expect(workerFencingFuture.thenApply(EasyMock.<KafkaFuture.BaseFunction<Void, Void>>anyObject())).andReturn(herderFencingFuture);
-
- // Two fencing callbacks are added - one is in ZombieFencing::start itself to remove the connector from the active
- // fencing list. The other is the callback passed from DistributedHerder::modifyConnectorOffsets in order to
- // queue up the actual alter offsets request if the zombie fencing succeeds.
- for (int i = 0; i < 2; i++) {
- Capture<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = EasyMock.newCapture();
- EasyMock.expect(herderFencingFuture.whenComplete(EasyMock.capture(herderFencingCallback))).andAnswer(() -> {
- herderFencingCallback.getValue().accept(null, null);
- return null;
- });
- }
-
- Capture<Callback<Message>> workerCallbackCapture = Capture.newInstance();
- Message msg = new Message("The offsets for this connector have been altered successfully");
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), EasyMock.eq(offsets), capture(workerCallbackCapture));
- EasyMock.expectLastCall().andAnswer(() -> {
- workerCallbackCapture.getValue().onCompletion(null, msg);
+ KafkaFuture<Void> workerFencingFuture = mock(KafkaFuture.class);
+ KafkaFuture<Void> herderFencingFuture = mock(KafkaFuture.class);
+ when(worker.fenceZombies(CONN1, SNAPSHOT_STOPPED_CONN1.taskCountRecord(CONN1), CONN1_CONFIG)).thenReturn(workerFencingFuture);
+ when(workerFencingFuture.thenApply(any(KafkaFuture.BaseFunction.class))).thenReturn(herderFencingFuture);
+
+ ArgumentCaptor<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = ArgumentCaptor.forClass(KafkaFuture.BiConsumer.class);
+ when(herderFencingFuture.whenComplete(herderFencingCallback.capture())).thenAnswer(invocation -> {
+ herderFencingCallback.getValue().accept(null, null);
return null;
});
- // Handle the second alter connector offsets request. No zombie fencing request to the worker is expected now since we
- // already did a round of zombie fencing last time and no new tasks came up in the meanwhile. The config snapshot is
- // refreshed once at the beginning of the DistributedHerder::modifyConnectorOffsets method, once before checking
- // whether zombie fencing is required, and once before actually proceeding to alter connector offsets.
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1_FENCED);
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1_FENCED);
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1_FENCED);
- Capture<Callback<Message>> workerCallbackCapture2 = Capture.newInstance();
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), EasyMock.eq(offsets), capture(workerCallbackCapture2));
- EasyMock.expectLastCall().andAnswer(() -> {
- workerCallbackCapture2.getValue().onCompletion(null, msg);
- return null;
- });
+ ArgumentCaptor<Callback<Message>> workerCallbackCapture = ArgumentCaptor.forClass(Callback.class);
+ Message msg = new Message("The offsets for this connector have been altered successfully");
- PowerMock.replayAll(workerFencingFuture, herderFencingFuture);
+ Map<Map<String, ?>, Map<String, ?>> offsets = Collections.singletonMap(
+ Collections.singletonMap("partitionKey", "partitionValue"),
+ Collections.singletonMap("offsetKey", "offsetValue"));
+ doAnswer(invocation -> {
+ workerCallbackCapture.getValue().onCompletion(null, msg);
+ return null;
+ }).when(worker).modifyConnectorOffsets(eq(CONN1), eq(CONN1_CONFIG), eq(offsets), workerCallbackCapture.capture());
- herder.tick();
FutureCallback<Message> callback = new FutureCallback<>();
herder.alterConnectorOffsets(CONN1, offsets, callback);
- // Process the zombie fencing request
- herder.tick();
- // Process the alter offsets request
+ // Process the zombie fencing request that is queued up first followed by the actual alter offsets request
Review Comment:
Great, thanks for humoring me!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [kafka] yashmayya commented on pull request #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
Posted by "yashmayya (via GitHub)" <gi...@apache.org>.
yashmayya commented on PR #14102:
URL: https://github.com/apache/kafka/pull/14102#issuecomment-1657931548
Hm I just noticed that the `DistributedHerderTest` is failing on the CI run (only for JDK 11) with:
```
org.mockito.exceptions.base.MockitoException: Unable to create mock instance of type 'DistributedHerder'
at app//org.apache.kafka.connect.runtime.distributed.DistributedHerderTest.setUp(DistributedHerderTest.java:300)
at java.base@11.0.16.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base@11.0.16.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base@11.0.16.1/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base@11.0.16.1/java.lang.reflect.Method.invoke(Method.java:566)
at app//org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at app//org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at app//org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at app//org.junit.internal.runners.statements.RunBefores.invokeMethod(RunBefores.java:33)
at app//org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at app//org.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:55)
at app//org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at app//org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at app//org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at app//org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at app//org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at app//org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at app//org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at app//org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at app//org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at app//org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:100)
at app//org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:107)
at app//org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:41)
at app//org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:108)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:40)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:60)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:52)
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source)
at java.base@11.0.16.1/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base@11.0.16.1/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65)
at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: org.mockito.creation.instance.InstantiationException:
Unable to create instance of 'DistributedHerder'.
Please ensure the target class has a constructor that matches these argument types: [org.apache.kafka.connect.runtime.distributed.DistributedConfig, org.apache.kafka.connect.runtime.Worker, java.lang.String, java.lang.String, org.apache.kafka.connect.storage.StatusBackingStore$MockitoMock$p8qyFkRn, org.apache.kafka.connect.storage.ConfigBackingStore$MockitoMock$4oJiFNI3, org.apache.kafka.connect.runtime.distributed.WorkerGroupMember, java.lang.String, org.apache.kafka.connect.runtime.rest.RestClient, org.apache.kafka.connect.runtime.MockConnectMetrics, org.apache.kafka.common.utils.MockTime, org.apache.kafka.connect.runtime.distributed.SampleConnectorClientConfigOverridePolicy, java.util.Collections$EmptyList, null, [Ljava.lang.AutoCloseable;] and executes cleanly.
... 50 more
Caused by: java.lang.reflect.InvocationTargetException
at org.mockito.internal.util.reflection.InstrumentationMemberAccessor.newInstance(InstrumentationMemberAccessor.java:198)
at org.mockito.internal.util.reflection.InstrumentationMemberAccessor.newInstance(InstrumentationMemberAccessor.java:161)
at org.mockito.internal.util.reflection.ModuleMemberAccessor.newInstance(ModuleMemberAccessor.java:42)
at org.mockito.internal.creation.instance.ConstructorInstantiator.invokeConstructor(ConstructorInstantiator.java:70)
at org.mockito.internal.creation.instance.ConstructorInstantiator.withParams(ConstructorInstantiator.java:53)
at org.mockito.internal.creation.instance.ConstructorInstantiator.newInstance(ConstructorInstantiator.java:39)
at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.doCreateMock(InlineDelegateByteBuddyMockMaker.java:364)
at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.createMock(InlineDelegateByteBuddyMockMaker.java:334)
at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createMock(InlineByteBuddyMockMaker.java:56)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:99)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:88)
at org.mockito.Mockito.mock(Mockito.java:2037)
... 50 more
Caused by: java.lang.ClassCastException: Cannot cast [Ljava.lang.AutoCloseable; to java.lang.AutoCloseable
at java.base/java.lang.Class.cast(Class.java:3605)
at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:710)
at java.base/java.lang.invoke.MethodHandleImpl$AsVarargsCollector.invokeWithArguments(MethodHandleImpl.java:578)
at org.mockito.internal.util.reflection.InstrumentationMemberAccessor$Dispatcher$ByteBuddy$aMere0wp.invokeWithArguments(Unknown Source)
at org.mockito.internal.util.reflection.InstrumentationMemberAccessor.lambda$newInstance$0(InstrumentationMemberAccessor.java:191)
at org.mockito.internal.util.reflection.InstrumentationMemberAccessor.newInstance(InstrumentationMemberAccessor.java:188)
... 61 more
```
This is the same issue (https://github.com/mockito/mockito/issues/2601) that was discovered in https://github.com/apache/kafka/pull/11792 but supposed to be fixed in a newer version of Mockito (which we're already using). I'll take a closer look at why this is re-surfacing only on the JDK 11 build (and not on the other JDK builds; these tests were consistently passing for me locally with JDK 8).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [kafka] yashmayya commented on pull request #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
Posted by "yashmayya (via GitHub)" <gi...@apache.org>.
yashmayya commented on PR #14102:
URL: https://github.com/apache/kafka/pull/14102#issuecomment-1651865877
@C0urante @mimaison would you be able to take a look at this one? This effort was originally started 1 ½ years ago [here](https://github.com/apache/kafka/pull/11792) and has had multiple ownership changes. I started working on this quite a while back myself but had to park it midway numerous times because it was taking way too long but it's finally been completed (or well, at least the tests all consistently pass and I'm hoping we've not lost coverage) 🙂
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [kafka] C0urante merged pull request #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
Posted by "C0urante (via GitHub)" <gi...@apache.org>.
C0urante merged PR #14102:
URL: https://github.com/apache/kafka/pull/14102
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [kafka] yashmayya commented on a diff in pull request #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
Posted by "yashmayya (via GitHub)" <gi...@apache.org>.
yashmayya commented on code in PR #14102:
URL: https://github.com/apache/kafka/pull/14102#discussion_r1288249581
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3872,7 +2952,7 @@ public void testVerifyTaskGeneration() {
assertThrows(ConnectException.class, () -> herder.verifyTaskGenerationAndOwnership(unassignedTask, 1, verifyCallback));
assertThrows(ConnectException.class, () -> herder.verifyTaskGenerationAndOwnership(unassignedTask, 2, verifyCallback));
- PowerMock.verifyAll();
+ verify(verifyCallback, times(3)).onCompletion(isNull(), isNull());
Review Comment:
> We expect this to be invoked five times in the original test
There are only 3 calls to `herder::verifyTaskGenerationAndOwnership` in this test that aren't expected to throw an exception.
> Were we never properly verifying the number of invocations in the existing test?
Yep, the `verifyCallback` mock isn't passed into the `PowerMock::replayAll` call which means that it isn't "known" to `PowerMock` and the `PowerMock::verifyAll` call will not make any verifications on it. If this line in the original test -
https://github.com/apache/kafka/blob/f23394336a7741bf4eb23fcde951af0a23a69bd0/connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java#L3838
is changed to `PowerMock.replayAll(verifyCallback);` then the original test fails and only passes when we change this loop from 5 iterations to 3 -
https://github.com/apache/kafka/blob/f23394336a7741bf4eb23fcde951af0a23a69bd0/connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java#L3833-L3836
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [kafka] C0urante commented on a diff in pull request #14102: KAFKA-13187: Replace EasyMock / PowerMock with Mockito in DistributedHerderTest
Posted by "C0urante (via GitHub)" <gi...@apache.org>.
C0urante commented on code in PR #14102:
URL: https://github.com/apache/kafka/pull/14102#discussion_r1286368942
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -320,132 +321,97 @@ public void tearDown() {
@Test
public void testJoinAssignment() throws Exception {
// Join group and get assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Arrays.asList(CONN1), Arrays.asList(TASK1));
expectConfigRefreshAndSnapshot(SNAPSHOT);
- Capture<Callback<TargetState>> onStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
onStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- worker.startSourceTask(EasyMock.eq(TASK1), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
+ when(worker.startSourceTask(eq(TASK1), any(), any(), any(), eq(herder), eq(TargetState.STARTED))).thenReturn(true);
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
herder.tick();
time.sleep(1000L);
assertStatistics(3, 1, 100, 1000L);
-
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(member, configBackingStore, statusBackingStore, worker);
}
@Test
public void testRebalance() throws Exception {
// Join group and get assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Arrays.asList(CONN1), Arrays.asList(TASK1));
expectConfigRefreshAndSnapshot(SNAPSHOT);
- Capture<Callback<TargetState>> onFirstStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onFirstStart));
- PowerMock.expectLastCall().andAnswer(() -> {
- onFirstStart.getValue().onCompletion(null, TargetState.STARTED);
- return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- worker.startSourceTask(EasyMock.eq(TASK1), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- expectRebalance(Arrays.asList(CONN1), Arrays.asList(TASK1), ConnectProtocol.Assignment.NO_ERROR,
- 1, Arrays.asList(CONN1), Arrays.asList());
-
- // and the new assignment started
- Capture<Callback<TargetState>> onSecondStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onSecondStart));
- PowerMock.expectLastCall().andAnswer(() -> {
- onSecondStart.getValue().onCompletion(null, TargetState.STARTED);
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ onStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
+ when(worker.startSourceTask(eq(TASK1), any(), any(), any(), eq(herder), eq(TargetState.STARTED))).thenReturn(true);
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
time.sleep(1000L);
assertStatistics(0, 0, 0, Double.POSITIVE_INFINITY);
herder.tick();
+ expectRebalance(Arrays.asList(CONN1), Arrays.asList(TASK1), ConnectProtocol.Assignment.NO_ERROR,
+ 1, Arrays.asList(CONN1), Arrays.asList());
+
time.sleep(2000L);
assertStatistics(3, 1, 100, 2000);
herder.tick();
time.sleep(3000L);
assertStatistics(3, 2, 100, 3000);
Review Comment:
Nit: we can make the intent of this cleaner with a comment and a few whitespace tweaks
```suggestion
time.sleep(1000L);
assertStatistics(0, 0, 0, Double.POSITIVE_INFINITY);
herder.tick();
time.sleep(2000L);
assertStatistics(3, 1, 100, 2000);
// Rebalance and get a new assignment
expectRebalance(Arrays.asList(CONN1), Arrays.asList(TASK1), ConnectProtocol.Assignment.NO_ERROR,
1, Arrays.asList(CONN1), Arrays.asList());
herder.tick();
time.sleep(3000L);
assertStatistics(3, 2, 100, 3000);
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -320,132 +321,97 @@ public void tearDown() {
@Test
public void testJoinAssignment() throws Exception {
// Join group and get assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Arrays.asList(CONN1), Arrays.asList(TASK1));
expectConfigRefreshAndSnapshot(SNAPSHOT);
- Capture<Callback<TargetState>> onStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
onStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- worker.startSourceTask(EasyMock.eq(TASK1), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
+ when(worker.startSourceTask(eq(TASK1), any(), any(), any(), eq(herder), eq(TargetState.STARTED))).thenReturn(true);
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
herder.tick();
time.sleep(1000L);
assertStatistics(3, 1, 100, 1000L);
-
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(member, configBackingStore, statusBackingStore, worker);
}
@Test
public void testRebalance() throws Exception {
// Join group and get assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Arrays.asList(CONN1), Arrays.asList(TASK1));
expectConfigRefreshAndSnapshot(SNAPSHOT);
- Capture<Callback<TargetState>> onFirstStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onFirstStart));
- PowerMock.expectLastCall().andAnswer(() -> {
- onFirstStart.getValue().onCompletion(null, TargetState.STARTED);
- return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- worker.startSourceTask(EasyMock.eq(TASK1), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- expectRebalance(Arrays.asList(CONN1), Arrays.asList(TASK1), ConnectProtocol.Assignment.NO_ERROR,
- 1, Arrays.asList(CONN1), Arrays.asList());
-
- // and the new assignment started
- Capture<Callback<TargetState>> onSecondStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onSecondStart));
- PowerMock.expectLastCall().andAnswer(() -> {
- onSecondStart.getValue().onCompletion(null, TargetState.STARTED);
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ onStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
+ when(worker.startSourceTask(eq(TASK1), any(), any(), any(), eq(herder), eq(TargetState.STARTED))).thenReturn(true);
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
time.sleep(1000L);
assertStatistics(0, 0, 0, Double.POSITIVE_INFINITY);
herder.tick();
+ expectRebalance(Arrays.asList(CONN1), Arrays.asList(TASK1), ConnectProtocol.Assignment.NO_ERROR,
+ 1, Arrays.asList(CONN1), Arrays.asList());
+
time.sleep(2000L);
assertStatistics(3, 1, 100, 2000);
herder.tick();
time.sleep(3000L);
assertStatistics(3, 2, 100, 3000);
- PowerMock.verifyAll();
+ // Verify that the connector is started twice but the task is only started once (the first mocked rebalance assigns CONN1 and TASK1,
+ // the second mocked rebalance revokes CONN1 and TASK1 and (re)assigns CONN1)
+ verify(worker, times(2)).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), any());
+ verify(worker, times(2)).connectorTaskConfigs(eq(CONN1), eq(conn1SinkConfig));
+ verify(worker).startSourceTask(eq(TASK1), any(), any(), any(), eq(herder), eq(TargetState.STARTED));
Review Comment:
Optional suggestion: add assertions about the number of calls to `worker::startConnector` and `worker::startSourceTask` after the first call to `herder::tick`, so that we know not just the total number of these calls after both rebalances, but how many were made during each herder tick.
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -687,101 +617,72 @@ public void revokeAndReassign(boolean incompleteRebalance) throws TimeoutExcepti
);
expectConfigRefreshAndSnapshot(secondSnapshot);
}
- member.requestRejoin();
- PowerMock.expectLastCall();
+
+ doNothing().when(member).requestRejoin();
+
+ herder.tick();
// re-assign the connector back to the same worker to ensure state was cleaned up
expectRebalance(configOffset, Arrays.asList(CONN1), Arrays.asList());
- Capture<Callback<TargetState>> onSecondStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(),
- EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onSecondStart));
- PowerMock.expectLastCall().andAnswer(() -> {
- onSecondStart.getValue().onCompletion(null, TargetState.STARTED);
- return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
- if (incompleteRebalance) {
- herder.tick();
- }
- herder.tick();
- herder.tick();
- PowerMock.verifyAll();
+ verify(worker, times(2)).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), any());
+ verify(worker, times(2)).connectorTaskConfigs(eq(CONN1), eq(conn1SinkConfig));
+ verify(worker).startSourceTask(eq(TASK1), any(), any(), any(), eq(herder), eq(TargetState.STARTED));
+
Review Comment:
This test case was introduced in https://github.com/apache/kafka/pull/9765, which addressed a bug in incremental rebalancing that caused duplicate connectors/tasks on a worker, caused by failure to stop the connectors/tasks during a rebalance.
It currently passes without the fix from https://github.com/apache/kafka/pull/9765, but in the same situation, fails if we add an assertion about stopping the connector:
```suggestion
verify(worker).stopAndAwaitConnector(CONN1);
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
Review Comment:
I see that continuing to include this expectation would cause a test failure due to unused stubbing. But, at the same time, it also seems a little risky to leave it out in case we change the herder to rely on this method later on. What do you think about adding the `restartPlan` mock to the `verifyNoMoreInteractions` call as a way to ensure that we set up correct expectations in our tests if we start to use `RestartPlan::shouldRestartTasks` in the distributed herder?
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
stateCallback.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
Review Comment:
This is a little weak in terms of coverage; we could be failing the connector here instead of marking it as restarting.
There are a handful of ways to address this. One could be to set up a mock expectation with the exact status we expect to see reported to the status store:
```suggestion
ConnectorStatus status = new ConnectorStatus(CONN1, AbstractStatus.State.RESTARTING, WORKER_ID, 0);
doNothing().when(statusBackingStore).put(status);
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3724,62 +2818,54 @@ public void testExternalZombieFencingRequestDelayedCompletion() throws Exception
pendingFencing,
tasksPerConnector
);
- tasksPerConnector.keySet().forEach(c -> expectConfigRefreshAndSnapshot(configState));
+ expectConfigRefreshAndSnapshot(configState);
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
// The callbacks that the herder has accrued for outstanding fencing futures, which will be completed after
// a successful round of fencing and a task record write to the config topic
- Map<String, Capture<KafkaFuture.BiConsumer<Void, Throwable>>> herderFencingCallbacks = new HashMap<>();
+ Map<String, ArgumentCaptor<KafkaFuture.BiConsumer<Void, Throwable>>> herderFencingCallbacks = new HashMap<>();
// The callbacks that the herder has installed for after a successful round of zombie fencing, but before writing
// a task record to the config topic
- Map<String, Capture<KafkaFuture.BaseFunction<Void, Void>>> workerFencingFollowups = new HashMap<>();
+ Map<String, ArgumentCaptor<KafkaFuture.BaseFunction<Void, Void>>> workerFencingFollowups = new HashMap<>();
Map<String, CountDownLatch> callbacksInstalled = new HashMap<>();
tasksPerConnector.forEach((connector, numStackedRequests) -> {
Review Comment:
Nit: we don't need to know the task count in this block anymore
```suggestion
tasksPerConnector.keySet().forEach(connector -> {
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3003,41 +2192,39 @@ public void testKeyRotationWhenWorkerBecomesLeader() throws Exception {
Collections.emptySet(),
Collections.emptySet());
expectConfigRefreshAndSnapshot(snapshotWithKey);
- // Second rebalance: poll indefinitely as worker is follower, so expiration still doesn't come into play
- member.poll(Long.MAX_VALUE);
- EasyMock.expectLastCall();
+
+ configUpdateListener.onSessionKeyUpdate(initialKey);
+ herder.tick();
expectRebalance(2, Collections.emptyList(), Collections.emptyList(), "member", MEMBER_URL, true);
- Capture<SessionKey> updatedKey = EasyMock.newCapture();
- configBackingStore.putSessionKey(EasyMock.capture(updatedKey));
- EasyMock.expectLastCall().andAnswer(() -> {
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ ArgumentCaptor<SessionKey> updatedKey = ArgumentCaptor.forClass(SessionKey.class);
+ doAnswer(invocation -> {
configUpdateListener.onSessionKeyUpdate(updatedKey.getValue());
return null;
- });
- // Third rebalance: poll for a limited time as worker has become leader and must wake up for key expiration
- member.poll(leq(rotationTtlDelay));
- EasyMock.expectLastCall();
+ }).when(configBackingStore).putSessionKey(updatedKey.capture());
- PowerMock.replayAll();
-
- herder.tick();
- configUpdateListener.onSessionKeyUpdate(initialKey);
- herder.tick();
herder.tick();
- PowerMock.verifyAll();
+ // First rebalance: poll indefinitely as no key has been read yet, so expiration doesn't come into play
+ // Second rebalance: poll indefinitely as worker is follower, so expiration still doesn't come into play
+ verify(member, times(2)).poll(eq(Long.MAX_VALUE));
Review Comment:
Optional suggestion: move these assertions directly after the calls to `herder::tick` that should satisfy them.
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3872,7 +2952,7 @@ public void testVerifyTaskGeneration() {
assertThrows(ConnectException.class, () -> herder.verifyTaskGenerationAndOwnership(unassignedTask, 1, verifyCallback));
assertThrows(ConnectException.class, () -> herder.verifyTaskGenerationAndOwnership(unassignedTask, 2, verifyCallback));
- PowerMock.verifyAll();
+ verify(verifyCallback, times(3)).onCompletion(isNull(), isNull());
Review Comment:
We expect this to be invoked five times in the original test. Any idea why this needs to be changed to three? Were we never properly verifying the number of invocations in the existing test?
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -844,73 +737,57 @@ public void testCreateConnectorConfigBackingStoreError() {
time.sleep(1000L);
assertStatistics(3, 1, 100, 1000L);
- PowerMock.verifyAll();
+ // Verify that the exception thrown during the config backing store write is propagated via the callback
+ verify(putConnectorCallback).onCompletion(any(ConnectException.class), isNull());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore, putConnectorCallback);
}
@Test
public void testCreateConnectorFailedValidation() throws Exception {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ // Initial rebalance where this member becomes the leader
+ herder.tick();
Review Comment:
Why do we need a new tick here, when this test passed previously without it?
It doesn't seem terrible to add an extra tick to simulate the worker joining the group first before handle the connector creation request, but it would be nice to understand the context and if we're losing coverage somehow.
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -320,132 +321,97 @@ public void tearDown() {
@Test
public void testJoinAssignment() throws Exception {
// Join group and get assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Arrays.asList(CONN1), Arrays.asList(TASK1));
expectConfigRefreshAndSnapshot(SNAPSHOT);
- Capture<Callback<TargetState>> onStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
onStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- worker.startSourceTask(EasyMock.eq(TASK1), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
+ when(worker.startSourceTask(eq(TASK1), any(), any(), any(), eq(herder), eq(TargetState.STARTED))).thenReturn(true);
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
herder.tick();
time.sleep(1000L);
assertStatistics(3, 1, 100, 1000L);
-
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(member, configBackingStore, statusBackingStore, worker);
}
@Test
public void testRebalance() throws Exception {
// Join group and get assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Arrays.asList(CONN1), Arrays.asList(TASK1));
expectConfigRefreshAndSnapshot(SNAPSHOT);
- Capture<Callback<TargetState>> onFirstStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onFirstStart));
- PowerMock.expectLastCall().andAnswer(() -> {
- onFirstStart.getValue().onCompletion(null, TargetState.STARTED);
- return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- worker.startSourceTask(EasyMock.eq(TASK1), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- expectRebalance(Arrays.asList(CONN1), Arrays.asList(TASK1), ConnectProtocol.Assignment.NO_ERROR,
- 1, Arrays.asList(CONN1), Arrays.asList());
-
- // and the new assignment started
- Capture<Callback<TargetState>> onSecondStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onSecondStart));
- PowerMock.expectLastCall().andAnswer(() -> {
- onSecondStart.getValue().onCompletion(null, TargetState.STARTED);
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ onStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
+ when(worker.startSourceTask(eq(TASK1), any(), any(), any(), eq(herder), eq(TargetState.STARTED))).thenReturn(true);
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
time.sleep(1000L);
assertStatistics(0, 0, 0, Double.POSITIVE_INFINITY);
herder.tick();
+ expectRebalance(Arrays.asList(CONN1), Arrays.asList(TASK1), ConnectProtocol.Assignment.NO_ERROR,
+ 1, Arrays.asList(CONN1), Arrays.asList());
+
time.sleep(2000L);
assertStatistics(3, 1, 100, 2000);
herder.tick();
time.sleep(3000L);
assertStatistics(3, 2, 100, 3000);
- PowerMock.verifyAll();
+ // Verify that the connector is started twice but the task is only started once (the first mocked rebalance assigns CONN1 and TASK1,
+ // the second mocked rebalance revokes CONN1 and TASK1 and (re)assigns CONN1)
+ verify(worker, times(2)).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), any());
+ verify(worker, times(2)).connectorTaskConfigs(eq(CONN1), eq(conn1SinkConfig));
+ verify(worker).startSourceTask(eq(TASK1), any(), any(), any(), eq(herder), eq(TargetState.STARTED));
+ verifyNoMoreInteractions(member, configBackingStore, statusBackingStore, worker);
}
@Test
public void testIncrementalCooperativeRebalanceForNewMember() throws Exception {
connectProtocolVersion = CONNECT_PROTOCOL_V1;
// Join group. First rebalance contains revocations from other members. For the new
// member the assignment should be empty
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V1);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V1);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
+
+ time.sleep(1000L);
+ assertStatistics(0, 0, 0, Double.POSITIVE_INFINITY);
+ herder.tick();
Review Comment:
Similar though RE spacing:
```suggestion
time.sleep(1000L);
assertStatistics(0, 0, 0, Double.POSITIVE_INFINITY);
herder.tick();
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -921,498 +798,197 @@ public void testConnectorNameConflictsWithWorkerGroupId() {
assertEquals(
Collections.singletonList("Consumer group for sink connector named test-group conflicts with Connect worker group connect-test-group"),
nameConfig.errorMessages());
-
- PowerMock.verifyAll();
}
@Test
- public void testExactlyOnceSourceSupportValidation() {
- herder = exactlyOnceHerder();
- Map<String, String> config = new HashMap<>();
- config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+ public void testCreateConnectorAlreadyExists() throws Exception {
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
- SourceConnector connectorMock = PowerMock.createMock(SourceConnector.class);
- EasyMock.expect(connectorMock.exactlyOnceSupport(EasyMock.eq(config)))
- .andReturn(ExactlyOnceSupport.SUPPORTED);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll(connectorMock);
+ // Initial rebalance where this member becomes the leader
+ herder.tick();
Review Comment:
Same question about the extra tick
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3929,543 +3008,636 @@ public void testPollDurationOnSlowConnectorOperations() {
final int rebalanceDelayMs = 20000;
final long operationDelayMs = 10000;
final long maxPollWaitMs = rebalanceDelayMs - operationDelayMs;
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(connectProtocolVersion);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(connectProtocolVersion);
// Assign the connector to this worker, and have it start
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- Capture<Callback<TargetState>> onFirstStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onFirstStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> onFirstStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
time.sleep(operationDelayMs);
onFirstStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- // We should poll for less than the delay - time to start the connector, meaning that a long connector start
- // does not delay the poll timeout
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), eq(CONN1_CONFIG), any(), eq(herder), eq(TargetState.STARTED), onFirstStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
+
+ herder.tick();
// Rebalance again due to config update
- member.wakeup();
- PowerMock.expectLastCall();
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT_UPDATED_CONN1_CONFIG);
-
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(connectProtocolVersion);
- Capture<Callback<TargetState>> onSecondStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onSecondStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT_UPDATED_CONN1_CONFIG);
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
+
+ ArgumentCaptor<Callback<TargetState>> onSecondStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
time.sleep(operationDelayMs);
onSecondStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), eq(CONN1_CONFIG_UPDATED), any(), eq(herder), eq(TargetState.STARTED), onSecondStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, invocation -> TASK_CONFIGS);
+
+ configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
+ herder.tick();
// Third tick should resolve all outstanding requests
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
// which includes querying the connector task configs after the update
- expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, () -> {
+ expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, invocation -> {
time.sleep(operationDelayMs);
return TASK_CONFIGS;
});
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
- herder.tick();
- configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
- herder.tick();
herder.tick();
- PowerMock.verifyAll();
+
+ // We should poll for less than the delay - time to start the connector, meaning that a long connector start
+ // does not delay the poll timeout
+ verify(member, times(3)).poll(leq(maxPollWaitMs));
+ verify(worker, times(2)).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), any());
+ verifyNoMoreInteractions(member, worker, configBackingStore);
}
@Test
public void shouldThrowWhenStartAndStopExecutorThrowsRejectedExecutionExceptionAndHerderNotStopping() {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Arrays.asList(CONN1), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
-
- PowerMock.replayAll();
herder.startAndStopExecutor.shutdown();
assertThrows(RejectedExecutionException.class, herder::tick);
+ }
+
+ @Test
+ public void testTaskReconfigurationRetriesWithConnectorTaskConfigsException() {
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
+
+ when(worker.isRunning(CONN1)).thenReturn(true);
+ when(worker.getPlugins()).thenReturn(plugins);
+
+ SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+
+ when(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig))
+ .thenThrow(new ConnectException("Failed to generate task configs"))
+ .thenThrow(new ConnectException("Failed to generate task configs"))
+ .thenReturn(TASK_CONFIGS);
+
+ expectAndVerifyTaskReconfigurationRetries();
+ }
+
+ @Test
+ public void testTaskReconfigurationRetriesWithLeaderRequestForwardingException() {
+ herder = mock(DistributedHerder.class, withSettings().defaultAnswer(CALLS_REAL_METHODS).useConstructor(new DistributedConfig(HERDER_CONFIG),
+ worker, WORKER_ID, KAFKA_CLUSTER_ID, statusBackingStore, configBackingStore, member, MEMBER_URL, restClient, metrics, time,
+ noneConnectorClientConfigOverridePolicy, Collections.emptyList(), new MockSynchronousExecutor(), new AutoCloseable[]{}));
+
+ rebalanceListener = herder.new RebalanceListener(time);
+
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList(), false);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
+
+ when(worker.isRunning(CONN1)).thenReturn(true);
+ when(worker.getPlugins()).thenReturn(plugins);
+
+ SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+
+ List<Map<String, String>> changedTaskConfigs = new ArrayList<>(TASK_CONFIGS);
+ changedTaskConfigs.add(TASK_CONFIG);
+ when(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig)).thenReturn(changedTaskConfigs);
+
+ when(restClient.httpRequest(any(), eq("POST"), any(), any(), any(), any(), any()))
+ .thenThrow(new ConnectException("Request to leader to reconfigure connector tasks failed"))
+ .thenThrow(new ConnectException("Request to leader to reconfigure connector tasks failed"))
+ .thenReturn(null);
+
+ expectAndVerifyTaskReconfigurationRetries();
+ }
+
+ private void expectAndVerifyTaskReconfigurationRetries() {
+ // initial tick
+ herder.tick();
+ herder.requestTaskReconfiguration(CONN1);
+ // process the task reconfiguration request in this tick
+ herder.tick();
+ // advance the time by 250ms so that the task reconfiguration request with initial retry backoff is processed
+ time.sleep(250);
+ herder.tick();
+ // advance the time by 500ms so that the task reconfiguration request with double the initial retry backoff is processed
+ time.sleep(500);
+ herder.tick();
+
+ // 1. end of initial tick when no request has been added to the herder queue yet
+ // 2. the third task reconfiguration request is expected to pass; so expect no more retries (a Long.MAX_VALUE poll
+ // timeout indicates that there is no herder request currently in the queue)
+ verify(member, times(2)).poll(eq(Long.MAX_VALUE));
+
+ // task reconfiguration herder request with initial retry backoff
+ verify(member).poll(eq(250L));
+
+ // task reconfiguration herder request with double the initial retry backoff
+ verify(member).poll(eq(500L));
+
+ verifyNoMoreInteractions(member, worker);
+ }
+
+ @Test
+ public void processRestartRequestsFailureSuppression() {
+ doNothing().when(member).wakeup();
+
+ final String connectorName = "foo";
+ RestartRequest restartRequest = new RestartRequest(connectorName, false, false);
+ doThrow(new RuntimeException()).when(herder).buildRestartPlan(restartRequest);
+
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(1, herder.pendingRestartRequests.size());
+ herder.processRestartRequests();
+ assertTrue(herder.pendingRestartRequests.isEmpty());
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
+ }
+
+ @Test
+ public void processRestartRequestsDequeue() {
+ doNothing().when(member).wakeup();
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(any(RestartRequest.class));
+
+ RestartRequest restartRequest = new RestartRequest("foo", false, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+ restartRequest = new RestartRequest("bar", false, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(2, herder.pendingRestartRequests.size());
+ herder.processRestartRequests();
+ assertTrue(herder.pendingRestartRequests.isEmpty());
+ }
+
+ @Test
+ public void preserveHighestImpactRestartRequest() {
+ doNothing().when(member).wakeup();
+
+ final String connectorName = "foo";
+ RestartRequest restartRequest = new RestartRequest(connectorName, false, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+
+ // Will overwrite as this is higher impact
+ restartRequest = new RestartRequest(connectorName, false, true);
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(1, herder.pendingRestartRequests.size());
+ assertFalse(herder.pendingRestartRequests.get(connectorName).onlyFailed());
+ assertTrue(herder.pendingRestartRequests.get(connectorName).includeTasks());
+
+ // Will be ignored as the existing request has higher impact
+ restartRequest = new RestartRequest(connectorName, true, false);
+ configUpdateListener.onRestartRequest(restartRequest);
+ assertEquals(1, herder.pendingRestartRequests.size());
+ // Compare against existing request
+ assertFalse(herder.pendingRestartRequests.get(connectorName).onlyFailed());
+ assertTrue(herder.pendingRestartRequests.get(connectorName).includeTasks());
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidation() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(ExactlyOnceSupport.SUPPORTED);
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
+
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertEquals(Collections.emptyList(), errors);
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidationOnUnsupportedConnector() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(ExactlyOnceSupport.UNSUPPORTED);
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
+
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertEquals(
+ Collections.singletonList("The connector does not support exactly-once semantics with the provided configuration."),
+ errors);
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidationOnUnknownConnector() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(null);
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
+
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("The connector does not implement the API required for preflight validation of exactly-once source support."));
+ assertEquals(1, errors.size());
+ }
+
+ @Test
+ public void testExactlyOnceSourceSupportValidationHandlesConnectorErrorsGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ String errorMessage = "time to add a new unit test :)";
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenThrow(new NullPointerException(errorMessage));
+
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- PowerMock.verifyAll();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains(errorMessage));
+ assertEquals(1, errors.size());
}
@Test
- public void testTaskReconfigurationRetriesWithConnectorTaskConfigsException() {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
- expectConfigRefreshAndSnapshot(SNAPSHOT);
+ public void testExactlyOnceSourceSupportValidationWhenExactlyOnceNotEnabledOnWorker() {
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
- // end of initial tick
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.exactlyOnceSupport(eq(config))).thenReturn(ExactlyOnceSupport.SUPPORTED);
- member.wakeup();
- PowerMock.expectLastCall().anyTimes();
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- member.ensureActive();
- PowerMock.expectLastCall().anyTimes();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertEquals(
+ Collections.singletonList("This worker does not have exactly-once source support enabled."),
+ errors);
+ }
- EasyMock.expect(worker.isRunning(CONN1)).andReturn(true).anyTimes();
- EasyMock.expect(worker.getPlugins()).andReturn(plugins).anyTimes();
+ @Test
+ public void testExactlyOnceSourceSupportValidationHandlesInvalidValuesGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, "invalid");
- SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig))
- .andThrow(new ConnectException("Failed to generate task configs")).times(2);
+ SourceConnector connectorMock = mock(SourceConnector.class);
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig)).andReturn(TASK_CONFIGS);
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- expectAndVerifyTaskReconfigurationRetries();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("String must be one of (case insensitive): "));
+ assertEquals(1, errors.size());
}
@Test
- public void testTaskReconfigurationRetriesWithLeaderRequestForwardingException() {
- herder = PowerMock.createPartialMock(DistributedHerder.class,
- new String[]{"connectorType", "updateDeletedConnectorStatus", "updateDeletedTaskStatus", "validateConnectorConfig", "buildRestartPlan", "recordRestarting"},
- new DistributedConfig(HERDER_CONFIG), worker, WORKER_ID, KAFKA_CLUSTER_ID,
- statusBackingStore, configBackingStore, member, MEMBER_URL, restClient, metrics, time, noneConnectorClientConfigOverridePolicy,
- Collections.emptyList(), new MockSynchronousExecutor(), new AutoCloseable[]{});
+ public void testConnectorTransactionBoundaryValidation() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, CONNECTOR.toString());
- rebalanceListener = herder.new RebalanceListener(time);
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.canDefineTransactionBoundaries(eq(config)))
+ .thenReturn(ConnectorTransactionBoundaries.SUPPORTED);
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- expectRebalance(1, Collections.emptyList(), Collections.emptyList(), false);
- expectConfigRefreshAndSnapshot(SNAPSHOT);
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- // end of initial tick
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertEquals(Collections.emptyList(), errors);
+ }
- member.wakeup();
- PowerMock.expectLastCall().anyTimes();
+ @Test
+ public void testConnectorTransactionBoundaryValidationOnUnsupportedConnector() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, CONNECTOR.toString());
- member.ensureActive();
- PowerMock.expectLastCall().anyTimes();
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ when(connectorMock.canDefineTransactionBoundaries(eq(config)))
+ .thenReturn(ConnectorTransactionBoundaries.UNSUPPORTED);
- EasyMock.expect(worker.isRunning(CONN1)).andReturn(true).anyTimes();
- EasyMock.expect(worker.getPlugins()).andReturn(plugins).anyTimes();
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("The connector does not support connector-defined transaction boundaries with the given configuration."));
+ assertEquals(1, errors.size());
+ }
- List<Map<String, String>> changedTaskConfigs = new ArrayList<>(TASK_CONFIGS);
- changedTaskConfigs.add(TASK_CONFIG);
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig)).andReturn(changedTaskConfigs).anyTimes();
+ @Test
+ public void testConnectorTransactionBoundaryValidationHandlesConnectorErrorsGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, CONNECTOR.toString());
- EasyMock.expect(restClient.httpRequest(
- EasyMock.anyString(), EasyMock.eq("POST"), EasyMock.anyObject(HttpHeaders.class),
- EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(SecretKey.class), EasyMock.anyString())
- ).andThrow(new ConnectException("Request to leader to reconfigure connector tasks failed")).times(2);
+ SourceConnector connectorMock = mock(SourceConnector.class);
+ String errorMessage = "Wait I thought we tested for this?";
+ when(connectorMock.canDefineTransactionBoundaries(eq(config))).thenThrow(new ConnectException(errorMessage));
- EasyMock.expect(restClient.httpRequest(
- EasyMock.anyString(), EasyMock.eq("POST"), EasyMock.anyObject(HttpHeaders.class),
- EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(SecretKey.class), EasyMock.anyString())
- ).andReturn(null);
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- expectAndVerifyTaskReconfigurationRetries();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains(errorMessage));
+ assertEquals(1, errors.size());
}
- private void expectAndVerifyTaskReconfigurationRetries() {
- // task reconfiguration herder request with initial retry backoff
- member.poll(EasyMock.eq(250L));
- PowerMock.expectLastCall();
-
- // task reconfiguration herder request with double the initial retry backoff
- member.poll(EasyMock.eq(500L));
- PowerMock.expectLastCall();
-
- // the third task reconfiguration request is expected to pass; so expect no more retries (a Long.MAX_VALUE poll
- // timeout indicates that there is no herder request currently in the queue)
- member.poll(EasyMock.eq(Long.MAX_VALUE));
- PowerMock.expectLastCall();
+ @Test
+ public void testConnectorTransactionBoundaryValidationHandlesInvalidValuesGracefully() {
+ herder = exactlyOnceHerder();
+ Map<String, String> config = new HashMap<>();
+ config.put(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG, "CONNECTOR.toString()");
- PowerMock.replayAll();
+ SourceConnector connectorMock = mock(SourceConnector.class);
- // initial tick
- herder.tick();
- herder.requestTaskReconfiguration(CONN1);
- // process the task reconfiguration request in this tick
- herder.tick();
- // advance the time by 250ms so that the task reconfiguration request with initial retry backoff is processed
- time.sleep(250);
- herder.tick();
- // advance the time by 500ms so that the task reconfiguration request with double the initial retry backoff is processed
- time.sleep(500);
- herder.tick();
+ Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
+ connectorMock, SourceConnectorConfig.configDef(), config);
- PowerMock.verifyAll();
+ List<String> errors = validatedConfigs.get(SourceConnectorConfig.TRANSACTION_BOUNDARY_CONFIG).errorMessages();
+ assertFalse(errors.isEmpty());
+ assertTrue(
+ "Error message did not contain expected text: " + errors.get(0),
+ errors.get(0).contains("String must be one of (case insensitive): "));
+ assertEquals(1, errors.size());
}
@Test
public void testConnectorOffsets() throws Exception {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- expectAnyTicks();
- member.wakeup();
- PowerMock.expectLastCall();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
+ herder.tick();
+
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
ConnectorOffsets offsets = new ConnectorOffsets(Collections.singletonList(new ConnectorOffset(
Collections.singletonMap("partitionKey", "partitionValue"),
Collections.singletonMap("offsetKey", "offsetValue"))));
- Capture<Callback<ConnectorOffsets>> callbackCapture = newCapture();
- worker.connectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), capture(callbackCapture));
- PowerMock.expectLastCall().andAnswer(() -> {
+
+ ArgumentCaptor<Callback<ConnectorOffsets>> callbackCapture = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
callbackCapture.getValue().onCompletion(null, offsets);
return null;
- });
+ }).when(worker).connectorOffsets(eq(CONN1), eq(CONN1_CONFIG), callbackCapture.capture());
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<ConnectorOffsets> cb = new FutureCallback<>();
herder.connectorOffsets(CONN1, cb);
herder.tick();
assertEquals(offsets, cb.get(1000, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testModifyConnectorOffsetsUnknownConnector() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // Now handle the connector offsets modification request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
+
+ // Now handle the connector offsets modification request
FutureCallback<Message> callback = new FutureCallback<>();
herder.modifyConnectorOffsets("connector-does-not-exist", new HashMap<>(), callback);
herder.tick();
ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(e.getCause() instanceof NotFoundException);
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsConnectorNotInStoppedState() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // Now handle the connector offsets modification request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
+
+ // Now handle the connector offsets modification request
FutureCallback<Message> callback = new FutureCallback<>();
herder.modifyConnectorOffsets(CONN1, null, callback);
herder.tick();
ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(e.getCause() instanceof BadRequestException);
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsNotLeader() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), false);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // Now handle the connector offsets modification request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
herder.tick();
+
+ // Now handle the connector offsets modification request
FutureCallback<Message> callback = new FutureCallback<>();
herder.modifyConnectorOffsets(CONN1, new HashMap<>(), callback);
herder.tick();
ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(e.getCause() instanceof NotLeaderException);
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsSinkConnector() throws Exception {
- EasyMock.reset(herder);
- EasyMock.expect(herder.connectorType(EasyMock.anyObject())).andReturn(ConnectorType.SINK).anyTimes();
- PowerMock.expectPrivate(herder, "updateDeletedConnectorStatus").andVoid().anyTimes();
- PowerMock.expectPrivate(herder, "updateDeletedTaskStatus").andVoid().anyTimes();
-
+ when(herder.connectorType(any())).thenReturn(ConnectorType.SINK);
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ herder.tick();
// Now handle the alter connector offsets request
Map<Map<String, ?>, Map<String, ?>> offsets = Collections.singletonMap(
Collections.singletonMap("partitionKey", "partitionValue"),
Collections.singletonMap("offsetKey", "offsetValue"));
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- Capture<Callback<Message>> workerCallbackCapture = Capture.newInstance();
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), EasyMock.eq(offsets), capture(workerCallbackCapture));
+
+ ArgumentCaptor<Callback<Message>> workerCallbackCapture = ArgumentCaptor.forClass(Callback.class);
Message msg = new Message("The offsets for this connector have been altered successfully");
- EasyMock.expectLastCall().andAnswer(() -> {
+ doAnswer(invocation -> {
workerCallbackCapture.getValue().onCompletion(null, msg);
return null;
- });
+ }).when(worker).modifyConnectorOffsets(eq(CONN1), eq(CONN1_CONFIG), eq(offsets), workerCallbackCapture.capture());
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Message> callback = new FutureCallback<>();
herder.alterConnectorOffsets(CONN1, offsets, callback);
herder.tick();
assertEquals(msg, callback.get(1000L, TimeUnit.MILLISECONDS));
assertEquals("The offsets for this connector have been altered successfully", msg.message());
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsSourceConnectorExactlyOnceDisabled() throws Exception {
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
// Now handle the reset connector offsets request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- Capture<Callback<Message>> workerCallbackCapture = Capture.newInstance();
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), isNull(), capture(workerCallbackCapture));
+ ArgumentCaptor<Callback<Message>> workerCallbackCapture = ArgumentCaptor.forClass(Callback.class);
Message msg = new Message("The offsets for this connector have been reset successfully");
- EasyMock.expectLastCall().andAnswer(() -> {
+ doAnswer(invocation -> {
workerCallbackCapture.getValue().onCompletion(null, msg);
return null;
- });
+ }).when(worker).modifyConnectorOffsets(eq(CONN1), eq(CONN1_CONFIG), isNull(), workerCallbackCapture.capture());
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Message> callback = new FutureCallback<>();
herder.resetConnectorOffsets(CONN1, callback);
herder.tick();
assertEquals(msg, callback.get(1000L, TimeUnit.MILLISECONDS));
assertEquals("The offsets for this connector have been reset successfully", msg.message());
-
- PowerMock.verifyAll();
}
@Test
public void testModifyOffsetsSourceConnectorExactlyOnceEnabled() throws Exception {
// Setup herder with exactly-once support for source connectors enabled
herder = exactlyOnceHerder();
rebalanceListener = herder.new RebalanceListener(time);
- PowerMock.expectPrivate(herder, "updateDeletedConnectorStatus").andVoid().anyTimes();
- PowerMock.expectPrivate(herder, "updateDeletedTaskStatus").andVoid().anyTimes();
-
// Get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall().anyTimes();
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// Now handle the alter connector offsets request
- Map<Map<String, ?>, Map<String, ?>> offsets = Collections.singletonMap(
- Collections.singletonMap("partitionKey", "partitionValue"),
- Collections.singletonMap("offsetKey", "offsetValue"));
- member.wakeup();
- PowerMock.expectLastCall().anyTimes();
- member.ensureActive();
- PowerMock.expectLastCall().anyTimes();
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- EasyMock.expect(herder.connectorType(EasyMock.anyObject())).andReturn(ConnectorType.SOURCE).anyTimes();
+ doNothing().when(member).ensureActive();
+ when(herder.connectorType(any())).thenReturn(ConnectorType.SOURCE);
// Expect a round of zombie fencing to occur
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1);
- KafkaFuture<Void> workerFencingFuture = EasyMock.mock(KafkaFuture.class);
- KafkaFuture<Void> herderFencingFuture = EasyMock.mock(KafkaFuture.class);
- EasyMock.expect(worker.fenceZombies(CONN1, SNAPSHOT_STOPPED_CONN1.taskCountRecord(CONN1), CONN1_CONFIG)).andReturn(workerFencingFuture);
- EasyMock.expect(workerFencingFuture.thenApply(EasyMock.<KafkaFuture.BaseFunction<Void, Void>>anyObject())).andReturn(herderFencingFuture);
-
- // Two fencing callbacks are added - one is in ZombieFencing::start itself to remove the connector from the active
- // fencing list. The other is the callback passed from DistributedHerder::modifyConnectorOffsets in order to
- // queue up the actual alter offsets request if the zombie fencing succeeds.
- for (int i = 0; i < 2; i++) {
- Capture<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = EasyMock.newCapture();
- EasyMock.expect(herderFencingFuture.whenComplete(EasyMock.capture(herderFencingCallback))).andAnswer(() -> {
- herderFencingCallback.getValue().accept(null, null);
- return null;
- });
- }
-
- Capture<Callback<Message>> workerCallbackCapture = Capture.newInstance();
- Message msg = new Message("The offsets for this connector have been altered successfully");
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), EasyMock.eq(offsets), capture(workerCallbackCapture));
- EasyMock.expectLastCall().andAnswer(() -> {
- workerCallbackCapture.getValue().onCompletion(null, msg);
+ KafkaFuture<Void> workerFencingFuture = mock(KafkaFuture.class);
+ KafkaFuture<Void> herderFencingFuture = mock(KafkaFuture.class);
+ when(worker.fenceZombies(CONN1, SNAPSHOT_STOPPED_CONN1.taskCountRecord(CONN1), CONN1_CONFIG)).thenReturn(workerFencingFuture);
+ when(workerFencingFuture.thenApply(any(KafkaFuture.BaseFunction.class))).thenReturn(herderFencingFuture);
+
+ ArgumentCaptor<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = ArgumentCaptor.forClass(KafkaFuture.BiConsumer.class);
+ when(herderFencingFuture.whenComplete(herderFencingCallback.capture())).thenAnswer(invocation -> {
+ herderFencingCallback.getValue().accept(null, null);
return null;
});
- // Handle the second alter connector offsets request. No zombie fencing request to the worker is expected now since we
- // already did a round of zombie fencing last time and no new tasks came up in the meanwhile. The config snapshot is
- // refreshed once at the beginning of the DistributedHerder::modifyConnectorOffsets method, once before checking
- // whether zombie fencing is required, and once before actually proceeding to alter connector offsets.
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1_FENCED);
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1_FENCED);
- expectConfigRefreshAndSnapshot(SNAPSHOT_STOPPED_CONN1_FENCED);
- Capture<Callback<Message>> workerCallbackCapture2 = Capture.newInstance();
- worker.modifyConnectorOffsets(EasyMock.eq(CONN1), EasyMock.eq(CONN1_CONFIG), EasyMock.eq(offsets), capture(workerCallbackCapture2));
- EasyMock.expectLastCall().andAnswer(() -> {
- workerCallbackCapture2.getValue().onCompletion(null, msg);
- return null;
- });
+ ArgumentCaptor<Callback<Message>> workerCallbackCapture = ArgumentCaptor.forClass(Callback.class);
+ Message msg = new Message("The offsets for this connector have been altered successfully");
- PowerMock.replayAll(workerFencingFuture, herderFencingFuture);
+ Map<Map<String, ?>, Map<String, ?>> offsets = Collections.singletonMap(
+ Collections.singletonMap("partitionKey", "partitionValue"),
+ Collections.singletonMap("offsetKey", "offsetValue"));
+ doAnswer(invocation -> {
+ workerCallbackCapture.getValue().onCompletion(null, msg);
+ return null;
+ }).when(worker).modifyConnectorOffsets(eq(CONN1), eq(CONN1_CONFIG), eq(offsets), workerCallbackCapture.capture());
- herder.tick();
FutureCallback<Message> callback = new FutureCallback<>();
herder.alterConnectorOffsets(CONN1, offsets, callback);
- // Process the zombie fencing request
- herder.tick();
- // Process the alter offsets request
+ // Process the zombie fencing request that is queued up first followed by the actual alter offsets request
Review Comment:
Looks like we're doing the zombie fencing and offset altering in the same tick, whereas before we were doing that in one tick. Is this intentional? Shouldn't the two operations still be split across two ticks?
Ah... is it because we're adding the followup request to alter the connector offsets to the herder's request queue while it's in the middle of handling the initial request to fence zombie source tasks, and so the herder immediately handles the follow-up request once it's done with the first one?
I guess this should be fine, although the scenario it simulates isn't exactly standard (most of the time zombie fencing should take a little longer).
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -921,498 +798,197 @@ public void testConnectorNameConflictsWithWorkerGroupId() {
assertEquals(
Collections.singletonList("Consumer group for sink connector named test-group conflicts with Connect worker group connect-test-group"),
nameConfig.errorMessages());
-
- PowerMock.verifyAll();
}
@Test
- public void testExactlyOnceSourceSupportValidation() {
- herder = exactlyOnceHerder();
- Map<String, String> config = new HashMap<>();
- config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+ public void testCreateConnectorAlreadyExists() throws Exception {
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
- SourceConnector connectorMock = PowerMock.createMock(SourceConnector.class);
- EasyMock.expect(connectorMock.exactlyOnceSupport(EasyMock.eq(config)))
- .andReturn(ExactlyOnceSupport.SUPPORTED);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll(connectorMock);
+ // Initial rebalance where this member becomes the leader
+ herder.tick();
- Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
- connectorMock, SourceConnectorConfig.configDef(), config);
+ // mock the actual validation since its asynchronous nature is difficult to test and should
+ // be covered sufficiently by the unit tests for the AbstractHerder class
+ ArgumentCaptor<Callback<ConfigInfos>> validateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ validateCallback.getValue().onCompletion(null, CONN1_CONFIG_INFOS);
+ return null;
+ }).when(herder).validateConnectorConfig(eq(CONN1_CONFIG), validateCallback.capture());
- List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
- assertEquals(Collections.emptyList(), errors);
+ // This will occur just before/during the second tick
+ doNothing().when(member).ensureActive();
- PowerMock.verifyAll();
- }
+ herder.putConnectorConfig(CONN1, CONN1_CONFIG, false, putConnectorCallback);
+ herder.tick();
+ herder.tick();
- @Test
- public void testExactlyOnceSourceSupportValidationOnUnsupportedConnector() {
- herder = exactlyOnceHerder();
- Map<String, String> config = new HashMap<>();
- config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+ time.sleep(1000L);
+ assertStatistics(3, 1, 100, 1000L);
- SourceConnector connectorMock = PowerMock.createMock(SourceConnector.class);
- EasyMock.expect(connectorMock.exactlyOnceSupport(EasyMock.eq(config)))
- .andReturn(ExactlyOnceSupport.UNSUPPORTED);
+ // CONN1 already exists
+ verify(putConnectorCallback).onCompletion(any(AlreadyExistsException.class), isNull());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore, putConnectorCallback);
+ }
- PowerMock.replayAll(connectorMock);
+ @Test
+ public void testDestroyConnector() throws Exception {
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ // Start with one connector
+ expectRebalance(1, Arrays.asList(CONN1), Collections.emptyList(), true);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
- Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
- connectorMock, SourceConnectorConfig.configDef(), config);
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ onStart.getValue().onCompletion(null, TargetState.STARTED);
+ return true;
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
- List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
- assertEquals(
- Collections.singletonList("The connector does not support exactly-once semantics with the provided configuration."),
- errors);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- PowerMock.verifyAll();
- }
+ // And delete the connector
+ doNothing().when(configBackingStore).removeConnectorConfig(CONN1);
+ doNothing().when(putConnectorCallback).onCompletion(null, new Herder.Created<>(false, null));
- @Test
- public void testExactlyOnceSourceSupportValidationOnUnknownConnector() {
- herder = exactlyOnceHerder();
- Map<String, String> config = new HashMap<>();
- config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+ herder.deleteConnectorConfig(CONN1, putConnectorCallback);
+ herder.tick();
- SourceConnector connectorMock = PowerMock.createMock(SourceConnector.class);
- EasyMock.expect(connectorMock.exactlyOnceSupport(EasyMock.eq(config)))
- .andReturn(null);
+ time.sleep(1000L);
+ assertStatistics("leaderUrl", false, 3, 1, 100, 1000L);
- PowerMock.replayAll(connectorMock);
+ // The change eventually is reflected to the config topic and the deleted connector and
+ // tasks are revoked
+ TopicStatus fooStatus = new TopicStatus(FOO_TOPIC, CONN1, 0, time.milliseconds());
+ TopicStatus barStatus = new TopicStatus(BAR_TOPIC, CONN1, 0, time.milliseconds());
+ when(statusBackingStore.getAllTopics(eq(CONN1))).thenReturn(new HashSet<>(Arrays.asList(fooStatus, barStatus)));
+ doNothing().when(statusBackingStore).deleteTopic(eq(CONN1), eq(FOO_TOPIC));
+ doNothing().when(statusBackingStore).deleteTopic(eq(CONN1), eq(BAR_TOPIC));
- Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
- connectorMock, SourceConnectorConfig.configDef(), config);
+ expectRebalance(Arrays.asList(CONN1), Arrays.asList(TASK1),
+ ConnectProtocol.Assignment.NO_ERROR, 2, "leader", "leaderUrl",
+ Collections.emptyList(), Collections.emptyList(), 0, true);
+ expectConfigRefreshAndSnapshot(ClusterConfigState.EMPTY);
+ doNothing().when(member).requestRejoin();
- List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
- assertFalse(errors.isEmpty());
- assertTrue(
- "Error message did not contain expected text: " + errors.get(0),
- errors.get(0).contains("The connector does not implement the API required for preflight validation of exactly-once source support."));
- assertEquals(1, errors.size());
+ configUpdateListener.onConnectorConfigRemove(CONN1); // read updated config that removes the connector
+ herder.configState = ClusterConfigState.EMPTY;
+ herder.tick();
+ time.sleep(1000L);
+ assertStatistics("leaderUrl", true, 3, 1, 100, 2100L);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore, putConnectorCallback);
}
@Test
- public void testExactlyOnceSourceSupportValidationHandlesConnectorErrorsGracefully() {
- herder = exactlyOnceHerder();
- Map<String, String> config = new HashMap<>();
- config.put(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG, REQUIRED.toString());
+ public void testRestartConnector() throws Exception {
- SourceConnector connectorMock = PowerMock.createMock(SourceConnector.class);
- String errorMessage = "time to add a new unit test :)";
- EasyMock.expect(connectorMock.exactlyOnceSupport(EasyMock.eq(config)))
- .andThrow(new NullPointerException(errorMessage));
+ // get the initial assignment
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, singletonList(CONN1), Collections.emptyList(), true);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
- PowerMock.replayAll(connectorMock);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- Map<String, ConfigValue> validatedConfigs = herder.validateSourceConnectorConfig(
- connectorMock, SourceConnectorConfig.configDef(), config);
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ onStart.getValue().onCompletion(null, TargetState.STARTED);
+ return true;
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
- List<String> errors = validatedConfigs.get(SourceConnectorConfig.EXACTLY_ONCE_SUPPORT_CONFIG).errorMessages();
- assertFalse(errors.isEmpty());
- assertTrue(
- "Error message did not contain expected text: " + errors.get(0),
- errors.get(0).contains(errorMessage));
- assertEquals(1, errors.size());
+ // Initial rebalance where this member becomes the leader
+ herder.tick();
+
+ doNothing().when(member).ensureActive();
+
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
+
+ FutureCallback<Void> callback = new FutureCallback<>();
+ herder.restartConnector(CONN1, callback);
+ herder.tick();
+ callback.get(1000L, TimeUnit.MILLISECONDS);
- PowerMock.verifyAll();
+ verify(worker, times(2)).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), any());
+ verify(worker, times(2)).connectorTaskConfigs(eq(CONN1), any());
Review Comment:
Optional suggestion: add a verification for the number of times we stopped the connector
```suggestion
verify(worker, times(2)).connectorTaskConfigs(eq(CONN1), any());
verify(worker).stopAndAwaitConnector(CONN1);
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
Review Comment:
I know this isn't necessary for the test to pass, but right now we end up starting the connector with a null config, which is a little strange (and doesn't accurately test the scenario we want to cover with this test). We can fix this with the same approach that's used in other tests to update the herder's config snapshot:
```suggestion
when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
herder.configState = SNAPSHOT;
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
stateCallback.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
-
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
-
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyTasks() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
// The connector has three tasks
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Arrays.asList(TASK0, TASK1, TASK2)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ when(restartPlan.taskIdsToRestart()).thenReturn(Arrays.asList(TASK0, TASK1, TASK2));
+ when(restartPlan.totalTaskCount()).thenReturn(3);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.emptyList());
// But only one task is assigned to this worker
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(TASK0)).anyTimes();
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(TASK0));
herder.configState = SNAPSHOT;
- worker.stopAndAwaitTasks(Collections.singletonList(TASK0));
- PowerMock.expectLastCall();
-
- herder.onRestart(TASK0);
- EasyMock.expectLastCall();
-
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(TASK0));
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksBoth() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(taskId)).anyTimes();
-
- herder.configState = SNAPSHOT;
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
+ when(restartPlan.totalTaskCount()).thenReturn(1);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
-
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(taskId));
+ herder.configState = SNAPSHOT;
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- worker.stopAndAwaitTasks(Collections.singletonList(taskId));
- PowerMock.expectLastCall();
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ stateCallback.getValue().onCompletion(null, TargetState.STARTED);
+ return true;
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
- herder.onRestart(taskId);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(taskId));
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTask() throws Exception {
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, conn1SinkConfig)).andStubReturn(TASK_CONFIGS);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), singletonList(TASK0), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.stopAndAwaitTask(TASK0);
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
+ doNothing().when(worker).stopAndAwaitTask(TASK0);
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
callback.get(1000L, TimeUnit.MILLISECONDS);
- PowerMock.verifyAll();
+ verify(worker, times(2)).startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any());
Review Comment:
Optional suggestion: similar to above, add an assertion about exactly how many times we stopped the task
```suggestion
verify(worker, times(2)).startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any());
verify(worker).stopAndAwaitTask(TASK0);
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
stateCallback.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
-
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
-
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyTasks() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
// The connector has three tasks
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Arrays.asList(TASK0, TASK1, TASK2)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ when(restartPlan.taskIdsToRestart()).thenReturn(Arrays.asList(TASK0, TASK1, TASK2));
+ when(restartPlan.totalTaskCount()).thenReturn(3);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.emptyList());
// But only one task is assigned to this worker
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(TASK0)).anyTimes();
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(TASK0));
herder.configState = SNAPSHOT;
- worker.stopAndAwaitTasks(Collections.singletonList(TASK0));
- PowerMock.expectLastCall();
-
- herder.onRestart(TASK0);
- EasyMock.expectLastCall();
-
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(TASK0));
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksBoth() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(taskId)).anyTimes();
-
- herder.configState = SNAPSHOT;
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
+ when(restartPlan.totalTaskCount()).thenReturn(1);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
-
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(taskId));
+ herder.configState = SNAPSHOT;
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- worker.stopAndAwaitTasks(Collections.singletonList(taskId));
- PowerMock.expectLastCall();
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ stateCallback.getValue().onCompletion(null, TargetState.STARTED);
+ return true;
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
- herder.onRestart(taskId);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(taskId));
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTask() throws Exception {
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, conn1SinkConfig)).andStubReturn(TASK_CONFIGS);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), singletonList(TASK0), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.stopAndAwaitTask(TASK0);
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
+ doNothing().when(worker).stopAndAwaitTask(TASK0);
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
callback.get(1000L, TimeUnit.MILLISECONDS);
- PowerMock.verifyAll();
+ verify(worker, times(2)).startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartUnknownTask() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ herder.tick();
+ doNothing().when(member).ensureActive();
FutureCallback<Void> callback = new FutureCallback<>();
- herder.tick();
herder.restartTask(new ConnectorTaskId("blah", 0), callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotFoundException);
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotFoundException.class, e.getCause().getClass());
- PowerMock.verifyAll();
- }
-
- @Test
- public void testRequestProcessingOrder() {
- final DistributedHerder.DistributedHerderRequest req1 = herder.addRequest(100, null, null);
- final DistributedHerder.DistributedHerderRequest req2 = herder.addRequest(10, null, null);
- final DistributedHerder.DistributedHerderRequest req3 = herder.addRequest(200, null, null);
- final DistributedHerder.DistributedHerderRequest req4 = herder.addRequest(200, null, null);
-
- assertEquals(req2, herder.requests.pollFirst()); // lowest delay
- assertEquals(req1, herder.requests.pollFirst()); // next lowest delay
- assertEquals(req3, herder.requests.pollFirst()); // same delay as req4, but added first
- assertEquals(req4, herder.requests.pollFirst());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTaskRedirectToLeader() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotLeaderException);
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotLeaderException.class, e.getCause().getClass());
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTaskRedirectToOwner() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the task restart
String ownerUrl = "ownerUrl";
- EasyMock.expect(member.ownerUrl(TASK0)).andReturn(ownerUrl);
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ when(member.ownerUrl(TASK0)).thenReturn(ownerUrl);
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotAssignedException);
- NotAssignedException notAssignedException = (NotAssignedException) e.getCause();
- assertEquals(ownerUrl, notAssignedException.forwardUrl());
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotAssignedException.class, e.getCause().getClass());
+ assertEquals(ownerUrl, ((NotAssignedException) e.getCause()).forwardUrl());
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
- public void testConnectorConfigAdded() {
+ public void testRequestProcessingOrder() {
+ final DistributedHerder.DistributedHerderRequest req1 = herder.addRequest(100, null, null);
+ final DistributedHerder.DistributedHerderRequest req2 = herder.addRequest(10, null, null);
+ final DistributedHerder.DistributedHerderRequest req3 = herder.addRequest(200, null, null);
+ final DistributedHerder.DistributedHerderRequest req4 = herder.addRequest(200, null, null);
+
+ assertEquals(req2, herder.requests.pollFirst()); // lowest delay
+ assertEquals(req1, herder.requests.pollFirst()); // next lowest delay
+ assertEquals(req3, herder.requests.pollFirst()); // same delay as req4, but added first
+ assertEquals(req4, herder.requests.pollFirst());
+ }
+
+ @Test
+ public void testConnectorConfigAdded() throws Exception {
// If a connector was added, we need to rebalance
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join, no configs so no need to catch up on config topic
expectRebalance(-1, Collections.emptyList(), Collections.emptyList());
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick(); // join
- // apply config
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
// Checks for config updates and starts rebalance
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
- member.requestRejoin();
- PowerMock.expectLastCall();
+ doNothing().when(configBackingStore).refresh(anyLong(), any());
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
// Performs rebalance and gets new assignment
expectRebalance(Collections.emptyList(), Collections.emptyList(),
ConnectProtocol.Assignment.NO_ERROR, 1, Arrays.asList(CONN1), Collections.emptyList());
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- Capture<Callback<TargetState>> onStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
onStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
- PowerMock.replayAll();
+ herder.tick(); // apply config
- herder.tick(); // join
+ doNothing().when(member).ensureActive();
configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
Review Comment:
We've changed the order of calls from herder tick, config update, herder tick, herder tick to herder tick, herder tick, config update, herder tick. Shouldn't the order remain the same?
Based on the comment below ("The connector will first be started when the assignment is received. The second start will occur when the updated config is processed (after the previous instance is stopped)") it seems like we're testing both when a connector is added to the cluster, and when that same connector is reconfigured.
IIUC, the purpose of the ticks/config updates in the original test was something like this:
- First tick: join group, no assignment
- Config update: new connector config is picked up from the config topic
- Second tick: force a rebalance to allocate the new connector
- Third tick: get assigned the new connector and start it
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3051,202 +2238,174 @@ public void testKeyRotationDisabledWhenWorkerBecomesFollower() throws Exception
Collections.emptySet(),
Collections.emptySet());
expectConfigRefreshAndSnapshot(snapshotWithKey);
- // First rebalance: poll for a limited time as worker is leader and must wake up for key expiration
- member.poll(leq(rotationTtlDelay));
- EasyMock.expectLastCall();
-
- expectRebalance(1, Collections.emptyList(), Collections.emptyList());
- // Second rebalance: poll indefinitely as worker is no longer leader, so key expiration doesn't come into play
- member.poll(Long.MAX_VALUE);
- EasyMock.expectLastCall();
-
- PowerMock.replayAll(initialSecretKey);
+ doNothing().when(member).poll(anyLong());
configUpdateListener.onSessionKeyUpdate(initialKey);
herder.tick();
+
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList());
herder.tick();
- PowerMock.verifyAll();
+ // First rebalance: poll for a limited time as worker is leader and must wake up for key expiration
+ verify(member).poll(leq(rotationTtlDelay));
+ // Second rebalance: poll indefinitely as worker is no longer leader, so key expiration doesn't come into play
+ verify(member).poll(eq(Long.MAX_VALUE));
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testPutTaskConfigsSignatureNotRequiredV0() {
- Callback<Void> taskConfigCb = EasyMock.mock(Callback.class);
-
- member.wakeup();
- EasyMock.expectLastCall().once();
- EasyMock.expect(member.currentProtocolVersion()).andReturn(CONNECT_PROTOCOL_V0).anyTimes();
- PowerMock.replayAll(taskConfigCb);
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ Callback<Void> taskConfigCb = mock(Callback.class);
herder.putTaskConfigs(CONN1, TASK_CONFIGS, taskConfigCb, null);
- PowerMock.verifyAll();
+ // Expect a wakeup call after the request to write task configs is added to the herder's request queue
+ verify(member).wakeup();
Review Comment:
We also have to make sure that the callback wasn't completed with an error:
```suggestion
verify(member).wakeup();
verifyNoMoreInteractions(taskConfigCb);
```
(This also applies to `testPutTaskConfigsSignatureNotRequiredV1`, and would probably be useful in all the other request signing test cases)
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -2870,127 +2087,99 @@ public void testAccessors() throws Exception {
new TaskInfo(TASK2, TASK_CONFIG)),
taskConfigsCb.get());
- PowerMock.verifyAll();
+ // Config transformation should not occur when requesting connector or task info
+ verify(configTransformer, never()).transform(eq(CONN1), any());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testPutConnectorConfig() throws Exception {
- // connectorConfig uses an async request
- member.wakeup();
- PowerMock.expectLastCall();
-
- // putConnectorConfig uses an async request
- member.wakeup();
- PowerMock.expectLastCall();
-
- EasyMock.expect(member.memberId()).andStubReturn("leader");
+ // connectorConfig and putConnectorConfig use an async request
Review Comment:
Nit: do we still need this comment? Previously it was justifying calls to `member::wakeup`, but it seems like we aren't explicitly setting expectations for them anymore in this test case.
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -1466,560 +1032,421 @@ public void testRestartConnectorAndTasksUnknownConnector() throws Exception {
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Unknown connector:"));
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksNotLeader() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick();
- PowerMock.replayAll();
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotLeaderException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksUnknownStatus() throws Exception {
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- PowerMock.replayAll();
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the connector restart
+ doNothing().when(member).ensureActive();
+ when(statusBackingStore.get(CONN1)).thenReturn(null);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
+
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
ExecutionException ee = assertThrows(ExecutionException.class, () -> callback.get(1000L, TimeUnit.MILLISECONDS));
assertTrue(ee.getCause() instanceof NotFoundException);
assertTrue(ee.getMessage().contains("Status for connector"));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartConnectorAndTasksSuccess() throws Exception {
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- ConnectorStateInfo connectorStateInfo = PowerMock.createMock(ConnectorStateInfo.class);
- EasyMock.expect(restartPlan.restartConnectorStateInfo()).andReturn(connectorStateInfo).anyTimes();
-
- RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- configBackingStore.putRestartRequest(restartRequest);
- PowerMock.expectLastCall();
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the connector restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ ConnectorStateInfo connectorStateInfo = mock(ConnectorStateInfo.class);
+ when(restartPlan.restartConnectorStateInfo()).thenReturn(connectorStateInfo);
+ RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
+ doNothing().when(configBackingStore).putRestartRequest(restartRequest);
- herder.tick();
FutureCallback<ConnectorStateInfo> callback = new FutureCallback<>();
herder.restartConnectorAndTasks(restartRequest, callback);
herder.tick();
assertEquals(connectorStateInfo, callback.get(1000L, TimeUnit.MILLISECONDS));
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksEmptyPlan() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.empty()).anyTimes();
-
- PowerMock.replayAll();
-
+ doReturn(Optional.empty()).when(herder).buildRestartPlan(restartRequest);
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksNoAssignments() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- PowerMock.replayAll();
herder.assignment = ExtendedAssignment.empty();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyConnector() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.emptyList());
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
stateCallback.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
-
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
-
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksOnlyTasks() {
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
// The connector has three tasks
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Arrays.asList(TASK0, TASK1, TASK2)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(3).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
+ when(restartPlan.taskIdsToRestart()).thenReturn(Arrays.asList(TASK0, TASK1, TASK2));
+ when(restartPlan.totalTaskCount()).thenReturn(3);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.emptyList()).anyTimes();
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.emptyList());
// But only one task is assigned to this worker
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(TASK0)).anyTimes();
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(TASK0));
herder.configState = SNAPSHOT;
- worker.stopAndAwaitTasks(Collections.singletonList(TASK0));
- PowerMock.expectLastCall();
-
- herder.onRestart(TASK0);
- EasyMock.expectLastCall();
-
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(TASK0));
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testDoRestartConnectorAndTasksBoth() {
ConnectorTaskId taskId = new ConnectorTaskId(CONN1, 0);
RestartRequest restartRequest = new RestartRequest(CONN1, false, true);
- RestartPlan restartPlan = PowerMock.createMock(RestartPlan.class);
- EasyMock.expect(restartPlan.shouldRestartConnector()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.shouldRestartTasks()).andReturn(true).anyTimes();
- EasyMock.expect(restartPlan.taskIdsToRestart()).andReturn(Collections.singletonList(taskId)).anyTimes();
- EasyMock.expect(restartPlan.restartTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(restartPlan.totalTaskCount()).andReturn(1).anyTimes();
- EasyMock.expect(herder.buildRestartPlan(restartRequest)).andReturn(Optional.of(restartPlan)).anyTimes();
-
- herder.assignment = PowerMock.createMock(ExtendedAssignment.class);
- EasyMock.expect(herder.assignment.connectors()).andReturn(Collections.singletonList(CONN1)).anyTimes();
- EasyMock.expect(herder.assignment.tasks()).andReturn(Collections.singletonList(taskId)).anyTimes();
-
- herder.configState = SNAPSHOT;
+ RestartPlan restartPlan = mock(RestartPlan.class);
+ when(restartPlan.shouldRestartConnector()).thenReturn(true);
+ when(restartPlan.taskIdsToRestart()).thenReturn(Collections.singletonList(taskId));
+ when(restartPlan.totalTaskCount()).thenReturn(1);
+ doReturn(Optional.of(restartPlan)).when(herder).buildRestartPlan(restartRequest);
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
-
- Capture<Callback<TargetState>> stateCallback = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class), capture(stateCallback));
+ herder.assignment = mock(ExtendedAssignment.class);
+ when(herder.assignment.connectors()).thenReturn(Collections.singletonList(CONN1));
+ when(herder.assignment.tasks()).thenReturn(Collections.singletonList(taskId));
+ herder.configState = SNAPSHOT;
- herder.onRestart(CONN1);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
- worker.stopAndAwaitTasks(Collections.singletonList(taskId));
- PowerMock.expectLastCall();
+ ArgumentCaptor<Callback<TargetState>> stateCallback = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ stateCallback.getValue().onCompletion(null, TargetState.STARTED);
+ return true;
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), any(), stateCallback.capture());
+ doNothing().when(member).wakeup();
+ doNothing().when(statusBackingStore).put(any(ConnectorStatus.class));
- herder.onRestart(taskId);
- EasyMock.expectLastCall();
+ doNothing().when(worker).stopAndAwaitTasks(Collections.singletonList(taskId));
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.anyObject(TargetState.class));
- PowerMock.expectLastCall().andReturn(true);
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
+ doNothing().when(statusBackingStore).put(any(TaskStatus.class));
- PowerMock.replayAll();
herder.doRestartConnectorAndTasks(restartRequest);
- PowerMock.verifyAll();
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTask() throws Exception {
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, conn1SinkConfig)).andStubReturn(TASK_CONFIGS);
-
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), singletonList(TASK0), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
- worker.stopAndAwaitTask(TASK0);
- PowerMock.expectLastCall();
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any())).thenReturn(true);
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
+ doNothing().when(worker).stopAndAwaitTask(TASK0);
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
callback.get(1000L, TimeUnit.MILLISECONDS);
- PowerMock.verifyAll();
+ verify(worker, times(2)).startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), any());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartUnknownTask() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- PowerMock.replayAll();
+ herder.tick();
+ doNothing().when(member).ensureActive();
FutureCallback<Void> callback = new FutureCallback<>();
- herder.tick();
herder.restartTask(new ConnectorTaskId("blah", 0), callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotFoundException);
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotFoundException.class, e.getCause().getClass());
- PowerMock.verifyAll();
- }
-
- @Test
- public void testRequestProcessingOrder() {
- final DistributedHerder.DistributedHerderRequest req1 = herder.addRequest(100, null, null);
- final DistributedHerder.DistributedHerderRequest req2 = herder.addRequest(10, null, null);
- final DistributedHerder.DistributedHerderRequest req3 = herder.addRequest(200, null, null);
- final DistributedHerder.DistributedHerderRequest req4 = herder.addRequest(200, null, null);
-
- assertEquals(req2, herder.requests.pollFirst()); // lowest delay
- assertEquals(req1, herder.requests.pollFirst()); // next lowest delay
- assertEquals(req3, herder.requests.pollFirst()); // same delay as req4, but added first
- assertEquals(req4, herder.requests.pollFirst());
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTaskRedirectToLeader() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // now handle the task restart
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
+ doNothing().when(member).poll(anyLong());
herder.tick();
+
+ // now handle the task restart
+ doNothing().when(member).ensureActive();
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotLeaderException);
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotLeaderException.class, e.getCause().getClass());
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testRestartTaskRedirectToOwner() throws Exception {
// get the initial assignment
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick();
// now handle the task restart
String ownerUrl = "ownerUrl";
- EasyMock.expect(member.ownerUrl(TASK0)).andReturn(ownerUrl);
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ when(member.ownerUrl(TASK0)).thenReturn(ownerUrl);
+ doNothing().when(member).ensureActive();
- PowerMock.replayAll();
-
- herder.tick();
FutureCallback<Void> callback = new FutureCallback<>();
herder.restartTask(TASK0, callback);
herder.tick();
- try {
- callback.get(1000L, TimeUnit.MILLISECONDS);
- fail("Expected NotLeaderException to be raised");
- } catch (ExecutionException e) {
- assertTrue(e.getCause() instanceof NotAssignedException);
- NotAssignedException notAssignedException = (NotAssignedException) e.getCause();
- assertEquals(ownerUrl, notAssignedException.forwardUrl());
- }
+ ExecutionException e = assertThrows(ExecutionException.class, () -> callback.get(1000, TimeUnit.MILLISECONDS));
+ assertEquals(NotAssignedException.class, e.getCause().getClass());
+ assertEquals(ownerUrl, ((NotAssignedException) e.getCause()).forwardUrl());
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
- public void testConnectorConfigAdded() {
+ public void testRequestProcessingOrder() {
+ final DistributedHerder.DistributedHerderRequest req1 = herder.addRequest(100, null, null);
+ final DistributedHerder.DistributedHerderRequest req2 = herder.addRequest(10, null, null);
+ final DistributedHerder.DistributedHerderRequest req3 = herder.addRequest(200, null, null);
+ final DistributedHerder.DistributedHerderRequest req4 = herder.addRequest(200, null, null);
+
+ assertEquals(req2, herder.requests.pollFirst()); // lowest delay
+ assertEquals(req1, herder.requests.pollFirst()); // next lowest delay
+ assertEquals(req3, herder.requests.pollFirst()); // same delay as req4, but added first
+ assertEquals(req4, herder.requests.pollFirst());
+ }
+
+ @Test
+ public void testConnectorConfigAdded() throws Exception {
// If a connector was added, we need to rebalance
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join, no configs so no need to catch up on config topic
expectRebalance(-1, Collections.emptyList(), Collections.emptyList());
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick(); // join
- // apply config
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
// Checks for config updates and starts rebalance
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
- member.requestRejoin();
- PowerMock.expectLastCall();
+ doNothing().when(configBackingStore).refresh(anyLong(), any());
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
Review Comment:
We have a utility method for this:
```suggestion
expectConfigRefreshAndSnapshot(SNAPSHOT);
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3683,29 +2781,25 @@ public void testExternalZombieFencingRequestAsynchronousFailure() throws Excepti
*/
@Test
public void testExternalZombieFencingRequestDelayedCompletion() throws Exception {
- final String conn3 = "SourceC";
+ final String conn3 = "sourceC";
final Map<String, Integer> tasksPerConnector = new HashMap<>();
tasksPerConnector.put(CONN1, 5);
tasksPerConnector.put(CONN2, 3);
tasksPerConnector.put(conn3, 12);
expectHerderStartup();
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V2);
- expectConfigRefreshAndSnapshot(SNAPSHOT);
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V2);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
SessionKey sessionKey = expectNewSessionKey();
- expectAnyTicks();
-
// We invoke the herder's fenceZombies method repeatedly, which adds a new request to the queue.
// If the queue is empty, the member is woken up; however, if two or more requests are issued in rapid
// succession, the member won't be woken up. We allow the member to be woken up any number of times
// here since it's not critical to the testing logic and it's difficult to mock things in order to lead to an
// exact number of wakeups.
Review Comment:
We can just remove this comment now since every mock expectation comes with an unlimited number of calls by default. In fact, we can even remove the line below if that expectation is already set up elsewhere.
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -2034,323 +1461,215 @@ public void testConnectorConfigUpdateFailedTransformation() throws Exception {
Collections.emptySet(),
configTransformer
);
- EasyMock.expect(configBackingStore.snapshot()).andReturn(snapshotWithTransform);
- EasyMock.expect(configTransformer.transform(EasyMock.eq(CONN1), EasyMock.anyObject()))
- .andThrow(new ConfigException("Simulated exception thrown during config transformation"));
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
- Capture<ConnectorStatus> failedStatus = newCapture();
- statusBackingStore.putSafe(capture(failedStatus));
- PowerMock.expectLastCall();
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // These will occur just before/during the third tick
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- EasyMock.replay(configTransformer);
- PowerMock.replayAll();
+ when(configBackingStore.snapshot()).thenReturn(snapshotWithTransform);
+ when(configTransformer.transform(eq(CONN1), any()))
+ .thenThrow(new ConfigException("Simulated exception thrown during config transformation"));
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
+
+ ArgumentCaptor<ConnectorStatus> failedStatus = ArgumentCaptor.forClass(ConnectorStatus.class);
+ doNothing().when(statusBackingStore).putSafe(failedStatus.capture());
- herder.tick(); // join
configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
herder.tick(); // apply config
herder.tick();
- PowerMock.verifyAll();
-
assertEquals(CONN1, failedStatus.getValue().id());
assertEquals(FAILED, failedStatus.getValue().state());
+
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore, configTransformer);
}
@Test
public void testConnectorPaused() throws Exception {
// ensure that target state changes are propagated to the worker
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- EasyMock.expect(worker.connectorNames()).andStubReturn(Collections.singleton(CONN1));
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join
expectRebalance(1, Arrays.asList(CONN1), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- Capture<Callback<TargetState>> onStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ doNothing().when(member).poll(anyLong());
+
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
onStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
- // handle the state change
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
+ herder.tick(); // join
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT_PAUSED_CONN1);
- PowerMock.expectLastCall();
+ // handle the state change
+ doNothing().when(member).ensureActive();
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT_PAUSED_CONN1);
- Capture<Callback<TargetState>> onPause = newCapture();
- worker.setTargetState(EasyMock.eq(CONN1), EasyMock.eq(TargetState.PAUSED), capture(onPause));
- PowerMock.expectLastCall().andAnswer(() -> {
- onStart.getValue().onCompletion(null, TargetState.PAUSED);
+ ArgumentCaptor<Callback<TargetState>> onPause = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ onPause.getValue().onCompletion(null, TargetState.PAUSED);
return null;
- });
-
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // These will occur just before/during the third tick
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).setTargetState(eq(CONN1), eq(TargetState.PAUSED), onPause.capture());
- PowerMock.replayAll();
-
- herder.tick(); // join
configUpdateListener.onConnectorTargetStateChange(CONN1); // state changes to paused
herder.tick(); // worker should apply the state change
herder.tick();
- PowerMock.verifyAll();
+ verify(worker).setTargetState(eq(CONN1), eq(TargetState.PAUSED), any(Callback.class));
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testConnectorResumed() throws Exception {
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- EasyMock.expect(worker.connectorNames()).andStubReturn(Collections.singleton(CONN1));
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// start with the connector paused
expectRebalance(1, Arrays.asList(CONN1), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT_PAUSED_CONN1);
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- Capture<Callback<TargetState>> onStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.PAUSED), capture(onStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ doNothing().when(member).poll(anyLong());
+
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
onStart.getValue().onCompletion(null, TargetState.PAUSED);
return true;
- });
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.PAUSED), onStart.capture());
- // handle the state change
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
+ herder.tick(); // join
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
- PowerMock.expectLastCall();
+ // handle the state change
+ doNothing().when(member).ensureActive();
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
- Capture<Callback<TargetState>> onResume = newCapture();
- worker.setTargetState(EasyMock.eq(CONN1), EasyMock.eq(TargetState.STARTED), capture(onResume));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> onResume = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
onResume.getValue().onCompletion(null, TargetState.STARTED);
return null;
- });
- member.wakeup();
- PowerMock.expectLastCall();
-
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
+ }).when(worker).setTargetState(eq(CONN1), eq(TargetState.STARTED), onResume.capture());
// we expect reconfiguration after resuming
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
-
- // These will occur just before/during the third tick
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
- herder.tick(); // join
configUpdateListener.onConnectorTargetStateChange(CONN1); // state changes to started
herder.tick(); // apply state change
herder.tick();
- PowerMock.verifyAll();
+ verify(worker).setTargetState(eq(CONN1), eq(TargetState.STARTED), any(Callback.class));
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testConnectorStopped() throws Exception {
// ensure that target state changes are propagated to the worker
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- EasyMock.expect(worker.connectorNames()).andStubReturn(Collections.singleton(CONN1));
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join
expectRebalance(1, Arrays.asList(CONN1), Collections.emptyList());
expectConfigRefreshAndSnapshot(SNAPSHOT);
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- Capture<Callback<TargetState>> onStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ doNothing().when(member).poll(anyLong());
+
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
onStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), onStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
- // handle the state change
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
+ herder.tick(); // join
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT_STOPPED_CONN1);
+ // handle the state change
+ doNothing().when(member).ensureActive();
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT_STOPPED_CONN1);
- Capture<Callback<TargetState>> onStop = newCapture();
- worker.setTargetState(EasyMock.eq(CONN1), EasyMock.eq(TargetState.STOPPED), capture(onStop));
- PowerMock.expectLastCall().andAnswer(() -> {
- onStart.getValue().onCompletion(null, TargetState.STOPPED);
+ ArgumentCaptor<Callback<TargetState>> onStop = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ onStop.getValue().onCompletion(null, TargetState.STOPPED);
return null;
- });
-
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).setTargetState(eq(CONN1), eq(TargetState.STOPPED), onStop.capture());
- // These will occur just before/during the third tick
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
-
- herder.tick(); // join
configUpdateListener.onConnectorTargetStateChange(CONN1); // state changes to stopped
herder.tick(); // worker should apply the state change
herder.tick();
- PowerMock.verifyAll();
+ verify(worker).setTargetState(eq(CONN1), eq(TargetState.STOPPED), any(Callback.class));
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testUnknownConnectorPaused() throws Exception {
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- EasyMock.expect(worker.connectorNames()).andStubReturn(Collections.singleton(CONN1));
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join
expectRebalance(1, Collections.emptyList(), singletonList(TASK0));
expectConfigRefreshAndSnapshot(SNAPSHOT);
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // state change is ignored since we have no target state
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
- PowerMock.expectLastCall();
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), eq(TargetState.STARTED))).thenReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ herder.tick(); // join
- PowerMock.replayAll();
+ // state change is ignored since we have no target state
+ doNothing().when(member).ensureActive();
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
- herder.tick(); // join
configUpdateListener.onConnectorTargetStateChange("unknown-connector");
herder.tick(); // continue
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testStopConnector() throws Exception {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- EasyMock.expect(worker.connectorNames()).andStubReturn(Collections.singleton(CONN1));
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join as leader
expectRebalance(1, Collections.emptyList(), singletonList(TASK0), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
+
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), eq(TargetState.STARTED))).thenReturn(true);
+
+ herder.tick(); // join
// handle stop request
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
+ doNothing().when(member).ensureActive();
expectConfigRefreshAndSnapshot(SNAPSHOT);
- configBackingStore.putTaskConfigs(CONN1, Collections.emptyList());
- PowerMock.expectLastCall();
- configBackingStore.putTargetState(CONN1, TargetState.STOPPED);
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
+ doNothing().when(configBackingStore).putTaskConfigs(CONN1, Collections.emptyList());
+ doNothing().when(configBackingStore).putTargetState(CONN1, TargetState.STOPPED);
FutureCallback<Void> cb = new FutureCallback<>();
- herder.tick(); // join
herder.stopConnector(CONN1, cb); // external request
herder.tick(); // continue
assertTrue("Callback should already have been invoked by herder", cb.isDone());
cb.get(0, TimeUnit.MILLISECONDS);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testStopConnectorNotLeader() throws Exception {
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- EasyMock.expect(worker.connectorNames()).andStubReturn(Collections.singleton(CONN1));
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
- // join as leader
+ // join as member (non-leader)
Review Comment:
Whoops!
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3724,62 +2818,54 @@ public void testExternalZombieFencingRequestDelayedCompletion() throws Exception
pendingFencing,
tasksPerConnector
);
- tasksPerConnector.keySet().forEach(c -> expectConfigRefreshAndSnapshot(configState));
+ expectConfigRefreshAndSnapshot(configState);
+
+ when(statusBackingStore.connectors()).thenReturn(Collections.emptySet());
+ doNothing().when(member).poll(anyLong());
// The callbacks that the herder has accrued for outstanding fencing futures, which will be completed after
// a successful round of fencing and a task record write to the config topic
- Map<String, Capture<KafkaFuture.BiConsumer<Void, Throwable>>> herderFencingCallbacks = new HashMap<>();
+ Map<String, ArgumentCaptor<KafkaFuture.BiConsumer<Void, Throwable>>> herderFencingCallbacks = new HashMap<>();
// The callbacks that the herder has installed for after a successful round of zombie fencing, but before writing
// a task record to the config topic
- Map<String, Capture<KafkaFuture.BaseFunction<Void, Void>>> workerFencingFollowups = new HashMap<>();
+ Map<String, ArgumentCaptor<KafkaFuture.BaseFunction<Void, Void>>> workerFencingFollowups = new HashMap<>();
Map<String, CountDownLatch> callbacksInstalled = new HashMap<>();
tasksPerConnector.forEach((connector, numStackedRequests) -> {
// The future returned by Worker::fenceZombies
- KafkaFuture<Void> workerFencingFuture = EasyMock.mock(KafkaFuture.class);
- // The future tracked by the herder (which tracks the fencing performed by the worker and the possible followup write to the config topic)
- KafkaFuture<Void> herderFencingFuture = EasyMock.mock(KafkaFuture.class);
+ KafkaFuture<Void> workerFencingFuture = mock(KafkaFuture.class);
+ // The future tracked by the herder (which tracks the fencing performed by the worker and the possible followup write to the config topic)
+ KafkaFuture<Void> herderFencingFuture = mock(KafkaFuture.class);
- Capture<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = EasyMock.newCapture(CaptureType.ALL);
+ ArgumentCaptor<KafkaFuture.BiConsumer<Void, Throwable>> herderFencingCallback = ArgumentCaptor.forClass(KafkaFuture.BiConsumer.class);
herderFencingCallbacks.put(connector, herderFencingCallback);
// Don't immediately invoke callbacks that the herder sets up for when the worker fencing and writes to the config topic have completed
// Instead, wait for them to be installed, then invoke them explicitly after the fact on a thread separate from the herder's tick thread
- EasyMock.expect(herderFencingFuture.whenComplete(EasyMock.capture(herderFencingCallback)))
- .andReturn(null)
- .times(numStackedRequests + 1);
+ when(herderFencingFuture.whenComplete(herderFencingCallback.capture())).thenReturn(null);
- Capture<KafkaFuture.BaseFunction<Void, Void>> fencingFollowup = EasyMock.newCapture();
+ ArgumentCaptor<KafkaFuture.BaseFunction<Void, Void>> fencingFollowup = ArgumentCaptor.forClass(KafkaFuture.BaseFunction.class);
CountDownLatch callbackInstalled = new CountDownLatch(1);
workerFencingFollowups.put(connector, fencingFollowup);
callbacksInstalled.put(connector, callbackInstalled);
- EasyMock.expect(workerFencingFuture.thenApply(EasyMock.capture(fencingFollowup))).andAnswer(() -> {
+ when(workerFencingFuture.thenApply(fencingFollowup.capture())).thenAnswer(invocation -> {
callbackInstalled.countDown();
return herderFencingFuture;
});
// We should only perform a single physical zombie fencing; all the subsequent requests should be stacked onto the first one
- EasyMock.expect(worker.fenceZombies(
- EasyMock.eq(connector), EasyMock.eq(taskCountRecords.get(connector)), EasyMock.anyObject())
- ).andReturn(workerFencingFuture);
-
- for (int i = 0; i < numStackedRequests; i++) {
- expectConfigRefreshAndSnapshot(configState);
- }
-
- PowerMock.replay(workerFencingFuture, herderFencingFuture);
+ when(worker.fenceZombies(eq(connector), eq(taskCountRecords.get(connector)), any()))
+ .thenReturn(workerFencingFuture)
+ .thenAnswer(invocation -> {
+ fail("Expected only a single zombie fencing per connector");
Review Comment:
IMO we should replace this with an explicit verification since there's less guarantee that calling `fail` on the herder's tick thread will correctly bubble up and cause the test itself to fail.
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -3929,543 +3008,636 @@ public void testPollDurationOnSlowConnectorOperations() {
final int rebalanceDelayMs = 20000;
final long operationDelayMs = 10000;
final long maxPollWaitMs = rebalanceDelayMs - operationDelayMs;
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(connectProtocolVersion);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(connectProtocolVersion);
// Assign the connector to this worker, and have it start
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- Capture<Callback<TargetState>> onFirstStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onFirstStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> onFirstStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
time.sleep(operationDelayMs);
onFirstStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- expectExecuteTaskReconfiguration(true, conn1SinkConfig, () -> TASK_CONFIGS);
- // We should poll for less than the delay - time to start the connector, meaning that a long connector start
- // does not delay the poll timeout
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), eq(CONN1_CONFIG), any(), eq(herder), eq(TargetState.STARTED), onFirstStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfig, invocation -> TASK_CONFIGS);
+
+ herder.tick();
// Rebalance again due to config update
- member.wakeup();
- PowerMock.expectLastCall();
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT_UPDATED_CONN1_CONFIG);
-
- worker.stopAndAwaitConnector(CONN1);
- PowerMock.expectLastCall();
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(connectProtocolVersion);
- Capture<Callback<TargetState>> onSecondStart = newCapture();
- worker.startConnector(EasyMock.eq(CONN1), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED), capture(onSecondStart));
- PowerMock.expectLastCall().andAnswer(() -> {
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT_UPDATED_CONN1_CONFIG);
+ doNothing().when(worker).stopAndAwaitConnector(CONN1);
+
+ ArgumentCaptor<Callback<TargetState>> onSecondStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
time.sleep(operationDelayMs);
onSecondStart.getValue().onCompletion(null, TargetState.STARTED);
return true;
- });
- member.wakeup();
- PowerMock.expectLastCall();
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
+ }).when(worker).startConnector(eq(CONN1), eq(CONN1_CONFIG_UPDATED), any(), eq(herder), eq(TargetState.STARTED), onSecondStart.capture());
+ expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, invocation -> TASK_CONFIGS);
+
+ configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
+ herder.tick();
// Third tick should resolve all outstanding requests
expectRebalance(Collections.emptyList(), Collections.emptyList(), ConnectProtocol.Assignment.NO_ERROR, 1, singletonList(CONN1), Collections.emptyList(), rebalanceDelayMs);
// which includes querying the connector task configs after the update
- expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, () -> {
+ expectExecuteTaskReconfiguration(true, conn1SinkConfigUpdated, invocation -> {
time.sleep(operationDelayMs);
return TASK_CONFIGS;
});
- member.poll(leq(maxPollWaitMs));
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
- herder.tick();
- configUpdateListener.onConnectorConfigUpdate(CONN1); // read updated config
- herder.tick();
herder.tick();
- PowerMock.verifyAll();
+
+ // We should poll for less than the delay - time to start the connector, meaning that a long connector start
+ // does not delay the poll timeout
+ verify(member, times(3)).poll(leq(maxPollWaitMs));
+ verify(worker, times(2)).startConnector(eq(CONN1), any(), any(), eq(herder), eq(TargetState.STARTED), any());
+ verifyNoMoreInteractions(member, worker, configBackingStore);
}
@Test
public void shouldThrowWhenStartAndStopExecutorThrowsRejectedExecutionExceptionAndHerderNotStopping() {
- EasyMock.expect(member.memberId()).andStubReturn("leader");
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Arrays.asList(CONN1), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
-
- PowerMock.replayAll();
herder.startAndStopExecutor.shutdown();
assertThrows(RejectedExecutionException.class, herder::tick);
+ }
+
+ @Test
+ public void testTaskReconfigurationRetriesWithConnectorTaskConfigsException() {
+ when(member.memberId()).thenReturn("leader");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
+
+ when(worker.isRunning(CONN1)).thenReturn(true);
+ when(worker.getPlugins()).thenReturn(plugins);
+
+ SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+
+ when(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig))
+ .thenThrow(new ConnectException("Failed to generate task configs"))
+ .thenThrow(new ConnectException("Failed to generate task configs"))
+ .thenReturn(TASK_CONFIGS);
+
+ expectAndVerifyTaskReconfigurationRetries();
+ }
+
+ @Test
+ public void testTaskReconfigurationRetriesWithLeaderRequestForwardingException() {
+ herder = mock(DistributedHerder.class, withSettings().defaultAnswer(CALLS_REAL_METHODS).useConstructor(new DistributedConfig(HERDER_CONFIG),
+ worker, WORKER_ID, KAFKA_CLUSTER_ID, statusBackingStore, configBackingStore, member, MEMBER_URL, restClient, metrics, time,
+ noneConnectorClientConfigOverridePolicy, Collections.emptyList(), new MockSynchronousExecutor(), new AutoCloseable[]{}));
+
+ rebalanceListener = herder.new RebalanceListener(time);
+
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
+ expectRebalance(1, Collections.emptyList(), Collections.emptyList(), false);
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
+
+ when(worker.isRunning(CONN1)).thenReturn(true);
+ when(worker.getPlugins()).thenReturn(plugins);
+
+ SinkConnectorConfig sinkConnectorConfig = new SinkConnectorConfig(plugins, CONN1_CONFIG);
+
+ List<Map<String, String>> changedTaskConfigs = new ArrayList<>(TASK_CONFIGS);
+ changedTaskConfigs.add(TASK_CONFIG);
+ when(worker.connectorTaskConfigs(CONN1, sinkConnectorConfig)).thenReturn(changedTaskConfigs);
+
+ when(restClient.httpRequest(any(), eq("POST"), any(), any(), any(), any(), any()))
+ .thenThrow(new ConnectException("Request to leader to reconfigure connector tasks failed"))
+ .thenThrow(new ConnectException("Request to leader to reconfigure connector tasks failed"))
+ .thenReturn(null);
+
+ expectAndVerifyTaskReconfigurationRetries();
+ }
+
+ private void expectAndVerifyTaskReconfigurationRetries() {
+ // initial tick
+ herder.tick();
+ herder.requestTaskReconfiguration(CONN1);
+ // process the task reconfiguration request in this tick
+ herder.tick();
+ // advance the time by 250ms so that the task reconfiguration request with initial retry backoff is processed
+ time.sleep(250);
+ herder.tick();
+ // advance the time by 500ms so that the task reconfiguration request with double the initial retry backoff is processed
+ time.sleep(500);
+ herder.tick();
+
+ // 1. end of initial tick when no request has been added to the herder queue yet
+ // 2. the third task reconfiguration request is expected to pass; so expect no more retries (a Long.MAX_VALUE poll
+ // timeout indicates that there is no herder request currently in the queue)
+ verify(member, times(2)).poll(eq(Long.MAX_VALUE));
+
+ // task reconfiguration herder request with initial retry backoff
+ verify(member).poll(eq(250L));
+
+ // task reconfiguration herder request with double the initial retry backoff
+ verify(member).poll(eq(500L));
+
+ verifyNoMoreInteractions(member, worker);
Review Comment:
Optional suggestion: also ensure we haven't made/forwarded any more REST calls
```suggestion
verifyNoMoreInteractions(member, worker, restClient);
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -2411,193 +1721,125 @@ public void testStopConnectorFailToWriteTaskConfigs() throws Exception {
);
assertEquals(e.getCause(), taskConfigsWriteException);
- PowerMock.verifyAll();
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testConnectorPausedRunningTaskOnly() throws Exception {
// even if we don't own the connector, we should still propagate target state
// changes to the worker so that tasks will transition correctly
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- EasyMock.expect(worker.connectorNames()).andStubReturn(Collections.emptySet());
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join
expectRebalance(1, Collections.emptyList(), singletonList(TASK0));
expectConfigRefreshAndSnapshot(SNAPSHOT);
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // handle the state change
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), eq(TargetState.STARTED))).thenReturn(true);
+
+ herder.tick(); // join
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT_PAUSED_CONN1);
- PowerMock.expectLastCall();
+ // handle the state change
+ doNothing().when(member).ensureActive();
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT_PAUSED_CONN1);
- Capture<Callback<TargetState>> onPause = newCapture();
- worker.setTargetState(EasyMock.eq(CONN1), EasyMock.eq(TargetState.PAUSED), capture(onPause));
- PowerMock.expectLastCall().andAnswer(() -> {
+ ArgumentCaptor<Callback<TargetState>> onPause = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
onPause.getValue().onCompletion(null, TargetState.PAUSED);
return null;
- });
-
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ }).when(worker).setTargetState(eq(CONN1), eq(TargetState.PAUSED), onPause.capture());
- PowerMock.replayAll();
-
- herder.tick(); // join
configUpdateListener.onConnectorTargetStateChange(CONN1); // state changes to paused
herder.tick(); // apply state change
- PowerMock.verifyAll();
+ verify(worker).setTargetState(eq(CONN1), eq(TargetState.PAUSED), any(Callback.class));
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testConnectorResumedRunningTaskOnly() throws Exception {
// even if we don't own the connector, we should still propagate target state
// changes to the worker so that tasks will transition correctly
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
- EasyMock.expect(worker.connectorNames()).andStubReturn(Collections.emptySet());
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join
expectRebalance(1, Collections.emptyList(), singletonList(TASK0));
expectConfigRefreshAndSnapshot(SNAPSHOT_PAUSED_CONN1);
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.PAUSED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
- // handle the state change
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), eq(TargetState.PAUSED))).thenReturn(true);
+
+ herder.tick(); // join
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
- PowerMock.expectLastCall();
+ // handle the state change
+ doNothing().when(member).ensureActive();
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
- Capture<Callback<TargetState>> onStart = newCapture();
- worker.setTargetState(EasyMock.eq(CONN1), EasyMock.eq(TargetState.STARTED), capture(onStart));
- PowerMock.expectLastCall().andAnswer(() -> {
- onStart.getValue().onCompletion(null, TargetState.STARTED);
+ ArgumentCaptor<Callback<TargetState>> onStart = ArgumentCaptor.forClass(Callback.class);
+ doAnswer(invocation -> {
+ onStart.getValue().onCompletion(null, TargetState.PAUSED);
return null;
- });
- member.wakeup();
- PowerMock.expectLastCall();
+ }).when(worker).setTargetState(eq(CONN1), eq(TargetState.STARTED), onStart.capture());
expectExecuteTaskReconfiguration(false, null, null);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- // These will occur just before/during the third tick
- member.ensureActive();
- PowerMock.expectLastCall();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
-
- herder.tick(); // join
configUpdateListener.onConnectorTargetStateChange(CONN1); // state changes to paused
herder.tick(); // apply state change
herder.tick();
- PowerMock.verifyAll();
+ verify(worker).setTargetState(eq(CONN1), eq(TargetState.STARTED), any(Callback.class));
+ verifyNoMoreInteractions(worker, member, configBackingStore, statusBackingStore);
}
@Test
public void testTaskConfigAdded() {
// Task config always requires rebalance
- EasyMock.expect(member.memberId()).andStubReturn("member");
- EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
+ when(member.memberId()).thenReturn("member");
+ when(member.currentProtocolVersion()).thenReturn(CONNECT_PROTOCOL_V0);
// join
expectRebalance(-1, Collections.emptyList(), Collections.emptyList());
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
+ doNothing().when(member).poll(anyLong());
+
+ herder.tick(); // join
- // apply config
- member.wakeup();
- PowerMock.expectLastCall();
- member.ensureActive();
- PowerMock.expectLastCall();
// Checks for config updates and starts rebalance
- EasyMock.expect(configBackingStore.snapshot()).andReturn(SNAPSHOT);
- member.requestRejoin();
- PowerMock.expectLastCall();
+ when(configBackingStore.snapshot()).thenReturn(SNAPSHOT);
+ doNothing().when(member).requestRejoin();
+
// Performs rebalance and gets new assignment
expectRebalance(Collections.emptyList(), Collections.emptyList(),
ConnectProtocol.Assignment.NO_ERROR, 1, Collections.emptyList(),
Arrays.asList(TASK0));
- worker.startSourceTask(EasyMock.eq(TASK0), EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(),
- EasyMock.eq(herder), EasyMock.eq(TargetState.STARTED));
- PowerMock.expectLastCall().andReturn(true);
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall();
-
- PowerMock.replayAll();
+ expectConfigRefreshAndSnapshot(SNAPSHOT);
+ when(worker.startSourceTask(eq(TASK0), any(), any(), any(), eq(herder), eq(TargetState.STARTED))).thenReturn(true);
- herder.tick(); // join
configUpdateListener.onTaskConfigUpdate(Arrays.asList(TASK0, TASK1, TASK2)); // read updated config
herder.tick(); // apply config
herder.tick(); // do rebalance
Review Comment:
I know this isn't the fault of this PR, but shouldn't these two be reversed?
```suggestion
herder.tick(); // do rebalance
herder.tick(); // apply config
```
##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/distributed/DistributedHerderTest.java:
##########
@@ -4617,40 +3783,30 @@ private ClusterConfigState exactlyOnceSnapshot(
Collections.emptySet());
}
- private void expectExecuteTaskReconfiguration(boolean running, ConnectorConfig connectorConfig, IAnswer<List<Map<String, String>>> answer) {
- EasyMock.expect(worker.isRunning(CONN1)).andReturn(running);
+ private void expectExecuteTaskReconfiguration(boolean running, ConnectorConfig connectorConfig, Answer<List<Map<String, String>>> answer) {
+ when(worker.isRunning(CONN1)).thenReturn(running);
if (running) {
- EasyMock.expect(worker.getPlugins()).andReturn(plugins);
- EasyMock.expect(worker.connectorTaskConfigs(CONN1, connectorConfig)).andAnswer(answer);
+ when(worker.getPlugins()).thenReturn(plugins);
+ when(worker.connectorTaskConfigs(CONN1, connectorConfig)).thenAnswer(answer);
}
}
private void expectAnyTicks() {
- member.ensureActive();
- EasyMock.expectLastCall().anyTimes();
- member.poll(EasyMock.anyInt());
- PowerMock.expectLastCall().anyTimes();
+ doNothing().when(member).ensureActive();
+ doNothing().when(member).poll(anyLong());
}
Review Comment:
Looks like this method is no longer used. Can we get rid of it?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: jira-unsubscribe@kafka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org