Browse Source

Add parameter to exclude indices in a snapshot from response (#86269)

Adds a parameter `index_names` to the get snapshots API so that users may exclude the potentially very long index name lists when listing out snapshots.

closes #82937
Armin Braun 3 years ago
parent
commit
b323e8e1db

+ 6 - 0
docs/changelog/86269.yaml

@@ -0,0 +1,6 @@
+pr: 86269
+summary: Add parameter to exclude indices in a snapshot from response
+area: Snapshot/Restore
+type: enhancement
+issues:
+ - 82937

+ 5 - 0
docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc

@@ -99,6 +99,11 @@ version of Elasticsearch which took the snapshot, the start and end times of
 the snapshot, and the number of shards snapshotted. Defaults to `true`. If
 `false`, omits the additional information.
 
+`index_names`::
+(Optional, Boolean)
+If `true`, returns the list of index names included in each snapshot in the response.
+Defaults to `true`.
+
 `index_details`::
 (Optional, Boolean)
 If `true`, returns additional information about each index in the snapshot

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

@@ -90,40 +90,45 @@ public class RestGetSnapshotsIT extends AbstractSnapshotRestTestCase {
     }
 
     private void doTestSortOrder(String repoName, Collection<String> allSnapshotNames, SortOrder order) throws IOException {
-        final List<SnapshotInfo> defaultSorting = clusterAdmin().prepareGetSnapshots(repoName).setOrder(order).get().getSnapshots();
+        final boolean includeIndexNames = randomBoolean();
+        final List<SnapshotInfo> defaultSorting = clusterAdmin().prepareGetSnapshots(repoName)
+            .setOrder(order)
+            .setIncludeIndexNames(includeIndexNames)
+            .get()
+            .getSnapshots();
         assertSnapshotListSorted(defaultSorting, null, order);
         assertSnapshotListSorted(
-            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.NAME, order),
+            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.NAME, order, includeIndexNames),
             GetSnapshotsRequest.SortBy.NAME,
             order
         );
         assertSnapshotListSorted(
-            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.DURATION, order),
+            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.DURATION, order, includeIndexNames),
             GetSnapshotsRequest.SortBy.DURATION,
             order
         );
         assertSnapshotListSorted(
-            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.INDICES, order),
+            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.INDICES, order, includeIndexNames),
             GetSnapshotsRequest.SortBy.INDICES,
             order
         );
         assertSnapshotListSorted(
-            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.START_TIME, order),
+            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.START_TIME, order, includeIndexNames),
             GetSnapshotsRequest.SortBy.START_TIME,
             order
         );
         assertSnapshotListSorted(
-            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.SHARDS, order),
+            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.SHARDS, order, includeIndexNames),
             GetSnapshotsRequest.SortBy.SHARDS,
             order
         );
         assertSnapshotListSorted(
-            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.FAILED_SHARDS, order),
+            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.FAILED_SHARDS, order, includeIndexNames),
             GetSnapshotsRequest.SortBy.FAILED_SHARDS,
             order
         );
         assertSnapshotListSorted(
-            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.REPOSITORY, order),
+            allSnapshotsSorted(allSnapshotNames, repoName, GetSnapshotsRequest.SortBy.REPOSITORY, order, includeIndexNames),
             GetSnapshotsRequest.SortBy.REPOSITORY,
             order
         );
