Skip to content

Commit 3c1f331

Browse files
committed
Add SnapshotRetentionConfiguration for retention configuration (#43777)
* Add SnapshotRetentionConfiguration for retention configuration This commit adds the `SnapshotRetentionConfiguration` class and its HLRC counterpart to encapsulate the configuration for SLM retention. Currently only a single parameter is supported as an example (we still need to discuss the different options we want to support and their names) to keep the size of the PR down. It also does not yet include version serialization checks since the original SLM branch has not yet been merged. Relates to #43663 * Fix REST tests * Fix more documentation * Use Objects.equals to avoid NPE * Put `randomSnapshotLifecyclePolicy` in only one place * Occasionally return retention with no configuration
1 parent 23cfe40 commit 3c1f331

File tree

15 files changed

+312
-82
lines changed

15 files changed

+312
-82
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/SnapshotLifecyclePolicy.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@ public class SnapshotLifecyclePolicy implements ToXContentObject {
3838
private final String schedule;
3939
private final String repository;
4040
private final Map<String, Object> configuration;
41+
private final SnapshotRetentionConfiguration retentionPolicy;
4142

4243
private static final ParseField NAME = new ParseField("name");
4344
private static final ParseField SCHEDULE = new ParseField("schedule");
4445
private static final ParseField REPOSITORY = new ParseField("repository");
4546
private static final ParseField CONFIG = new ParseField("config");
47+
private static final ParseField RETENTION = new ParseField("retention");
4648
private static final IndexNameExpressionResolver.DateMathExpressionResolver DATE_MATH_RESOLVER =
4749
new IndexNameExpressionResolver.DateMathExpressionResolver();
4850

@@ -54,23 +56,27 @@ public class SnapshotLifecyclePolicy implements ToXContentObject {
5456
String schedule = (String) a[1];
5557
String repo = (String) a[2];
5658
Map<String, Object> config = (Map<String, Object>) a[3];
57-
return new SnapshotLifecyclePolicy(id, name, schedule, repo, config);
59+
SnapshotRetentionConfiguration retention = (SnapshotRetentionConfiguration) a[4];
60+
return new SnapshotLifecyclePolicy(id, name, schedule, repo, config, retention);
5861
});
5962

6063
static {
6164
PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
6265
PARSER.declareString(ConstructingObjectParser.constructorArg(), SCHEDULE);
6366
PARSER.declareString(ConstructingObjectParser.constructorArg(), REPOSITORY);
6467
PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, c) -> p.map(), CONFIG);
68+
PARSER.declareObject(ConstructingObjectParser.constructorArg(), SnapshotRetentionConfiguration::parse, RETENTION);
6569
}
6670

6771
public SnapshotLifecyclePolicy(final String id, final String name, final String schedule,
68-
final String repository, Map<String, Object> configuration) {
72+
final String repository, Map<String, Object> configuration,
73+
SnapshotRetentionConfiguration retentionPolicy) {
6974
this.id = Objects.requireNonNull(id);
7075
this.name = name;
7176
this.schedule = schedule;
7277
this.repository = repository;
7378
this.configuration = configuration;
79+
this.retentionPolicy = retentionPolicy;
7480
}
7581

7682
public String getId() {
@@ -93,6 +99,10 @@ public Map<String, Object> getConfig() {
9399
return this.configuration;
94100
}
95101

102+
public SnapshotRetentionConfiguration getRetentionPolicy() {
103+
return this.retentionPolicy;
104+
}
105+
96106
public static SnapshotLifecyclePolicy parse(XContentParser parser, String id) {
97107
return PARSER.apply(parser, id);
98108
}
@@ -104,13 +114,14 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
104114
builder.field(SCHEDULE.getPreferredName(), this.schedule);
105115
builder.field(REPOSITORY.getPreferredName(), this.repository);
106116
builder.field(CONFIG.getPreferredName(), this.configuration);
117+
builder.field(RETENTION.getPreferredName(), this.retentionPolicy);
107118
builder.endObject();
108119
return builder;
109120
}
110121

111122
@Override
112123
public int hashCode() {
113-
return Objects.hash(id, name, schedule, repository, configuration);
124+
return Objects.hash(id, name, schedule, repository, configuration, retentionPolicy);
114125
}
115126

