Skip to content

Commit 480800a

Browse files
authored
Implement SnapshotRetentionTask's snapshot filtering and delet… (#44764)
* Implement SnapshotRetentionTask's snapshot filtering and deletion This commit implements the snapshot filtering and deletion for `SnapshotRetentionTask`. Currently only the expire-after age is used for determining whether a snapshot is eligible for deletion. Relates to #43663 * Fix deletes running on the wrong thread * Handle missing or null policy in snap metadata differently * Convert Tuple<String, List<SnapshotInfo>> to Map<String, List<SnapshotInfo>> * Use the `OriginSettingClient` to work with security, enhance logging * Prevent NPE in test by mocking Client
1 parent c30265a commit 480800a

File tree

6 files changed

+492
-25
lines changed

6 files changed

+492
-25
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/snapshotlifecycle/SnapshotLifecyclePolicy.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
public class SnapshotLifecyclePolicy extends AbstractDiffable<SnapshotLifecyclePolicy>
4848
implements Writeable, Diffable<SnapshotLifecyclePolicy>, ToXContentObject {
4949

50+
public static final String POLICY_ID_METADATA_FIELD = "policy";
51+
5052
private final String id;
5153
private final String name;
5254
private final String schedule;
@@ -61,7 +63,6 @@ public class SnapshotLifecyclePolicy extends AbstractDiffable<SnapshotLifecycleP
6163
private static final ParseField RETENTION = new ParseField("retention");
6264
private static final IndexNameExpressionResolver.DateMathExpressionResolver DATE_MATH_RESOLVER =
6365
new IndexNameExpressionResolver.DateMathExpressionResolver();
64-
private static final String POLICY_ID_METADATA_FIELD = "policy";
6566
private static final String METADATA_FIELD_NAME = "metadata";
6667

6768
@SuppressWarnings("unchecked")

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/snapshotlifecycle/SnapshotRetentionConfiguration.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
import org.elasticsearch.common.xcontent.ToXContentObject;
1818
import org.elasticsearch.common.xcontent.XContentBuilder;
1919
import org.elasticsearch.common.xcontent.XContentParser;
20+
import org.elasticsearch.snapshots.SnapshotInfo;
2021

2122
import java.io.IOException;
23+
import java.util.List;
2224
import java.util.Objects;
25+
import java.util.function.Predicate;
2326

2427
public class SnapshotRetentionConfiguration implements ToXContentObject, Writeable {
2528

@@ -56,6 +59,26 @@ public TimeValue getExpireAfter() {
5659
return this.expireAfter;
5760
}
5861

62+
/**
63+
* Return a predicate by which a SnapshotInfo can be tested to see
64+
* whether it should be deleted according to this retention policy.
65+
* @param allSnapshots a list of all snapshot pertaining to this SLM policy and repository
66+
*/
67+
public Predicate<SnapshotInfo> getSnapshotDeletionPredicate(final List<SnapshotInfo> allSnapshots) {
68+
return si -> {
69+
if (this.expireAfter != null) {
70+
TimeValue snapshotAge = new TimeValue(System.currentTimeMillis() - si.startTime());
71+
if (snapshotAge.compareTo(this.expireAfter) > 0) {
72+
return true;
73+
} else {
74+
return false;
75+
}
76+
}
77+
// If nothing matched, the snapshot is not eligible for deletion
78+
return false;
79+
};
80+
}
81+
5982
@Override
6083
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
6184
builder.startObject();

x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/slm/SnapshotLifecycleIT.java

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
package org.elasticsearch.xpack.slm;
88

99
import org.apache.http.util.EntityUtils;
10+
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
1011
import org.elasticsearch.action.index.IndexRequestBuilder;
1112
import org.elasticsearch.client.Request;
1213
import org.elasticsearch.client.Response;
1314
import org.elasticsearch.client.ResponseException;
1415
import org.elasticsearch.client.RestClient;
1516
import org.elasticsearch.common.Strings;
17+
import org.elasticsearch.common.settings.Settings;
18+
import org.elasticsearch.common.unit.TimeValue;
1619
import org.elasticsearch.common.xcontent.DeprecationHandler;
1720
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
1821
import org.elasticsearch.common.xcontent.ToXContent;
@@ -22,6 +25,7 @@
2225
import org.elasticsearch.common.xcontent.XContentType;
2326
import org.elasticsearch.common.xcontent.json.JsonXContent;
2427
import org.elasticsearch.test.rest.ESRestTestCase;
28+
import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings;
2529
import org.elasticsearch.xpack.core.snapshotlifecycle.SnapshotLifecyclePolicy;
2630
import org.elasticsearch.xpack.core.snapshotlifecycle.SnapshotRetentionConfiguration;
2731

@@ -222,6 +226,93 @@ public void testPolicyManualExecution() throws Exception {
222226
});
223227
}
224228

