Expects configuration from SnowflakeStreamingSinkConnector * *
Creates sink service instance, takes records loaded from those Kafka partitions and ingests to
* Snowflake via Sink service
*/
public class SnowflakeSinkTask extends SinkTask {
private static final long WAIT_TIME = 5 * 1000; // 5 sec
private static final int REPEAT_TIME = 12; // 60 sec
// the dynamic logger is intended to be attached per task instance. the instance id will be set
// during task start, however if it is not set, it falls back to the static logger
private static final KCLogger STATIC_LOGGER =
new KCLogger(SnowflakeSinkTask.class.getName() + "_STATIC");
private KCLogger DYNAMIC_LOGGER;
private volatile SnowflakeSinkService sink = null;
// snowflake JDBC connection provides methods to interact with user's
// snowflake
// account and execute queries
private SnowflakeConnectionService conn = null;
// tracks number of tasks the config wants to create
private String taskConfigId = "-1";
private long taskStartTime;
private final SnowflakeSinkTaskAuthorizationExceptionTracker authorizationExceptionTracker =
new SnowflakeSinkTaskAuthorizationExceptionTracker();
// Stores channel error exception detected in preCommit to fail on next put() call
private volatile SnowflakeKafkaConnectorException channelErrorToFailOn = null;
// Periodic telemetry reporter for channel status
private PeriodicTelemetryReporter telemetryReporter = null;
// Task-level JMX metrics (lifecycle, throughput, duration)
private TaskMetrics taskMetrics = TaskMetrics.noop();
/** default constructor, invoked by kafka connect framework */
public SnowflakeSinkTask() {
DYNAMIC_LOGGER = new KCLogger(this.getClass().getName());
}
@VisibleForTesting
public SnowflakeSinkTask(
SnowflakeSinkService service, SnowflakeConnectionService connectionService) {
DYNAMIC_LOGGER = new KCLogger(this.getClass().getName());
this.sink = service;
this.conn = connectionService;
}
private SnowflakeConnectionService getConnection() {
try {
waitFor(() -> conn != null);
} catch (Exception e) {
throw SnowflakeErrors.ERROR_5013.getException();
}
return conn;
}
/**
* Return an instance of SnowflakeConnection if it was set previously by calling Start(). Else,
* return an empty
*
* @return Optional of SnowflakeConnectionService
*/
public Optional Note that calling this method does not perform synchronous cleanup in Snowpipe based
* implementation
*/
@Override
public void stop() {
this.DYNAMIC_LOGGER.info("stopping task {}", this.taskConfigId);
// Stop telemetry reporter first
if (this.telemetryReporter != null) {
this.telemetryReporter.stop();
}
this.taskMetrics.unregister();
if (this.sink != null) {
this.sink.stop();
}
this.DYNAMIC_LOGGER.info(
"task stopped, total task runtime: {} milliseconds",
getDurationFromStartMs(this.taskStartTime));
}
/**
* init ingestion task in Sink service
*
* @param partitions - The list of all partitions that are now assigned to the task
*/
@Override
public void open(final Collection Closes all running task because the parameter of open function contains all partition info
* but not only the new partition
*
* @param partitions - The list of all partitions that were assigned to the task
*/
@Override
public void close(final Collection Note that exceptions thrown during preCommit() are swallowed by Kafka Connect and will not
* cause task failure.
*/
public class SnowflakeSinkTaskAuthorizationExceptionTracker {
private static final String AUTHORIZATION_EXCEPTION_MESSAGE = "Authorization failed after retry";
private boolean authorizationTaskFailureEnabled;
private boolean authorizationErrorReported;
public SnowflakeSinkTaskAuthorizationExceptionTracker() {
this.authorizationTaskFailureEnabled = true;
this.authorizationErrorReported = false;
}
public void updateStateOnTaskStart(Map Expected configuration: including topic names, partition numbers, snowflake connection info
* and credentials info
*
* Creates snowflake internal stages, snowflake tables provides configuration to SinkTasks
* running on Kafka Connect Workers.
*/
public class SnowflakeStreamingSinkConnector extends SinkConnector {
// create logger without correlationId for now
private static final KCLogger LOGGER =
new KCLogger(SnowflakeStreamingSinkConnector.class.getName());
private Map Creates snowflake internal stages and snowflake tables
*
* @param parsedConfig has the configuration settings
*/
@Override
public void start(final Map Cleans up pipes, after making sure there are no pending files to ingest.
*
* Also ensures that there are no leaked stages, no leaked staged files, and no leaked pipes
*/
@Override
public void stop() {
LOGGER.info("SnowflakeStreamingSinkConnector connector stopping...");
setupComplete = false;
if (telemetryClient != null) {
telemetryClient.reportKafkaConnectStop(connectorStartTime);
}
}
/**
* @return Sink task class
*/
@Override
public Class extends Task> taskClass() {
return SnowflakeSinkTask.class;
}
/**
* taskConfigs method returns a set of configurations for SinkTasks based on the current
* configuration, producing at most 'maxTasks' configurations
*
* @param maxTasks maximum number of SinkTasks for this instance of
* SnowflakeStreamingSinkConnector
* @return a list containing 'maxTasks' copies of the configuration
*/
@Override
public List