Skip to content

Commit 51d7f0e

Browse files
authored
Record most recent snapshot policy success/failure (#40619)
Keeping a record of the results of the successes and failures will aid troubleshooting of policies and make users more confident that their snapshots are being taken as expected. This is the first step toward writing history in a more permanent fashion.
1 parent c0b2a70 commit 51d7f0e

File tree

12 files changed

+702
-69
lines changed

12 files changed

+702
-69
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.core.snapshotlifecycle;
8+
9+
import org.elasticsearch.cluster.AbstractDiffable;
10+
import org.elasticsearch.cluster.Diffable;
11+
import org.elasticsearch.common.ParseField;
12+
import org.elasticsearch.common.io.stream.StreamInput;
13+
import org.elasticsearch.common.io.stream.StreamOutput;
14+
import org.elasticsearch.common.io.stream.Writeable;
15+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
16+
import org.elasticsearch.common.xcontent.ToXContentObject;
17+
import org.elasticsearch.common.xcontent.XContentBuilder;
18+
import org.elasticsearch.common.xcontent.XContentParser;
19+
20+
import java.io.IOException;
21+
import java.util.Objects;
22+
23+
/**
24+
* Holds information about Snapshots kicked off by Snapshot Lifecycle Management in the cluster state, so that this information can be
25+
* presented to the user. This class is used for both successes and failures as the structure of the data is very similar.
26+
*/
27+
public class SnapshotInvocationRecord extends AbstractDiffable<SnapshotInvocationRecord>
28+
implements Writeable, ToXContentObject, Diffable<SnapshotInvocationRecord> {
29+
30+
static final ParseField SNAPSHOT_NAME = new ParseField("snapshot_name");
31+
static final ParseField TIMESTAMP = new ParseField("time");
32+
static final ParseField DETAILS = new ParseField("details");
33+
34+
private String snapshotName;
35+
private long timestamp;
36+
private String details;
37+
38+
public static final ConstructingObjectParser<SnapshotInvocationRecord, String> PARSER =
39+
new ConstructingObjectParser<>("snapshot_policy_invocation_record", true,
40+
a -> new SnapshotInvocationRecord((String) a[0], (long) a[1], (String) a[2]));
41+
42+
static {
43+
PARSER.declareString(ConstructingObjectParser.constructorArg(), SNAPSHOT_NAME);
44+
PARSER.declareLong(ConstructingObjectParser.constructorArg(), TIMESTAMP);
45+
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), DETAILS);
46+
}
47+
48+
public static SnapshotInvocationRecord parse(XContentParser parser, String name) {
49+
return PARSER.apply(parser, name);
50+
}
51+
52+
public SnapshotInvocationRecord(String snapshotName, long timestamp, String details) {
53+
this.snapshotName = Objects.requireNonNull(snapshotName, "snapshot name must be provided");
54+
this.timestamp = timestamp;
55+
this.details = details;
56+
}
57+
58+
public SnapshotInvocationRecord(StreamInput in) throws IOException {
59+
this.snapshotName = in.readString();
60+
this.timestamp = in.readVLong();
61+
this.details = in.readOptionalString();
62+
}
63+
64+
public String getSnapshotName() {
65+
return snapshotName;
66+
}
67+
68+
public long getTimestamp() {
69+
return timestamp;
70+
}
71+
72+
public String getDetails() {
73+
return details;
74+
}
75+
76+
@Override
77+
public void writeTo(StreamOutput out) throws IOException {
78+
out.writeString(snapshotName);
79+
out.writeVLong(timestamp);
80+
out.writeOptionalString(details);
81+
}
82+
83+
@Override
84+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
85+
builder.startObject();
86+
{
87+
builder.field(SNAPSHOT_NAME.getPreferredName(), snapshotName);
88+
builder.timeField(TIMESTAMP.getPreferredName(), "time_string", timestamp);
89+
if (Objects.nonNull(details)) {
90+
builder.field(DETAILS.getPreferredName(), details);
91+
}
92+
}
93+
builder.endObject();
94+
return builder;
95+
}
96+
97+
@Override
98+
public boolean equals(Object o) {
99+
if (this == o) return true;
100+
if (o == null || getClass() != o.getClass()) return false;
101+
SnapshotInvocationRecord that = (SnapshotInvocationRecord) o;
102+
return getTimestamp() == that.getTimestamp() &&
103+
Objects.equals(getSnapshotName(), that.getSnapshotName()) &&
104+
Objects.equals(getDetails(), that.getDetails());
105+
}
106+
107+
@Override
108+
public int hashCode() {
109+
return Objects.hash(getSnapshotName(), getTimestamp(), getDetails());
110+
}
111+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public class SnapshotLifecyclePolicy extends AbstractDiffable<SnapshotLifecycleP
7373

