Skip to content

Commit 094db94

Browse files
authored
Fixing the crawler framework to handle ddb outage scenario (#6207)
* Fixing the crawler framework to handle ddb outage scenario
1 parent bf1de9e commit 094db94

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

  • data-prepper-plugins/saas-source-plugins/source-crawler/src
    • main/java/org/opensearch/dataprepper/plugins/source/source_crawler/coordination/scheduler
    • test/java/org/opensearch/dataprepper/plugins/source/source_crawler/coordination/scheduler

data-prepper-plugins/saas-source-plugins/source-crawler/src/main/java/org/opensearch/dataprepper/plugins/source/source_crawler/coordination/scheduler/LeaderScheduler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ public void run() {
6565
try {
6666
coordinator.saveProgressStateForPartition(leaderPartition, DEFAULT_EXTEND_LEASE_MINUTES);
6767
} catch (final Exception e) {
68+
// For any reason, if the leader schedule unable to reach source coordination ddb table,
69+
// it should give up leader partition and require whenever ddb store reachable again
70+
// Not giving up will make this to continue hold on to an ownership expired record which will create inconsistent state issues
71+
// if you are not the owner in ddb table, then you are not supposed to hold the ownership
72+
leaderPartition = null;
6873
LOG.error("Failed to save Leader partition state. This process will retry.");
6974
}
7075
}

data-prepper-plugins/saas-source-plugins/source-crawler/src/test/java/org/opensearch/dataprepper/plugins/source/source_crawler/coordination/scheduler/LeaderSchedulerTest.java

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.opensearch.dataprepper.plugins.source.source_crawler.coordination.scheduler;
22

3+
import org.junit.jupiter.api.DisplayName;
34
import org.junit.jupiter.api.Test;
45
import org.junit.jupiter.api.extension.ExtendWith;
56
import org.junit.jupiter.params.ParameterizedTest;
@@ -18,6 +19,7 @@
1819
import java.util.concurrent.ExecutorService;
1920
import java.util.concurrent.Executors;
2021

22+
import static org.junit.jupiter.api.Assertions.assertThrows;
2123
import static org.mockito.ArgumentMatchers.any;
2224
import static org.mockito.ArgumentMatchers.eq;
2325
import static org.mockito.BDDMockito.given;
@@ -27,7 +29,6 @@
2729
import static org.mockito.Mockito.verify;
2830
import static org.mockito.Mockito.verifyNoInteractions;
2931
import static org.mockito.Mockito.when;
30-
import static org.junit.jupiter.api.Assertions.assertThrows;
3132

3233
@ExtendWith(MockitoExtension.class)
3334
public class LeaderSchedulerTest {
@@ -169,13 +170,56 @@ void testWhileLoopRunnningAfterTheSleep() throws InterruptedException {
169170
void testSetLeaderProgressState_throwsExceptionOnStateMismatch() {
170171
// Create a LeaderPartition with TokenPaginationCrawlerLeaderProgressState
171172
LeaderPartition leaderPartition = new LeaderPartition(new TokenPaginationCrawlerLeaderProgressState(""));
172-
173+
173174
// Try to set a different type of state (PaginationCrawlerLeaderProgressState)
174175
PaginationCrawlerLeaderProgressState incompatibleState = new PaginationCrawlerLeaderProgressState(Instant.now());
175-
176+
176177
// Verify that RuntimeException is thrown due to state type mismatch
177178
RuntimeException exception = assertThrows(RuntimeException.class, () -> {
178179
leaderPartition.setLeaderProgressState(incompatibleState);
179180
});
180181
}
182+
183+
@Test
184+
@DisplayName("Ensure that if DynamoDB becomes unreachable, the leader gives up the partition and retries acquisition")
185+
void testLeaderPartitionGivenUpOnSaveFailure_andRetryAcquire() throws InterruptedException {
186+
// This test verifies the fix for line 72: leaderPartition = null when saveProgressStateForPartition fails
187+
// This ensures that if DynamoDB becomes unreachable, the leader gives up the partition and retries acquisition
188+
189+
LeaderScheduler leaderScheduler = new LeaderScheduler(coordinator, crawler);
190+
leaderScheduler.setLeaseInterval(Duration.ofMillis(10));
191+
192+
LeaderPartition leaderPartition = new LeaderPartition(new TokenPaginationCrawlerLeaderProgressState(""));
193+
TokenPaginationCrawlerLeaderProgressState state = (TokenPaginationCrawlerLeaderProgressState) leaderPartition.getProgressState().get();
194+
state.setInitialized(true);
195+
state.setLastToken(LAST_TOKEN);
196+
197+
// First acquisition succeeds, but subsequent saveProgressStateForPartition fails (simulating DynamoDB outage)
198+
// Then second acquisition should be attempted after giving up the partition
199+
when(coordinator.acquireAvailablePartition(LeaderPartition.PARTITION_TYPE))
200+
.thenReturn(Optional.of(leaderPartition)) // First acquisition succeeds
201+
.thenReturn(Optional.of(leaderPartition)); // Second acquisition after giving up partition
202+
203+
// saveProgressStateForPartition throws exception to simulate DynamoDB outage
204+
doThrow(new RuntimeException("DynamoDB unreachable"))
205+
.doNothing() // Second time succeeds after recovery
206+
.when(coordinator).saveProgressStateForPartition(any(LeaderPartition.class), any(Duration.class));
207+
208+
ExecutorService executorService = Executors.newSingleThreadExecutor();
209+
executorService.submit(leaderScheduler);
210+
211+
// Wait long enough for multiple iterations
212+
Thread.sleep(100);
213+
executorService.shutdownNow();
214+
215+
// Verify that crawler was called multiple times (showing the scheduler continued to work)
216+
verify(crawler, atLeast(2)).crawl(any(LeaderPartition.class), any(EnhancedSourceCoordinator.class));
217+
218+
// Verify that acquireAvailablePartition was called at least twice:
219+
// once for initial acquisition, and again after giving up due to save failure
220+
verify(coordinator, atLeast(2)).acquireAvailablePartition(LeaderPartition.PARTITION_TYPE);
221+
222+
// Verify that saveProgressStateForPartition was attempted multiple times
223+
verify(coordinator, atLeast(2)).saveProgressStateForPartition(any(LeaderPartition.class), any(Duration.class));
224+
}
181225
}

0 commit comments

Comments
 (0)