2121
2222import org .apache .logging .log4j .LogManager ;
2323import org .apache .logging .log4j .Logger ;
24+ import org .apache .lucene .util .SetOnce ;
2425import org .elasticsearch .ExceptionsHelper ;
2526import org .elasticsearch .Version ;
2627import org .elasticsearch .action .ActionListener ;
143144import org .elasticsearch .env .TestEnvironment ;
144145import org .elasticsearch .gateway .MetaStateService ;
145146import org .elasticsearch .gateway .TransportNodesListGatewayStartedShards ;
147+ import org .elasticsearch .index .Index ;
146148import org .elasticsearch .index .analysis .AnalysisRegistry ;
147149import org .elasticsearch .index .seqno .GlobalCheckpointSyncAction ;
148150import org .elasticsearch .index .seqno .RetentionLeaseSyncer ;
213215import static org .hamcrest .Matchers .empty ;
214216import static org .hamcrest .Matchers .hasSize ;
215217import static org .hamcrest .Matchers .instanceOf ;
218+ import static org .hamcrest .Matchers .is ;
216219import static org .hamcrest .Matchers .lessThanOrEqualTo ;
217220import static org .mockito .Mockito .mock ;
218221
@@ -505,7 +508,7 @@ public void testConcurrentSnapshotCreateAndDeleteOther() {
505508 }
506509 }
507510
508- public void testConcurrentSnapshotDeleteAndDeleteIndex () {
511+ public void testConcurrentSnapshotDeleteAndDeleteIndex () throws IOException {
509512 setupTestCluster (randomFrom (1 , 3 , 5 ), randomIntBetween (2 , 10 ));
510513
511514 String repoName = "repo" ;
@@ -516,11 +519,13 @@ public void testConcurrentSnapshotDeleteAndDeleteIndex() {
516519 testClusterNodes .currentMaster (testClusterNodes .nodes .values ().iterator ().next ().clusterService .state ());
517520
518521 final StepListener <Collection <CreateIndexResponse >> createIndicesListener = new StepListener <>();
522+ final int indices = randomIntBetween (5 , 20 );
519523
524+ final SetOnce <Index > firstIndex = new SetOnce <>();
520525 continueOrDie (createRepoAndIndex (repoName , index , 1 ), createIndexResponse -> {
526+ firstIndex .set (masterNode .clusterService .state ().metaData ().index (index ).getIndex ());
521527 // create a few more indices to make it more likely that the subsequent index delete operation happens before snapshot
522528 // finalization
523- final int indices = randomIntBetween (5 , 20 );
524529 final GroupedActionListener <CreateIndexResponse > listener = new GroupedActionListener <>(createIndicesListener , indices );
525530 for (int i = 0 ; i < indices ; ++i ) {
526531 client ().admin ().indices ().create (new CreateIndexRequest ("index-" + i ), listener );
@@ -529,23 +534,55 @@ public void testConcurrentSnapshotDeleteAndDeleteIndex() {
529534
530535 final StepListener <CreateSnapshotResponse > createSnapshotResponseStepListener = new StepListener <>();
531536
537+ final boolean partialSnapshot = randomBoolean ();
538+
532539 continueOrDie (createIndicesListener , createIndexResponses ->
533540 client ().admin ().cluster ().prepareCreateSnapshot (repoName , snapshotName ).setWaitForCompletion (false )
534- .execute (createSnapshotResponseStepListener ));
541+ .setPartial ( partialSnapshot ). execute (createSnapshotResponseStepListener ));
535542
536543 continueOrDie (createSnapshotResponseStepListener ,
537- createSnapshotResponse -> client ().admin ().indices ().delete (new DeleteIndexRequest (index ), noopListener ()));
544+ createSnapshotResponse -> client ().admin ().indices ().delete (new DeleteIndexRequest (index ),
545+ new ActionListener <AcknowledgedResponse >() {
546+ @ Override
547+ public void onResponse (AcknowledgedResponse acknowledgedResponse ) {
548+ if (partialSnapshot ) {
549+ // Recreate index by the same name to test that we don't snapshot conflicting metadata in this scenario
550+ client ().admin ().indices ().create (new CreateIndexRequest (index ), noopListener ());
551+ }
552+ }
553+
554+ @ Override
555+ public void onFailure (Exception e ) {
556+ if (partialSnapshot ) {
557+ throw new AssertionError ("Delete index should always work during partial snapshots" , e );
558+ }
559+ }
560+ }));
538561
539562 deterministicTaskQueue .runAllRunnableTasks ();
540563
541564 SnapshotsInProgress finalSnapshotsInProgress = masterNode .clusterService .state ().custom (SnapshotsInProgress .TYPE );
542565 assertFalse (finalSnapshotsInProgress .entries ().stream ().anyMatch (entry -> entry .state ().completed () == false ));
543566 final Repository repository = masterNode .repositoriesService .repository (repoName );
544- Collection <SnapshotId > snapshotIds = getRepositoryData (repository ).getSnapshotIds ();
567+ final RepositoryData repositoryData = getRepositoryData (repository );
568+ Collection <SnapshotId > snapshotIds = repositoryData .getSnapshotIds ();
545569 assertThat (snapshotIds , hasSize (1 ));
546570
547571 final SnapshotInfo snapshotInfo = repository .getSnapshotInfo (snapshotIds .iterator ().next ());
548572 assertEquals (SnapshotState .SUCCESS , snapshotInfo .state ());
573+ if (partialSnapshot ) {
574+ // Single shard for each index so we either get all indices or all except for the deleted index
575+ assertThat (snapshotInfo .successfulShards (), either (is (indices + 1 )).or (is (indices )));
576+ if (snapshotInfo .successfulShards () == indices + 1 ) {
577+ final IndexMetaData indexMetaData =
578+ repository .getSnapshotIndexMetaData (snapshotInfo .snapshotId (), repositoryData .resolveIndexId (index ));
579+ // Make sure we snapshotted the metadata of this index and not the recreated version
580+ assertEquals (indexMetaData .getIndex (), firstIndex .get ());
581+ }
582+ } else {
583+ // Index delete must be blocked for non-partial snapshots and we get a snapshot for every index
584+ assertEquals (snapshotInfo .successfulShards (), indices + 1 );
585+ }
549586 assertEquals (0 , snapshotInfo .failedShards ());
550587 }
551588
0 commit comments