diff --git a/docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc b/docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc index 708f96001177d..60ccf2fec4084 100644 --- a/docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc +++ b/docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc @@ -148,12 +148,20 @@ Offset identifier to start pagination from as returned by the `next` field in th Numeric offset to start pagination from based on the snapshots matching this request. Using a non-zero value for this parameter is mutually exclusive with using the `after` parameter. Defaults to `0`. +`slm_policy_filter`:: +(Optional, string) +Filter snapshots by a comma-separated list of SLM policy names that snapshots belong to. Also accepts wildcards (`\*`) and combinations +of wildcards followed by exclude patterns starting in `-`. For example, the pattern `*,-policy-a-\*` will return all snapshots except +for those that were created by an SLM policy with a name starting in `policy-a-`. Note that the wildcard pattern `*` matches all snapshots +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 +policy you can use the special pattern `_none` that will match all snapshots without an SLM policy. + NOTE: The `after` parameter and `next` field allow for iterating through snapshots with some consistency guarantees regarding concurrent creation or deletion of snapshots. It is guaranteed that any snapshot that exists at the beginning of the iteration and not concurrently deleted will be seen during the iteration. Snapshots concurrently created may be seen during an iteration. -NOTE: The pagination parameters `size`, `order`, `after`, `offset` and `sort` are not supported when using `verbose=false` and the sort -order for requests with `verbose=false` is undefined. +NOTE: The parameters `size`, `order`, `after`, `offset`, `slm_policy_filter` and `sort` are not supported when using `verbose=false` and +the sort order for requests with `verbose=false` is undefined. [role="child_attributes"] [[get-snapshot-api-response-body]] 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 48acee1f9de70..637b8254101c2 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 @@ -15,6 +15,7 @@ import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xcontent.DeprecationHandler; import org.elasticsearch.xcontent.NamedXContentRegistry; @@ -23,6 +24,7 @@ import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.snapshots.AbstractSnapshotIntegTestCase; import org.elasticsearch.snapshots.SnapshotInfo; +import org.elasticsearch.snapshots.SnapshotsService; import org.elasticsearch.threadpool.ThreadPool; import java.io.IOException; @@ -33,6 +35,7 @@ import java.util.List; import static org.elasticsearch.snapshots.AbstractSnapshotIntegTestCase.assertSnapshotListSorted; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.is; @@ -183,6 +186,69 @@ public void testSortAndPaginateWithInProgress() throws Exception { assertStablePagination(repoName, allSnapshotNames, GetSnapshotsRequest.SortBy.INDICES); } + public void testFilterBySLMPolicy() throws Exception { + final String repoName = "test-repo"; + AbstractSnapshotIntegTestCase.createRepository(logger, repoName, "fs"); + AbstractSnapshotIntegTestCase.createNSnapshots(logger, repoName, randomIntBetween(1, 5)); + final List snapshotsWithoutPolicy = clusterAdmin().prepareGetSnapshots("*").setSnapshots("*") + .setSort(GetSnapshotsRequest.SortBy.NAME).get().getSnapshots(); + final String snapshotWithPolicy = "snapshot-with-policy"; + final String policyName = "some-policy"; + final SnapshotInfo withPolicy = AbstractSnapshotIntegTestCase.assertSuccessful( + logger, + clusterAdmin().prepareCreateSnapshot(repoName, snapshotWithPolicy) + .setUserMetadata(org.elasticsearch.core.Map.of(SnapshotsService.POLICY_ID_METADATA_FIELD, policyName)) + .setWaitForCompletion(true) + .execute() + ); + + assertThat(getAllSnapshotsForPolicies(policyName), is(org.elasticsearch.core.List.of(withPolicy))); + assertThat(getAllSnapshotsForPolicies("some-*"), is(org.elasticsearch.core.List.of(withPolicy))); + assertThat(getAllSnapshotsForPolicies("*", "-" + policyName), empty()); + assertThat(getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN), is(snapshotsWithoutPolicy)); + assertThat( + getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN, "-" + policyName), is(snapshotsWithoutPolicy)); + assertThat(getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN), is(snapshotsWithoutPolicy)); + assertThat(getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN, "-*"), is(snapshotsWithoutPolicy)); + assertThat(getAllSnapshotsForPolicies("no-such-policy"), empty()); + assertThat(getAllSnapshotsForPolicies("no-such-policy*"), empty()); + + final String snapshotWithOtherPolicy = "snapshot-with-other-policy"; + final String otherPolicyName = "other-policy"; + final SnapshotInfo withOtherPolicy = AbstractSnapshotIntegTestCase.assertSuccessful( + logger, + clusterAdmin().prepareCreateSnapshot(repoName, snapshotWithOtherPolicy) + .setUserMetadata(org.elasticsearch.core.Map.of(SnapshotsService.POLICY_ID_METADATA_FIELD, otherPolicyName)) + .setWaitForCompletion(true) + .execute() + ); + assertThat(getAllSnapshotsForPolicies("*"), is(org.elasticsearch.core.List.of(withOtherPolicy, withPolicy))); + assertThat(getAllSnapshotsForPolicies(policyName, otherPolicyName), + is(org.elasticsearch.core.List.of(withOtherPolicy, withPolicy))); + assertThat(getAllSnapshotsForPolicies(policyName, otherPolicyName, "no-such-policy*"), + is(org.elasticsearch.core.List.of(withOtherPolicy, withPolicy))); + final List allSnapshots = clusterAdmin().prepareGetSnapshots("*") + .setSnapshots("*") + .setSort(GetSnapshotsRequest.SortBy.NAME) + .get() + .getSnapshots(); + assertThat( + getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN, policyName, otherPolicyName), + is(allSnapshots) + ); + assertThat( + getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN, "*"), + is(allSnapshots) + ); + } + + private static List getAllSnapshotsForPolicies(String... policies) throws IOException { + final Request requestWithPolicy = new Request(HttpGet.METHOD_NAME, "/_snapshot/*/*"); + requestWithPolicy.addParameter("slm_policy_filter", Strings.arrayToCommaDelimitedString(policies)); + requestWithPolicy.addParameter("sort", GetSnapshotsRequest.SortBy.NAME.toString()); + return readSnapshotInfos(getRestClient().performRequest(requestWithPolicy)).getSnapshots(); + } + private void createIndexWithContent(String indexName) { logger.info("--> creating index [{}]", indexName); createIndex(indexName, AbstractSnapshotIntegTestCase.SINGLE_SHARD_NO_REPLICA); diff --git a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java index 4ddaa44c16934..564f76d178944 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java @@ -24,6 +24,7 @@ import java.util.HashSet; import java.util.List; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.is; @@ -209,6 +210,70 @@ public void testPaginationRequiresVerboseListing() throws Exception { ); } + public void testFilterBySLMPolicy() throws Exception { + final String repoName = "test-repo"; + createRepository(repoName, "fs"); + createNSnapshots(repoName, randomIntBetween(1, 5)); + final List snapshotsWithoutPolicy = clusterAdmin().prepareGetSnapshots("*") + .setSnapshots("*") + .setSort(GetSnapshotsRequest.SortBy.NAME) + .get() + .getSnapshots(); + final String snapshotWithPolicy = "snapshot-with-policy"; + final String policyName = "some-policy"; + final SnapshotInfo withPolicy = assertSuccessful( + clusterAdmin().prepareCreateSnapshot(repoName, snapshotWithPolicy) + .setUserMetadata(org.elasticsearch.core.Map.of(SnapshotsService.POLICY_ID_METADATA_FIELD, policyName)) + .setWaitForCompletion(true) + .execute() + ); + + assertThat(getAllSnapshotsForPolicies(policyName), is(org.elasticsearch.core.List.of(withPolicy))); + assertThat(getAllSnapshotsForPolicies("some-*"), is(org.elasticsearch.core.List.of(withPolicy))); + assertThat(getAllSnapshotsForPolicies("*", "-" + policyName), empty()); + assertThat(getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN), is(snapshotsWithoutPolicy)); + assertThat(getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN, "-" + policyName), is(snapshotsWithoutPolicy)); + assertThat(getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN), is(snapshotsWithoutPolicy)); + assertThat(getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN, "-*"), is(snapshotsWithoutPolicy)); + assertThat(getAllSnapshotsForPolicies("no-such-policy"), empty()); + assertThat(getAllSnapshotsForPolicies("no-such-policy*"), empty()); + + final String snapshotWithOtherPolicy = "snapshot-with-other-policy"; + final String otherPolicyName = "other-policy"; + final SnapshotInfo withOtherPolicy = assertSuccessful( + clusterAdmin().prepareCreateSnapshot(repoName, snapshotWithOtherPolicy) + .setUserMetadata(org.elasticsearch.core.Map.of(SnapshotsService.POLICY_ID_METADATA_FIELD, otherPolicyName)) + .setWaitForCompletion(true) + .execute() + ); + assertThat(getAllSnapshotsForPolicies("*"), is(org.elasticsearch.core.List.of(withOtherPolicy, withPolicy))); + assertThat( + getAllSnapshotsForPolicies(policyName, otherPolicyName), + is(org.elasticsearch.core.List.of(withOtherPolicy, withPolicy)) + ); + assertThat( + getAllSnapshotsForPolicies(policyName, otherPolicyName, "no-such-policy*"), + is(org.elasticsearch.core.List.of(withOtherPolicy, withPolicy)) + ); + + final List allSnapshots = clusterAdmin().prepareGetSnapshots("*") + .setSnapshots("*") + .setSort(GetSnapshotsRequest.SortBy.NAME) + .get() + .getSnapshots(); + assertThat(getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN, policyName, otherPolicyName), is(allSnapshots)); + assertThat(getAllSnapshotsForPolicies(GetSnapshotsRequest.NO_POLICY_PATTERN, "*"), is(allSnapshots)); + } + + private static List getAllSnapshotsForPolicies(String... policies) { + return clusterAdmin().prepareGetSnapshots("*") + .setSnapshots("*") + .setPolicies(policies) + .setSort(GetSnapshotsRequest.SortBy.NAME) + .get() + .getSnapshots(); + } + private static void assertStablePagination(String repoName, Collection allSnapshotNames, GetSnapshotsRequest.SortBy sort) { final SortOrder order = randomFrom(SortOrder.values()); final List allSorted = allSnapshotsSorted(allSnapshotNames, repoName, sort, order); 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 3283ea1ed5e15..ba701222c0ab9 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 @@ -38,8 +38,11 @@ public class GetSnapshotsRequest extends MasterNodeRequest public static final String ALL_SNAPSHOTS = "_all"; public static final String CURRENT_SNAPSHOT = "_current"; + public static final String NO_POLICY_PATTERN = "_none"; public static final boolean DEFAULT_VERBOSE_MODE = true; + public static final Version SLM_POLICY_FILTERING_VERSION = Version.V_7_16_0; + public static final Version MULTIPLE_REPOSITORIES_SUPPORT_ADDED = Version.V_7_14_0; public static final Version PAGINATED_GET_SNAPSHOTS_VERSION = Version.V_7_14_0; @@ -71,6 +74,8 @@ public class GetSnapshotsRequest extends MasterNodeRequest private String[] snapshots = Strings.EMPTY_ARRAY; + private String[] policies = Strings.EMPTY_ARRAY; + private boolean ignoreUnavailable; private boolean verbose = DEFAULT_VERBOSE_MODE; @@ -126,6 +131,9 @@ public GetSnapshotsRequest(StreamInput in) throws IOException { if (in.getVersion().onOrAfter(NUMERIC_PAGINATION_VERSION)) { offset = in.readVInt(); } + if (in.getVersion().onOrAfter(SLM_POLICY_FILTERING_VERSION)) { + policies = in.readStringArray(); + } } } @@ -168,6 +176,13 @@ public void writeTo(StreamOutput out) throws IOException { } else if (sort != SortBy.START_TIME || size != NO_LIMIT || after != null || order != SortOrder.ASC) { throw new IllegalArgumentException("can't use paginated get snapshots request with node version [" + out.getVersion() + "]"); } + if (out.getVersion().onOrAfter(SLM_POLICY_FILTERING_VERSION)) { + out.writeStringArray(policies); + } else if (policies.length > 0) { + throw new IllegalArgumentException( + "can't use slm policy filter in snapshots request with node version [" + out.getVersion() + "]" + ); + } } @Override @@ -195,6 +210,9 @@ public ActionRequestValidationException validate() { if (order != SortOrder.ASC) { validationException = addValidationError("can't use non-default sort order with verbose=false", validationException); } + if (policies.length != 0) { + validationException = addValidationError("can't use slm policy filter with verbose=false", validationException); + } } else if (after != null && offset > 0) { validationException = addValidationError("can't use after and offset simultaneously", validationException); } @@ -221,6 +239,26 @@ public String[] repositories() { return this.repositories; } + /** + * Sets slm policy patterns + * + * @param policies policy patterns + * @return this request + */ + public GetSnapshotsRequest policies(String... policies) { + this.policies = policies; + return this; + } + + /** + * Returns policy patterns + * + * @return policy patterns + */ + public String[] policies() { + return policies; + } + public boolean isSingleRepositoryRequest() { return repositories.length == 1 && repositories[0] != null diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java index 6e57b60c4e2c2..8c43d3e2d961c 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java @@ -65,6 +65,17 @@ public GetSnapshotsRequestBuilder setRepositories(String... repositories) { return this; } + /** + * Sets slm policy patterns + * + * @param policies slm policy patterns + * @return this builder + */ + public GetSnapshotsRequestBuilder setPolicies(String... policies) { + request.policies(policies); + return this; + } + /** * Sets list of snapshots to return * 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 0effb94d0c00b..7fd930299b69c 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 @@ -23,6 +23,7 @@ import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.metadata.RepositoryMetadata; import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.core.Nullable; @@ -122,6 +123,7 @@ protected void masterOperation( request.offset(), request.size(), request.order(), + request.policies(), listener ); } @@ -139,6 +141,7 @@ private void getMultipleReposSnapshotInfo( int offset, int size, SortOrder order, + String[] slmPolicies, ActionListener listener ) { // short-circuit if there are no repos, because we can not create GroupedActionListener of size 0 @@ -158,7 +161,7 @@ private void getMultipleReposSnapshotInfo( .map(Tuple::v1) .filter(Objects::nonNull) .collect(Collectors.toMap(Tuple::v1, Tuple::v2)); - final SnapshotsInRepo snInfos = sortSnapshots(allSnapshots, sortBy, after, offset, size, order); + final SnapshotsInRepo snInfos = sortAndFilterSnapshots(allSnapshots, sortBy, after, offset, size, order, slmPolicies); final List snapshotInfos = snInfos.snapshotInfos; final int remaining = snInfos.remaining + responses.stream() .map(Tuple::v2) @@ -182,6 +185,7 @@ private void getMultipleReposSnapshotInfo( snapshotsInProgress, repoName, snapshots, + slmPolicies, ignoreUnavailable, verbose, cancellableTask, @@ -203,6 +207,7 @@ private void getSingleRepoSnapshotInfo( SnapshotsInProgress snapshotsInProgress, String repo, String[] snapshots, + String[] slmPolicies, boolean ignoreUnavailable, boolean verbose, CancellableTask task, @@ -240,6 +245,7 @@ private void getSingleRepoSnapshotInfo( sortBy, after, order, + slmPolicies, listener ), listener::onFailure @@ -279,6 +285,7 @@ private void loadSnapshotInfos( GetSnapshotsRequest.SortBy sortBy, @Nullable final GetSnapshotsRequest.After after, SortOrder order, + String[] slmPolicies, ActionListener listener ) { if (task.notifyIfCancelled(listener)) { @@ -328,9 +335,14 @@ private void loadSnapshotInfos( sortBy, after, order, + slmPolicies, listener ); } else { + assert slmPolicies.length == 0 + : "slm policy filtering not support for non-verbose request but saw [" + + Strings.arrayToCommaDelimitedString(slmPolicies) + + "]"; final SnapshotsInRepo snapshotInfos; if (repositoryData != null) { // want non-current snapshots as well, which are found in the repository data @@ -366,6 +378,7 @@ private void snapshots( GetSnapshotsRequest.SortBy sortBy, @Nullable GetSnapshotsRequest.After after, SortOrder order, + String[] slmPolicies, ActionListener listener ) { if (task.notifyIfCancelled(listener)) { @@ -394,7 +407,7 @@ private void snapshots( final ActionListener allDoneListener = listener.delegateFailure((l, v) -> { final ArrayList snapshotList = new ArrayList<>(snapshotInfos); snapshotList.addAll(snapshotSet); - listener.onResponse(sortSnapshots(snapshotList, sortBy, after, 0, GetSnapshotsRequest.NO_LIMIT, order)); + listener.onResponse(sortAndFilterSnapshots(snapshotList, sortBy, after, 0, GetSnapshotsRequest.NO_LIMIT, order, slmPolicies)); }); if (snapshotIdsToIterate.isEmpty()) { allDoneListener.onResponse(null); @@ -486,13 +499,31 @@ private static SnapshotsInRepo buildSimpleSnapshotInfos( private static final Comparator BY_REPOSITORY = Comparator.comparing(SnapshotInfo::repository) .thenComparing(SnapshotInfo::snapshotId); - private static SnapshotsInRepo sortSnapshots( + private static SnapshotsInRepo sortAndFilterSnapshots( final List snapshotInfos, final GetSnapshotsRequest.SortBy sortBy, final @Nullable GetSnapshotsRequest.After after, final int offset, final int size, - final SortOrder order + final SortOrder order, + final String[] slmPolicies + ) { + final List filteredSnapshotInfos; + if (slmPolicies.length == 0) { + filteredSnapshotInfos = snapshotInfos; + } else { + filteredSnapshotInfos = filterBySLMPolicies(snapshotInfos, slmPolicies); + } + return sortSnapshots(filteredSnapshotInfos, sortBy, after, offset, size, order); + } + + private static SnapshotsInRepo sortSnapshots( + List snapshotInfos, + GetSnapshotsRequest.SortBy sortBy, + @Nullable GetSnapshotsRequest.After after, + int offset, + int size, + SortOrder order ) { final Comparator comparator; switch (sortBy) { @@ -591,6 +622,45 @@ private static SnapshotsInRepo sortSnapshots( return new SnapshotsInRepo(resultSet, snapshotInfos.size(), allSnapshots.size() - resultSet.size()); } + private static List filterBySLMPolicies(List snapshotInfos, String[] slmPolicies) { + final List includePatterns = new ArrayList<>(); + final List excludePatterns = new ArrayList<>(); + boolean seenWildcard = false; + boolean matchNoPolicy = false; + for (String slmPolicy : slmPolicies) { + if (seenWildcard && slmPolicy.length() > 1 && slmPolicy.startsWith("-")) { + excludePatterns.add(slmPolicy.substring(1)); + } else { + if (Regex.isSimpleMatchPattern(slmPolicy)) { + seenWildcard = true; + } else if (GetSnapshotsRequest.NO_POLICY_PATTERN.equals(slmPolicy)) { + matchNoPolicy = true; + } + includePatterns.add(slmPolicy); + } + } + final String[] includes = includePatterns.toArray(Strings.EMPTY_ARRAY); + final String[] excludes = excludePatterns.toArray(Strings.EMPTY_ARRAY); + final boolean matchWithoutPolicy = matchNoPolicy; + return Collections.unmodifiableList(snapshotInfos.stream().filter(snapshotInfo -> { + final Map metadata = snapshotInfo.userMetadata(); + final String policy; + if (metadata == null) { + policy = null; + } else { + final Object policyFound = metadata.get(SnapshotsService.POLICY_ID_METADATA_FIELD); + policy = policyFound instanceof String ? (String) policyFound : null; + } + if (policy == null) { + return matchWithoutPolicy; + } + if (Regex.simpleMatch(includes, policy) == false) { + return false; + } + return excludes.length == 0 || Regex.simpleMatch(excludes, policy) == false; + }).collect(Collectors.toList())); + } + private static Predicate filterByLongOffset( ToLongFunction extractor, long after, diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetSnapshotsAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetSnapshotsAction.java index 59a7958f3cb2a..732ae489c41e9 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetSnapshotsAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetSnapshotsAction.java @@ -65,6 +65,8 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC if (afterString != null) { getSnapshotsRequest.after(GetSnapshotsRequest.After.fromQueryParam(afterString)); } + final String[] policies = request.paramAsStringArray("slm_policy_filter", Strings.EMPTY_ARRAY); + getSnapshotsRequest.policies(policies); final SortOrder order = SortOrder.fromString(request.param("order", getSnapshotsRequest.order().toString())); getSnapshotsRequest.order(order); diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java index 912d9ecf8f68d..6aa44f63b3a38 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java +++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java @@ -149,6 +149,8 @@ public class SnapshotsService extends AbstractLifecycleComponent implements Clus public static final Version INDEX_DETAILS_INTRODUCED = Version.V_7_13_0; + public static final String POLICY_ID_METADATA_FIELD = "policy"; + private static final Logger logger = LogManager.getLogger(SnapshotsService.class); public static final String UPDATE_SNAPSHOT_STATUS_ACTION_NAME = "internal:cluster/snapshot/update_snapshot_status"; diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestTests.java index 127ad20af6226..589043a5a6565 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestTests.java @@ -62,6 +62,11 @@ public void testValidateParameters() { final ActionRequestValidationException e = request.validate(); assertThat(e.getMessage(), containsString("can't use after and offset simultaneously")); } + { + final GetSnapshotsRequest request = new GetSnapshotsRequest("repo", "snapshot").policies("some-policy").verbose(false); + final ActionRequestValidationException e = request.validate(); + assertThat(e.getMessage(), containsString("can't use slm policy filter with verbose=false")); + } } public void testGetDescription() { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/slm/SnapshotLifecyclePolicy.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/slm/SnapshotLifecyclePolicy.java index 9276d33aee2fd..23e0a94ce93dd 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/slm/SnapshotLifecyclePolicy.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/slm/SnapshotLifecyclePolicy.java @@ -24,6 +24,7 @@ import org.elasticsearch.xcontent.ToXContentObject; import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.snapshots.SnapshotsService; import org.elasticsearch.xpack.core.scheduler.Cron; import java.io.IOException; @@ -44,8 +45,6 @@ public class SnapshotLifecyclePolicy extends AbstractDiffable implements Writeable, Diffable, ToXContentObject { - public static final String POLICY_ID_METADATA_FIELD = "policy"; - private final String id; private final String name; private final String schedule; @@ -199,9 +198,9 @@ public ActionRequestValidationException validate() { } else { @SuppressWarnings("unchecked") Map metadata = (Map) configuration.get(METADATA_FIELD_NAME); - if (metadata.containsKey(POLICY_ID_METADATA_FIELD)) { - err.addValidationError("invalid configuration." + METADATA_FIELD_NAME + ": field name [" + POLICY_ID_METADATA_FIELD + - "] is reserved and will be added automatically"); + if (metadata.containsKey(SnapshotsService.POLICY_ID_METADATA_FIELD)) { + err.addValidationError("invalid configuration." + METADATA_FIELD_NAME + ": field name [" + + SnapshotsService.POLICY_ID_METADATA_FIELD + "] is reserved and will be added automatically"); } else { Map metadataWithPolicyField = addPolicyNameToMetadata(metadata); int serializedSizeOriginal = CreateSnapshotRequest.metadataSize(metadata); @@ -232,7 +231,7 @@ private Map addPolicyNameToMetadata(final Map me } else { newMetadata = new HashMap<>(metadata); } - newMetadata.put(POLICY_ID_METADATA_FIELD, this.id); + newMetadata.put(SnapshotsService.POLICY_ID_METADATA_FIELD, this.id); return newMetadata; } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/slm/SnapshotRetentionConfigurationTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/slm/SnapshotRetentionConfigurationTests.java index d432e23452d49..6a92b00298a7e 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/slm/SnapshotRetentionConfigurationTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/slm/SnapshotRetentionConfigurationTests.java @@ -14,6 +14,7 @@ import org.elasticsearch.snapshots.SnapshotInfo; import org.elasticsearch.snapshots.SnapshotShardFailure; import org.elasticsearch.snapshots.SnapshotState; +import org.elasticsearch.snapshots.SnapshotsService; import org.elasticsearch.test.ESTestCase; import java.util.ArrayList; @@ -284,7 +285,7 @@ public void testFewerSuccessesThanMinWithPartial() { private SnapshotInfo makeInfo(long startTime) { final Map meta = new HashMap<>(); - meta.put(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD, REPO); + meta.put(SnapshotsService.POLICY_ID_METADATA_FIELD, REPO); final int totalShards = between(1,20); SnapshotInfo snapInfo = new SnapshotInfo( new Snapshot(REPO, new SnapshotId("snap-" + randomAlphaOfLength(3), "uuid")), @@ -314,7 +315,7 @@ private SnapshotInfo makeFailureOrPartial(long startTime, boolean failure) { private SnapshotInfo makeFailureInfo(long startTime) { final Map meta = new HashMap<>(); - meta.put(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD, REPO); + meta.put(SnapshotsService.POLICY_ID_METADATA_FIELD, REPO); final int totalShards = between(1,20); final List failures = new ArrayList<>(); final int failureCount = between(1,totalShards); @@ -342,7 +343,7 @@ private SnapshotInfo makeFailureInfo(long startTime) { private SnapshotInfo makePartialInfo(long startTime) { final Map meta = new HashMap<>(); - meta.put(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD, REPO); + meta.put(SnapshotsService.POLICY_ID_METADATA_FIELD, REPO); final int totalShards = between(2,20); final List failures = new ArrayList<>(); final int failureCount = between(1,totalShards - 1); diff --git a/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java b/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java index 176a6ba11a518..790039ce28df8 100644 --- a/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java +++ b/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java @@ -29,6 +29,7 @@ import org.elasticsearch.snapshots.SnapshotInfo; import org.elasticsearch.snapshots.SnapshotMissingException; import org.elasticsearch.snapshots.SnapshotState; +import org.elasticsearch.snapshots.SnapshotsService; import org.elasticsearch.snapshots.mockstore.MockRepository; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin; @@ -346,7 +347,7 @@ private void testUnsuccessfulSnapshotRetention(boolean partialSuccess) throws Ex } else { final String snapshotName = "failed-snapshot-1"; addBwCFailedSnapshot(REPO, snapshotName, - Collections.singletonMap(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD, policyId)); + Collections.singletonMap(SnapshotsService.POLICY_ID_METADATA_FIELD, policyId)); failedSnapshotName.set(snapshotName); } diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java index bc40924e3a6e2..bd5d023ad5958 100644 --- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java +++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java @@ -47,7 +47,7 @@ import java.util.function.LongSupplier; import java.util.stream.Collectors; -import static org.elasticsearch.xpack.core.slm.SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD; +import static org.elasticsearch.snapshots.SnapshotsService.POLICY_ID_METADATA_FIELD; /** * The {@code SnapshotRetentionTask} is invoked by the scheduled job from the @@ -276,9 +276,9 @@ void getAllRetainableSnapshots(Collection repositories, ActionListener meta.get(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD) != null) - .filter(meta -> meta.get(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD) instanceof String) - .map(meta -> (String) meta.get(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD)) + .filter(meta -> meta.get(POLICY_ID_METADATA_FIELD) != null) + .filter(meta -> meta.get(POLICY_ID_METADATA_FIELD) instanceof String) + .map(meta -> (String) meta.get(POLICY_ID_METADATA_FIELD)) .orElseThrow(() -> new IllegalStateException("expected snapshot " + snapshotInfo + " to have a policy in its metadata, but it did not")); } diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/TransportGetSnapshotLifecycleAction.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/TransportGetSnapshotLifecycleAction.java index 534daf21951b2..1ec889ce88f77 100644 --- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/TransportGetSnapshotLifecycleAction.java +++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/TransportGetSnapshotLifecycleAction.java @@ -18,10 +18,11 @@ import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; + +import org.elasticsearch.snapshots.SnapshotsService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.slm.SnapshotLifecycleMetadata; -import org.elasticsearch.xpack.core.slm.SnapshotLifecyclePolicy; import org.elasticsearch.xpack.core.slm.SnapshotLifecyclePolicyItem; import org.elasticsearch.xpack.core.slm.action.GetSnapshotLifecycleAction; import org.elasticsearch.xpack.core.slm.SnapshotLifecycleStats; @@ -69,12 +70,12 @@ protected void masterOperation(final GetSnapshotLifecycleAction.Request request, for (SnapshotsInProgress.Entry entry : sip.entries()) { Map meta = entry.userMetadata(); if (meta == null || - meta.get(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD) == null || - (meta.get(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD) instanceof String == false)) { + meta.get(SnapshotsService.POLICY_ID_METADATA_FIELD) == null || + (meta.get(SnapshotsService.POLICY_ID_METADATA_FIELD) instanceof String == false)) { continue; } - String policyId = (String) meta.get(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD); + String policyId = (String) meta.get(SnapshotsService.POLICY_ID_METADATA_FIELD); inProgress.put(policyId, SnapshotLifecyclePolicyItem.SnapshotInProgress.fromEntry(entry)); } }