Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,15 @@ public void onPause(ConnectorTaskId id) {
@Override
public void onDeletion(String connector) {
for (TaskStatus status : statusBackingStore.getAll(connector))
statusBackingStore.put(new TaskStatus(status.id(), TaskStatus.State.DESTROYED, workerId, generation()));
onDeletion(status.id());
statusBackingStore.put(new ConnectorStatus(connector, ConnectorStatus.State.DESTROYED, workerId, generation()));
}

@Override
public void onDeletion(ConnectorTaskId id) {
statusBackingStore.put(new TaskStatus(id, TaskStatus.State.DESTROYED, workerId, generation()));
}

@Override
public void pauseConnector(String connector) {
if (!configBackingStore.contains(connector))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,12 @@ public interface Listener {
*/
void onShutdown(ConnectorTaskId id);

/**
* Invoked after the task has been deleted. Can be called if the
* connector tasks have been reduced, or if the connector itself has
* been deleted.
* @param id The id of the task
*/
void onDeletion(ConnectorTaskId id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,12 @@ public void onShutdown(ConnectorTaskId id) {
delegateListener.onShutdown(id);
}

@Override
public void onDeletion(ConnectorTaskId id) {
taskStateTimer.changeState(State.DESTROYED, time.milliseconds());
delegateListener.onDeletion(id);
}

public void recordState(TargetState state) {
switch (state) {
case STARTED:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.apache.kafka.connect.runtime.SinkConnectorConfig;
import org.apache.kafka.connect.runtime.SourceConnectorConfig;
import org.apache.kafka.connect.runtime.TargetState;
import org.apache.kafka.connect.runtime.TaskStatus;
import org.apache.kafka.connect.runtime.Worker;
import org.apache.kafka.connect.runtime.rest.InternalRequestSignature;
import org.apache.kafka.connect.runtime.rest.RestClient;
Expand Down Expand Up @@ -1522,6 +1523,18 @@ private void updateDeletedConnectorStatus() {
}
}

private void updateDeletedTaskStatus() {
ClusterConfigState snapshot = configBackingStore.snapshot();
for (String connector : statusBackingStore.connectors()) {
Set<ConnectorTaskId> remainingTasks = new HashSet<>(snapshot.tasks(connector));

statusBackingStore.getAll(connector).stream()
.map(TaskStatus::id)
.filter(task -> !remainingTasks.contains(task))
.forEach(this::onDeletion);
}
}

protected HerderMetrics herderMetrics() {
return herderMetrics;
}
Expand Down Expand Up @@ -1584,11 +1597,13 @@ public void onAssigned(ExtendedAssignment assignment, int generation) {
herderMetrics.rebalanceStarted(time.milliseconds());
}

// Delete the statuses of all connectors removed prior to the start of this rebalance. This has to
// be done after the rebalance completes to avoid race conditions as the previous generation attempts
// to change the state to UNASSIGNED after tasks have been stopped.
if (isLeader())
// Delete the statuses of all connectors and tasks removed prior to the start of this rebalance. This
// has to be done after the rebalance completes to avoid race conditions as the previous generation
// attempts to change the state to UNASSIGNED after tasks have been stopped.
if (isLeader()) {
updateDeletedConnectorStatus();
updateDeletedTaskStatus();
}

// We *must* interrupt any poll() call since this could occur when the poll starts, and we might then
// sleep in the poll() for a long time. Forcing a wakeup ensures we'll get to process this event in the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ private void removeConnectorTasks(String connName) {
if (!tasks.isEmpty()) {
worker.stopAndAwaitTasks(tasks);
configBackingStore.removeTaskConfigs(connName);
tasks.forEach(this::onDeletion);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public void testAddAndRemoveWorker() throws Exception {
// create test topic
connect.kafka().createTopic("test-topic", NUM_TOPIC_PARTITIONS);

// setup up props for the sink connector
// set up props for the source connector
Map<String, String> props = new HashMap<>();
props.put(CONNECTOR_CLASS_CONFIG, MonitorableSourceConnector.class.getSimpleName());
props.put(TASKS_MAX_CONFIG, String.valueOf(numTasks));
Expand Down Expand Up @@ -193,7 +193,7 @@ public void testBrokerCoordinator() throws Exception {
// create test topic
connect.kafka().createTopic("test-topic", NUM_TOPIC_PARTITIONS);

// setup up props for the sink connector
// set up props for the source connector
Map<String, String> props = new HashMap<>();
props.put(CONNECTOR_CLASS_CONFIG, MonitorableSourceConnector.class.getSimpleName());
props.put(TASKS_MAX_CONFIG, String.valueOf(numTasks));
Expand Down Expand Up @@ -236,4 +236,39 @@ public void testBrokerCoordinator() throws Exception {
"Connector tasks did not start in time.");
}

/**
* Verify that the number of tasks listed in the REST API is updated correctly after changes to
* the "tasks.max" connector configuration.
*/
@Test
public void testTaskStatuses() throws Exception {
connect = connectBuilder.build();
// start the clusters
connect.start();

connect.assertions().assertAtLeastNumWorkersAreUp(NUM_WORKERS,
"Initial group of workers did not start in time.");

// base connector props
Map<String, String> connectorProps = new HashMap<>();
connectorProps.put(CONNECTOR_CLASS_CONFIG, MonitorableSourceConnector.class.getSimpleName());

// start the connector with only one task
final int initialNumTasks = 1;
connectorProps.put(TASKS_MAX_CONFIG, String.valueOf(initialNumTasks));
connect.configureConnector(CONNECTOR_NAME, connectorProps);
connect.assertions().assertConnectorAndExactlyNumTasksAreRunning(CONNECTOR_NAME, initialNumTasks, "Connector tasks did not start in time");

// then reconfigure it to use more tasks
final int increasedNumTasks = 5;
connectorProps.put(TASKS_MAX_CONFIG, String.valueOf(increasedNumTasks));
connect.configureConnector(CONNECTOR_NAME, connectorProps);
connect.assertions().assertConnectorAndExactlyNumTasksAreRunning(CONNECTOR_NAME, increasedNumTasks, "Connector task statuses did not update in time.");

// then reconfigure it to use fewer tasks
final int decreasedNumTasks = 3;
connectorProps.put(TASKS_MAX_CONFIG, String.valueOf(decreasedNumTasks));
connect.configureConnector(CONNECTOR_NAME, connectorProps);
connect.assertions().assertConnectorAndExactlyNumTasksAreRunning(CONNECTOR_NAME, decreasedNumTasks, "Connector task statuses did not update in time.");
Comment thread
C0urante marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ public void setUp() throws Exception {
connectProtocolVersion = CONNECT_PROTOCOL_V0;

herder = PowerMock.createPartialMock(DistributedHerder.class,
new String[]{"backoff", "connectorTypeForClass", "updateDeletedConnectorStatus"},
new String[]{"backoff", "connectorTypeForClass", "updateDeletedConnectorStatus", "updateDeletedTaskStatus"},
new DistributedConfig(HERDER_CONFIG), worker, WORKER_ID, KAFKA_CLUSTER_ID,
statusBackingStore, configBackingStore, member, MEMBER_URL, metrics, time, noneConnectorClientConfigOverridePolicy);

Expand All @@ -217,6 +217,7 @@ public void setUp() throws Exception {
delegatingLoader = PowerMock.createMock(DelegatingClassLoader.class);
PowerMock.mockStatic(Plugins.class);
PowerMock.expectPrivate(herder, "updateDeletedConnectorStatus").andVoid().anyTimes();
PowerMock.expectPrivate(herder, "updateDeletedTaskStatus").andVoid().anyTimes();
}

@After
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ public void testDestroyConnector() throws Exception {

EasyMock.expect(statusBackingStore.getAll(CONNECTOR_NAME)).andReturn(Collections.<TaskStatus>emptyList());
statusBackingStore.put(new ConnectorStatus(CONNECTOR_NAME, AbstractStatus.State.DESTROYED, WORKER_ID, 0));
statusBackingStore.put(new TaskStatus(new ConnectorTaskId(CONNECTOR_NAME, 0), TaskStatus.State.DESTROYED, WORKER_ID, 0));

expectDestroy();

Expand Down Expand Up @@ -434,6 +435,8 @@ public void testCreateAndStop() throws Exception {
// herder.stop() should stop any running connectors and tasks even if destroyConnector was not invoked
expectStop();

statusBackingStore.put(new TaskStatus(new ConnectorTaskId(CONNECTOR_NAME, 0), AbstractStatus.State.DESTROYED, WORKER_ID, 0));

statusBackingStore.stop();
EasyMock.expectLastCall();
worker.stop();
Expand Down