116127
@Override
@@ -127,7 +138,8 @@ public boolean equals(Object obj) {
127138
Objects.equals(name, other.name) &&
128139
Objects.equals(schedule, other.schedule) &&
129140
Objects.equals(repository, other.repository) &&
130-
Objects.equals(configuration, other.configuration);
141+
Objects.equals(configuration, other.configuration) &&
142+
Objects.equals(retentionPolicy, other.retentionPolicy);
131143
}
132144

133145
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.snapshotlifecycle;
21+
22+
import org.elasticsearch.common.ParseField;
23+
import org.elasticsearch.common.Strings;
24+
import org.elasticsearch.common.unit.TimeValue;
25+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
26+
import org.elasticsearch.common.xcontent.ToXContentObject;
27+
import org.elasticsearch.common.xcontent.XContentBuilder;
28+
import org.elasticsearch.common.xcontent.XContentParser;
29+
30+
import java.io.IOException;
31+
import java.util.Objects;
32+
33+
public class SnapshotRetentionConfiguration implements ToXContentObject {
34+
35+
public static final SnapshotRetentionConfiguration EMPTY = new SnapshotRetentionConfiguration((TimeValue) null);
36+
37+
private static final ParseField EXPIRE_AFTER = new ParseField("expire_after");
38+
39+
private static final ConstructingObjectParser<SnapshotRetentionConfiguration, Void> PARSER =
40+
new ConstructingObjectParser<>("snapshot_retention", true, a -> {
41+
TimeValue expireAfter = a[0] == null ? null : TimeValue.parseTimeValue((String) a[0], EXPIRE_AFTER.getPreferredName());
42+
return new SnapshotRetentionConfiguration(expireAfter);
43+
});
44+
45+
static {
46+
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), EXPIRE_AFTER);
47+
}
48+
49+
// TODO: add the rest of the configuration values
50+
private final TimeValue expireAfter;
51+
52+
public SnapshotRetentionConfiguration(TimeValue expireAfter) {
53+
this.expireAfter = expireAfter;
54+
}
55+
56+
public static SnapshotRetentionConfiguration parse(XContentParser parser, String name) {
57+
return PARSER.apply(parser, null);
58+
}
59+
60+
public TimeValue getExpireAfter() {
61+
return this.expireAfter;
62+
}
63+
64+
@Override
65+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
66+
builder.startObject();
67+
if (expireAfter != null) {
68+
builder.field(EXPIRE_AFTER.getPreferredName(), expireAfter.getStringRep());
69+
}
70+
builder.endObject();
71+
return builder;
72+
}
73+
74+
@Override
75+
public int hashCode() {
76+
return Objects.hash(expireAfter);
77+
}
78+
79+
@Override
80+
public boolean equals(Object obj) {
81+
if (obj == null) {
82+
return false;
83+
}
84+
if (obj.getClass() != getClass()) {
85+
return false;
86+
}
87+
SnapshotRetentionConfiguration other = (SnapshotRetentionConfiguration) obj;
88+
return Objects.equals(this.expireAfter, other.expireAfter);
89+
}
90+
91+
@Override
92+
public String toString() {
93+
return Strings.toString(this);
94+
}
95+
}

client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import org.elasticsearch.client.snapshotlifecycle.SnapshotInvocationRecord;
6464
import org.elasticsearch.client.snapshotlifecycle.SnapshotLifecyclePolicy;
6565
import org.elasticsearch.client.snapshotlifecycle.SnapshotLifecyclePolicyMetadata;
66+
import org.elasticsearch.client.snapshotlifecycle.SnapshotRetentionConfiguration;
6667
import org.elasticsearch.cluster.metadata.IndexMetaData;
6768
import org.elasticsearch.common.Strings;
6869
import org.elasticsearch.common.collect.ImmutableOpenMap;
@@ -773,8 +774,11 @@ public void testAddSnapshotLifecyclePolicy() throws Exception {
773774
// tag::slm-put-snapshot-lifecycle-policy
774775
Map<String, Object> config = new HashMap<>();
775776
config.put("indices", Collections.singletonList("idx"));
777+
SnapshotRetentionConfiguration retention =
778+
new SnapshotRetentionConfiguration(TimeValue.timeValueDays(30));
776779
SnapshotLifecyclePolicy policy = new SnapshotLifecyclePolicy(
777-
"policy_id", "name", "1 2 3 * * ?", "my_repository", config);
780+
"policy_id", "name", "1 2 3 * * ?",
781+
"my_repository", config, retention);
778782
PutSnapshotLifecyclePolicyRequest request =
779783
new PutSnapshotLifecyclePolicyRequest(policy);
780784
// end::slm-put-snapshot-lifecycle-policy

