5555import java .nio .file .Files ;
5656import java .nio .file .Path ;
5757import java .util .ArrayList ;
58+ import java .util .Arrays ;
5859import java .util .Collection ;
5960import java .util .Collections ;
61+ import java .util .EnumSet ;
6062import java .util .HashSet ;
6163import java .util .List ;
6264import java .util .Map ;
6365import java .util .Objects ;
6466import java .util .Set ;
67+ import java .util .function .Function ;
6568import java .util .function .Predicate ;
6669import java .util .stream .Collectors ;
6770
@@ -635,6 +638,63 @@ public void testRetrievingSnapshotsWhenRepositoryIsMissing() throws Exception {
635638 expectThrows (RepositoryMissingException .class , multiRepoFuture ::actionGet );
636639 }
637640
641+ public void testFilterByState () throws Exception {
642+ final String repoName = "test-repo" ;
643+ final Path repoPath = randomRepoPath ();
644+ createRepository (repoName , "mock" , repoPath );
645+
646+ // Create a successful snapshot
647+ createFullSnapshot (repoName , "snapshot-success" );
648+
649+ final Function <EnumSet <SnapshotState >, List <SnapshotInfo >> getSnapshotsForStates = (states ) -> {
650+ return clusterAdmin ().prepareGetSnapshots (TEST_REQUEST_TIMEOUT , repoName ).setStates (states ).get ().getSnapshots ();
651+ };
652+
653+ // Fetch snapshots with state=SUCCESS
654+ var snapshots = getSnapshotsForStates .apply (EnumSet .of (SnapshotState .SUCCESS ));
655+ assertThat (snapshots , hasSize (1 ));
656+ assertThat (snapshots .getFirst ().state (), is (SnapshotState .SUCCESS ));
657+
658+ // Create a snapshot in progress
659+ blockAllDataNodes (repoName );
660+ startFullSnapshot (repoName , "snapshot-in-progress" );
661+ awaitNumberOfSnapshotsInProgress (1 );
662+
663+ // Fetch snapshots with state=IN_PROGRESS
664+ snapshots = getSnapshotsForStates .apply (EnumSet .of (SnapshotState .IN_PROGRESS ));
665+ assertThat (snapshots , hasSize (1 ));
666+ assertThat (snapshots .getFirst ().state (), is (SnapshotState .IN_PROGRESS ));
667+
668+ // Fetch snapshots with multiple states (SUCCESS, IN_PROGRESS)
669+ snapshots = getSnapshotsForStates .apply (EnumSet .of (SnapshotState .SUCCESS , SnapshotState .IN_PROGRESS ));
670+ assertThat (snapshots , hasSize (2 ));
671+ var states = snapshots .stream ().map (SnapshotInfo ::state ).collect (Collectors .toSet ());
672+ assertTrue (states .contains (SnapshotState .SUCCESS ));
673+ assertTrue (states .contains (SnapshotState .IN_PROGRESS ));
674+
675+ // Fetch all snapshots (without state)
676+ snapshots = clusterAdmin ().prepareGetSnapshots (TEST_REQUEST_TIMEOUT , repoName ).get ().getSnapshots ();
677+ assertThat (snapshots , hasSize (2 ));
678+
679+ // Fetch snapshots with an invalid state
680+ IllegalArgumentException e = expectThrows (
681+ IllegalArgumentException .class ,
682+ () -> getSnapshotsForStates .apply (EnumSet .of (SnapshotState .valueOf ("FOO" )))
683+ );
684+ assertThat (e .getMessage (), is ("No enum constant org.elasticsearch.snapshots.SnapshotState.FOO" ));
685+
686+ // Allow the IN_PROGRESS snapshot to finish, then verify GET using SUCCESS has results and IN_PROGRESS does not.
687+ unblockAllDataNodes (repoName );
688+ awaitNumberOfSnapshotsInProgress (0 );
689+ snapshots = clusterAdmin ().prepareGetSnapshots (TEST_REQUEST_TIMEOUT , repoName ).get ().getSnapshots ();
690+ assertThat (snapshots , hasSize (2 ));
691+ states = snapshots .stream ().map (SnapshotInfo ::state ).collect (Collectors .toSet ());
692+ assertThat (states , hasSize (1 ));
693+ assertTrue (states .contains (SnapshotState .SUCCESS ));
694+ snapshots = getSnapshotsForStates .apply (EnumSet .of (SnapshotState .IN_PROGRESS ));
695+ assertThat (snapshots , hasSize (0 ));
696+ }
697+
638698 public void testRetrievingSnapshotsWhenRepositoryIsUnreadable () throws Exception {
639699 final String repoName = randomIdentifier ();
640700 final Path repoPath = randomRepoPath ();
@@ -956,6 +1016,12 @@ public void testAllFeatures() {
9561016 // INDICES and by SHARDS. The actual sorting behaviour for these cases is tested elsewhere, here we're just checking that sorting
9571017 // interacts correctly with the other parameters to the API.
9581018
1019+ final EnumSet <SnapshotState > states = EnumSet .copyOf (randomNonEmptySubsetOf (Arrays .asList (SnapshotState .values ())));
1020+ // Note: The selected state(s) may not match any existing snapshots.
1021+ // The actual filtering behaviour for such cases is tested in the dedicated test.
1022+ // Here we're just checking that states interacts correctly with the other parameters to the API.
1023+ snapshotInfoPredicate = snapshotInfoPredicate .and (si -> states .contains (si .state ()));
1024+
9591025 // compute the ordered sequence of snapshots which match the repository/snapshot name filters and SLM policy filter
9601026 final var selectedSnapshots = snapshotInfos .stream ()
9611027 .filter (snapshotInfoPredicate )
@@ -967,7 +1033,8 @@ public void testAllFeatures() {
9671033 )
9681034 // apply sorting params
9691035 .sort (sortKey )
970- .order (order );
1036+ .order (order )
1037+ .states (states );
9711038
9721039 // sometimes use ?from_sort_value to skip some items; note that snapshots skipped in this way are subtracted from
9731040 // GetSnapshotsResponse.totalCount whereas snapshots skipped by ?after and ?offset are not
@@ -1054,7 +1121,8 @@ public void testAllFeatures() {
10541121 .sort (sortKey )
10551122 .order (order )
10561123 .size (nextSize )
1057- .after (SnapshotSortKey .decodeAfterQueryParam (nextRequestAfter ));
1124+ .after (SnapshotSortKey .decodeAfterQueryParam (nextRequestAfter ))
1125+ .states (states );
10581126 final GetSnapshotsResponse nextResponse = safeAwait (l -> client ().execute (TransportGetSnapshotsAction .TYPE , nextRequest , l ));
10591127
10601128 assertEquals (
0 commit comments