229+
public void testBasicTimeBasedRetenion() throws Exception {
230+
final String indexName = "test";
231+
final String policyName = "test-policy";
232+
final String repoId = "my-repo";
233+
int docCount = randomIntBetween(10, 50);
234+
List<IndexRequestBuilder> indexReqs = new ArrayList<>();
235+
for (int i = 0; i < docCount; i++) {
236+
index(client(), indexName, "" + i, "foo", "bar");
237+
}
238+
239+
// Create a snapshot repo
240+
inializeRepo(repoId);
241+
242+
// Create a policy with a retention period of 1 millisecond
243+
createSnapshotPolicy(policyName, "snap", "1 2 3 4 5 ?", repoId, indexName, true,
244+
new SnapshotRetentionConfiguration(TimeValue.timeValueMillis(1)));
245+
246+
// Manually create a snapshot
247+
Response executeResp = client().performRequest(new Request("PUT", "/_slm/policy/" + policyName + "/_execute"));
248+
249+
final String snapshotName;
250+
try (XContentParser parser = JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY,
251+
DeprecationHandler.THROW_UNSUPPORTED_OPERATION, EntityUtils.toByteArray(executeResp.getEntity()))) {
252+
snapshotName = parser.mapStrings().get("snapshot_name");
253+
254+
// Check that the executed snapshot is created
255+
assertBusy(() -> {
256+
try {
257+
Response response = client().performRequest(new Request("GET", "/_snapshot/" + repoId + "/" + snapshotName));
258+
Map<String, Object> snapshotResponseMap;
259+
try (InputStream is = response.getEntity().getContent()) {
260+
snapshotResponseMap = XContentHelper.convertToMap(XContentType.JSON.xContent(), is, true);
261+
}
262+
assertThat(snapshotResponseMap.size(), greaterThan(0));
263+
final Map<String, Object> metadata = extractMetadata(snapshotResponseMap, snapshotName);
264+
assertNotNull(metadata);
265+
assertThat(metadata.get("policy"), equalTo(policyName));
266+
assertHistoryIsPresent(policyName, true, repoId);
267+
} catch (ResponseException e) {
268+
fail("expected snapshot to exist but it does not: " + EntityUtils.toString(e.getResponse().getEntity()));
269+
}
270+
});
271+
}
272+
273+
// Run retention every second
274+
ClusterUpdateSettingsRequest req = new ClusterUpdateSettingsRequest();
275+
req.transientSettings(Settings.builder().put(LifecycleSettings.SLM_RETENTION_SCHEDULE, "*/1 * * * * ?"));
276+
try (XContentBuilder builder = jsonBuilder()) {
277+
req.toXContent(builder, ToXContent.EMPTY_PARAMS);
278+
Request r = new Request("PUT", "/_cluster/settings");
279+
r.setJsonEntity(Strings.toString(builder));
280+
Response updateSettingsResp = client().performRequest(r);
281+
}
282+
283+
try {
284+
// Check that the snapshot created by the policy has been removed by retention
285+
assertBusy(() -> {
286+
// We expect a failed response because the snapshot should not exist
287+
try {
288+
Response response = client().performRequest(new Request("GET", "/_snapshot/" + repoId + "/" + snapshotName));
289+
assertThat(EntityUtils.toString(response.getEntity()), containsString("snapshot_missing_exception"));
290+
} catch (ResponseException e) {
291+
assertThat(EntityUtils.toString(e.getResponse().getEntity()), containsString("snapshot_missing_exception"));
292+
}
293+
});
294+
295+
Request delReq = new Request("DELETE", "/_slm/policy/" + policyName);
296+
assertOK(client().performRequest(delReq));
297+
298+
// It's possible there could have been a snapshot in progress when the
299+
// policy is deleted, so wait for it to be finished
300+
assertBusy(() -> {
301+
assertThat(wipeSnapshots().size(), equalTo(0));
302+
});
303+
} finally {
304+
// Unset retention
305+
ClusterUpdateSettingsRequest unsetRequest = new ClusterUpdateSettingsRequest();
306+
unsetRequest.transientSettings(Settings.builder().put(LifecycleSettings.SLM_RETENTION_SCHEDULE, (String) null));
307+
try (XContentBuilder builder = jsonBuilder()) {
308+
unsetRequest.toXContent(builder, ToXContent.EMPTY_PARAMS);
309+
Request r = new Request("PUT", "/_cluster/settings");
310+
r.setJsonEntity(Strings.toString(builder));
311+
client().performRequest(r);
312+
}
313+
}
314+
}
315+
225316
@SuppressWarnings("unchecked")
226317
private static Map<String, Object> extractMetadata(Map<String, Object> snapshotResponseMap, String snapshotPrefix) {
227318
List<Map<String, Object>> snapResponse = ((List<Map<String, Object>>) snapshotResponseMap.get("responses")).stream()
@@ -284,6 +375,13 @@ private void assertHistoryIsPresent(String policyName, boolean success, String r
284375

285376
private void createSnapshotPolicy(String policyName, String snapshotNamePattern, String schedule, String repoId,
286377
String indexPattern, boolean ignoreUnavailable) throws IOException {
378+
createSnapshotPolicy(policyName, snapshotNamePattern, schedule, repoId, indexPattern,
379+
ignoreUnavailable, SnapshotRetentionConfiguration.EMPTY);
380+
}
381+
382+
private void createSnapshotPolicy(String policyName, String snapshotNamePattern, String schedule, String repoId,
383+
String indexPattern, boolean ignoreUnavailable,
384+
SnapshotRetentionConfiguration retention) throws IOException {
287385
Map<String, Object> snapConfig = new HashMap<>();
288386
snapConfig.put("indices", Collections.singletonList(indexPattern));
289387
snapConfig.put("ignore_unavailable", ignoreUnavailable);
@@ -295,8 +393,8 @@ private void createSnapshotPolicy(String policyName, String snapshotNamePattern,
295393
() -> randomAlphaOfLength(5)), randomAlphaOfLength(4));
296394
}
297395
}
298-
SnapshotLifecyclePolicy policy = new SnapshotLifecyclePolicy(policyName, snapshotNamePattern, schedule, repoId, snapConfig,
299-
SnapshotRetentionConfiguration.EMPTY);
396+
SnapshotLifecyclePolicy policy = new SnapshotLifecyclePolicy(policyName, snapshotNamePattern, schedule,
397+
repoId, snapConfig, retention);
300398

301399
Request putLifecycle = new Request("PUT", "/_slm/policy/" + policyName);
302400
XContentBuilder lifecycleBuilder = JsonXContent.contentBuilder();

0 commit comments

Comments
 (0)