docs/reference/ilm/apis/slm-api.asciidoc

+9-5
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ PUT /_slm/policy/daily-snapshots
5959
"indices": ["data-*", "important"], <5>
6060
"ignore_unavailable": false,
6161
"include_global_state": false
62-
}
62+
},
63+
"retention": {}
6364
}
6465
--------------------------------------------------
6566
// CONSOLE
@@ -136,7 +137,8 @@ The output looks similar to the following:
136137
"indices": ["data-*", "important"],
137138
"ignore_unavailable": false,
138139
"include_global_state": false
139-
}
140+
},
141+
"retention": {}
140142
},
141143
"next_execution": "2019-04-24T01:30:00.000Z", <3>
142144
"next_execution_millis": 1556048160000
@@ -221,8 +223,9 @@ Which, in this case shows an error because the index did not exist:
221223
"indices": ["data-*", "important"],
222224
"ignore_unavailable": false,
223225
"include_global_state": false
224-
}
225-
},
226+
},
227+
"retention": {},
228+
}
226229
"last_failure": { <1>
227230
"snapshot_name": "daily-snap-2019.04.02-lohisb5ith2n8hxacaq3mw",
228231
"time_string": "2019-04-02T01:30:00.000Z",
@@ -304,7 +307,8 @@ Which now includes the successful snapshot information:
304307
"indices": ["data-*", "important"],
305308
"ignore_unavailable": true,
306309
"include_global_state": false
307-
}
310+
},
311+
"retention": {}
308312
},
309313
"last_success": { <2>
310314
"snapshot_name": "daily-snap-2019.04.24-tmtnyjtrsxkhbrrdcgg18a",

docs/reference/ilm/getting-started-slm.asciidoc

+4-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ PUT /_slm/policy/nightly-snapshots
9595
"repository": "my_repository", <3>
9696
"config": { <4>
9797
"indices": ["*"] <5>
98-
}
98+
},
99+
"retention": {}
99100
}
100101
--------------------------------------------------
101102
// CONSOLE
@@ -171,7 +172,8 @@ next time the policy will be executed.
171172
"repository": "my_repository",
172173
"config": {
173174
"indices": ["*"],
174-
}
175+
},
176+
"retention": {}
175177
},
176178
"last_success": { <1>
177179
"snapshot_name": "nightly-snap-2019.04.24-tmtnyjtrsxkhbrrdcgg18a", <2>

libs/core/src/main/java/org/elasticsearch/common/unit/TimeValue.java

+8
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ public static TimeValue timeValueHours(long hours) {
7171
return new TimeValue(hours, TimeUnit.HOURS);
7272
}
7373

74+
public static TimeValue timeValueDays(long days) {
75+
// 106751.9 days is Long.MAX_VALUE nanoseconds, so we cannot store 106752 days
76+
if (days > 106751) {
77+
throw new IllegalArgumentException("time value cannot store values greater than 106751 days");
78+
}
79+
return new TimeValue(days, TimeUnit.DAYS);
80+
}
81+
7482
/**
7583
* @return the unit used for the this time value, see {@link #duration()}
7684
*/

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

+18-4
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ public class SnapshotLifecyclePolicy extends AbstractDiffable<SnapshotLifecycleP
5151
private final String schedule;
5252
private final String repository;
5353
private final Map<String, Object> configuration;
54+
private final SnapshotRetentionConfiguration retentionPolicy;
5455

5556
private static final ParseField NAME = new ParseField("name");
5657
private static final ParseField SCHEDULE = new ParseField("schedule");
5758
private static final ParseField REPOSITORY = new ParseField("repository");
5859
private static final ParseField CONFIG = new ParseField("config");
60+
private static final ParseField RETENTION = new ParseField("retention");
5961
private static final IndexNameExpressionResolver.DateMathExpressionResolver DATE_MATH_RESOLVER =
6062
new IndexNameExpressionResolver.DateMathExpressionResolver();
6163
private static final String POLICY_ID_METADATA_FIELD = "policy";
@@ -69,23 +71,27 @@ public class SnapshotLifecyclePolicy extends AbstractDiffable<SnapshotLifecycleP
6971
String schedule = (String) a[1];
7072
String repo = (String) a[2];
7173
Map<String, Object> config = (Map<String, Object>) a[3];
72-
return new SnapshotLifecyclePolicy(id, name, schedule, repo, config);
74+
SnapshotRetentionConfiguration retention = (SnapshotRetentionConfiguration) a[4];
75+
return new SnapshotLifecyclePolicy(id, name, schedule, repo, config, retention);
7376
});
7477

