5050import org .elasticsearch .xpack .transform .notifications .MockTransformAuditor ;
5151import org .elasticsearch .xpack .transform .notifications .TransformAuditor ;
5252import org .elasticsearch .xpack .transform .persistence .IndexBasedTransformConfigManager ;
53+ import org .elasticsearch .xpack .transform .persistence .SeqNoPrimaryTermAndIndex ;
5354import org .junit .After ;
5455import org .junit .Before ;
5556
6061import java .util .concurrent .CountDownLatch ;
6162import java .util .concurrent .TimeUnit ;
6263import java .util .concurrent .atomic .AtomicBoolean ;
64+ import java .util .concurrent .atomic .AtomicInteger ;
6365import java .util .concurrent .atomic .AtomicReference ;
6466import java .util .function .Consumer ;
6567import java .util .function .Function ;
@@ -86,7 +88,7 @@ public class TransformIndexerFailureHandlingTests extends ESTestCase {
8688 private Client client ;
8789 private ThreadPool threadPool ;
8890
89- class MockedTransformIndexer extends TransformIndexer {
91+ static class MockedTransformIndexer extends ClientTransformIndexer {
9092
9193 private final Function <SearchRequest , SearchResponse > searchFunction ;
9294 private final Function <BulkRequest , BulkResponse > bulkFunction ;
@@ -121,11 +123,15 @@ class MockedTransformIndexer extends TransformIndexer {
121123 transformConfig ,
122124 initialState ,
123125 initialPosition ,
126+ mock (Client .class ),
124127 jobStats ,
128+ transformConfig ,
125129 /* TransformProgress */ null ,
126130 TransformCheckpoint .EMPTY ,
127131 TransformCheckpoint .EMPTY ,
128- context
132+ new SeqNoPrimaryTermAndIndex (1 , 1 , "foo" ),
133+ context ,
134+ false
129135 );
130136 this .searchFunction = searchFunction ;
131137 this .bulkFunction = bulkFunction ;
@@ -180,7 +186,7 @@ protected void doNextBulk(BulkRequest request, ActionListener<BulkResponse> next
180186
181187 try {
182188 BulkResponse response = bulkFunction .apply (request );
183- nextPhase . onResponse (response );
189+ super . handleBulkResponse (response , nextPhase );
184190 } catch (Exception e ) {
185191 nextPhase .onFailure (e );
186192 }
@@ -245,7 +251,7 @@ void doGetInitialProgress(SearchRequest request, ActionListener<SearchResponse>
245251 }
246252
247253 @ Override
248- void doDeleteByQuery (DeleteByQueryRequest deleteByQueryRequest , ActionListener <BulkByScrollResponse > responseListener ) {
254+ protected void doDeleteByQuery (DeleteByQueryRequest deleteByQueryRequest , ActionListener <BulkByScrollResponse > responseListener ) {
249255 try {
250256 BulkByScrollResponse response = deleteByQueryFunction .apply (deleteByQueryRequest );
251257 responseListener .onResponse (response );
@@ -255,7 +261,7 @@ void doDeleteByQuery(DeleteByQueryRequest deleteByQueryRequest, ActionListener<B
255261 }
256262
257263 @ Override
258- void refreshDestinationIndex (ActionListener <RefreshResponse > responseListener ) {
264+ protected void refreshDestinationIndex (ActionListener <RefreshResponse > responseListener ) {
259265 responseListener .onResponse (new RefreshResponse (1 , 1 , 0 , Collections .emptyList ()));
260266 }
261267
@@ -700,6 +706,116 @@ public void testRetentionPolicyDeleteByQueryThrowsTemporaryProblem() throws Exce
700706 assertEquals (1 , context .getFailureCount ());
701707 }
702708
709+ public void testFailureCounterIsResetOnSuccess () throws Exception {
710+ String transformId = randomAlphaOfLength (10 );
711+ TransformConfig config = new TransformConfig (
712+ transformId ,
713+ randomSourceConfig (),
714+ randomDestConfig (),
715+ null ,
716+ null ,
717+ null ,
718+ randomPivotConfig (),
719+ null ,
720+ randomBoolean () ? null : randomAlphaOfLengthBetween (1 , 1000 ),
721+ null ,
722+ null ,
723+ null ,
724+ null
725+ );
726+
727+ final SearchResponse searchResponse = new SearchResponse (
728+ new InternalSearchResponse (
729+ new SearchHits (new SearchHit [] { new SearchHit (1 ) }, new TotalHits (1L , TotalHits .Relation .EQUAL_TO ), 1.0f ),
730+ // Simulate completely null aggs
731+ null ,
732+ new Suggest (Collections .emptyList ()),
733+ new SearchProfileShardResults (Collections .emptyMap ()),
734+ false ,
735+ false ,
736+ 1
737+ ),
738+ "" ,
739+ 1 ,
740+ 1 ,
741+ 0 ,
742+ 0 ,
743+ ShardSearchFailure .EMPTY_ARRAY ,
744+ SearchResponse .Clusters .EMPTY
745+ );
746+
747+ AtomicReference <IndexerState > state = new AtomicReference <>(IndexerState .STOPPED );
748+ Function <SearchRequest , SearchResponse > searchFunction = new Function <>() {
749+ final AtomicInteger calls = new AtomicInteger (0 );
750+
751+ @ Override
752+ public SearchResponse apply (SearchRequest searchRequest ) {
753+ int call = calls .getAndIncrement ();
754+ if (call == 0 ) {
755+ throw new SearchPhaseExecutionException (
756+ "query" ,
757+ "Partial shards failure" ,
758+ new ShardSearchFailure [] { new ShardSearchFailure (new Exception ()) }
759+ );
760+ }
761+ return searchResponse ;
762+ }
763+ };
764+
765+ Function <BulkRequest , BulkResponse > bulkFunction = request -> new BulkResponse (new BulkItemResponse [0 ], 1 );
766+
767+ final AtomicBoolean failIndexerCalled = new AtomicBoolean (false );
768+ final AtomicReference <String > failureMessage = new AtomicReference <>();
769+ Consumer <String > failureConsumer = message -> {
770+ failIndexerCalled .compareAndSet (false , true );
771+ failureMessage .compareAndSet (null , message );
772+ };
773+
774+ MockTransformAuditor auditor = MockTransformAuditor .createMockAuditor ();
775+ TransformContext .Listener contextListener = mock (TransformContext .Listener .class );
776+ TransformContext context = new TransformContext (TransformTaskState .STARTED , "" , 0 , contextListener );
777+
778+ MockedTransformIndexer indexer = createMockIndexer (
779+ config ,
780+ state ,
781+ searchFunction ,
782+ bulkFunction ,
783+ null ,
784+ failureConsumer ,
785+ threadPool ,
786+ ThreadPool .Names .GENERIC ,
787+ auditor ,
788+ context
789+ );
790+
791+ final CountDownLatch latch = indexer .newLatch (1 );
792+
793+ indexer .start ();
794+ assertThat (indexer .getState (), equalTo (IndexerState .STARTED ));
795+ assertTrue (indexer .maybeTriggerAsyncJob (System .currentTimeMillis ()));
796+ assertThat (indexer .getState (), equalTo (IndexerState .INDEXING ));
797+
798+ latch .countDown ();
799+ assertBusy (() -> assertThat (indexer .getState (), equalTo (IndexerState .STARTED )), 10 , TimeUnit .SECONDS );
800+ assertFalse (failIndexerCalled .get ());
801+ assertThat (indexer .getState (), equalTo (IndexerState .STARTED ));
802+ assertEquals (1 , context .getFailureCount ());
803+
804+ final CountDownLatch secondLatch = indexer .newLatch (1 );
805+
806+ indexer .start ();
807+ assertThat (indexer .getState (), equalTo (IndexerState .STARTED ));
808+ assertTrue (indexer .maybeTriggerAsyncJob (System .currentTimeMillis ()));
809+ assertThat (indexer .getState (), equalTo (IndexerState .INDEXING ));
810+
811+ secondLatch .countDown ();
812+ assertBusy (() -> assertThat (indexer .getState (), equalTo (IndexerState .STARTED )), 10 , TimeUnit .SECONDS );
813+ assertFalse (failIndexerCalled .get ());
814+ assertThat (indexer .getState (), equalTo (IndexerState .STARTED ));
815+ auditor .assertAllExpectationsMatched ();
816+ assertEquals (0 , context .getFailureCount ());
817+ }
818+
703819 private MockedTransformIndexer createMockIndexer (
704820 TransformConfig config ,
705821 AtomicReference <IndexerState > state ,
0 commit comments