7474
public SnapshotLifecyclePolicy(final String id, final String name, final String schedule,
7575
final String repository, Map<String, Object> configuration) {
76-
this.id = id;
76+
this.id = Objects.requireNonNull(id);
7777
this.name = name;
7878
this.schedule = schedule;
7979
this.repository = repository;

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

+128-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import org.elasticsearch.cluster.AbstractDiffable;
1010
import org.elasticsearch.cluster.Diffable;
11+
import org.elasticsearch.common.Nullable;
1112
import org.elasticsearch.common.ParseField;
1213
import org.elasticsearch.common.io.stream.StreamInput;
1314
import org.elasticsearch.common.io.stream.StreamOutput;
@@ -21,8 +22,10 @@
2122
import java.time.Instant;
2223
import java.time.ZoneOffset;
2324
import java.time.ZonedDateTime;
25+
import java.util.HashMap;
2426
import java.util.Map;
2527
import java.util.Objects;
28+
import java.util.Optional;
2629

2730
/**
2831
* {@code SnapshotLifecyclePolicyMetadata} encapsulates a {@link SnapshotLifecyclePolicy} as well as
@@ -37,18 +40,34 @@ public class SnapshotLifecyclePolicyMetadata extends AbstractDiffable<SnapshotLi
3740
static final ParseField VERSION = new ParseField("version");
3841
static final ParseField MODIFIED_DATE = new ParseField("modified_date");
3942
static final ParseField MODIFIED_DATE_STRING = new ParseField("modified_date_string");
43+
static final ParseField LAST_SUCCESS = new ParseField("last_success");
44+
static final ParseField LAST_FAILURE = new ParseField("last_failure");
4045

4146
private final SnapshotLifecyclePolicy policy;
4247
private final Map<String, String> headers;
4348
private final long version;
4449
private final long modifiedDate;
50+
@Nullable
51+
private final SnapshotInvocationRecord lastSuccess;
52+
@Nullable
53+
private final SnapshotInvocationRecord lastFailure;
4554

4655
@SuppressWarnings("unchecked")
4756
public static final ConstructingObjectParser<SnapshotLifecyclePolicyMetadata, String> PARSER =
4857
new ConstructingObjectParser<>("snapshot_policy_metadata",
4958
a -> {
5059
SnapshotLifecyclePolicy policy = (SnapshotLifecyclePolicy) a[0];
51-
return new SnapshotLifecyclePolicyMetadata(policy, (Map<String, String>) a[1], (long) a[2], (long) a[3]);
60+
SnapshotInvocationRecord lastSuccess = (SnapshotInvocationRecord) a[5];
61+
SnapshotInvocationRecord lastFailure = (SnapshotInvocationRecord) a[6];
62+
63+
return builder()
64+
.setPolicy(policy)
65+
.setHeaders((Map<String, String>) a[1])
66+
.setVersion((long) a[2])
67+
.setModifiedDate((long) a[3])
68+
.setLastSuccess(lastSuccess)
69+
.setLastFailure(lastFailure)
70+
.build();
5271
});
5372

5473
static {
@@ -57,17 +76,22 @@ public class SnapshotLifecyclePolicyMetadata extends AbstractDiffable<SnapshotLi
5776
PARSER.declareLong(ConstructingObjectParser.constructorArg(), VERSION);
5877
PARSER.declareLong(ConstructingObjectParser.constructorArg(), MODIFIED_DATE);
5978
PARSER.declareString(ConstructingObjectParser.constructorArg(), MODIFIED_DATE_STRING);
79+
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), SnapshotInvocationRecord::parse, LAST_SUCCESS);
80+
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), SnapshotInvocationRecord::parse, LAST_FAILURE);
6081
}
6182

6283
public static SnapshotLifecyclePolicyMetadata parse(XContentParser parser, String name) {
6384
return PARSER.apply(parser, name);
6485
}
6586

66-
public SnapshotLifecyclePolicyMetadata(SnapshotLifecyclePolicy policy, Map<String, String> headers, long version, long modifiedDate) {
87+
SnapshotLifecyclePolicyMetadata(SnapshotLifecyclePolicy policy, Map<String, String> headers, long version, long modifiedDate,
88+
SnapshotInvocationRecord lastSuccess, SnapshotInvocationRecord lastFailure) {
6789
this.policy = policy;
6890
this.headers = headers;
6991
this.version = version;
7092
this.modifiedDate = modifiedDate;
93+
this.lastSuccess = lastSuccess;
94+
this.lastFailure = lastFailure;
7195
}
7296

7397
@SuppressWarnings("unchecked")
@@ -76,6 +100,8 @@ public SnapshotLifecyclePolicyMetadata(SnapshotLifecyclePolicy policy, Map<Strin
76100
this.headers = (Map<String, String>) in.readGenericValue();
77101
this.version = in.readVLong();
78102
this.modifiedDate = in.readVLong();
103+
this.lastSuccess = in.readOptionalWriteable(SnapshotInvocationRecord::new);
104+
this.lastFailure = in.readOptionalWriteable(SnapshotInvocationRecord::new);
79105
}
80106

81107
@Override
@@ -84,6 +110,25 @@ public void writeTo(StreamOutput out) throws IOException {
84110
out.writeGenericValue(this.headers);
85111
out.writeVLong(this.version);
86112
out.writeVLong(this.modifiedDate);
113+
out.writeOptionalWriteable(this.lastSuccess);
114+
out.writeOptionalWriteable(this.lastFailure);
115+
}
116+
117+
public static Builder builder() {
118+
return new Builder();
119+
}
120+
121+
public static Builder builder(SnapshotLifecyclePolicyMetadata metadata) {
122+
if (metadata == null) {
123+
return builder();
124+
}
125+
return new Builder()
126+
.setHeaders(metadata.getHeaders())
127+
.setPolicy(metadata.getPolicy())
128+
.setVersion(metadata.getVersion())
129+
.setModifiedDate(metadata.getModifiedDate())
130+
.setLastSuccess(metadata.getLastSuccess())
131+
.setLastFailure(metadata.getLastFailure());
87132
}
88133

89134
public Map<String, String> getHeaders() {
@@ -106,9 +151,24 @@ public long getModifiedDate() {
106151
return modifiedDate;
107152
}
108153

154+
private String dateToDateString(Long date) {
155+
if (Objects.isNull(date)) {
156+
return null;
157+
}
158+
ZonedDateTime dateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(date), ZoneOffset.UTC);
159+
return dateTime.toString();
160+
}
161+
109162
public String getModifiedDateString() {
110-
ZonedDateTime modifiedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(modifiedDate), ZoneOffset.UTC);
111-
return modifiedDateTime.toString();
163+
return dateToDateString(modifiedDate);
164+
}
165+
166+
public SnapshotInvocationRecord getLastSuccess() {
167+
return lastSuccess;
168+
}
169+
170+
public SnapshotInvocationRecord getLastFailure() {
171+
return lastFailure;
112172
}
113173

114174
@Override
@@ -119,13 +179,19 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
119179
builder.field(VERSION.getPreferredName(), version);
120180
builder.field(MODIFIED_DATE.getPreferredName(), modifiedDate);
121181
builder.field(MODIFIED_DATE_STRING.getPreferredName(), getModifiedDateString());
182+
if (Objects.nonNull(lastSuccess)) {
183+
builder.field(LAST_SUCCESS.getPreferredName(), lastSuccess);
184+
}
185+
if (Objects.nonNull(lastFailure)) {
186+
builder.field(LAST_FAILURE.getPreferredName(), lastFailure);
187+
}
122188
builder.endObject();
123189
return builder;
124190
}
125191

126192
@Override
127193
public int hashCode() {
128-
return Objects.hash(policy, headers, version, modifiedDate);
194+
return Objects.hash(policy, headers, version, modifiedDate, lastSuccess, lastFailure);
129195
}
130196

131197
@Override
@@ -140,7 +206,9 @@ public boolean equals(Object obj) {
140206
return Objects.equals(policy, other.policy) &&
141207
Objects.equals(headers, other.headers) &&
142208
Objects.equals(version, other.version) &&
143-
Objects.equals(modifiedDate, other.modifiedDate);
209+
Objects.equals(modifiedDate, other.modifiedDate) &&
210+
Objects.equals(lastSuccess, other.lastSuccess) &&
211+
Objects.equals(lastFailure, other.lastFailure);
144212
}
145213

146214
@Override
@@ -150,4 +218,58 @@ public String toString() {
150218
// should not emit them in case it accidentally gets logged.
151219
return super.toString();
152220
}
221+
222+
public static class Builder {
223+
224+
private Builder() {
225+
}
226+
227+
private SnapshotLifecyclePolicy policy;
228+
private Map<String, String> headers;
229+
private long version = 1L;
230+
private Long modifiedDate;
231+
private SnapshotInvocationRecord lastSuccessDate;
232+
private SnapshotInvocationRecord lastFailureDate;
233+
234+
public Builder setPolicy(SnapshotLifecyclePolicy policy) {
235+
this.policy = policy;
236+
return this;
237+
}
238+
239+
public Builder setHeaders(Map<String, String> headers) {
240+
this.headers = headers;
241+
return this;
242+
}
243+
244+
public Builder setVersion(long version) {
245+
this.version = version;
246+
return this;
247+
}
248+
249+
public Builder setModifiedDate(long modifiedDate) {
250+
this.modifiedDate = modifiedDate;
251+
return this;
252+
}
253+
254+
public Builder setLastSuccess(SnapshotInvocationRecord lastSuccessDate) {
255+
this.lastSuccessDate = lastSuccessDate;
256+
return this;
257+
}
258+
259+
public Builder setLastFailure(SnapshotInvocationRecord lastFailureDate) {
260+
this.lastFailureDate = lastFailureDate;
261+
return this;
262+
}
263+
264+
public SnapshotLifecyclePolicyMetadata build() {
265+
return new SnapshotLifecyclePolicyMetadata(
266+
Objects.requireNonNull(policy),
267+
Optional.ofNullable(headers).orElse(new HashMap<>()),
268+
version,
269+
Objects.requireNonNull(modifiedDate, "modifiedDate must be set"),
270+
lastSuccessDate,
271+
lastFailureDate);
272+
}
273+
}
274+
153275
}

0 commit comments

Comments
 (0)