7578
static {
7679
PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
7780
PARSER.declareString(ConstructingObjectParser.constructorArg(), SCHEDULE);
7881
PARSER.declareString(ConstructingObjectParser.constructorArg(), REPOSITORY);
7982
PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, c) -> p.map(), CONFIG);
83+
PARSER.declareObject(ConstructingObjectParser.constructorArg(), SnapshotRetentionConfiguration::parse, RETENTION);
8084
}
8185

8286
public SnapshotLifecyclePolicy(final String id, final String name, final String schedule,
83-
final String repository, Map<String, Object> configuration) {
87+
final String repository, Map<String, Object> configuration,
88+
SnapshotRetentionConfiguration retentionPolicy) {
8489
this.id = Objects.requireNonNull(id);
8590
this.name = name;
8691
this.schedule = schedule;
8792
this.repository = repository;
8893
this.configuration = configuration;
94+
this.retentionPolicy = retentionPolicy;
8995
}
9096

9197
public SnapshotLifecyclePolicy(StreamInput in) throws IOException {
@@ -94,6 +100,7 @@ public SnapshotLifecyclePolicy(StreamInput in) throws IOException {
94100
this.schedule = in.readString();
95101
this.repository = in.readString();
96102
this.configuration = in.readMap();
103+
this.retentionPolicy = new SnapshotRetentionConfiguration(in);
97104
}
98105

99106
public String getId() {
@@ -116,6 +123,10 @@ public Map<String, Object> getConfig() {
116123
return this.configuration;
117124
}
118125

126+
public SnapshotRetentionConfiguration getRetentionPolicy() {
127+
return this.retentionPolicy;
128+
}
129+
119130
public long calculateNextExecution() {
120131
final Cron schedule = new Cron(this.schedule);
121132
return schedule.getNextValidTimeAfter(System.currentTimeMillis());
@@ -257,6 +268,7 @@ public void writeTo(StreamOutput out) throws IOException {
257268
out.writeString(this.schedule);
258269
out.writeString(this.repository);
259270
out.writeMap(this.configuration);
271+
this.retentionPolicy.writeTo(out);
260272
}
261273

262274
@Override
@@ -266,13 +278,14 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
266278
builder.field(SCHEDULE.getPreferredName(), this.schedule);
267279
builder.field(REPOSITORY.getPreferredName(), this.repository);
268280
builder.field(CONFIG.getPreferredName(), this.configuration);
281+
builder.field(RETENTION.getPreferredName(), this.retentionPolicy);
269282
builder.endObject();
270283
return builder;
271284
}
272285

273286
@Override
274287
public int hashCode() {
275-
return Objects.hash(id, name, schedule, repository, configuration);
288+
return Objects.hash(id, name, schedule, repository, configuration, retentionPolicy);
276289
}
277290

278291
@Override
@@ -289,7 +302,8 @@ public boolean equals(Object obj) {
289302
Objects.equals(name, other.name) &&
290303
Objects.equals(schedule, other.schedule) &&
291304
Objects.equals(repository, other.repository) &&
292-
Objects.equals(configuration, other.configuration);
305+
Objects.equals(configuration, other.configuration) &&
306+
Objects.equals(retentionPolicy, other.retentionPolicy);
293307
}
294308

295309
@Override

0 commit comments

Comments
 (0)