1717import org .opensearch .dataprepper .model .acknowledgements .AcknowledgementSet ;
1818import org .opensearch .dataprepper .model .acknowledgements .AcknowledgementSetManager ;
1919import org .opensearch .dataprepper .model .source .coordinator .enhanced .EnhancedSourceCoordinator ;
20+ import org .opensearch .dataprepper .model .source .coordinator .exceptions .PartitionUpdateException ;
2021import org .opensearch .dataprepper .plugins .source .dynamodb .DynamoDBSourceConfig ;
2122import org .opensearch .dataprepper .plugins .source .dynamodb .coordination .partition .StreamPartition ;
2223import org .opensearch .dataprepper .plugins .source .dynamodb .coordination .state .StreamProgressState ;
2526import java .util .Optional ;
2627import java .util .function .Consumer ;
2728
29+ import static org .hamcrest .MatcherAssert .assertThat ;
30+ import static org .hamcrest .Matchers .equalTo ;
31+ import static org .junit .jupiter .api .Assertions .assertThrows ;
32+ import static org .mockito .Mockito .doThrow ;
33+ import static org .mockito .Mockito .mock ;
2834import static org .mockito .Mockito .verifyNoInteractions ;
2935import static org .mockito .Mockito .verifyNoMoreInteractions ;
3036
@@ -78,6 +84,8 @@ void testCreateAcknowledgmentSet() {
7884 when (acknowledgementSetManager .create (any (Consumer .class ), any (Duration .class )))
7985 .thenReturn (acknowledgementSet );
8086
87+ shardAcknowledgementManager .startUpdatingOwnershipForShard (streamPartition );
88+
8189 AcknowledgementSet result = shardAcknowledgementManager .createAcknowledgmentSet (
8290 streamPartition , "seq123" , false );
8391
@@ -91,6 +99,8 @@ void testCreateAcknowledgmentSet_withNullSequenceNumber() {
9199 when (acknowledgementSetManager .create (any (Consumer .class ), any (Duration .class )))
92100 .thenReturn (acknowledgementSet );
93101
102+ shardAcknowledgementManager .startUpdatingOwnershipForShard (streamPartition );
103+
94104 AcknowledgementSet result = shardAcknowledgementManager .createAcknowledgmentSet (
95105 streamPartition , null , false );
96106
@@ -181,6 +191,8 @@ void testPositiveAcknowledgment_finalAcknowledgmentSet_completes_partition() {
181191 shardAcknowledgementManager .runMonitorAcknowledgmentLoop (stopWorkerConsumer );
182192
183193 verify (sourceCoordinator ).completePartition (streamPartition );
194+
195+ assertThat (shardAcknowledgementManager .isStillTrackingShard (streamPartition ), equalTo (false ));
184196 }
185197
186198 @ Test
@@ -199,6 +211,9 @@ void testAcknowledgmentTimeout() throws Exception {
199211 Thread .sleep (10 );
200212
201213 // Run the acknowledgment loop
214+ assertThat (shardAcknowledgementManager .isStillTrackingShard (streamPartition ), equalTo (true ));
215+ shardAcknowledgementManager .runMonitorAcknowledgmentLoop (stopWorkerConsumer );
216+ assertThat (shardAcknowledgementManager .isStillTrackingShard (streamPartition ), equalTo (false ));
202217 shardAcknowledgementManager .runMonitorAcknowledgmentLoop (stopWorkerConsumer );
203218
204219 verify (sourceCoordinator ).giveUpPartition (streamPartition );
@@ -215,6 +230,7 @@ void testMultipleAcknowledgments_handledCorrectly() {
215230 .thenReturn (acknowledgementSet );
216231 when (streamPartition .getProgressState ()).thenReturn (Optional .of (streamProgressState ));
217232
233+ shardAcknowledgementManager .startUpdatingOwnershipForShard (streamPartition );
218234 // Create first acknowledgment set and capture its callback
219235 shardAcknowledgementManager .createAcknowledgmentSet (streamPartition , "seq123" , false );
220236 Consumer <Boolean > callback1 = ackCallbackCaptor .getValue ();
@@ -228,6 +244,9 @@ void testMultipleAcknowledgments_handledCorrectly() {
228244 callback2 .accept (false ); // Negative ack
229245
230246 // Run the acknowledgment loop
247+ assertThat (shardAcknowledgementManager .isStillTrackingShard (streamPartition ), equalTo (true ));
248+ shardAcknowledgementManager .runMonitorAcknowledgmentLoop (stopWorkerConsumer );
249+ assertThat (shardAcknowledgementManager .isStillTrackingShard (streamPartition ), equalTo (false ));
231250 shardAcknowledgementManager .runMonitorAcknowledgmentLoop (stopWorkerConsumer );
232251
233252 final InOrder inOrder = Mockito .inOrder (sourceCoordinator , streamProgressState );
@@ -237,4 +256,67 @@ void testMultipleAcknowledgments_handledCorrectly() {
237256
238257 verifyNoMoreInteractions (streamProgressState );
239258 }
259+
260+ @ Test
261+ void create_acknowledgment_set_throws_ShardNotTrackedException_if_shard_is_not_being_tracked () {
262+ assertThrows (ShardNotTrackedException .class , () -> shardAcknowledgementManager .createAcknowledgmentSet (streamPartition , "seq123" , false ));
263+ }
264+
265+ @ Test
266+ void failure_to_update_state_for_one_partition_stops_tracking_that_partition_and_continues_on_others () {
267+ final StreamPartition secondPartition = mock (StreamPartition .class );
268+
269+
270+ when (streamPartition .getProgressState ()).thenReturn (Optional .of (streamProgressState ));
271+
272+ when (secondPartition .getProgressState ()).thenReturn (Optional .of (streamProgressState ));
273+
274+ // Setup
275+ when (dynamoDBSourceConfig .getShardAcknowledgmentTimeout ()).thenReturn (Duration .ofMinutes (15 ));
276+ when (acknowledgementSetManager .create (ackCallbackCaptor .capture (), any (Duration .class )))
277+ .thenReturn (acknowledgementSet );
278+ when (streamPartition .getProgressState ()).thenReturn (Optional .of (streamProgressState ));
279+
280+ shardAcknowledgementManager .startUpdatingOwnershipForShard (streamPartition );
281+ // Create first acknowledgment set and capture its callback
282+ shardAcknowledgementManager .createAcknowledgmentSet (streamPartition , "seq123" , false );
283+ Consumer <Boolean > callback1 = ackCallbackCaptor .getValue ();
284+
285+ // Create second acknowledgment set and capture its callback
286+ shardAcknowledgementManager .createAcknowledgmentSet (streamPartition , "seq124" , false );
287+ Consumer <Boolean > callback2 = ackCallbackCaptor .getValue ();
288+
289+ shardAcknowledgementManager .startUpdatingOwnershipForShard (secondPartition );
290+ // Create first acknowledgment set and capture its callback
291+ shardAcknowledgementManager .createAcknowledgmentSet (secondPartition , "seq123" , false );
292+ Consumer <Boolean > callback1_2 = ackCallbackCaptor .getValue ();
293+
294+ // Create second acknowledgment set and capture its callback
295+ shardAcknowledgementManager .createAcknowledgmentSet (secondPartition , "seq124" , false );
296+ Consumer <Boolean > callback2_2 = ackCallbackCaptor .getValue ();
297+
298+ // Simulate mixed acknowledgments
299+ callback1 .accept (true ); // Positive ack
300+ callback2 .accept (true );
301+
302+ callback1_2 .accept (true );
303+ callback2_2 .accept (true );
304+
305+ doThrow (PartitionUpdateException .class ).when (sourceCoordinator ).saveProgressStateForPartition (eq (streamPartition ),
306+ any (Duration .class ));
307+
308+
309+ // Run the acknowledgment loop
310+ assertThat (shardAcknowledgementManager .isStillTrackingShard (streamPartition ), equalTo (true ));
311+ assertThat (shardAcknowledgementManager .isStillTrackingShard (secondPartition ), equalTo (true ));
312+ shardAcknowledgementManager .runMonitorAcknowledgmentLoop (stopWorkerConsumer );
313+ assertThat (shardAcknowledgementManager .isStillTrackingShard (streamPartition ), equalTo (false ));
314+ assertThat (shardAcknowledgementManager .isStillTrackingShard (secondPartition ), equalTo (true ));
315+ shardAcknowledgementManager .runMonitorAcknowledgmentLoop (stopWorkerConsumer );
316+
317+ final InOrder inOrder = Mockito .inOrder (sourceCoordinator , streamProgressState );
318+ inOrder .verify (streamProgressState ).setSequenceNumber ("seq124" );
319+ inOrder .verify (sourceCoordinator ).saveProgressStateForPartition (secondPartition , dynamoDBSourceConfig .getShardAcknowledgmentTimeout ());
320+ inOrder .verify (sourceCoordinator ).giveUpPartition (streamPartition );
321+ }
240322}
0 commit comments