Skip to content

Commit d356a4b

Browse files
Implement Numeric Pagination in Get Snapshots API (#76532)
* Return Total Result Count and Remaining Count in Get Snapshots Response (#76150) Add total result count and remaining count to get snapshots response. * Implement Numeric Offset Parameter in Get Snapshots API (#76233) Add numeric offset parameter to this API. Relates #74350
1 parent 77b0d33 commit d356a4b

File tree

11 files changed

+325
-110
lines changed

11 files changed

+325
-110
lines changed

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

+76-6
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,17 @@ Sort order. Valid values are `asc` for ascending and `desc` for descending order
134134
(Optional, string)
135135
Offset identifier to start pagination from as returned by the `next` field in the response body.
136136

137+
`offset`::
138+
(Optional, integer)
139+
Numeric offset to start pagination from based on the snapshots matching this request. Using a non-zero value for this parameter is mutually
140+
exclusive with using the `after` parameter. Defaults to `0`.
141+
137142
NOTE: The `after` parameter and `next` field allow for iterating through snapshots with some consistency guarantees regarding concurrent
138143
creation or deletion of snapshots. It is guaranteed that any snapshot that exists at the beginning of the iteration and not concurrently
139144
deleted will be seen during the iteration. Snapshots concurrently created may be seen during an iteration.
140145

141-
NOTE: The pagination parameters `size`, `order`, `after` and `sort` are not supported when using `verbose=false` and the sort order for
142-
requests with `verbose=false` is undefined.
146+
NOTE: The pagination parameters `size`, `order`, `after`, `offset` and `sort` are not supported when using `verbose=false` and the sort
147+
order for requests with `verbose=false` is undefined.
143148

144149
[role="child_attributes"]
145150
[[get-snapshot-api-response-body]]
@@ -283,6 +288,15 @@ The snapshot `state` can be one of the following values:
283288
If the request contained a size limit and there might be more results, a `next` field will be added to the response and can be used as the
284289
`after` query parameter to fetch additional results.
285290

291+
`total`::
292+
(integer)
293+
The total number of snapshots that match the request when ignoring size limit or `after` query parameter.
294+
295+
`remaining`::
296+
(integer)
297+
The number of remaining snapshots that were not returned due to size limits and that can be fetched by additional requests using the `next`
298+
field value.
299+
286300
[[get-snapshot-api-example]]
287301
==== {api-examples-title}
288302

@@ -322,7 +336,9 @@ The API returns the following response:
322336
"successful": 0
323337
}
324338
}
325-
]
339+
],
340+
"total": 1,
341+
"remaining": 0
326342
}
327343
----
328344
// TESTRESPONSE[s/"uuid": "vdRctLCxSketdKb54xw67g"/"uuid": $body.snapshots.0.uuid/]
@@ -392,10 +408,12 @@ The API returns the following response:
392408
"total": 0,
393409
"failed": 0,
394410
"successful": 0
395-
}
411+
},
396412
}
397413
],
398-
"next": "c25hcHNob3RfMixteV9yZXBvc2l0b3J5LHNuYXBzaG90XzI="
414+
"next": "c25hcHNob3RfMixteV9yZXBvc2l0b3J5LHNuYXBzaG90XzI=",
415+
"total": 3,
416+
"remaining": 1
399417
}
400418
----
401419
// TESTRESPONSE[s/"uuid": "dKb54xw67gvdRctLCxSket"/"uuid": $body.snapshots.0.uuid/]
@@ -449,7 +467,59 @@ The API returns the following response:
449467
"successful": 0
450468
}
451469
}
452-
]
470+
],
471+
"total": 3,
472+
"remaining": 0
473+
}
474+
----
475+
// TESTRESPONSE[s/"uuid": "dRctdKb54xw67gvLCxSket"/"uuid": $body.snapshots.0.uuid/]
476+
// TESTRESPONSE[s/"version_id": <version_id>/"version_id": $body.snapshots.0.version_id/]
477+
// TESTRESPONSE[s/"version": <version>/"version": $body.snapshots.0.version/]
478+
// TESTRESPONSE[s/"start_time": "2020-07-06T21:55:18.129Z"/"start_time": $body.snapshots.0.start_time/]
479+
// TESTRESPONSE[s/"start_time_in_millis": 1593093628850/"start_time_in_millis": $body.snapshots.0.start_time_in_millis/]
480+
// TESTRESPONSE[s/"end_time": "2020-07-06T21:55:18.129Z"/"end_time": $body.snapshots.0.end_time/]
481+
// TESTRESPONSE[s/"end_time_in_millis": 1593094752018/"end_time_in_millis": $body.snapshots.0.end_time_in_millis/]
482+
// TESTRESPONSE[s/"duration_in_millis": 0/"duration_in_millis": $body.snapshots.0.duration_in_millis/]
483+
484+
Alternatively, the same result could be retrieved by using an offset value of `2` to skip the two snapshot already seen.
485+
486+
[source,console]
487+
----
488+
GET /_snapshot/my_repository/snapshot*?size=2&sort=name&offset=2
489+
----
490+
491+
The API returns the following response:
492+
493+
[source,console-result]
494+
----
495+
{
496+
"snapshots": [
497+
{
498+
"snapshot": "snapshot_3",
499+
"uuid": "dRctdKb54xw67gvLCxSket",
500+
"repository": "my_repository",
501+
"version_id": <version_id>,
502+
"version": <version>,
503+
"indices": [],
504+
"data_streams": [],
505+
"feature_states": [],
506+
"include_global_state": true,
507+
"state": "SUCCESS",
508+
"start_time": "2020-07-06T21:55:18.129Z",
509+
"start_time_in_millis": 1593093628850,
510+
"end_time": "2020-07-06T21:55:18.129Z",
511+
"end_time_in_millis": 1593094752018,
512+
"duration_in_millis": 0,
513+
"failures": [],
514+
"shards": {
515+
"total": 0,
516+
"failed": 0,
517+
"successful": 0
518+
}
519+
}
520+
],
521+
"total": 3,
522+
"remaining": 0
453523
}
454524
----
455525
// TESTRESPONSE[s/"uuid": "dRctdKb54xw67gvLCxSket"/"uuid": $body.snapshots.0.uuid/]