@@ -143,18 +148,26 @@ public class RestGetSnapshotsIT extends AbstractSnapshotRestTestCase {
 
     private void doTestPagination(String repoName, List<String> names, GetSnapshotsRequest.SortBy sort, SortOrder order)
         throws IOException {
-        final List<SnapshotInfo> allSnapshotsSorted = allSnapshotsSorted(names, repoName, sort, order);
-        final GetSnapshotsResponse batch1 = sortedWithLimit(repoName, sort, null, 2, order);
+        final boolean includeIndexNames = randomBoolean();
+        final List<SnapshotInfo> allSnapshotsSorted = allSnapshotsSorted(names, repoName, sort, order, includeIndexNames);
+        final GetSnapshotsResponse batch1 = sortedWithLimit(repoName, sort, null, 2, order, includeIndexNames);
         assertEquals(allSnapshotsSorted.subList(0, 2), batch1.getSnapshots());
-        final GetSnapshotsResponse batch2 = sortedWithLimit(repoName, sort, batch1.next(), 2, order);
+        final GetSnapshotsResponse batch2 = sortedWithLimit(repoName, sort, batch1.next(), 2, order, includeIndexNames);
         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);
+        final GetSnapshotsResponse batch3 = sortedWithLimit(repoName, sort, batch2.next(), lastBatch, order, includeIndexNames);
         assertEquals(
             batch3.getSnapshots(),
             allSnapshotsSorted.subList(batch1.getSnapshots().size() + batch2.getSnapshots().size(), names.size())
         );
-        final GetSnapshotsResponse batch3NoLimit = sortedWithLimit(repoName, sort, batch2.next(), GetSnapshotsRequest.NO_LIMIT, order);
+        final GetSnapshotsResponse batch3NoLimit = sortedWithLimit(
+            repoName,
+            sort,
+            batch2.next(),
+            GetSnapshotsRequest.NO_LIMIT,
+            order,
+            includeIndexNames
+        );
         assertNull(batch3NoLimit.next());
         assertEquals(batch3.getSnapshots(), batch3NoLimit.getSnapshots());
         final GetSnapshotsResponse batch3LargeLimit = sortedWithLimit(
@@ -162,7 +175,8 @@ public class RestGetSnapshotsIT extends AbstractSnapshotRestTestCase {
             sort,
             batch2.next(),
             lastBatch + randomIntBetween(1, 100),
-            order
+            order,
+            includeIndexNames
         );
         assertEquals(batch3.getSnapshots(), batch3LargeLimit.getSnapshots());
         assertNull(batch3LargeLimit.next());
@@ -356,10 +370,11 @@ public class RestGetSnapshotsIT extends AbstractSnapshotRestTestCase {
     private static void assertStablePagination(String repoName, Collection<String> allSnapshotNames, GetSnapshotsRequest.SortBy sort)
         throws IOException {
         final SortOrder order = randomFrom(SortOrder.values());
-        final List<SnapshotInfo> allSorted = allSnapshotsSorted(allSnapshotNames, repoName, sort, order);
+        final boolean includeIndexNames = sort == GetSnapshotsRequest.SortBy.INDICES || randomBoolean();
+        final List<SnapshotInfo> allSorted = allSnapshotsSorted(allSnapshotNames, repoName, sort, order, includeIndexNames);
 
         for (int i = 1; i <= allSnapshotNames.size(); i++) {
-            final List<SnapshotInfo> subsetSorted = sortedWithLimit(repoName, sort, null, i, order).getSnapshots();
+            final List<SnapshotInfo> subsetSorted = sortedWithLimit(repoName, sort, null, i, order, includeIndexNames).getSnapshots();
             assertEquals(subsetSorted, allSorted.subList(0, i));
         }
 
@@ -371,9 +386,17 @@ public class RestGetSnapshotsIT extends AbstractSnapshotRestTestCase {
                     sort,
                     GetSnapshotsRequest.After.from(after, sort).asQueryParam(),
                     i,
-                    order
+                    order,
+                    includeIndexNames
+                );
+                final GetSnapshotsResponse getSnapshotsResponseNumeric = sortedWithLimit(
+                    repoName,
+                    sort,
+                    j + 1,
+                    i,
+                    order,
+                    includeIndexNames
                 );
-                final GetSnapshotsResponse getSnapshotsResponseNumeric = sortedWithLimit(repoName, sort, j + 1, i, order);
                 final List<SnapshotInfo> subsetSorted = getSnapshotsResponse.getSnapshots();
                 assertEquals(subsetSorted, getSnapshotsResponseNumeric.getSnapshots());
                 assertEquals(subsetSorted, allSorted.subList(j + 1, j + i + 1));
@@ -389,15 +412,22 @@ public class RestGetSnapshotsIT extends AbstractSnapshotRestTestCase {
         Collection<String> allSnapshotNames,
         String repoName,
         GetSnapshotsRequest.SortBy sortBy,
-        SortOrder order
+        SortOrder order,
+        boolean includeIndices
     ) throws IOException {
         final Request request = baseGetSnapshotsRequest(repoName);
         request.addParameter("sort", sortBy.toString());
         if (order == SortOrder.DESC || randomBoolean()) {
             request.addParameter("order", order.toString());
         }
+        addIndexNamesParameter(includeIndices, request);
         final GetSnapshotsResponse getSnapshotsResponse = readSnapshotInfos(getRestClient().performRequest(request));
         final List<SnapshotInfo> snapshotInfos = getSnapshotsResponse.getSnapshots();
+        if (includeIndices == false) {
+            for (SnapshotInfo snapshotInfo : snapshotInfos) {
+                assertThat(snapshotInfo.indices(), empty());
+            }
+        }
         assertEquals(snapshotInfos.size(), allSnapshotNames.size());
         assertEquals(getSnapshotsResponse.totalCount(), allSnapshotNames.size());
         assertEquals(0, getSnapshotsResponse.remaining());
@@ -429,7 +459,8 @@ public class RestGetSnapshotsIT extends AbstractSnapshotRestTestCase {
         GetSnapshotsRequest.SortBy sortBy,
         String after,
         int size,
-        SortOrder order
+        SortOrder order,
+        boolean includeIndices
     ) throws IOException {
         final Request request = baseGetSnapshotsRequest(repoName);
         request.addParameter("sort", sortBy.toString());
@@ -442,16 +473,26 @@ public class RestGetSnapshotsIT extends AbstractSnapshotRestTestCase {
         if (order == SortOrder.DESC || randomBoolean()) {
             request.addParameter("order", order.toString());
         }
+        addIndexNamesParameter(includeIndices, request);
         final Response response = getRestClient().performRequest(request);
         return readSnapshotInfos(response);
     }
 
+    private static void addIndexNamesParameter(boolean includeIndices, Request request) {
+        if (includeIndices == false) {
+            request.addParameter(SnapshotInfo.INDEX_NAMES_XCONTENT_PARAM, "false");
+        } else if (randomBoolean()) {
+            request.addParameter(SnapshotInfo.INDEX_NAMES_XCONTENT_PARAM, "true");
+        }
+    }
+
     private static GetSnapshotsResponse sortedWithLimit(
         String repoName,
         GetSnapshotsRequest.SortBy sortBy,
         int offset,
         int size,
-        SortOrder order
+        SortOrder order,
+        boolean includeIndices
     ) throws IOException {
         final Request request = baseGetSnapshotsRequest(repoName);
         request.addParameter("sort", sortBy.toString());
@@ -462,6 +503,7 @@ public class RestGetSnapshotsIT extends AbstractSnapshotRestTestCase {
         if (order == SortOrder.DESC || randomBoolean()) {
             request.addParameter("order", order.toString());
         }
+        addIndexNamesParameter(includeIndices, request);
         final Response response = getRestClient().performRequest(request);
         return readSnapshotInfos(response);
     }

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

@@ -38,6 +38,10 @@
         "type":"boolean",
         "description":"Whether to ignore unavailable snapshots, defaults to false which means a SnapshotMissingException is thrown"
       },
+      "index_names":{
+        "type":"boolean",
+        "description":"Whether to include the name of each index in the snapshot. Defaults to true."
+      },
       "index_details":{
         "type":"boolean",
         "description":"Whether to include details of each index in the snapshot, if those details are available. Defaults to false."

+ 37 - 0
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/snapshot.get/10_basic.yml

@@ -233,6 +233,43 @@ setup:
         repository: test_repo_get_1
         snapshot: test_snapshot_with_index_details
 
+---
+"Get snapshot info without index names":
+  - skip:
+      version: " - 8.2.99"
+      reason: "Introduced in 8.3.0"
+
+  - do:
+      indices.create:
+        index: test_index
+        body:
+          settings:
+            number_of_shards:   1
+            number_of_replicas: 0
+
+  - do:
+      snapshot.create:
+        repository: test_repo_get_1
+        snapshot: test_snapshot_without_index_names
+        wait_for_completion: true
+
+  - do:
+      snapshot.get:
+        repository: test_repo_get_1
+        snapshot: test_snapshot_without_index_names
+        index_names: false
+        human: true
+
+  - is_true: snapshots
+  - match: { snapshots.0.snapshot: test_snapshot_without_index_names }
+  - match: { snapshots.0.state: SUCCESS }
+  - is_false:  snapshots.0.indices
+
+  - do:
+      snapshot.delete:
+        repository: test_repo_get_1
+        snapshot: test_snapshot_without_index_names
+
 ---
 "Get snapshot info without repository names":
 

+ 19 - 0
server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java

@@ -52,6 +52,8 @@ public class GetSnapshotsRequest extends MasterNodeRequest<GetSnapshotsRequest>
 
     private static final Version SORT_BY_SHARDS_OR_REPO_VERSION = Version.V_7_16_0;
 
+    private static final Version INDICES_FLAG_VERSION = Version.V_8_3_0;
+
     public static final int NO_LIMIT = -1;
 
     /**
@@ -84,6 +86,8 @@ public class GetSnapshotsRequest extends MasterNodeRequest<GetSnapshotsRequest>
 
     private boolean verbose = DEFAULT_VERBOSE_MODE;
 
+    private boolean includeIndexNames = true;
+
     public GetSnapshotsRequest() {}
 
     /**
@@ -130,6 +134,9 @@ public class GetSnapshotsRequest extends MasterNodeRequest<GetSnapshotsRequest>
             if (in.getVersion().onOrAfter(FROM_SORT_VALUE_VERSION)) {
                 fromSortValue = in.readOptionalString();
             }
+            if (in.getVersion().onOrAfter(INDICES_FLAG_VERSION)) {
+                includeIndexNames = in.readBoolean();
+            }
         }
     }
 
@@ -184,6 +191,9 @@ public class GetSnapshotsRequest extends MasterNodeRequest<GetSnapshotsRequest>
         } else if (fromSortValue != null) {
             throw new IllegalArgumentException("can't use after-value in snapshot request with node version [" + out.getVersion() + "]");
         }
+        if (out.getVersion().onOrAfter(INDICES_FLAG_VERSION)) {
+            out.writeBoolean(includeIndexNames);
+        }
     }
 
     @Override
@@ -324,6 +334,15 @@ public class GetSnapshotsRequest extends MasterNodeRequest<GetSnapshotsRequest>
         return this;
     }
 
+    public GetSnapshotsRequest includeIndexNames(boolean indices) {
+        this.includeIndexNames = indices;
+        return this;
+    }
+
+    public boolean includeIndexNames() {
+        return includeIndexNames;
+    }
+
     public After after() {
         return after;
     }

+ 6 - 0
server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java

@@ -142,4 +142,10 @@ public class GetSnapshotsRequestBuilder extends MasterNodeOperationRequestBuilde
         return this;
     }
 
+    public GetSnapshotsRequestBuilder setIncludeIndexNames(boolean indices) {
+        request.includeIndexNames(indices);
+        return this;
+
+    }
+
 }

+ 3 - 1
server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java

@@ -128,6 +128,7 @@ public class TransportGetSnapshotsAction extends TransportMasterNodeAction<GetSn
             request.size(),
             request.order(),
             SnapshotPredicates.fromRequest(request),
+            request.includeIndexNames(),
             listener
         );
     }
@@ -166,6 +167,7 @@ public class TransportGetSnapshotsAction extends TransportMasterNodeAction<GetSn
         int size,
         SortOrder order,
         SnapshotPredicates predicates,
+        boolean indices,
         ActionListener<GetSnapshotsResponse> listener
     ) {
         // short-circuit if there are no repos, because we can not create GroupedActionListener of size 0
@@ -193,7 +195,7 @@ public class TransportGetSnapshotsAction extends TransportMasterNodeAction<GetSn
                     .mapToInt(s -> s.remaining)
                     .sum();
                 return new GetSnapshotsResponse(
-                    snapshotInfos,
+                    indices ? snapshotInfos : snapshotInfos.stream().map(SnapshotInfo::withoutIndices).toList(),
                     failures,
                     remaining > 0
                         ? GetSnapshotsRequest.After.from(snapshotInfos.get(snapshotInfos.size() - 1), sortBy).asQueryParam()

+ 3 - 1
server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetSnapshotsAction.java

@@ -26,6 +26,7 @@ import static org.elasticsearch.client.internal.Requests.getSnapshotsRequest;
 import static org.elasticsearch.rest.RestRequest.Method.GET;
 import static org.elasticsearch.snapshots.SnapshotInfo.INCLUDE_REPOSITORY_XCONTENT_PARAM;
 import static org.elasticsearch.snapshots.SnapshotInfo.INDEX_DETAILS_XCONTENT_PARAM;
+import static org.elasticsearch.snapshots.SnapshotInfo.INDEX_NAMES_XCONTENT_PARAM;
 
 /**
  * Returns information about snapshot
@@ -50,7 +51,7 @@ public class RestGetSnapshotsAction extends BaseRestHandler {
 
     @Override
     protected Set<String> responseParams() {
-        return Set.of(INDEX_DETAILS_XCONTENT_PARAM, INCLUDE_REPOSITORY_XCONTENT_PARAM);
+        return Set.of(INDEX_DETAILS_XCONTENT_PARAM, INCLUDE_REPOSITORY_XCONTENT_PARAM, INDEX_NAMES_XCONTENT_PARAM);
     }
 
     @Override
@@ -80,6 +81,7 @@ public class RestGetSnapshotsAction extends BaseRestHandler {
 
         final SortOrder order = SortOrder.fromString(request.param("order", getSnapshotsRequest.order().toString()));
         getSnapshotsRequest.order(order);
+        getSnapshotsRequest.includeIndexNames(request.paramAsBoolean(INDEX_NAMES_XCONTENT_PARAM, getSnapshotsRequest.includeIndexNames()));
         getSnapshotsRequest.masterNodeTimeout(request.paramAsTime("master_timeout", getSnapshotsRequest.masterNodeTimeout()));
         return channel -> new RestCancellableNodeClient(client, request.getHttpChannel()).admin()
             .cluster()

+ 28 - 4
server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java

@@ -48,6 +48,8 @@ import java.util.Objects;
 public final class SnapshotInfo implements Comparable<SnapshotInfo>, ToXContentFragment, Writeable {
 
     public static final String INDEX_DETAILS_XCONTENT_PARAM = "index_details";
+
+    public static final String INDEX_NAMES_XCONTENT_PARAM = "index_names";
     public static final String INCLUDE_REPOSITORY_XCONTENT_PARAM = "include_repository";
 
     private static final DateFormatter DATE_TIME_FORMATTER = DateFormatter.forPattern("strict_date_optional_time");
@@ -467,6 +469,29 @@ public final class SnapshotInfo implements Comparable<SnapshotInfo>, ToXContentF
         this.indexSnapshotDetails = Map.copyOf(indexSnapshotDetails);
     }
 
+    public SnapshotInfo withoutIndices() {
+        if (indices.isEmpty()) {
+            return this;
+        }
+        return new SnapshotInfo(
+            snapshot,
+            List.of(),
+            dataStreams,
+            featureStates,
+            reason,
+            version,
+            startTime,
+            endTime,
+            totalShards,
+            successfulShards,
+            shardFailures,
+            includeGlobalState,
+            userMetadata,
+            state,
+            indexSnapshotDetails
+        );
+    }
+
     /**
      * Constructs snapshot information from stream input
      */
@@ -748,11 +773,10 @@ public final class SnapshotInfo implements Comparable<SnapshotInfo>, ToXContentF
             builder.field(VERSION_ID, version.id);
             builder.field(VERSION, version.toString());
         }
-        builder.startArray(INDICES);
-        for (String index : indices) {
-            builder.value(index);
+
+        if (params.paramAsBoolean(INDEX_NAMES_XCONTENT_PARAM, true)) {
+            builder.stringListField(INDICES, indices);
         }
-        builder.endArray();
 
         if (params.paramAsBoolean(INDEX_DETAILS_XCONTENT_PARAM, false) && indexSnapshotDetails.isEmpty() == false) {
             builder.startObject(INDEX_DETAILS);

+ 1 - 0
x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java

@@ -274,6 +274,7 @@ public class SnapshotRetentionTask implements SchedulerEngine.Listener {
             .setMasterNodeTimeout(TimeValue.MAX_VALUE)
             .setIgnoreUnavailable(true)
             .setPolicies(policies.toArray(Strings.EMPTY_ARRAY))
+            .setIncludeIndexNames(false)
             .execute(ActionListener.wrap(resp -> {
                 if (logger.isTraceEnabled()) {
                     logger.trace(