diff --git a/docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc b/docs/reference/snapshot-restore/apis/get-snapshot-api.asciidoc index a708a20bd8b24..e941152de4969 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 3cdc32029babd..6b09b1e8299f2 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.common.xcontent.DeprecationHandler; import org.elasticsearch.common.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; @@ -31,8 +33,10 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; 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 +187,67 @@ 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(Map.of(SnapshotsService.POLICY_ID_METADATA_FIELD, policyName)) + .setWaitForCompletion(true) + .execute() + ); + + assertThat(getAllSnapshotsForPolicies(policyName), is(List.of(withPolicy))); + assertThat(getAllSnapshotsForPolicies("some-*"), is(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(Map.of(SnapshotsService.POLICY_ID_METADATA_FIELD, otherPolicyName)) + .setWaitForCompletion(true) + .execute() + ); + assertThat(getAllSnapshotsForPolicies("*"), is(List.of(withOtherPolicy, withPolicy))); + assertThat(getAllSnapshotsForPolicies(policyName, otherPolicyName), is(List.of(withOtherPolicy, withPolicy))); + assertThat(getAllSnapshotsForPolicies(policyName, otherPolicyName, "no-such-policy*"), is(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 e558dfbd0bcac..365434910b5b0 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java @@ -23,7 +23,9 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.is; @@ -208,6 +210,64 @@ 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(Map.of(SnapshotsService.POLICY_ID_METADATA_FIELD, policyName)) + .setWaitForCompletion(true) + .execute() + ); + + assertThat(getAllSnapshotsForPolicies(policyName), is(List.of(withPolicy))); + assertThat(getAllSnapshotsForPolicies("some-*"), is(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(Map.of(SnapshotsService.POLICY_ID_METADATA_FIELD, otherPolicyName)) + .setWaitForCompletion(true) + .execute() + ); + assertThat(getAllSnapshotsForPolicies("*"), is(List.of(withOtherPolicy, withPolicy))); + assertThat(getAllSnapshotsForPolicies(policyName, otherPolicyName), is(List.of(withOtherPolicy, withPolicy))); + assertThat(getAllSnapshotsForPolicies(policyName, otherPolicyName, "no-such-policy*"), is(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 0991b22cb8a36..54314bc872bd2 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_8_0_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; @@ -115,6 +120,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(); + } } } @@ -157,6 +165,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 @@ -184,6 +199,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); } @@ -210,6 +228,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 1a0ae82cc3929..ff202a070a52b 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 @@ -40,6 +40,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 7ac984014a770..ea416b476ac72 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 @@ -25,6 +25,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; @@ -120,6 +121,7 @@ protected void masterOperation( request.offset(), request.size(), request.order(), + request.policies(), listener ); } @@ -137,6 +139,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 @@ -156,7 +159,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) @@ -180,6 +183,7 @@ private void getMultipleReposSnapshotInfo( snapshotsInProgress, repoName, snapshots, + slmPolicies, ignoreUnavailable, verbose, cancellableTask, @@ -201,6 +205,7 @@ private void getSingleRepoSnapshotInfo( SnapshotsInProgress snapshotsInProgress, String repo, String[] snapshots, + String[] slmPolicies, boolean ignoreUnavailable, boolean verbose, CancellableTask task, @@ -238,6 +243,7 @@ private void getSingleRepoSnapshotInfo( sortBy, after, order, + slmPolicies, listener ), listener::onFailure @@ -277,6 +283,7 @@ private void loadSnapshotInfos( GetSnapshotsRequest.SortBy sortBy, @Nullable final GetSnapshotsRequest.After after, SortOrder order, + String[] slmPolicies, ActionListener listener ) { if (task.notifyIfCancelled(listener)) { @@ -326,9 +333,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 @@ -364,6 +376,7 @@ private void snapshots( GetSnapshotsRequest.SortBy sortBy, @Nullable GetSnapshotsRequest.After after, SortOrder order, + String[] slmPolicies, ActionListener listener ) { if (task.notifyIfCancelled(listener)) { @@ -392,7 +405,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); @@ -495,13 +508,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) { @@ -600,6 +631,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 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.toUnmodifiableList()); + } + 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 6c450e42096da..b05e61261ba3c 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 @@ -64,6 +64,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 3ad43c9f5c1d8..db23fea8428db 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java +++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java @@ -132,6 +132,8 @@ public class SnapshotsService extends AbstractLifecycleComponent implements Clus public static final Version OLD_SNAPSHOT_FORMAT = Version.V_7_5_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 584f4a321d6d8..dd8f31057aa0e 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 @@ -23,6 +23,7 @@ import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.snapshots.SnapshotsService; import org.elasticsearch.xpack.core.scheduler.Cron; import java.io.IOException; @@ -43,8 +44,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; @@ -194,9 +193,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); @@ -227,7 +226,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 9fcb487f202aa..0e07246601976 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; @@ -323,7 +324,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 837f05344faa9..1e2d22846fe6c 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 @@ -275,9 +275,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 629d0129ca651..ff67407d6c367 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,11 +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.tasks.Task; 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; @@ -70,12 +70,12 @@ protected void masterOperation(final Task task, final GetSnapshotLifecycleAction 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)); } }