Skip to content

Commit 18fd911

Browse files
committed
Add state query param to Get snapshots API
1 parent 3295149 commit 18fd911

File tree

8 files changed

+101
-2
lines changed

8 files changed

+101
-2
lines changed

docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ for those that were created by an SLM policy with a name starting with `policy-a
182182
created by an SLM policy but not those snapshots that were not created by an SLM policy. To include snapshots not created by an SLM
183183
policy you can use the special pattern `_none` that will match all snapshots without an SLM policy.
184184

185+
`state`::
186+
(Optional, string)
187+
Filter snapshots by state. Valid values are `SUCCESS`, `IN_PROGRESS`, `FAILED`, `PARTIAL`, or `INCOMPATIBLE`.
188+
185189
NOTE: The `after` parameter and `next` field allow for iterating through snapshots with some consistency guarantees regarding concurrent
186190
creation or deletion of snapshots. It is guaranteed that any snapshot that exists at the beginning of the iteration and is not concurrently
187191
deleted will be seen during the iteration. Snapshots concurrently created may be seen during an iteration.

rest-api-spec/src/main/resources/rest-api-spec/api/snapshot.get.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@
8585
"verbose":{
8686
"type":"boolean",
8787
"description":"Whether to show verbose snapshot info or only show the basic info found in the repository index blob"
88+
},
89+
"state": {
90+
"type": "string",
91+
"description": "Filter snapshots by state. Valid values are 'SUCCESS', 'IN_PROGRESS', 'FAILED', 'PARTIAL', or 'INCOMPATIBLE'."
8892
}
8993
}
9094
}

server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import static org.hamcrest.Matchers.hasSize;
7070
import static org.hamcrest.Matchers.in;
7171
import static org.hamcrest.Matchers.is;
72+
import static org.hamcrest.core.StringContains.containsString;
7273

7374
public class GetSnapshotsIT extends AbstractSnapshotIntegTestCase {
7475

@@ -633,6 +634,57 @@ public void testRetrievingSnapshotsWhenRepositoryIsMissing() throws Exception {
633634
expectThrows(RepositoryMissingException.class, multiRepoFuture::actionGet);
634635
}
635636

637+
public void testFilterByState() throws Exception {
638+
final String repoName = "test-repo";
639+
final Path repoPath = randomRepoPath();
640+
createRepository(repoName, "mock", repoPath);
641+
maybeInitWithOldSnapshotVersion(repoName, repoPath);
642+
643+
// Create a successful snapshot
644+
String successSnapshot = "snapshot-success";
645+
createFullSnapshot(repoName, successSnapshot);
646+
647+
// Create a snapshot in progress
648+
String inProgressSnapshot = "snapshot-in-progress";
649+
blockAllDataNodes(repoName);
650+
startFullSnapshot(repoName, inProgressSnapshot);
651+
awaitNumberOfSnapshotsInProgress(1);
652+
awaitClusterState(
653+
state -> SnapshotsInProgress.get(state)
654+
.asStream()
655+
.flatMap(s -> s.shards().entrySet().stream())
656+
.allMatch(
657+
e -> e.getKey().getIndexName().equals("test-index-1") == false
658+
|| e.getValue().state() == SnapshotsInProgress.ShardState.SUCCESS
659+
)
660+
);
661+
662+
// Fetch all snapshots
663+
GetSnapshotsResponse responseAll = clusterAdmin().prepareGetSnapshots(TEST_REQUEST_TIMEOUT, repoName).get();
664+
assertThat(responseAll.getSnapshots(), hasSize(2));
665+
666+
// Fetch snapshots with state=SUCCESS
667+
GetSnapshotsResponse responseSuccess = clusterAdmin().prepareGetSnapshots(TEST_REQUEST_TIMEOUT, repoName)
668+
.setState(String.valueOf(SnapshotState.SUCCESS))
669+
.get();
670+
assertThat(responseSuccess.getSnapshots(), hasSize(1));
671+
assertThat(responseSuccess.getSnapshots().get(0).state(), is(SnapshotState.SUCCESS));
672+
673+
// Fetch snapshots with state=IN_PROGRESS
674+
GetSnapshotsResponse responseInProgress = clusterAdmin().prepareGetSnapshots(TEST_REQUEST_TIMEOUT, repoName)
675+
.setState(String.valueOf(SnapshotState.IN_PROGRESS))
676+
.get();
677+
assertThat(responseInProgress.getSnapshots(), hasSize(1));
678+
assertThat(responseInProgress.getSnapshots().get(0).state(), is(SnapshotState.IN_PROGRESS));
679+
680+
// Fetch snapshots with invalid state
681+
IllegalArgumentException e = expectThrows(
682+
IllegalArgumentException.class,
683+
() -> clusterAdmin().prepareGetSnapshots(TEST_REQUEST_TIMEOUT, repoName).setState("INVALID_STATE").get()
684+
);
685+
assertThat(e.getMessage(), containsString("state must be SUCCESS, IN_PROGRESS, FAILED, PARTIAL, or INCOMPATIBLE"));
686+
}
687+
636688
// Create a snapshot that is guaranteed to have a unique start time and duration for tests around ordering by either.
637689
// Don't use this with more than 3 snapshots on platforms with low-resolution clocks as the durations could always collide there
638690
// causing an infinite loop

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.elasticsearch.core.Nullable;
2020
import org.elasticsearch.core.TimeValue;
2121
import org.elasticsearch.search.sort.SortOrder;
22+
import org.elasticsearch.snapshots.SnapshotState;
2223
import org.elasticsearch.tasks.CancellableTask;
2324
import org.elasticsearch.tasks.Task;
2425
import org.elasticsearch.tasks.TaskId;
@@ -77,6 +78,8 @@ public class GetSnapshotsRequest extends MasterNodeRequest<GetSnapshotsRequest>
7778

7879
private boolean includeIndexNames = true;
7980

81+
private String state;
82+
8083
public GetSnapshotsRequest(TimeValue masterNodeTimeout) {
8184
super(masterNodeTimeout);
8285
}
@@ -118,6 +121,7 @@ public GetSnapshotsRequest(StreamInput in) throws IOException {
118121
if (in.getTransportVersion().onOrAfter(INDICES_FLAG_VERSION)) {
119122
includeIndexNames = in.readBoolean();
120123
}
124+
state = in.readOptionalString();
121125
}
122126

123127
@Override
@@ -137,6 +141,7 @@ public void writeTo(StreamOutput out) throws IOException {
137141
if (out.getTransportVersion().onOrAfter(INDICES_FLAG_VERSION)) {
138142
out.writeBoolean(includeIndexNames);
139143
}
144+
out.writeOptionalString(state);
140145
}
141146

142147
@Override
@@ -177,6 +182,12 @@ public ActionRequestValidationException validate() {
177182
} else if (after != null && fromSortValue != null) {
178183
validationException = addValidationError("can't use after and from_sort_value simultaneously", validationException);
179184
}
185+
if (state != null && !(Arrays.stream(SnapshotState.values()).anyMatch(s -> s.name().equalsIgnoreCase(state)))) {
186+
validationException = addValidationError(
187+
"state must be SUCCESS, IN_PROGRESS, FAILED, PARTIAL, or INCOMPATIBLE",
188+
validationException
189+
);
190+
}
180191
return validationException;
181192
}
182193

@@ -342,6 +353,15 @@ public boolean verbose() {
342353
return verbose;
343354
}
344355

356+
public String state() {
357+
return state;
358+
}
359+
360+
public GetSnapshotsRequest state(String state) {
361+
this.state = state;
362+
return this;
363+
}
364+
345365
@Override
346366
public Task createTask(long id, String type, String action, TaskId parentTaskId, Map<String, String> headers) {
347367
return new CancellableTask(id, type, action, getDescription(), parentTaskId, headers);

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,8 @@ public GetSnapshotsRequestBuilder setIncludeIndexNames(boolean indices) {
150150

151151
}
152152

153+
public GetSnapshotsRequestBuilder setState(String state) {
154+
request.state(state);
155+
return this;
156+
}
153157
}

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ protected void masterOperation(
162162
request.size(),
163163
SnapshotsInProgress.get(state),
164164
request.verbose(),
165-
request.includeIndexNames()
165+
request.includeIndexNames(),
166+
request.state()
166167
).runOperation(listener);
167168
}
168169

@@ -183,6 +184,7 @@ private class GetSnapshotsOperation {
183184
private final SnapshotNamePredicate snapshotNamePredicate;
184185
private final SnapshotPredicates fromSortValuePredicates;
185186
private final Predicate<String> slmPolicyPredicate;
187+
private final String state;
186188

187189
// snapshot ordering/pagination
188190
private final SnapshotSortKey sortBy;
@@ -226,7 +228,8 @@ private class GetSnapshotsOperation {
226228
int size,
227229
SnapshotsInProgress snapshotsInProgress,
228230
boolean verbose,
229-
boolean indices
231+
boolean indices,
232+
String state
230233
) {
231234
this.cancellableTask = cancellableTask;
232235
this.repositories = repositories;
@@ -239,6 +242,7 @@ private class GetSnapshotsOperation {
239242
this.snapshotsInProgress = snapshotsInProgress;
240243
this.verbose = verbose;
241244
this.indices = indices;
245+
this.state = state;
242246

243247
this.snapshotNamePredicate = SnapshotNamePredicate.forSnapshots(ignoreUnavailable, snapshots);
244248
this.fromSortValuePredicates = SnapshotPredicates.forFromSortValue(fromSortValue, sortBy, order);
@@ -573,6 +577,10 @@ private boolean matchesPredicates(SnapshotInfo snapshotInfo) {
573577
return false;
574578
}
575579

580+
if (state != null) {
581+
return state.equalsIgnoreCase(snapshotInfo.state().toString());
582+
}
583+
576584
if (slmPolicyPredicate == SlmPolicyPredicate.MATCH_ALL_POLICIES) {
577585
return true;
578586
}

server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetSnapshotsAction.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC
8282
final SortOrder order = SortOrder.fromString(request.param("order", getSnapshotsRequest.order().toString()));
8383
getSnapshotsRequest.order(order);
8484
getSnapshotsRequest.includeIndexNames(request.paramAsBoolean(INDEX_NAMES_XCONTENT_PARAM, getSnapshotsRequest.includeIndexNames()));
85+
getSnapshotsRequest.state(request.param("state"));
8586
return channel -> new RestCancellableNodeClient(client, request.getHttpChannel()).admin()
8687
.cluster()
8788
.getSnapshots(getSnapshotsRequest, new RestRefCountedChunkedToXContentListener<>(channel));

server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ public void testValidateParameters() {
8686
final ActionRequestValidationException e = request.validate();
8787
assertThat(e.getMessage(), containsString("can't use slm policy filter with verbose=false"));
8888
}
89+
{
90+
final GetSnapshotsRequest request = new GetSnapshotsRequest(TEST_REQUEST_TIMEOUT, "repo", "snapshot").state("foo")
91+
.verbose(false);
92+
final ActionRequestValidationException e = request.validate();
93+
assertThat(e.getMessage(), containsString("state must be SUCCESS, IN_PROGRESS, FAILED, PARTIAL, or INCOMPATIBLE"));
94+
}
8995
}
9096

9197
public void testGetDescription() {

0 commit comments

Comments
 (0)