org.apache.kafka.connect.runtime.standalone.StandaloneHerder Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.kafka.connect.runtime.standalone;
import org.apache.kafka.common.utils.ThreadUtils;
import org.apache.kafka.common.utils.Time;
import org.apache.kafka.connect.connector.policy.ConnectorClientConfigOverridePolicy;
import org.apache.kafka.connect.errors.AlreadyExistsException;
import org.apache.kafka.connect.errors.ConnectException;
import org.apache.kafka.connect.errors.NotFoundException;
import org.apache.kafka.connect.runtime.AbstractHerder;
import org.apache.kafka.connect.runtime.ConnectorConfig;
import org.apache.kafka.connect.runtime.HerderConnectorContext;
import org.apache.kafka.connect.runtime.HerderRequest;
import org.apache.kafka.connect.runtime.RestartPlan;
import org.apache.kafka.connect.runtime.RestartRequest;
import org.apache.kafka.connect.runtime.SessionKey;
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.Worker;
import org.apache.kafka.connect.runtime.rest.InternalRequestSignature;
import org.apache.kafka.connect.runtime.rest.entities.ConfigInfos;
import org.apache.kafka.connect.runtime.rest.entities.ConnectorInfo;
import org.apache.kafka.connect.runtime.rest.entities.ConnectorOffsets;
import org.apache.kafka.connect.runtime.rest.entities.ConnectorStateInfo;
import org.apache.kafka.connect.runtime.rest.entities.Message;
import org.apache.kafka.connect.runtime.rest.entities.TaskInfo;
import org.apache.kafka.connect.runtime.rest.errors.BadRequestException;
import org.apache.kafka.connect.storage.ClusterConfigState;
import org.apache.kafka.connect.storage.ConfigBackingStore;
import org.apache.kafka.connect.storage.MemoryConfigBackingStore;
import org.apache.kafka.connect.storage.MemoryStatusBackingStore;
import org.apache.kafka.connect.storage.StatusBackingStore;
import org.apache.kafka.connect.util.Callback;
import org.apache.kafka.connect.util.ConnectUtils;
import org.apache.kafka.connect.util.ConnectorTaskId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* Single process, in-memory "herder". Useful for a standalone Kafka Connect process.
*/
public class StandaloneHerder extends AbstractHerder {
private static final Logger log = LoggerFactory.getLogger(StandaloneHerder.class);
private final AtomicLong requestSeqNum = new AtomicLong();
private final ScheduledExecutorService requestExecutorService;
private final HealthCheckThread healthCheckThread;
// Visible for testing
ClusterConfigState configState;
public StandaloneHerder(Worker worker, String kafkaClusterId,
ConnectorClientConfigOverridePolicy connectorClientConfigOverridePolicy) {
this(worker,
worker.workerId(),
kafkaClusterId,
new MemoryStatusBackingStore(),
new MemoryConfigBackingStore(worker.configTransformer()),
connectorClientConfigOverridePolicy,
Time.SYSTEM
);
}
// visible for testing
@SuppressWarnings("this-escape")
StandaloneHerder(Worker worker,
String workerId,
String kafkaClusterId,
StatusBackingStore statusBackingStore,
MemoryConfigBackingStore configBackingStore,
ConnectorClientConfigOverridePolicy connectorClientConfigOverridePolicy,
Time time) {
super(worker, workerId, kafkaClusterId, statusBackingStore, configBackingStore, connectorClientConfigOverridePolicy, time);
this.configState = ClusterConfigState.EMPTY;
this.requestExecutorService = Executors.newSingleThreadScheduledExecutor();
this.healthCheckThread = new HealthCheckThread(this);
configBackingStore.setUpdateListener(new ConfigUpdateListener());
}
@Override
public synchronized void start() {
log.info("Herder starting");
startServices();
log.info("Herder started");
}
@Override
public synchronized void stop() {
log.info("Herder stopping");
ThreadUtils.shutdownExecutorServiceQuietly(requestExecutorService, 30, TimeUnit.SECONDS);
// There's no coordination/hand-off to do here since this is all standalone. Instead, we
// should just clean up the stuff we normally would, i.e. cleanly checkpoint and shutdown all
// the tasks.
for (String connName : connectors()) {
removeConnectorTasks(connName);
worker.stopAndAwaitConnector(connName);
}
stopServices();
healthCheckThread.shutDown();
log.info("Herder stopped");
}
@Override
public void ready() {
super.ready();
healthCheckThread.start();
}
@Override
public void healthCheck(Callback cb) {
healthCheckThread.check(cb);
}
@Override
public int generation() {
return 0;
}
@Override
public synchronized void connectors(Callback> callback) {
callback.onCompletion(null, connectors());
}
@Override
public synchronized void connectorInfo(String connName, Callback callback) {
ConnectorInfo connectorInfo = connectorInfo(connName);
if (connectorInfo == null) {
callback.onCompletion(new NotFoundException("Connector " + connName + " not found"), null);
return;
}
callback.onCompletion(null, connectorInfo);
}
private synchronized ConnectorInfo createConnectorInfo(String connector) {
if (!configState.contains(connector))
return null;
Map config = configState.rawConnectorConfig(connector);
return new ConnectorInfo(connector, config, configState.tasks(connector), connectorType(config));
}
@Override
protected synchronized Map rawConfig(String connName) {
return configState.rawConnectorConfig(connName);
}
@Override
public synchronized void deleteConnectorConfig(String connName, Callback> callback) {
try {
if (!configState.contains(connName)) {
// Deletion, must already exist
callback.onCompletion(new NotFoundException("Connector " + connName + " not found", null), null);
return;
}
removeConnectorTasks(connName);
worker.stopAndAwaitConnector(connName);
configBackingStore.removeConnectorConfig(connName);
onDeletion(connName);
callback.onCompletion(null, new Created<>(false, null));
} catch (ConnectException e) {
callback.onCompletion(e, null);
}
}
@Override
public synchronized void putConnectorConfig(String connName,
final Map config,
boolean allowReplace,
final Callback> callback) {
putConnectorConfig(connName, config, null, allowReplace, callback);
}
@Override
public void putConnectorConfig(final String connName, final Map config, final TargetState targetState,
final boolean allowReplace, final Callback> callback) {
try {
validateConnectorConfig(config, (error, configInfos) -> {
if (error != null) {
callback.onCompletion(error, null);
return;
}
requestExecutorService.submit(
() -> putConnectorConfig(connName, config, targetState, allowReplace, callback, configInfos)
);
});
} catch (Throwable t) {
callback.onCompletion(t, null);
}
}
private synchronized void putConnectorConfig(String connName,
final Map config,
TargetState targetState,
boolean allowReplace,
final Callback> callback,
ConfigInfos configInfos) {
try {
if (maybeAddConfigErrors(configInfos, callback)) {
return;
}
final boolean created;
if (configState.contains(connName)) {
if (!allowReplace) {
callback.onCompletion(new AlreadyExistsException("Connector " + connName + " already exists"), null);
return;
}
worker.stopAndAwaitConnector(connName);
created = false;
} else {
created = true;
}
configBackingStore.putConnectorConfig(connName, config, targetState);
startConnector(connName, (error, result) -> {
if (error != null) {
callback.onCompletion(error, null);
return;
}
requestExecutorService.submit(() -> {
updateConnectorTasks(connName);
callback.onCompletion(null, new Created<>(created, createConnectorInfo(connName)));
});
});
} catch (Throwable t) {
callback.onCompletion(t, null);
}
}
@Override
public synchronized void patchConnectorConfig(String connName, Map configPatch, Callback> callback) {
try {
ConnectorInfo connectorInfo = connectorInfo(connName);
if (connectorInfo == null) {
callback.onCompletion(new NotFoundException("Connector " + connName + " not found", null), null);
return;
}
Map patchedConfig = ConnectUtils.patchConfig(connectorInfo.config(), configPatch);
validateConnectorConfig(patchedConfig, (error, configInfos) -> {
if (error != null) {
callback.onCompletion(error, null);
return;
}
requestExecutorService.submit(
() -> putConnectorConfig(connName, patchedConfig, null, true, callback, configInfos)
);
});
} catch (Throwable e) {
callback.onCompletion(e, null);
}
}
@Override
public synchronized void stopConnector(String connName, Callback callback) {
try {
removeConnectorTasks(connName);
configBackingStore.putTargetState(connName, TargetState.STOPPED);
callback.onCompletion(null, null);
} catch (Throwable t) {
callback.onCompletion(t, null);
}
}
@Override
public synchronized void requestTaskReconfiguration(String connName) {
if (!worker.connectorNames().contains(connName)) {
log.error("Task that requested reconfiguration does not exist: {}", connName);
return;
}
updateConnectorTasks(connName);
}
@Override
public synchronized void taskConfigs(String connName, Callback> callback) {
if (!configState.contains(connName)) {
callback.onCompletion(new NotFoundException("Connector " + connName + " not found", null), null);
return;
}
List result = new ArrayList<>();
for (ConnectorTaskId taskId : configState.tasks(connName))
result.add(new TaskInfo(taskId, configState.rawTaskConfig(taskId)));
callback.onCompletion(null, result);
}
@Override
public void putTaskConfigs(String connName, List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy