diff --git a/docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc b/docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc index 4ed67e3cc14d6..89861e20caea9 100644 --- a/docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc +++ b/docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc @@ -283,6 +283,15 @@ The snapshot `state` can be one of the following values: 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 `after` query parameter to fetch additional results. +`total`:: +(integer) +The total number of snapshots that match the request when ignoring size limit or `after` query parameter. + +`remaining`:: +(integer) +The number of remaining snapshots that were not returned due to size limits and that can be fetched by additional requests using the `next` +field value. + [[get-snapshot-api-example]] ==== {api-examples-title} @@ -322,7 +331,9 @@ The API returns the following response: "successful": 0 } } - ] + ], + "total": 1, + "remaining": 0 } ---- // TESTRESPONSE[s/"uuid": "vdRctLCxSketdKb54xw67g"/"uuid": $body.snapshots.0.uuid/] @@ -392,10 +403,12 @@ The API returns the following response: "total": 0, "failed": 0, "successful": 0 - } + }, } ], - "next": "c25hcHNob3RfMixteV9yZXBvc2l0b3J5LHNuYXBzaG90XzI=" + "next": "c25hcHNob3RfMixteV9yZXBvc2l0b3J5LHNuYXBzaG90XzI=", + "total": 3, + "remaining": 1 } ---- // TESTRESPONSE[s/"uuid": "dKb54xw67gvdRctLCxSket"/"uuid": $body.snapshots.0.uuid/] @@ -449,7 +462,9 @@ The API returns the following response: "successful": 0 } } - ] + ], + "total": 3, + "remaining": 0 } ---- // TESTRESPONSE[s/"uuid": "dRctdKb54xw67gvLCxSket"/"uuid": $body.snapshots.0.uuid/] diff --git a/qa/smoke-test-http/src/test/java/org/elasticsearch/http/snapshots/RestGetSnapshotsIT.java b/qa/smoke-test-http/src/test/java/org/elasticsearch/http/snapshots/RestGetSnapshotsIT.java index 95496e3e537c6..d3949e6a98e13 100644 --- a/qa/smoke-test-http/src/test/java/org/elasticsearch/http/snapshots/RestGetSnapshotsIT.java +++ b/qa/smoke-test-http/src/test/java/org/elasticsearch/http/snapshots/RestGetSnapshotsIT.java @@ -20,7 +20,6 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.json.JsonXContent; -import org.elasticsearch.core.Tuple; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.snapshots.AbstractSnapshotIntegTestCase; import org.elasticsearch.snapshots.SnapshotInfo; @@ -106,31 +105,34 @@ private void doTestPagination(String repoName, GetSnapshotsRequest.SortBy sort, SortOrder order) throws IOException { final List allSnapshotsSorted = allSnapshotsSorted(names, repoName, sort, order); - final Tuple> batch1 = sortedWithLimit(repoName, sort, null, 2, order); - assertEquals(allSnapshotsSorted.subList(0, 2), batch1.v2()); - final Tuple> batch2 = sortedWithLimit(repoName, sort, batch1.v1(), 2, order); - assertEquals(allSnapshotsSorted.subList(2, 4), batch2.v2()); - final int lastBatch = names.size() - batch1.v2().size() - batch2.v2().size(); - final Tuple> batch3 = sortedWithLimit(repoName, sort, batch2.v1(), lastBatch, order); - assertEquals(batch3.v2(), allSnapshotsSorted.subList(batch1.v2().size() + batch2.v2().size(), names.size())); - final Tuple> batch3NoLimit = sortedWithLimit( + final GetSnapshotsResponse batch1 = sortedWithLimit(repoName, sort, null, 2, order); + assertEquals(allSnapshotsSorted.subList(0, 2), batch1.getSnapshots()); + final GetSnapshotsResponse batch2 = sortedWithLimit(repoName, sort, batch1.next(), 2, order); + assertEquals(allSnapshotsSorted.subList(2, 4), batch2.getSnapshots()); + final int lastBatch = names.size() - batch1.getSnapshots().size() - batch2.getSnapshots().size(); + final GetSnapshotsResponse batch3 = sortedWithLimit(repoName, sort, batch2.next(), lastBatch, order); + assertEquals( + batch3.getSnapshots(), + allSnapshotsSorted.subList(batch1.getSnapshots().size() + batch2.getSnapshots().size(), names.size()) + ); + final GetSnapshotsResponse batch3NoLimit = sortedWithLimit( repoName, sort, - batch2.v1(), + batch2.next(), GetSnapshotsRequest.NO_LIMIT, order ); - assertNull(batch3NoLimit.v1()); - assertEquals(batch3.v2(), batch3NoLimit.v2()); - final Tuple> batch3LargeLimit = sortedWithLimit( + assertNull(batch3NoLimit.next()); + assertEquals(batch3.getSnapshots(), batch3NoLimit.getSnapshots()); + final GetSnapshotsResponse batch3LargeLimit = sortedWithLimit( repoName, sort, - batch2.v1(), + batch2.next(), lastBatch + randomIntBetween(1, 100), order ); - assertEquals(batch3.v2(), batch3LargeLimit.v2()); - assertNull(batch3LargeLimit.v1()); + assertEquals(batch3.getSnapshots(), batch3LargeLimit.getSnapshots()); + assertNull(batch3LargeLimit.next()); } public void testSortAndPaginateWithInProgress() throws Exception { @@ -180,16 +182,19 @@ private static void assertStablePagination(String repoName, final List allSorted = allSnapshotsSorted(allSnapshotNames, repoName, sort, order); for (int i = 1; i <= allSnapshotNames.size(); i++) { - final List subsetSorted = sortedWithLimit(repoName, sort, null, i, order).v2(); + final List subsetSorted = sortedWithLimit(repoName, sort, null, i, order).getSnapshots(); assertEquals(subsetSorted, allSorted.subList(0, i)); } for (int j = 0; j < allSnapshotNames.size(); j++) { final SnapshotInfo after = allSorted.get(j); for (int i = 1; i < allSnapshotNames.size() - j; i++) { - final List subsetSorted = sortedWithLimit( - repoName, sort, GetSnapshotsRequest.After.from(after, sort).asQueryParam(), i, order).v2(); + final GetSnapshotsResponse getSnapshotsResponse = + sortedWithLimit(repoName, sort, GetSnapshotsRequest.After.from(after, sort).asQueryParam(), i, order); + final List subsetSorted = getSnapshotsResponse.getSnapshots(); assertEquals(subsetSorted, allSorted.subList(j + 1, j + i + 1)); + assertEquals(allSnapshotNames.size(), getSnapshotsResponse.totalCount()); + assertEquals(allSnapshotNames.size() - (j + i + 1), getSnapshotsResponse.remaining()); } } } @@ -203,9 +208,11 @@ private static List allSnapshotsSorted(Collection allSnaps if (order == SortOrder.DESC || randomBoolean()) { request.addParameter("order", order.toString()); } - final Response response = getRestClient().performRequest(request); - final List snapshotInfos = readSnapshotInfos(response).v2(); + final GetSnapshotsResponse getSnapshotsResponse = readSnapshotInfos(getRestClient().performRequest(request)); + final List snapshotInfos = getSnapshotsResponse.getSnapshots(); assertEquals(snapshotInfos.size(), allSnapshotNames.size()); + assertEquals(getSnapshotsResponse.totalCount(), allSnapshotNames.size()); + assertEquals(0, getSnapshotsResponse.remaining()); for (SnapshotInfo snapshotInfo : snapshotInfos) { assertThat(snapshotInfo.snapshotId().getName(), is(in(allSnapshotNames))); } @@ -216,16 +223,15 @@ private static Request baseGetSnapshotsRequest(String repoName) { return new Request(HttpGet.METHOD_NAME, "/_snapshot/" + repoName + "/*"); } - private static Tuple> readSnapshotInfos(Response response) throws IOException { + private static GetSnapshotsResponse readSnapshotInfos(Response response) throws IOException { try (InputStream input = response.getEntity().getContent(); XContentParser parser = JsonXContent.jsonXContent.createParser( NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, input)) { - final GetSnapshotsResponse getSnapshotsResponse = GetSnapshotsResponse.fromXContent(parser); - return Tuple.tuple(getSnapshotsResponse.next(), getSnapshotsResponse.getSnapshots()); + return GetSnapshotsResponse.fromXContent(parser); } } - private static Tuple> sortedWithLimit(String repoName, + private static GetSnapshotsResponse sortedWithLimit(String repoName, GetSnapshotsRequest.SortBy sortBy, String after, int size, diff --git a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java index 93c217e3cd6a0..0ee1be41b7849 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java @@ -15,7 +15,6 @@ import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.core.Tuple; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.threadpool.ThreadPool; @@ -97,31 +96,28 @@ public void testResponseSizeLimit() throws Exception { private void doTestPagination(String repoName, List names, GetSnapshotsRequest.SortBy sort, SortOrder order) { final List allSnapshotsSorted = allSnapshotsSorted(names, repoName, sort, order); - final Tuple> batch1 = sortedWithLimit(repoName, sort, null, 2, order); - assertEquals(allSnapshotsSorted.subList(0, 2), batch1.v2()); - final Tuple> batch2 = sortedWithLimit(repoName, sort, batch1.v1(), 2, order); - assertEquals(allSnapshotsSorted.subList(2, 4), batch2.v2()); - final int lastBatch = names.size() - batch1.v2().size() - batch2.v2().size(); - final Tuple> batch3 = sortedWithLimit(repoName, sort, batch2.v1(), lastBatch, order); - assertEquals(batch3.v2(), allSnapshotsSorted.subList(batch1.v2().size() + batch2.v2().size(), names.size())); - final Tuple> batch3NoLimit = sortedWithLimit( - repoName, - sort, - batch2.v1(), - GetSnapshotsRequest.NO_LIMIT, - order + final GetSnapshotsResponse batch1 = sortedWithLimit(repoName, sort, null, 2, order); + assertEquals(allSnapshotsSorted.subList(0, 2), batch1.getSnapshots()); + final GetSnapshotsResponse batch2 = sortedWithLimit(repoName, sort, batch1.next(), 2, order); + assertEquals(allSnapshotsSorted.subList(2, 4), batch2.getSnapshots()); + final int lastBatch = names.size() - batch1.getSnapshots().size() - batch2.getSnapshots().size(); + final GetSnapshotsResponse batch3 = sortedWithLimit(repoName, sort, batch2.next(), lastBatch, order); + assertEquals( + batch3.getSnapshots(), + allSnapshotsSorted.subList(batch1.getSnapshots().size() + batch2.getSnapshots().size(), names.size()) ); - assertNull(batch3NoLimit.v1()); - assertEquals(batch3.v2(), batch3NoLimit.v2()); - final Tuple> batch3LargeLimit = sortedWithLimit( + final GetSnapshotsResponse batch3NoLimit = sortedWithLimit(repoName, sort, batch2.next(), GetSnapshotsRequest.NO_LIMIT, order); + assertNull(batch3NoLimit.next()); + assertEquals(batch3.getSnapshots(), batch3NoLimit.getSnapshots()); + final GetSnapshotsResponse batch3LargeLimit = sortedWithLimit( repoName, sort, - batch2.v1(), + batch2.next(), lastBatch + randomIntBetween(1, 100), order ); - assertEquals(batch3.v2(), batch3LargeLimit.v2()); - assertNull(batch3LargeLimit.v1()); + assertEquals(batch3.getSnapshots(), batch3LargeLimit.getSnapshots()); + assertNull(batch3LargeLimit.next()); } public void testSortAndPaginateWithInProgress() throws Exception { @@ -187,20 +183,24 @@ private static void assertStablePagination(String repoName, Collection a final List allSorted = allSnapshotsSorted(allSnapshotNames, repoName, sort, order); for (int i = 1; i <= allSnapshotNames.size(); i++) { - final Tuple> subsetSorted = sortedWithLimit(repoName, sort, null, i, order); - assertEquals(allSorted.subList(0, i), subsetSorted.v2()); + final GetSnapshotsResponse subsetSorted = sortedWithLimit(repoName, sort, null, i, order); + assertEquals(allSorted.subList(0, i), subsetSorted.getSnapshots()); } for (int j = 0; j < allSnapshotNames.size(); j++) { final SnapshotInfo after = allSorted.get(j); for (int i = 1; i < allSnapshotNames.size() - j; i++) { - final List subsetSorted = sortedWithLimit( + final GetSnapshotsResponse getSnapshotsResponse = sortedWithLimit( repoName, sort, GetSnapshotsRequest.After.from(after, sort).asQueryParam(), i, order - ).v2(); + ); + final List subsetSorted = getSnapshotsResponse.getSnapshots(); + assertEquals(subsetSorted, allSorted.subList(j + 1, j + i + 1)); + assertEquals(allSnapshotNames.size(), getSnapshotsResponse.totalCount()); + assertEquals(allSnapshotNames.size() - (j + i + 1), getSnapshotsResponse.remaining()); assertEquals(subsetSorted, allSorted.subList(j + 1, j + i + 1)); } } @@ -212,15 +212,18 @@ private static List allSnapshotsSorted( GetSnapshotsRequest.SortBy sortBy, SortOrder order ) { - final List snapshotInfos = sortedWithLimit(repoName, sortBy, null, GetSnapshotsRequest.NO_LIMIT, order).v2(); + final GetSnapshotsResponse getSnapshotsResponse = sortedWithLimit(repoName, sortBy, null, GetSnapshotsRequest.NO_LIMIT, order); + final List snapshotInfos = getSnapshotsResponse.getSnapshots(); assertEquals(snapshotInfos.size(), allSnapshotNames.size()); + assertEquals(getSnapshotsResponse.totalCount(), allSnapshotNames.size()); + assertEquals(0, getSnapshotsResponse.remaining()); for (SnapshotInfo snapshotInfo : snapshotInfos) { assertThat(snapshotInfo.snapshotId().getName(), is(in(allSnapshotNames))); } return snapshotInfos; } - private static Tuple> sortedWithLimit( + private static GetSnapshotsResponse sortedWithLimit( String repoName, GetSnapshotsRequest.SortBy sortBy, String after, @@ -232,7 +235,7 @@ private static Tuple> sortedWithLimit( .setSize(size) .setOrder(order) .get(); - return Tuple.tuple(response.next(), response.getSnapshots()); + return response; } private static GetSnapshotsRequestBuilder baseGetSnapshotsRequest(String repoName) { diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java index dc18e183323a0..0ba58d287d271 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java @@ -43,6 +43,8 @@ public class GetSnapshotsRequest extends MasterNodeRequest public static final Version PAGINATED_GET_SNAPSHOTS_VERSION = Version.V_7_14_0; + public static final Version NUMERIC_PAGINATION_VERSION = Version.V_8_0_0; + public static final int NO_LIMIT = -1; /** diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java index 247a1c878258e..f75c0929288cb 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java @@ -34,11 +34,19 @@ */ public class GetSnapshotsResponse extends ActionResponse implements ToXContentObject { + private static final int UNKNOWN_COUNT = -1; + @SuppressWarnings("unchecked") private static final ConstructingObjectParser GET_SNAPSHOT_PARSER = new ConstructingObjectParser<>( GetSnapshotsResponse.class.getName(), true, - (args) -> new GetSnapshotsResponse((List) args[0], (Map) args[1], (String) args[2]) + (args) -> new GetSnapshotsResponse( + (List) args[0], + (Map) args[1], + (String) args[2], + args[3] == null ? UNKNOWN_COUNT : (int) args[3], + args[4] == null ? UNKNOWN_COUNT : (int) args[4] + ) ); static { @@ -53,6 +61,8 @@ public class GetSnapshotsResponse extends ActionResponse implements ToXContentOb new ParseField("failures") ); GET_SNAPSHOT_PARSER.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), new ParseField("next")); + GET_SNAPSHOT_PARSER.declareIntOrNull(ConstructingObjectParser.optionalConstructorArg(), UNKNOWN_COUNT, new ParseField("total")); + GET_SNAPSHOT_PARSER.declareIntOrNull(ConstructingObjectParser.optionalConstructorArg(), UNKNOWN_COUNT, new ParseField("remaining")); } private final List snapshots; @@ -62,10 +72,22 @@ public class GetSnapshotsResponse extends ActionResponse implements ToXContentOb @Nullable private final String next; - public GetSnapshotsResponse(List snapshots, Map failures, @Nullable String next) { + private final int total; + + private final int remaining; + + public GetSnapshotsResponse( + List snapshots, + Map failures, + @Nullable String next, + final int total, + final int remaining + ) { this.snapshots = List.copyOf(snapshots); this.failures = failures == null ? Map.of() : Map.copyOf(failures); this.next = next; + this.total = total; + this.remaining = remaining; } public GetSnapshotsResponse(StreamInput in) throws IOException { @@ -78,6 +100,13 @@ public GetSnapshotsResponse(StreamInput in) throws IOException { this.failures = Collections.emptyMap(); this.next = null; } + if (in.getVersion().onOrAfter(GetSnapshotsRequest.NUMERIC_PAGINATION_VERSION)) { + this.total = in.readVInt(); + this.remaining = in.readVInt(); + } else { + this.total = UNKNOWN_COUNT; + this.remaining = UNKNOWN_COUNT; + } } /** @@ -108,6 +137,14 @@ public boolean isFailed() { return failures.isEmpty() == false; } + public int totalCount() { + return total; + } + + public int remaining() { + return remaining; + } + @Override public void writeTo(StreamOutput out) throws IOException { out.writeList(snapshots); @@ -120,6 +157,10 @@ public void writeTo(StreamOutput out) throws IOException { throw failures.values().iterator().next(); } } + if (out.getVersion().onOrAfter(GetSnapshotsRequest.NUMERIC_PAGINATION_VERSION)) { + out.writeVInt(total); + out.writeVInt(remaining); + } } @Override @@ -145,6 +186,12 @@ public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params par if (next != null) { builder.field("next", next); } + if (total >= 0) { + builder.field("total", total); + } + if (remaining >= 0) { + builder.field("remaining", remaining); + } builder.endObject(); return builder; } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java index 1dfab5d751c53..6688d0b7c8ace 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java @@ -139,7 +139,7 @@ private void getMultipleReposSnapshotInfo( ) { // short-circuit if there are no repos, because we can not create GroupedActionListener of size 0 if (repos.isEmpty()) { - listener.onResponse(new GetSnapshotsResponse(Collections.emptyList(), Collections.emptyMap(), null)); + listener.onResponse(new GetSnapshotsResponse(Collections.emptyList(), Collections.emptyMap(), null, 0, 0)); return; } final GroupedActionListener, SnapshotsInRepo>> groupedActionListener = @@ -156,12 +156,19 @@ private void getMultipleReposSnapshotInfo( .collect(Collectors.toMap(Tuple::v1, Tuple::v2)); final SnapshotsInRepo snInfos = sortSnapshots(allSnapshots, sortBy, after, size, order); final List snapshotInfos = snInfos.snapshotInfos; + final int remaining = snInfos.remaining + responses.stream() + .map(Tuple::v2) + .filter(Objects::nonNull) + .mapToInt(s -> s.remaining) + .sum(); return new GetSnapshotsResponse( snapshotInfos, failures, - snInfos.hasMore || responses.stream().anyMatch(r -> r.v2() != null && r.v2().hasMore) + remaining > 0 ? GetSnapshotsRequest.After.from(snapshotInfos.get(snapshotInfos.size() - 1), sortBy).asQueryParam() - : null + : null, + responses.stream().map(Tuple::v2).filter(Objects::nonNull).mapToInt(s -> s.totalCount).sum(), + remaining ); }), repos.size()); @@ -204,7 +211,7 @@ private void getSingleRepoSnapshotInfo( ) { final Map allSnapshotIds = new HashMap<>(); final List currentSnapshots = new ArrayList<>(); - for (SnapshotInfo snapshotInfo : sortedCurrentSnapshots(snapshotsInProgress, repo, sortBy, after, size, order).snapshotInfos) { + for (SnapshotInfo snapshotInfo : currentSnapshots(snapshotsInProgress, repo)) { Snapshot snapshot = snapshotInfo.snapshot(); allSnapshotIds.put(snapshot.getSnapshotId().getName(), snapshot); currentSnapshots.add(snapshotInfo); @@ -245,14 +252,7 @@ private void getSingleRepoSnapshotInfo( * @param repositoryName repository name * @return list of snapshots */ - private static SnapshotsInRepo sortedCurrentSnapshots( - SnapshotsInProgress snapshotsInProgress, - String repositoryName, - GetSnapshotsRequest.SortBy sortBy, - @Nullable final GetSnapshotsRequest.After after, - int size, - SortOrder order - ) { + private static List currentSnapshots(SnapshotsInProgress snapshotsInProgress, String repositoryName) { List snapshotList = new ArrayList<>(); List entries = SnapshotsService.currentSnapshots( snapshotsInProgress, @@ -262,7 +262,7 @@ private static SnapshotsInRepo sortedCurrentSnapshots( for (SnapshotsInProgress.Entry entry : entries) { snapshotList.add(new SnapshotInfo(entry)); } - return sortSnapshots(snapshotList, sortBy, after, size, order); + return snapshotList; } private void loadSnapshotInfos( @@ -491,11 +491,11 @@ private static SnapshotsInRepo buildSimpleSnapshotInfos( private static final Comparator BY_NAME = Comparator.comparing(sni -> sni.snapshotId().getName()); private static SnapshotsInRepo sortSnapshots( - List snapshotInfos, - GetSnapshotsRequest.SortBy sortBy, - @Nullable GetSnapshotsRequest.After after, - int size, - SortOrder order + final List snapshotInfos, + final GetSnapshotsRequest.SortBy sortBy, + final @Nullable GetSnapshotsRequest.After after, + final int size, + final SortOrder order ) { final Comparator comparator; switch (sortBy) { @@ -554,12 +554,17 @@ private static SnapshotsInRepo sortSnapshots( infos = infos.filter(isAfter); } infos = infos.sorted(order == SortOrder.DESC ? comparator.reversed() : comparator); + final List allSnapshots = infos.collect(Collectors.toUnmodifiableList()); + final List snapshots; if (size != GetSnapshotsRequest.NO_LIMIT) { - infos = infos.limit(size + 1); + snapshots = allSnapshots.stream().limit(size + 1).collect(Collectors.toUnmodifiableList()); + } else { + snapshots = allSnapshots; } - final List snapshots = infos.collect(Collectors.toUnmodifiableList()); - boolean hasMore = size != GetSnapshotsRequest.NO_LIMIT && size < snapshots.size(); - return new SnapshotsInRepo(hasMore ? snapshots.subList(0, size) : snapshots, hasMore); + final List resultSet = size != GetSnapshotsRequest.NO_LIMIT && size < snapshots.size() + ? snapshots.subList(0, size) + : snapshots; + return new SnapshotsInRepo(resultSet, snapshotInfos.size(), allSnapshots.size() - resultSet.size()); } private static Predicate filterByLongOffset( @@ -589,13 +594,16 @@ private static int compareName(String name, String repoName, SnapshotInfo info) private static final class SnapshotsInRepo { - private final boolean hasMore; - private final List snapshotInfos; - SnapshotsInRepo(List snapshotInfos, boolean hasMore) { - this.hasMore = hasMore; + private final int totalCount; + + private final int remaining; + + SnapshotsInRepo(List snapshotInfos, int totalCount, int remaining) { this.snapshotInfos = snapshotInfos; + this.totalCount = totalCount; + this.remaining = remaining; } } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponseTests.java index 97a49ec939ac9..5825aab091b44 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponseTests.java @@ -131,7 +131,9 @@ private GetSnapshotsResponse createTestInstance() { (randomAlphaOfLengthBetween(1, 5) + "," + randomAlphaOfLengthBetween(1, 5) + "," + randomAlphaOfLengthBetween(1, 5)) .getBytes(StandardCharsets.UTF_8) ) - : null + : null, + randomIntBetween(responses.size(), responses.size() + 100), + randomIntBetween(0, 100) ); } diff --git a/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionTaskTests.java b/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionTaskTests.java index 1e710376bfd60..d12139ea14d21 100644 --- a/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionTaskTests.java +++ b/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionTaskTests.java @@ -316,7 +316,8 @@ public void testErrStillRunsFailureHandlerWhenRetrieving() throws Exception { void doExecute(ActionType action, Request request, ActionListener listener) { if (request instanceof GetSnapshotsRequest) { logger.info("--> called"); - listener.onResponse((Response) new GetSnapshotsResponse(Collections.emptyList(), Collections.emptyMap(), null)); + listener.onResponse((Response) new GetSnapshotsResponse( + Collections.emptyList(), Collections.emptyMap(), null, 0, 0)); } else { super.doExecute(action, request, listener); }