-
Notifications
You must be signed in to change notification settings - Fork 100
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SNOW-1623269 Fail sink task on authorization exception from Snowflake (…
- Loading branch information
1 parent
d55cd76
commit 56f96bb
Showing
5 changed files
with
147 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
...in/java/com/snowflake/kafka/connector/SnowflakeSinkTaskAuthorizationExceptionTracker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.snowflake.kafka.connector; | ||
|
||
import static com.snowflake.kafka.connector.SnowflakeSinkConnectorConfig.ENABLE_TASK_FAIL_ON_AUTHORIZATION_ERRORS; | ||
import static com.snowflake.kafka.connector.SnowflakeSinkConnectorConfig.ENABLE_TASK_FAIL_ON_AUTHORIZATION_ERRORS_DEFAULT; | ||
import static com.snowflake.kafka.connector.internal.SnowflakeErrors.ERROR_1005; | ||
|
||
import java.util.Map; | ||
|
||
/** | ||
* When the user rotates Snowflake key that is stored in an external file the Connector hangs and | ||
* does not mark its tasks as failed. To fix this corner case we need to track the authorization | ||
* exception thrown during preCommit() and stop tasks during put(). | ||
* | ||
* <p>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<String, String> taskConfig) { | ||
authorizationTaskFailureEnabled = | ||
Boolean.parseBoolean( | ||
taskConfig.getOrDefault( | ||
ENABLE_TASK_FAIL_ON_AUTHORIZATION_ERRORS, | ||
Boolean.toString(ENABLE_TASK_FAIL_ON_AUTHORIZATION_ERRORS_DEFAULT))); | ||
} | ||
|
||
/** | ||
* Check if the thrown exception is related to authorization | ||
* | ||
* @param ex - any exception that occurred during preCommit | ||
*/ | ||
public void reportPrecommitException(Exception ex) { | ||
if (ex.getMessage().contains(AUTHORIZATION_EXCEPTION_MESSAGE)) { | ||
authorizationErrorReported = true; | ||
} | ||
} | ||
|
||
/** Throw exception if authorization has failed before */ | ||
public void throwExceptionIfAuthorizationFailed() { | ||
if (authorizationTaskFailureEnabled && authorizationErrorReported) { | ||
throw ERROR_1005.getException(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
...ava/com/snowflake/kafka/connector/SnowflakeSinkTaskAuthorizationExceptionTrackerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package com.snowflake.kafka.connector; | ||
|
||
import static com.snowflake.kafka.connector.SnowflakeSinkConnectorConfig.ENABLE_TASK_FAIL_ON_AUTHORIZATION_ERRORS; | ||
|
||
import com.snowflake.kafka.connector.internal.SnowflakeKafkaConnectorException; | ||
import com.snowflake.kafka.connector.internal.TestUtils; | ||
import java.util.Map; | ||
import java.util.stream.Stream; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
class SnowflakeSinkTaskAuthorizationExceptionTrackerTest { | ||
|
||
@Test | ||
public void shouldThrowExceptionOnAuthorizationError() { | ||
// given | ||
SnowflakeSinkTaskAuthorizationExceptionTracker tracker = | ||
new SnowflakeSinkTaskAuthorizationExceptionTracker(); | ||
Map<String, String> config = TestUtils.getConfig(); | ||
config.put(ENABLE_TASK_FAIL_ON_AUTHORIZATION_ERRORS, "true"); | ||
tracker.updateStateOnTaskStart(config); | ||
|
||
// when | ||
tracker.reportPrecommitException(new Exception("Authorization failed after retry")); | ||
|
||
// then | ||
Assertions.assertThrows( | ||
SnowflakeKafkaConnectorException.class, tracker::throwExceptionIfAuthorizationFailed); | ||
} | ||
|
||
@Test | ||
public void shouldNotThrowExceptionWhenNoExceptionReported() { | ||
// given | ||
SnowflakeSinkTaskAuthorizationExceptionTracker tracker = | ||
new SnowflakeSinkTaskAuthorizationExceptionTracker(); | ||
Map<String, String> config = TestUtils.getConfig(); | ||
config.put(ENABLE_TASK_FAIL_ON_AUTHORIZATION_ERRORS, "true"); | ||
tracker.updateStateOnTaskStart(config); | ||
|
||
// expect | ||
Assertions.assertDoesNotThrow(tracker::throwExceptionIfAuthorizationFailed); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("noExceptionConditions") | ||
public void shouldNotThrowException(boolean enabled, String exceptionMessage) { | ||
// given | ||
SnowflakeSinkTaskAuthorizationExceptionTracker tracker = | ||
new SnowflakeSinkTaskAuthorizationExceptionTracker(); | ||
Map<String, String> config = TestUtils.getConfig(); | ||
config.put(ENABLE_TASK_FAIL_ON_AUTHORIZATION_ERRORS, Boolean.toString(enabled)); | ||
tracker.updateStateOnTaskStart(config); | ||
|
||
// when | ||
tracker.reportPrecommitException(new Exception(exceptionMessage)); | ||
|
||
// then | ||
Assertions.assertDoesNotThrow(tracker::throwExceptionIfAuthorizationFailed); | ||
} | ||
|
||
public static Stream<Arguments> noExceptionConditions() { | ||
return Stream.of( | ||
Arguments.of(false, "Authorization failed after retry"), | ||
Arguments.of(true, "NullPointerException")); | ||
} | ||
} |