qa/smoke-test-http/src/test/java/org/elasticsearch/http/snapshots/RestGetSnapshotsIT.java

+57-29
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
2121
import org.elasticsearch.common.xcontent.XContentParser;
2222
import org.elasticsearch.common.xcontent.json.JsonXContent;
23-
import org.elasticsearch.core.Tuple;
2423
import org.elasticsearch.search.sort.SortOrder;
2524
import org.elasticsearch.snapshots.AbstractSnapshotIntegTestCase;
2625
import org.elasticsearch.snapshots.SnapshotInfo;
@@ -106,31 +105,34 @@ private void doTestPagination(String repoName,
106105
GetSnapshotsRequest.SortBy sort,
107106
SortOrder order) throws IOException {
108107
final List<SnapshotInfo> allSnapshotsSorted = allSnapshotsSorted(names, repoName, sort, order);
109-
final Tuple<String, List<SnapshotInfo>> batch1 = sortedWithLimit(repoName, sort, null, 2, order);
110-
assertEquals(allSnapshotsSorted.subList(0, 2), batch1.v2());
111-
final Tuple<String, List<SnapshotInfo>> batch2 = sortedWithLimit(repoName, sort, batch1.v1(), 2, order);
112-
assertEquals(allSnapshotsSorted.subList(2, 4), batch2.v2());
113-
final int lastBatch = names.size() - batch1.v2().size() - batch2.v2().size();
114-
final Tuple<String, List<SnapshotInfo>> batch3 = sortedWithLimit(repoName, sort, batch2.v1(), lastBatch, order);
115-
assertEquals(batch3.v2(), allSnapshotsSorted.subList(batch1.v2().size() + batch2.v2().size(), names.size()));
116-
final Tuple<String, List<SnapshotInfo>> batch3NoLimit = sortedWithLimit(
108+
final GetSnapshotsResponse batch1 = sortedWithLimit(repoName, sort, null, 2, order);
109+
assertEquals(allSnapshotsSorted.subList(0, 2), batch1.getSnapshots());
110+
final GetSnapshotsResponse batch2 = sortedWithLimit(repoName, sort, batch1.next(), 2, order);
111+
assertEquals(allSnapshotsSorted.subList(2, 4), batch2.getSnapshots());
112+
final int lastBatch = names.size() - batch1.getSnapshots().size() - batch2.getSnapshots().size();
113+
final GetSnapshotsResponse batch3 = sortedWithLimit(repoName, sort, batch2.next(), lastBatch, order);
114+
assertEquals(
115+
batch3.getSnapshots(),
116+
allSnapshotsSorted.subList(batch1.getSnapshots().size() + batch2.getSnapshots().size(), names.size())
117+
);
118+
final GetSnapshotsResponse batch3NoLimit = sortedWithLimit(
117119
repoName,
118120
sort,
119-
batch2.v1(),
121+
batch2.next(),
120122
GetSnapshotsRequest.NO_LIMIT,
121123
order
122124
);
123-
assertNull(batch3NoLimit.v1());
124-
assertEquals(batch3.v2(), batch3NoLimit.v2());
125-
final Tuple<String, List<SnapshotInfo>> batch3LargeLimit = sortedWithLimit(
125+
assertNull(batch3NoLimit.next());
126+
assertEquals(batch3.getSnapshots(), batch3NoLimit.getSnapshots());
127+
final GetSnapshotsResponse batch3LargeLimit = sortedWithLimit(
126128
repoName,
127129
sort,
128-
batch2.v1(),
130+
batch2.next(),
129131
lastBatch + randomIntBetween(1, 100),
130132
order
131133
);
132-
assertEquals(batch3.v2(), batch3LargeLimit.v2());
133-
assertNull(batch3LargeLimit.v1());
134+
assertEquals(batch3.getSnapshots(), batch3LargeLimit.getSnapshots());
135+
assertNull(batch3LargeLimit.next());
134136
}
135137

136138
public void testSortAndPaginateWithInProgress() throws Exception {
@@ -180,16 +182,23 @@ private static void assertStablePagination(String repoName,
180182
final List<SnapshotInfo> allSorted = allSnapshotsSorted(allSnapshotNames, repoName, sort, order);
181183

182184
for (int i = 1; i <= allSnapshotNames.size(); i++) {
183-
final List<SnapshotInfo> subsetSorted = sortedWithLimit(repoName, sort, null, i, order).v2();
185+
final List<SnapshotInfo> subsetSorted = sortedWithLimit(repoName, sort, null, i, order).getSnapshots();
184186
assertEquals(subsetSorted, allSorted.subList(0, i));
185187
}
186188

187189
for (int j = 0; j < allSnapshotNames.size(); j++) {
188190
final SnapshotInfo after = allSorted.get(j);
189191
for (int i = 1; i < allSnapshotNames.size() - j; i++) {
190-
final List<SnapshotInfo> subsetSorted = sortedWithLimit(
191-
repoName, sort, GetSnapshotsRequest.After.from(after, sort).asQueryParam(), i, order).v2();
192+
final GetSnapshotsResponse getSnapshotsResponse =
193+
sortedWithLimit(repoName, sort, GetSnapshotsRequest.After.from(after, sort).asQueryParam(), i, order);
194+
final GetSnapshotsResponse getSnapshotsResponseNumeric = sortedWithLimit(repoName, sort, j + 1, i, order);
195+
final List<SnapshotInfo> subsetSorted = getSnapshotsResponse.getSnapshots();
196+
assertEquals(subsetSorted, getSnapshotsResponseNumeric.getSnapshots());
192197
assertEquals(subsetSorted, allSorted.subList(j + 1, j + i + 1));
198+
assertEquals(allSnapshotNames.size(), getSnapshotsResponse.totalCount());
199+
assertEquals(allSnapshotNames.size() - (j + i + 1), getSnapshotsResponse.remaining());
200+
assertEquals(getSnapshotsResponseNumeric.totalCount(), getSnapshotsResponse.totalCount());
201+
assertEquals(getSnapshotsResponseNumeric.remaining(), getSnapshotsResponse.remaining());
193202
}
194203
}
195204
}
@@ -203,9 +212,11 @@ private static List<SnapshotInfo> allSnapshotsSorted(Collection<String> allSnaps
203212
if (order == SortOrder.DESC || randomBoolean()) {
204213
request.addParameter("order", order.toString());
205214
}
206-
final Response response = getRestClient().performRequest(request);
207-
final List<SnapshotInfo> snapshotInfos = readSnapshotInfos(response).v2();
215+
final GetSnapshotsResponse getSnapshotsResponse = readSnapshotInfos(getRestClient().performRequest(request));
216+
final List<SnapshotInfo> snapshotInfos = getSnapshotsResponse.getSnapshots();
208217
assertEquals(snapshotInfos.size(), allSnapshotNames.size());
218+
assertEquals(getSnapshotsResponse.totalCount(), allSnapshotNames.size());
219+
assertEquals(0, getSnapshotsResponse.remaining());
209220
for (SnapshotInfo snapshotInfo : snapshotInfos) {
210221
assertThat(snapshotInfo.snapshotId().getName(), is(in(allSnapshotNames)));
211222
}
@@ -216,20 +227,19 @@ private static Request baseGetSnapshotsRequest(String repoName) {
216227
return new Request(HttpGet.METHOD_NAME, "/_snapshot/" + repoName + "/*");
217228
}
218229

219-
private static Tuple<String, List<SnapshotInfo>> readSnapshotInfos(Response response) throws IOException {
230+
private static GetSnapshotsResponse readSnapshotInfos(Response response) throws IOException {
220231
try (InputStream input = response.getEntity().getContent();
221232
XContentParser parser = JsonXContent.jsonXContent.createParser(
222233
NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, input)) {
223-
final GetSnapshotsResponse getSnapshotsResponse = GetSnapshotsResponse.fromXContent(parser);
224-
return Tuple.tuple(getSnapshotsResponse.next(), getSnapshotsResponse.getSnapshots());
234+
return GetSnapshotsResponse.fromXContent(parser);
225235
}
226236
}
227237

228-
private static Tuple<String, List<SnapshotInfo>> sortedWithLimit(String repoName,
229-
GetSnapshotsRequest.SortBy sortBy,
230-
String after,
231-
int size,
232-
SortOrder order) throws IOException {
238+
private static GetSnapshotsResponse sortedWithLimit(String repoName,
239+
GetSnapshotsRequest.SortBy sortBy,
240+
String after,
241+
int size,
242+
SortOrder order) throws IOException {
233243
final Request request = baseGetSnapshotsRequest(repoName);
234244
request.addParameter("sort", sortBy.toString());
235245
if (size != GetSnapshotsRequest.NO_LIMIT || randomBoolean()) {
@@ -244,4 +254,22 @@ private static Tuple<String, List<SnapshotInfo>> sortedWithLimit(String repoName
244254
final Response response = getRestClient().performRequest(request);
245255
return readSnapshotInfos(response);
246256
}
257+
258+
private static GetSnapshotsResponse sortedWithLimit(String repoName,
259+
GetSnapshotsRequest.SortBy sortBy,
260+
int offset,
261+
int size,
262+
SortOrder order) throws IOException {
263+
final Request request = baseGetSnapshotsRequest(repoName);
264+
request.addParameter("sort", sortBy.toString());
265+
if (size != GetSnapshotsRequest.NO_LIMIT || randomBoolean()) {
266+
request.addParameter("size", String.valueOf(size));
267+
}
268+
request.addParameter("offset", String.valueOf(offset));
269+
if (order == SortOrder.DESC || randomBoolean()) {
270+
request.addParameter("order", order.toString());
271+
}
272+
final Response response = getRestClient().performRequest(request);
273+
return readSnapshotInfos(response);
274+
}
247275
}

0 commit comments

Comments
 (0)