+ * for more.
+ * @param request the request
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @return the response
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public GetSnapshotLifecyclePolicyResponse getSnapshotLifecyclePolicy(GetSnapshotLifecyclePolicyRequest request,
+ RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request, IndexLifecycleRequestConverters::getSnapshotLifecyclePolicy,
+ options, GetSnapshotLifecyclePolicyResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously retrieve one or more snapshot lifecycle policy definition.
+ * See
+ * for more.
+ * @param request the request
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @param listener the listener to be notified upon request completion
+ */
+ public void getSnapshotLifecyclePolicyAsync(GetSnapshotLifecyclePolicyRequest request, RequestOptions options,
+ ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(request, IndexLifecycleRequestConverters::getSnapshotLifecyclePolicy,
+ options, GetSnapshotLifecyclePolicyResponse::fromXContent, listener, emptySet());
+ }
+
+ /**
+ * Create or modify a snapshot lifecycle definition.
+ * See
+ * for more.
+ * @param request the request
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @return the response
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public AcknowledgedResponse putSnapshotLifecyclePolicy(PutSnapshotLifecyclePolicyRequest request,
+ RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request, IndexLifecycleRequestConverters::putSnapshotLifecyclePolicy,
+ options, AcknowledgedResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously create or modify a snapshot lifecycle definition.
+ * See
+ * for more.
+ * @param request the request
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @return the response
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public AcknowledgedResponse deleteSnapshotLifecyclePolicy(DeleteSnapshotLifecyclePolicyRequest request,
+ RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request, IndexLifecycleRequestConverters::deleteSnapshotLifecyclePolicy,
+ options, AcknowledgedResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously delete a snapshot lifecycle definition
+ * See
+ * for more.
+ * @param request the request
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @return the response
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public ExecuteSnapshotLifecyclePolicyResponse executeSnapshotLifecyclePolicy(ExecuteSnapshotLifecyclePolicyRequest request,
+ RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request, IndexLifecycleRequestConverters::executeSnapshotLifecyclePolicy,
+ options, ExecuteSnapshotLifecyclePolicyResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously execute a snapshot lifecycle definition
+ * See
+ * for more.
+ * @param request the request
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @param listener the listener to be notified upon request completion
+ */
+ public void executeSnapshotLifecyclePolicyAsync(ExecuteSnapshotLifecyclePolicyRequest request, RequestOptions options,
+ ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(request, IndexLifecycleRequestConverters::executeSnapshotLifecyclePolicy,
+ options, ExecuteSnapshotLifecyclePolicyResponse::fromXContent, listener, emptySet());
+ }
}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndexLifecycleRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndexLifecycleRequestConverters.java
index f39f2b36cebc0..f1d90adca1b6b 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndexLifecycleRequestConverters.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndexLifecycleRequestConverters.java
@@ -32,6 +32,10 @@
import org.elasticsearch.client.indexlifecycle.RetryLifecyclePolicyRequest;
import org.elasticsearch.client.indexlifecycle.StartILMRequest;
import org.elasticsearch.client.indexlifecycle.StopILMRequest;
+import org.elasticsearch.client.snapshotlifecycle.DeleteSnapshotLifecyclePolicyRequest;
+import org.elasticsearch.client.snapshotlifecycle.ExecuteSnapshotLifecyclePolicyRequest;
+import org.elasticsearch.client.snapshotlifecycle.GetSnapshotLifecyclePolicyRequest;
+import org.elasticsearch.client.snapshotlifecycle.PutSnapshotLifecyclePolicyRequest;
import org.elasticsearch.common.Strings;
import java.io.IOException;
@@ -159,4 +163,56 @@ static Request retryLifecycle(RetryLifecyclePolicyRequest retryLifecyclePolicyRe
request.addParameters(params.asMap());
return request;
}
+
+ static Request getSnapshotLifecyclePolicy(GetSnapshotLifecyclePolicyRequest getSnapshotLifecyclePolicyRequest) {
+ String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_slm/policy")
+ .addCommaSeparatedPathParts(getSnapshotLifecyclePolicyRequest.getPolicyIds()).build();
+ Request request = new Request(HttpGet.METHOD_NAME, endpoint);
+ RequestConverters.Params params = new RequestConverters.Params();
+ params.withMasterTimeout(getSnapshotLifecyclePolicyRequest.masterNodeTimeout());
+ params.withTimeout(getSnapshotLifecyclePolicyRequest.timeout());
+ request.addParameters(params.asMap());
+ return request;
+ }
+
+ static Request putSnapshotLifecyclePolicy(PutSnapshotLifecyclePolicyRequest putSnapshotLifecyclePolicyRequest) throws IOException {
+ String endpoint = new RequestConverters.EndpointBuilder()
+ .addPathPartAsIs("_slm/policy")
+ .addPathPartAsIs(putSnapshotLifecyclePolicyRequest.getPolicy().getId())
+ .build();
+ Request request = new Request(HttpPut.METHOD_NAME, endpoint);
+ RequestConverters.Params params = new RequestConverters.Params();
+ params.withMasterTimeout(putSnapshotLifecyclePolicyRequest.masterNodeTimeout());
+ params.withTimeout(putSnapshotLifecyclePolicyRequest.timeout());
+ request.addParameters(params.asMap());
+ request.setEntity(RequestConverters.createEntity(putSnapshotLifecyclePolicyRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
+ return request;
+ }
+
+ static Request deleteSnapshotLifecyclePolicy(DeleteSnapshotLifecyclePolicyRequest deleteSnapshotLifecyclePolicyRequest) {
+ Request request = new Request(HttpDelete.METHOD_NAME,
+ new RequestConverters.EndpointBuilder()
+ .addPathPartAsIs("_slm/policy")
+ .addPathPartAsIs(deleteSnapshotLifecyclePolicyRequest.getPolicyId())
+ .build());
+ RequestConverters.Params params = new RequestConverters.Params();
+ params.withMasterTimeout(deleteSnapshotLifecyclePolicyRequest.masterNodeTimeout());
+ params.withTimeout(deleteSnapshotLifecyclePolicyRequest.timeout());
+ request.addParameters(params.asMap());
+ return request;
+ }
+
+ static Request executeSnapshotLifecyclePolicy(ExecuteSnapshotLifecyclePolicyRequest executeSnapshotLifecyclePolicyRequest) {
+ Request request = new Request(HttpPut.METHOD_NAME,
+ new RequestConverters.EndpointBuilder()
+ .addPathPartAsIs("_slm/policy")
+ .addPathPartAsIs(executeSnapshotLifecyclePolicyRequest.getPolicyId())
+ .addPathPartAsIs("_execute")
+ .build());
+ RequestConverters.Params params = new RequestConverters.Params();
+ params.withMasterTimeout(executeSnapshotLifecyclePolicyRequest.masterNodeTimeout());
+ params.withTimeout(executeSnapshotLifecyclePolicyRequest.timeout());
+ request.addParameters(params.asMap());
+ return request;
+ }
}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/DeleteSnapshotLifecyclePolicyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/DeleteSnapshotLifecyclePolicyRequest.java
new file mode 100644
index 0000000000000..712151def4a50
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/DeleteSnapshotLifecyclePolicyRequest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.snapshotlifecycle;
+
+import org.elasticsearch.client.TimedRequest;
+
+import java.util.Objects;
+
+public class DeleteSnapshotLifecyclePolicyRequest extends TimedRequest {
+ private final String policyId;
+
+ public DeleteSnapshotLifecyclePolicyRequest(String policyId) {
+ this.policyId = policyId;
+ }
+
+ public String getPolicyId() {
+ return this.policyId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DeleteSnapshotLifecyclePolicyRequest other = (DeleteSnapshotLifecyclePolicyRequest) o;
+ return this.policyId.equals(other.policyId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.policyId);
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/ExecuteSnapshotLifecyclePolicyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/ExecuteSnapshotLifecyclePolicyRequest.java
new file mode 100644
index 0000000000000..3c32de6837405
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/ExecuteSnapshotLifecyclePolicyRequest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.snapshotlifecycle;
+
+import org.elasticsearch.client.TimedRequest;
+
+import java.util.Objects;
+
+public class ExecuteSnapshotLifecyclePolicyRequest extends TimedRequest {
+ private final String policyId;
+
+ public ExecuteSnapshotLifecyclePolicyRequest(String policyId) {
+ this.policyId = policyId;
+ }
+
+ public String getPolicyId() {
+ return this.policyId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ExecuteSnapshotLifecyclePolicyRequest other = (ExecuteSnapshotLifecyclePolicyRequest) o;
+ return this.policyId.equals(other.policyId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.policyId);
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/ExecuteSnapshotLifecyclePolicyResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/ExecuteSnapshotLifecyclePolicyResponse.java
new file mode 100644
index 0000000000000..b5698d715625b
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/ExecuteSnapshotLifecyclePolicyResponse.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.snapshotlifecycle;
+
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+
+public class ExecuteSnapshotLifecyclePolicyResponse implements ToXContentObject {
+
+ private static final ParseField SNAPSHOT_NAME = new ParseField("snapshot_name");
+ private static final ConstructingObjectParser PARSER =
+ new ConstructingObjectParser<>("excecute_snapshot_policy", true,
+ a -> new ExecuteSnapshotLifecyclePolicyResponse((String) a[0]));
+
+ static {
+ PARSER.declareString(ConstructingObjectParser.constructorArg(), SNAPSHOT_NAME);
+ }
+
+ private final String snapshotName;
+
+ public ExecuteSnapshotLifecyclePolicyResponse(String snapshotName) {
+ this.snapshotName = snapshotName;
+ }
+
+ public static ExecuteSnapshotLifecyclePolicyResponse fromXContent(XContentParser parser) {
+ return PARSER.apply(parser, null);
+ }
+
+ public String getSnapshotName() {
+ return this.snapshotName;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(SNAPSHOT_NAME.getPreferredName(), snapshotName);
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ExecuteSnapshotLifecyclePolicyResponse other = (ExecuteSnapshotLifecyclePolicyResponse) o;
+ return this.snapshotName.equals(other.snapshotName);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.snapshotName.hashCode();
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/GetSnapshotLifecyclePolicyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/GetSnapshotLifecyclePolicyRequest.java
new file mode 100644
index 0000000000000..c754cc8878d29
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/GetSnapshotLifecyclePolicyRequest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.snapshotlifecycle;
+
+import org.elasticsearch.client.TimedRequest;
+
+import java.util.Arrays;
+
+public class GetSnapshotLifecyclePolicyRequest extends TimedRequest {
+ private final String[] policyIds;
+
+ public GetSnapshotLifecyclePolicyRequest(String... ids) {
+ this.policyIds = ids;
+ }
+
+ public String[] getPolicyIds() {
+ return this.policyIds;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ GetSnapshotLifecyclePolicyRequest other = (GetSnapshotLifecyclePolicyRequest) o;
+ return Arrays.equals(this.policyIds, other.policyIds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(this.policyIds);
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/GetSnapshotLifecyclePolicyResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/GetSnapshotLifecyclePolicyResponse.java
new file mode 100644
index 0000000000000..68700bbb34bc4
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/GetSnapshotLifecyclePolicyResponse.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.snapshotlifecycle;
+
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+
+public class GetSnapshotLifecyclePolicyResponse implements ToXContentObject {
+
+ private final Map policies;
+
+ public GetSnapshotLifecyclePolicyResponse(Map policies) {
+ this.policies = policies;
+ }
+
+ public Map getPolicies() {
+ return this.policies;
+ }
+
+ public static GetSnapshotLifecyclePolicyResponse fromXContent(XContentParser parser) throws IOException {
+ if (parser.currentToken() == null) {
+ parser.nextToken();
+ }
+ ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation);
+ parser.nextToken();
+
+ Map policies = new HashMap<>();
+ while (parser.isClosed() == false) {
+ if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
+ final String policyId = parser.currentName();
+ SnapshotLifecyclePolicyMetadata policyDefinition = SnapshotLifecyclePolicyMetadata.parse(parser, policyId);
+ policies.put(policyId, policyDefinition);
+ } else {
+ parser.nextToken();
+ }
+ }
+ return new GetSnapshotLifecyclePolicyResponse(policies);
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ return builder;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ GetSnapshotLifecyclePolicyResponse other = (GetSnapshotLifecyclePolicyResponse) o;
+ return Objects.equals(this.policies, other.policies);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.policies);
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/PutSnapshotLifecyclePolicyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/PutSnapshotLifecyclePolicyRequest.java
new file mode 100644
index 0000000000000..7fb5794aee869
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/PutSnapshotLifecyclePolicyRequest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.snapshotlifecycle;
+
+import org.elasticsearch.client.TimedRequest;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+import java.util.Objects;
+
+public class PutSnapshotLifecyclePolicyRequest extends TimedRequest implements ToXContentObject {
+
+ private final SnapshotLifecyclePolicy policy;
+
+ public PutSnapshotLifecyclePolicyRequest(SnapshotLifecyclePolicy policy) {
+ this.policy = Objects.requireNonNull(policy, "policy definition cannot be null");
+ }
+
+ public SnapshotLifecyclePolicy getPolicy() {
+ return policy;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ policy.toXContent(builder, params);
+ return builder;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PutSnapshotLifecyclePolicyRequest other = (PutSnapshotLifecyclePolicyRequest) o;
+ return Objects.equals(this.policy, other.policy);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.policy);
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/SnapshotInvocationRecord.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/SnapshotInvocationRecord.java
new file mode 100644
index 0000000000000..ce5a7803c14e6
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/SnapshotInvocationRecord.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.snapshotlifecycle;
+
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Objects;
+
+public class SnapshotInvocationRecord implements ToXContentObject {
+ static final ParseField SNAPSHOT_NAME = new ParseField("snapshot_name");
+ static final ParseField TIMESTAMP = new ParseField("time");
+ static final ParseField DETAILS = new ParseField("details");
+
+ private String snapshotName;
+ private long timestamp;
+ private String details;
+
+ public static final ConstructingObjectParser PARSER =
+ new ConstructingObjectParser<>("snapshot_policy_invocation_record", true,
+ a -> new SnapshotInvocationRecord((String) a[0], (long) a[1], (String) a[2]));
+
+ static {
+ PARSER.declareString(ConstructingObjectParser.constructorArg(), SNAPSHOT_NAME);
+ PARSER.declareLong(ConstructingObjectParser.constructorArg(), TIMESTAMP);
+ PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), DETAILS);
+ }
+
+ public static SnapshotInvocationRecord parse(XContentParser parser, String name) {
+ return PARSER.apply(parser, name);
+ }
+
+ public SnapshotInvocationRecord(String snapshotName, long timestamp, String details) {
+ this.snapshotName = Objects.requireNonNull(snapshotName, "snapshot name must be provided");
+ this.timestamp = timestamp;
+ this.details = details;
+ }
+
+ public String getSnapshotName() {
+ return snapshotName;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public String getDetails() {
+ return details;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ {
+ builder.field(SNAPSHOT_NAME.getPreferredName(), snapshotName);
+ builder.timeField(TIMESTAMP.getPreferredName(), "time_string", timestamp);
+ if (Objects.nonNull(details)) {
+ builder.field(DETAILS.getPreferredName(), details);
+ }
+ }
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SnapshotInvocationRecord that = (SnapshotInvocationRecord) o;
+ return getTimestamp() == that.getTimestamp() &&
+ Objects.equals(getSnapshotName(), that.getSnapshotName()) &&
+ Objects.equals(getDetails(), that.getDetails());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getSnapshotName(), getTimestamp(), getDetails());
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/SnapshotLifecyclePolicy.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/SnapshotLifecyclePolicy.java
new file mode 100644
index 0000000000000..8d8e78184ff59
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/SnapshotLifecyclePolicy.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.snapshotlifecycle;
+
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+public class SnapshotLifecyclePolicy implements ToXContentObject {
+
+ private final String id;
+ private final String name;
+ private final String schedule;
+ private final String repository;
+ private final Map configuration;
+
+ private static final ParseField NAME = new ParseField("name");
+ private static final ParseField SCHEDULE = new ParseField("schedule");
+ private static final ParseField REPOSITORY = new ParseField("repository");
+ private static final ParseField CONFIG = new ParseField("config");
+ private static final IndexNameExpressionResolver.DateMathExpressionResolver DATE_MATH_RESOLVER =
+ new IndexNameExpressionResolver.DateMathExpressionResolver();
+
+ @SuppressWarnings("unchecked")
+ private static final ConstructingObjectParser PARSER =
+ new ConstructingObjectParser<>("snapshot_lifecycle", true,
+ (a, id) -> {
+ String name = (String) a[0];
+ String schedule = (String) a[1];
+ String repo = (String) a[2];
+ Map config = (Map) a[3];
+ return new SnapshotLifecyclePolicy(id, name, schedule, repo, config);
+ });
+
+ static {
+ PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
+ PARSER.declareString(ConstructingObjectParser.constructorArg(), SCHEDULE);
+ PARSER.declareString(ConstructingObjectParser.constructorArg(), REPOSITORY);
+ PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, c) -> p.map(), CONFIG);
+ }
+
+ public SnapshotLifecyclePolicy(final String id, final String name, final String schedule,
+ final String repository, Map configuration) {
+ this.id = Objects.requireNonNull(id);
+ this.name = name;
+ this.schedule = schedule;
+ this.repository = repository;
+ this.configuration = configuration;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getSchedule() {
+ return this.schedule;
+ }
+
+ public String getRepository() {
+ return this.repository;
+ }
+
+ public Map getConfig() {
+ return this.configuration;
+ }
+
+ public static SnapshotLifecyclePolicy parse(XContentParser parser, String id) {
+ return PARSER.apply(parser, id);
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(NAME.getPreferredName(), this.name);
+ builder.field(SCHEDULE.getPreferredName(), this.schedule);
+ builder.field(REPOSITORY.getPreferredName(), this.repository);
+ builder.field(CONFIG.getPreferredName(), this.configuration);
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, name, schedule, repository, configuration);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj.getClass() != getClass()) {
+ return false;
+ }
+ SnapshotLifecyclePolicy other = (SnapshotLifecyclePolicy) obj;
+ return Objects.equals(id, other.id) &&
+ Objects.equals(name, other.name) &&
+ Objects.equals(schedule, other.schedule) &&
+ Objects.equals(repository, other.repository) &&
+ Objects.equals(configuration, other.configuration);
+ }
+
+ @Override
+ public String toString() {
+ return Strings.toString(this);
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/SnapshotLifecyclePolicyMetadata.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/SnapshotLifecyclePolicyMetadata.java
new file mode 100644
index 0000000000000..dc68b2fc6e14e
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/snapshotlifecycle/SnapshotLifecyclePolicyMetadata.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.snapshotlifecycle;
+
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Objects;
+
+public class SnapshotLifecyclePolicyMetadata implements ToXContentObject {
+
+ static final ParseField POLICY = new ParseField("policy");
+ static final ParseField VERSION = new ParseField("version");
+ static final ParseField MODIFIED_DATE_MILLIS = new ParseField("modified_date_millis");
+ static final ParseField MODIFIED_DATE = new ParseField("modified_date");
+ static final ParseField LAST_SUCCESS = new ParseField("last_success");
+ static final ParseField LAST_FAILURE = new ParseField("last_failure");
+ static final ParseField NEXT_EXECUTION_MILLIS = new ParseField("next_execution_millis");
+ static final ParseField NEXT_EXECUTION = new ParseField("next_execution");
+
+ private final SnapshotLifecyclePolicy policy;
+ private final long version;
+ private final long modifiedDate;
+ private final long nextExecution;
+ @Nullable
+ private final SnapshotInvocationRecord lastSuccess;
+ @Nullable
+ private final SnapshotInvocationRecord lastFailure;
+
+ @SuppressWarnings("unchecked")
+ public static final ConstructingObjectParser PARSER =
+ new ConstructingObjectParser<>("snapshot_policy_metadata",
+ a -> {
+ SnapshotLifecyclePolicy policy = (SnapshotLifecyclePolicy) a[0];
+ long version = (long) a[1];
+ long modifiedDate = (long) a[2];
+ SnapshotInvocationRecord lastSuccess = (SnapshotInvocationRecord) a[3];
+ SnapshotInvocationRecord lastFailure = (SnapshotInvocationRecord) a[4];
+ long nextExecution = (long) a[5];
+
+ return new SnapshotLifecyclePolicyMetadata(policy, version, modifiedDate, lastSuccess, lastFailure, nextExecution);
+ });
+
+ static {
+ PARSER.declareObject(ConstructingObjectParser.constructorArg(), SnapshotLifecyclePolicy::parse, POLICY);
+ PARSER.declareLong(ConstructingObjectParser.constructorArg(), VERSION);
+ PARSER.declareLong(ConstructingObjectParser.constructorArg(), MODIFIED_DATE_MILLIS);
+ PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), SnapshotInvocationRecord::parse, LAST_SUCCESS);
+ PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), SnapshotInvocationRecord::parse, LAST_FAILURE);
+ PARSER.declareLong(ConstructingObjectParser.constructorArg(), NEXT_EXECUTION_MILLIS);
+ }
+
+ public static SnapshotLifecyclePolicyMetadata parse(XContentParser parser, String id) {
+ return PARSER.apply(parser, id);
+ }
+
+ public SnapshotLifecyclePolicyMetadata(SnapshotLifecyclePolicy policy, long version, long modifiedDate,
+ SnapshotInvocationRecord lastSuccess, SnapshotInvocationRecord lastFailure,
+ long nextExecution) {
+ this.policy = policy;
+ this.version = version;
+ this.modifiedDate = modifiedDate;
+ this.lastSuccess = lastSuccess;
+ this.lastFailure = lastFailure;
+ this.nextExecution = nextExecution;
+ }
+
+ public SnapshotLifecyclePolicy getPolicy() {
+ return policy;
+ }
+
+ public String getName() {
+ return policy.getName();
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ public long getModifiedDate() {
+ return modifiedDate;
+ }
+
+ public SnapshotInvocationRecord getLastSuccess() {
+ return lastSuccess;
+ }
+
+ public SnapshotInvocationRecord getLastFailure() {
+ return lastFailure;
+ }
+
+ public long getNextExecution() {
+ return this.nextExecution;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(POLICY.getPreferredName(), policy);
+ builder.field(VERSION.getPreferredName(), version);
+ builder.timeField(MODIFIED_DATE_MILLIS.getPreferredName(), MODIFIED_DATE.getPreferredName(), modifiedDate);
+ if (Objects.nonNull(lastSuccess)) {
+ builder.field(LAST_SUCCESS.getPreferredName(), lastSuccess);
+ }
+ if (Objects.nonNull(lastFailure)) {
+ builder.field(LAST_FAILURE.getPreferredName(), lastFailure);
+ }
+ builder.timeField(NEXT_EXECUTION_MILLIS.getPreferredName(), NEXT_EXECUTION.getPreferredName(), nextExecution);
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(policy, version, modifiedDate, lastSuccess, lastFailure, nextExecution);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SnapshotLifecyclePolicyMetadata other = (SnapshotLifecyclePolicyMetadata) obj;
+ return Objects.equals(policy, other.policy) &&
+ Objects.equals(version, other.version) &&
+ Objects.equals(modifiedDate, other.modifiedDate) &&
+ Objects.equals(lastSuccess, other.lastSuccess) &&
+ Objects.equals(lastFailure, other.lastFailure) &&
+ Objects.equals(nextExecution, other.nextExecution);
+ }
+
+}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java
index db9df0ac24c78..706a594a9aa33 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java
@@ -22,6 +22,9 @@
import org.apache.http.util.EntityUtils;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.LatchedActionListener;
+import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
+import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
+import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
import org.elasticsearch.client.RequestOptions;
@@ -51,6 +54,15 @@
import org.elasticsearch.client.indexlifecycle.StartILMRequest;
import org.elasticsearch.client.indexlifecycle.StopILMRequest;
import org.elasticsearch.client.indices.CreateIndexRequest;
+import org.elasticsearch.client.snapshotlifecycle.DeleteSnapshotLifecyclePolicyRequest;
+import org.elasticsearch.client.snapshotlifecycle.ExecuteSnapshotLifecyclePolicyRequest;
+import org.elasticsearch.client.snapshotlifecycle.ExecuteSnapshotLifecyclePolicyResponse;
+import org.elasticsearch.client.snapshotlifecycle.GetSnapshotLifecyclePolicyRequest;
+import org.elasticsearch.client.snapshotlifecycle.GetSnapshotLifecyclePolicyResponse;
+import org.elasticsearch.client.snapshotlifecycle.PutSnapshotLifecyclePolicyRequest;
+import org.elasticsearch.client.snapshotlifecycle.SnapshotInvocationRecord;
+import org.elasticsearch.client.snapshotlifecycle.SnapshotLifecyclePolicy;
+import org.elasticsearch.client.snapshotlifecycle.SnapshotLifecyclePolicyMetadata;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableOpenMap;
@@ -60,6 +72,9 @@
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.json.JsonXContent;
+import org.elasticsearch.repositories.fs.FsRepository;
+import org.elasticsearch.snapshots.SnapshotInfo;
+import org.elasticsearch.snapshots.SnapshotState;
import org.hamcrest.Matchers;
import java.io.IOException;
@@ -68,6 +83,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -740,6 +756,237 @@ public void onFailure(Exception e) {
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
+ public void testAddSnapshotLifecyclePolicy() throws Exception {
+ RestHighLevelClient client = highLevelClient();
+
+ PutRepositoryRequest repoRequest = new PutRepositoryRequest();
+
+ Settings.Builder settingsBuilder = Settings.builder().put("location", ".");
+ repoRequest.settings(settingsBuilder);
+ repoRequest.name("my_repository");
+ repoRequest.type(FsRepository.TYPE);
+ org.elasticsearch.action.support.master.AcknowledgedResponse response =
+ client.snapshot().createRepository(repoRequest, RequestOptions.DEFAULT);
+ assertTrue(response.isAcknowledged());
+
+ //////// PUT
+ // tag::slm-put-snapshot-lifecycle-policy
+ Map config = new HashMap<>();
+ config.put("indices", Collections.singletonList("idx"));
+ SnapshotLifecyclePolicy policy = new SnapshotLifecyclePolicy(
+ "policy_id", "name", "1 2 3 * * ?", "my_repository", config);
+ PutSnapshotLifecyclePolicyRequest request =
+ new PutSnapshotLifecyclePolicyRequest(policy);
+ // end::slm-put-snapshot-lifecycle-policy
+
+ // tag::slm-put-snapshot-lifecycle-policy-execute
+ AcknowledgedResponse resp = client.indexLifecycle()
+ .putSnapshotLifecyclePolicy(request, RequestOptions.DEFAULT);
+ // end::slm-put-snapshot-lifecycle-policy-execute
+
+ // tag::slm-put-snapshot-lifecycle-policy-response
+ boolean putAcknowledged = resp.isAcknowledged(); // <1>
+ // end::slm-put-snapshot-lifecycle-policy-response
+ assertTrue(putAcknowledged);
+
+ // tag::slm-put-snapshot-lifecycle-policy-execute-listener
+ ActionListener putListener =
+ new ActionListener() {
+ @Override
+ public void onResponse(AcknowledgedResponse resp) {
+ boolean acknowledged = resp.isAcknowledged(); // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::slm-put-snapshot-lifecycle-policy-execute-listener
+
+ // tag::slm-put-snapshot-lifecycle-policy-execute-async
+ client.indexLifecycle().putSnapshotLifecyclePolicyAsync(request,
+ RequestOptions.DEFAULT, putListener);
+ // end::slm-put-snapshot-lifecycle-policy-execute-async
+
+ //////// GET
+ // tag::slm-get-snapshot-lifecycle-policy
+ GetSnapshotLifecyclePolicyRequest getAllRequest =
+ new GetSnapshotLifecyclePolicyRequest(); // <1>
+ GetSnapshotLifecyclePolicyRequest getRequest =
+ new GetSnapshotLifecyclePolicyRequest("policy_id"); // <2>
+ // end::slm-get-snapshot-lifecycle-policy
+
+ // tag::slm-get-snapshot-lifecycle-policy-execute
+ GetSnapshotLifecyclePolicyResponse getResponse =
+ client.indexLifecycle()
+ .getSnapshotLifecyclePolicy(getRequest,
+ RequestOptions.DEFAULT);
+ // end::slm-get-snapshot-lifecycle-policy-execute
+
+ // tag::slm-get-snapshot-lifecycle-policy-execute-listener
+ ActionListener getListener =
+ new ActionListener() {
+ @Override
+ public void onResponse(GetSnapshotLifecyclePolicyResponse resp) {
+ Map policies =
+ resp.getPolicies(); // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::slm-get-snapshot-lifecycle-policy-execute-listener
+
+ // tag::slm-get-snapshot-lifecycle-policy-execute-async
+ client.indexLifecycle().getSnapshotLifecyclePolicyAsync(getRequest,
+ RequestOptions.DEFAULT, getListener);
+ // end::slm-get-snapshot-lifecycle-policy-execute-async
+
+ assertThat(getResponse.getPolicies().size(), equalTo(1));
+ // tag::slm-get-snapshot-lifecycle-policy-response
+ SnapshotLifecyclePolicyMetadata policyMeta =
+ getResponse.getPolicies().get("policy_id"); // <1>
+ long policyVersion = policyMeta.getVersion();
+ long policyModificationDate = policyMeta.getModifiedDate();
+ long nextPolicyExecutionDate = policyMeta.getNextExecution();
+ SnapshotInvocationRecord lastSuccess = policyMeta.getLastSuccess();
+ SnapshotInvocationRecord lastFailure = policyMeta.getLastFailure();
+ SnapshotLifecyclePolicy retrievedPolicy = policyMeta.getPolicy(); // <2>
+ String id = retrievedPolicy.getId();
+ String snapshotNameFormat = retrievedPolicy.getName();
+ String repositoryName = retrievedPolicy.getRepository();
+ String schedule = retrievedPolicy.getSchedule();
+ Map snapshotConfiguration = retrievedPolicy.getConfig();
+ // end::slm-get-snapshot-lifecycle-policy-response
+
+ assertNotNull(policyMeta);
+ assertThat(retrievedPolicy, equalTo(policy));
+ assertThat(policyVersion, equalTo(1L));
+
+ createIndex("idx", Settings.builder().put("index.number_of_shards", 1).build());
+
+ //////// EXECUTE
+ // tag::slm-execute-snapshot-lifecycle-policy
+ ExecuteSnapshotLifecyclePolicyRequest executeRequest =
+ new ExecuteSnapshotLifecyclePolicyRequest("policy_id"); // <1>
+ // end::slm-execute-snapshot-lifecycle-policy
+
+ // tag::slm-execute-snapshot-lifecycle-policy-execute
+ ExecuteSnapshotLifecyclePolicyResponse executeResponse =
+ client.indexLifecycle()
+ .executeSnapshotLifecyclePolicy(executeRequest,
+ RequestOptions.DEFAULT);
+ // end::slm-execute-snapshot-lifecycle-policy-execute
+
+ // tag::slm-execute-snapshot-lifecycle-policy-response
+ final String snapshotName = executeResponse.getSnapshotName(); // <1>
+ // end::slm-execute-snapshot-lifecycle-policy-response
+
+ assertSnapshotExists(client, "my_repository", snapshotName);
+
+ // tag::slm-execute-snapshot-lifecycle-policy-execute-listener
+ ActionListener executeListener =
+ new ActionListener() {
+ @Override
+ public void onResponse(ExecuteSnapshotLifecyclePolicyResponse r) {
+ String snapshotName = r.getSnapshotName(); // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::slm-execute-snapshot-lifecycle-policy-execute-listener
+
+ // We need a listener that will actually wait for the snapshot to be created
+ CountDownLatch latch = new CountDownLatch(1);
+ executeListener =
+ new ActionListener() {
+ @Override
+ public void onResponse(ExecuteSnapshotLifecyclePolicyResponse r) {
+ try {
+ assertSnapshotExists(client, "my_repository", r.getSnapshotName());
+ } catch (Exception e) {
+ // Ignore
+ } finally {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ latch.countDown();
+ fail("failed to execute slm execute: " + e);
+ }
+ };
+
+ // tag::slm-execute-snapshot-lifecycle-policy-execute-async
+ client.indexLifecycle()
+ .executeSnapshotLifecyclePolicyAsync(executeRequest,
+ RequestOptions.DEFAULT, executeListener);
+ // end::slm-execute-snapshot-lifecycle-policy-execute-async
+ latch.await(5, TimeUnit.SECONDS);
+
+ //////// DELETE
+ // tag::slm-delete-snapshot-lifecycle-policy
+ DeleteSnapshotLifecyclePolicyRequest deleteRequest =
+ new DeleteSnapshotLifecyclePolicyRequest("policy_id"); // <1>
+ // end::slm-delete-snapshot-lifecycle-policy
+
+ // tag::slm-delete-snapshot-lifecycle-policy-execute
+ AcknowledgedResponse deleteResp = client.indexLifecycle()
+ .deleteSnapshotLifecyclePolicy(deleteRequest, RequestOptions.DEFAULT);
+ // end::slm-delete-snapshot-lifecycle-policy-execute
+ assertTrue(deleteResp.isAcknowledged());
+
+ ActionListener deleteListener = new ActionListener() {
+ @Override
+ public void onResponse(AcknowledgedResponse resp) {
+ // no-op
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // no-op
+ }
+ };
+
+ // tag::slm-delete-snapshot-lifecycle-policy-execute-async
+ client.indexLifecycle()
+ .deleteSnapshotLifecyclePolicyAsync(deleteRequest,
+ RequestOptions.DEFAULT, deleteListener);
+ // end::slm-delete-snapshot-lifecycle-policy-execute-async
+
+ assertTrue(deleteResp.isAcknowledged());
+ }
+
+ private void assertSnapshotExists(final RestHighLevelClient client, final String repo, final String snapshotName) throws Exception {
+ assertBusy(() -> {
+ GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest(repo, new String[]{snapshotName});
+ try {
+ final GetSnapshotsResponse snaps = client.snapshot().get(getSnapshotsRequest, RequestOptions.DEFAULT);
+ Optional info = snaps.getSnapshots().stream().findFirst();
+ if (info.isPresent()) {
+ info.ifPresent(si -> {
+ assertThat(si.snapshotId().getName(), equalTo(snapshotName));
+ assertThat(si.state(), equalTo(SnapshotState.SUCCESS));
+ });
+ } else {
+ fail("unable to find snapshot; " + snapshotName);
+ }
+ } catch (Exception e) {
+ if (e.getMessage().contains("snapshot_missing_exception")) {
+ fail("snapshot does not exist: " + snapshotName);
+ }
+ throw e;
+ }
+ });
+ }
+
static Map toMap(Response response) throws IOException {
return XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false);
}
diff --git a/docs/build.gradle b/docs/build.gradle
index d13f4ca3b2edb..c54b7d93a5871 100644
--- a/docs/build.gradle
+++ b/docs/build.gradle
@@ -44,6 +44,7 @@ testClusters.integTest {
// enable regexes in painless so our tests don't complain about example snippets that use them
setting 'script.painless.regex.enabled', 'true'
+ setting 'path.repo', "${buildDir}/cluster/shared/repo"
Closure configFile = {
extraConfigFile it, file("src/test/cluster/config/$it")
}
@@ -1185,3 +1186,13 @@ buildRestTests.setups['logdata_job'] = buildRestTests.setups['setup_logdata'] +
}
}
'''
+// Used by snapshot lifecycle management docs
+buildRestTests.setups['setup-repository'] = '''
+ - do:
+ snapshot.create_repository:
+ repository: my_repository
+ body:
+ type: fs
+ settings:
+ location: buildDir/cluster/shared/repo
+'''
diff --git a/docs/java-rest/high-level/ilm/delete_snapshot_lifecycle_policy.asciidoc b/docs/java-rest/high-level/ilm/delete_snapshot_lifecycle_policy.asciidoc
new file mode 100644
index 0000000000000..66819d06187b7
--- /dev/null
+++ b/docs/java-rest/high-level/ilm/delete_snapshot_lifecycle_policy.asciidoc
@@ -0,0 +1,36 @@
+--
+:api: slm-delete-snapshot-lifecycle-policy
+:request: DeleteSnapshotLifecyclePolicyRequest
+:response: AcknowledgedResponse
+--
+
+[id="{upid}-{api}"]
+=== Delete Snapshot Lifecycle Policy API
+
+
+[id="{upid}-{api}-request"]
+==== Request
+
+The Delete Snapshot Lifecycle Policy API allows you to delete a Snapshot Lifecycle Management Policy
+from the cluster.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+<1> The policy with the id `policy_id` will be deleted.
+
+[id="{upid}-{api}-response"]
+==== Response
+
+The returned +{response}+ indicates if the delete snapshot lifecycle policy request was received.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> Whether or not the delete snapshot lifecycle policy request was acknowledged.
+
+include::../execution.asciidoc[]
+
+
diff --git a/docs/java-rest/high-level/ilm/execute_snapshot_lifecycle_policy.asciidoc b/docs/java-rest/high-level/ilm/execute_snapshot_lifecycle_policy.asciidoc
new file mode 100644
index 0000000000000..7b3af935a27c7
--- /dev/null
+++ b/docs/java-rest/high-level/ilm/execute_snapshot_lifecycle_policy.asciidoc
@@ -0,0 +1,36 @@
+--
+:api: slm-execute-snapshot-lifecycle-policy
+:request: ExecuteSnapshotLifecyclePolicyRequest
+:response: ExecuteSnapshotLifecyclePolicyResponse
+--
+
+[id="{upid}-{api}"]
+=== Execute Snapshot Lifecycle Policy API
+
+
+[id="{upid}-{api}-request"]
+==== Request
+
+The Execute Snapshot Lifecycle Policy API allows you to execute a Snapshot Lifecycle Management
+Policy, taking a snapshot immediately.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+<1> The policy id to execute
+
+[id="{upid}-{api}-response"]
+==== Response
+
+The returned +{response}+ contains the name of the snapshot that was created.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> The created snapshot name
+
+include::../execution.asciidoc[]
+
+
diff --git a/docs/java-rest/high-level/ilm/get_snapshot_lifecycle_policy.asciidoc b/docs/java-rest/high-level/ilm/get_snapshot_lifecycle_policy.asciidoc
new file mode 100644
index 0000000000000..eaa8af7969ee0
--- /dev/null
+++ b/docs/java-rest/high-level/ilm/get_snapshot_lifecycle_policy.asciidoc
@@ -0,0 +1,39 @@
+--
+:api: slm-get-snapshot-lifecycle-policy
+:request: GetSnapshotLifecyclePolicyRequest
+:response: GetSnapshotLifecyclePolicyResponse
+--
+
+[id="{upid}-{api}"]
+=== Get Snapshot Lifecycle Policy API
+
+
+[id="{upid}-{api}-request"]
+==== Request
+
+The Get Snapshot Lifecycle Policy API allows you to retrieve the definition of a Snapshot Lifecycle
+Management Policy from the cluster.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+<1> Gets all policies.
+<2> Gets `policy_id`
+
+[id="{upid}-{api}-response"]
+==== Response
+
+The returned +{response}+ contains a map of `SnapshotLifecyclePolicyMetadata`, accessible by the id
+of the policy, which contains data about each policy, as well as the policy definition.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> The retrieved policies are retrieved by id.
+<2> The policy definition itself.
+
+include::../execution.asciidoc[]
+
+
diff --git a/docs/java-rest/high-level/ilm/put_snapshot_lifecycle_policy.asciidoc b/docs/java-rest/high-level/ilm/put_snapshot_lifecycle_policy.asciidoc
new file mode 100644
index 0000000000000..7fe7fec26c3b7
--- /dev/null
+++ b/docs/java-rest/high-level/ilm/put_snapshot_lifecycle_policy.asciidoc
@@ -0,0 +1,35 @@
+--
+:api: slm-put-snapshot-lifecycle-policy
+:request: PutSnapshotLifecyclePolicyRequest
+:response: AcknowledgedResponse
+--
+
+[id="{upid}-{api}"]
+=== Put Snapshot Lifecycle Policy API
+
+
+[id="{upid}-{api}-request"]
+==== Request
+
+The Put Snapshot Lifecycle Policy API allows you to add of update the definition of a Snapshot
+Lifecycle Management Policy in the cluster.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+[id="{upid}-{api}-response"]
+==== Response
+
+The returned +{response}+ indicates if the put snapshot lifecycle policy request was received.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> Whether or not the put snapshot lifecycle policy was acknowledged.
+
+include::../execution.asciidoc[]
+
+
diff --git a/docs/reference/ilm/apis/slm-api.asciidoc b/docs/reference/ilm/apis/slm-api.asciidoc
new file mode 100644
index 0000000000000..a27297593e9f5
--- /dev/null
+++ b/docs/reference/ilm/apis/slm-api.asciidoc
@@ -0,0 +1,350 @@
+[role="xpack"]
+[testenv="basic"]
+[[snapshot-lifecycle-management-api]]
+== Snapshot Lifecycle Management API
+
+The Snapshot Lifecycle Management APIs are used to manage policies for the time
+and frequency of automatic snapshots. Snapshot Lifecycle Management is related
+to <>, however, instead
+of managing a lifecycle of actions that are performed on a single index, SLM
+allows configuring policies spanning multiple indices.
+
+SLM policy management is split into three different CRUD APIs, a way to put or update
+policies, a way to retrieve policies, and a way to delete unwanted policies, as
+well as a separate API for immediately invoking a snapshot based on a policy.
+
+Since SLM falls under the same category as ILM, it is stopped and started by
+using the <> ILM APIs.
+
+[[slm-api-put]]
+=== Put Snapshot Lifecycle Policy API
+
+Creates or updates a snapshot policy. If the policy already exists, the version
+is incremented. Only the latest version of a policy is stored.
+
+When a policy is created it is immediately scheduled based on the schedule of
+the policy, when a policy is updated its schedule changes are immediately
+applied.
+
+==== Path Parameters
+
+`policy_id` (required)::
+ (string) Identifier (id) for the policy.
+
+==== Request Parameters
+
+include::{docdir}/rest-api/timeoutparms.asciidoc[]
+
+==== Authorization
+
+You must have the `manage_slm` cluster privilege to use this API. You must also
+have the `manage` index privilege on all indices being managed by `policy`. All
+operations executed by {slm} for a policy are executed as the user that put the
+latest version of a policy. For more information, see
+{stack-ov}/security-privileges.html[Security Privileges].
+
+==== Example
+
+The following creates a snapshot lifecycle policy with an id of
+`daily-snapshots`:
+
+[source,js]
+--------------------------------------------------
+PUT /_slm/policy/daily-snapshots
+{
+ "schedule": "0 30 1 * * ?", <1>
+ "name": "", <2>
+ "repository": "my_repository", <3>
+ "config": { <4>
+ "indices": ["data-*", "important"], <5>
+ "ignore_unavailable": false,
+ "include_global_state": false
+ }
+}
+--------------------------------------------------
+// CONSOLE
+// TEST[setup:setup-repository]
+<1> When the snapshot should be taken, in this case, 1:30am daily
+<2> The name each snapshot should be given
+<3> Which repository to take the snapshot in
+<4> Any extra snapshot configuration
+<5> Which indices the snapshot should contain
+
+The top-level keys that the policy supports are described below:
+
+|==================
+| Key | Description
+
+| `schedule` | A periodic or absolute time schedule. Supports all values
+ supported by the cron scheduler:
+ {xpack-ref}/trigger-schedule.html#schedule-cron[Cron scheduler configuration]
+
+| `name` | A name automatically given to each snapshot performed by this policy.
+ Supports the same <> supported in index
+ names. A UUID is automatically appended to the end of the name to prevent
+ conflicting snapshot names.
+
+| `repository` | The snapshot repository that will contain snapshots created by
+ this policy. The repository must exist prior to the policy's creation and can
+ be created with the <>.
+
+| `config` | Configuration for each snapshot that will be created by this
+ policy. Any configuration is included with <> issued by this policy.
+|==================
+
+To update an existing policy, simply use the put snapshot lifecycle policy API
+with the same policy id as an existing policy.
+
+[[slm-api-get]]
+=== Get Snapshot Lifecycle Policy API
+
+Once a policy is in place, you can retrieve one or more of the policies using
+the get snapshot lifecycle policy API. This also includes information about the
+latest successful and failed invocation that the automatic snapshots have taken.
+
+==== Path Parameters
+
+`policy_ids` (optional)::
+ (string) Comma-separated ids of policies to retrieve.
+
+==== Examples
+
+To retrieve a policy, perform a `GET` with the policy's id
+
+[source,js]
+--------------------------------------------------
+GET /_slm/policy/daily-snapshots?human
+--------------------------------------------------
+// CONSOLE
+// TEST[continued]
+
+The output looks similar to the following:
+
+[source,js]
+--------------------------------------------------
+{
+ "daily-snapshots" : {
+ "version": 1, <1>
+ "modified_date": "2019-04-23T01:30:00.000Z", <2>
+ "modified_date_millis": 1556048137314,
+ "policy" : {
+ "schedule": "0 30 1 * * ?",
+ "name": "",
+ "repository": "my_repository",
+ "config": {
+ "indices": ["data-*", "important"],
+ "ignore_unavailable": false,
+ "include_global_state": false
+ }
+ },
+ "next_execution": "2019-04-24T01:30:00.000Z", <3>
+ "next_execution_millis": 1556048160000
+ }
+}
+--------------------------------------------------
+// TESTRESPONSE[s/"modified_date": "2019-04-23T01:30:00.000Z"/"modified_date": $body.daily-snapshots.modified_date/ s/"modified_date_millis": 1556048137314/"modified_date_millis": $body.daily-snapshots.modified_date_millis/ s/"next_execution": "2019-04-24T01:30:00.000Z"/"next_execution": $body.daily-snapshots.next_execution/ s/"next_execution_millis": 1556048160000/"next_execution_millis": $body.daily-snapshots.next_execution_millis/]
+<1> The version of the snapshot policy, only the latest verison is stored and incremented when the policy is updated
+<2> The last time this policy was modified
+<3> The next time this policy will be executed
+
+Or, to retrieve all policies:
+
+[source,js]
+--------------------------------------------------
+GET /_slm/policy
+--------------------------------------------------
+// CONSOLE
+// TEST[continued]
+
+[[slm-api-execute]]
+=== Execute Snapshot Lifecycle Policy API
+
+Sometimes it can be useful to immediately execute a snapshot based on policy,
+perhaps before an upgrade or before performing other maintenance on indices. The
+execute snapshot policy API allows you to perform a snapshot immediately without
+waiting for a policy's scheduled invocation.
+
+==== Path Parameters
+
+`policy_id` (required)::
+ (string) Id of the policy to execute
+
+==== Example
+
+To take an immediate snapshot using a policy, use the following
+
+[source,js]
+--------------------------------------------------
+PUT /_slm/policy/daily-snapshots/_execute
+--------------------------------------------------
+// CONSOLE
+// TEST[skip:we can't easily handle snapshots from docs tests]
+
+This API will immediately return with the generated snapshot name
+
+[source,js]
+--------------------------------------------------
+{
+ "snapshot_name": "daily-snap-2019.04.24-gwrqoo2xtea3q57vvg0uea"
+}
+--------------------------------------------------
+// TESTRESPONSE[skip:we can't handle snapshots from docs tests]
+
+The snapshot will be taken in the background, you can use the
+<> to monitor the status of the snapshot.
+
+Once a snapshot has been kicked off, you can see the latest successful or failed
+snapshot using the get snapshot lifecycle policy API:
+
+[source,js]
+--------------------------------------------------
+GET /_slm/policy/daily-snapshots?human
+--------------------------------------------------
+// CONSOLE
+// TEST[skip:we already tested get policy above, the last_failure may not be present though]
+
+Which, in this case shows an error because the index did not exist:
+
+[source,js]
+--------------------------------------------------
+{
+ "daily-snapshots" : {
+ "version": 1,
+ "modified_date": "2019-04-23T01:30:00.000Z",
+ "modified_date_millis": 1556048137314,
+ "policy" : {
+ "schedule": "0 30 1 * * ?",
+ "name": "",
+ "repository": "my_repository",
+ "config": {
+ "indices": ["data-*", "important"],
+ "ignore_unavailable": false,
+ "include_global_state": false
+ }
+ },
+ "last_failure": { <1>
+ "snapshot_name": "daily-snap-2019.04.02-lohisb5ith2n8hxacaq3mw",
+ "time_string": "2019-04-02T01:30:00.000Z",
+ "time": 1556042030000,
+ "details": "{\"type\":\"index_not_found_exception\",\"reason\":\"no such index [important]\",\"resource.type\":\"index_or_alias\",\"resource.id\":\"important\",\"index_uuid\":\"_na_\",\"index\":\"important\",\"stack_trace\":\"[important] IndexNotFoundException[no such index [important]]\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver$WildcardExpressionResolver.indexNotFoundException(IndexNameExpressionResolver.java:762)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver$WildcardExpressionResolver.innerResolve(IndexNameExpressionResolver.java:714)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver$WildcardExpressionResolver.resolve(IndexNameExpressionResolver.java:670)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.concreteIndices(IndexNameExpressionResolver.java:163)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.concreteIndexNames(IndexNameExpressionResolver.java:142)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.concreteIndexNames(IndexNameExpressionResolver.java:102)\\n\\tat org.elasticsearch.snapshots.SnapshotsService$1.execute(SnapshotsService.java:280)\\n\\tat org.elasticsearch.cluster.ClusterStateUpdateTask.execute(ClusterStateUpdateTask.java:47)\\n\\tat org.elasticsearch.cluster.service.MasterService.executeTasks(MasterService.java:687)\\n\\tat org.elasticsearch.cluster.service.MasterService.calculateTaskOutputs(MasterService.java:310)\\n\\tat org.elasticsearch.cluster.service.MasterService.runTasks(MasterService.java:210)\\n\\tat org.elasticsearch.cluster.service.MasterService$Batcher.run(MasterService.java:142)\\n\\tat org.elasticsearch.cluster.service.TaskBatcher.runIfNotProcessed(TaskBatcher.java:150)\\n\\tat org.elasticsearch.cluster.service.TaskBatcher$BatchedTask.run(TaskBatcher.java:188)\\n\\tat org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingRunnable.run(ThreadContext.java:688)\\n\\tat org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.runAndClean(PrioritizedEsThreadPoolExecutor.java:252)\\n\\tat org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.run(PrioritizedEsThreadPoolExecutor.java:215)\\n\\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\\n\\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\\n\\tat java.base/java.lang.Thread.run(Thread.java:834)\\n\"}"
+ } ,
+ "next_execution": "2019-04-24T01:30:00.000Z",
+ "next_execution_millis": 1556048160000
+ }
+}
+--------------------------------------------------
+// TESTRESPONSE[skip:the presence of last_failure is asynchronous and will be present for users, but is untestable]
+<1> The last unsuccessfully initiated snapshot by this policy, along with the details of its failure
+
+In this case, it failed due to the "important" index not existing and
+`ignore_unavailable` setting being set to `false`.
+
+Updating the policy to change the `ignore_unavailable` setting is done using the
+same put snapshot lifecycle policy API:
+
+[source,js]
+--------------------------------------------------
+PUT /_slm/policy/daily-snapshots
+{
+ "schedule": "0 30 1 * * ?",
+ "name": "",
+ "repository": "my_repository",
+ "config": {
+ "indices": ["data-*", "important"],
+ "ignore_unavailable": true,
+ "include_global_state": false
+ }
+}
+--------------------------------------------------
+// CONSOLE
+// TEST[continued]
+
+Another snapshot can immediately be executed to ensure the new policy works:
+
+[source,js]
+--------------------------------------------------
+PUT /_slm/policy/daily-snapshots/_execute
+--------------------------------------------------
+// CONSOLE
+// TEST[skip:we can't handle snapshots in docs tests]
+
+[source,js]
+--------------------------------------------------
+{
+ "snapshot_name": "daily-snap-2019.04.24-tmtnyjtrsxkhbrrdcgg18a"
+}
+--------------------------------------------------
+// TESTRESPONSE[skip:we can't handle snapshots in docs tests]
+
+Now retriving the policy shows that the policy has successfully been executed:
+
+
+[source,js]
+--------------------------------------------------
+GET /_slm/policy/daily-snapshots?human
+--------------------------------------------------
+// CONSOLE
+// TEST[skip:we already tested this above and the output may not be available yet]
+
+Which now includes the successful snapshot information:
+
+[source,js]
+--------------------------------------------------
+{
+ "daily-snapshots" : {
+ "version": 2, <1>
+ "modified_date": "2019-04-23T01:30:00.000Z",
+ "modified_date_millis": 1556048137314,
+ "policy" : {
+ "schedule": "0 30 1 * * ?",
+ "name": "",
+ "repository": "my_repository",
+ "config": {
+ "indices": ["data-*", "important"],
+ "ignore_unavailable": true,
+ "include_global_state": false
+ }
+ },
+ "last_success": { <2>
+ "snapshot_name": "daily-snap-2019.04.24-tmtnyjtrsxkhbrrdcgg18a",
+ "time_string": "2019-04-24T16:43:49.316Z",
+ "time": 1556124229316
+ } ,
+ "last_failure": {
+ "snapshot_name": "daily-snap-2019.04.02-lohisb5ith2n8hxacaq3mw",
+ "time_string": "2019-04-02T01:30:00.000Z",
+ "time": 1556042030000,
+ "details": "{\"type\":\"index_not_found_exception\",\"reason\":\"no such index [important]\",\"resource.type\":\"index_or_alias\",\"resource.id\":\"important\",\"index_uuid\":\"_na_\",\"index\":\"important\",\"stack_trace\":\"[important] IndexNotFoundException[no such index [important]]\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver$WildcardExpressionResolver.indexNotFoundException(IndexNameExpressionResolver.java:762)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver$WildcardExpressionResolver.innerResolve(IndexNameExpressionResolver.java:714)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver$WildcardExpressionResolver.resolve(IndexNameExpressionResolver.java:670)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.concreteIndices(IndexNameExpressionResolver.java:163)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.concreteIndexNames(IndexNameExpressionResolver.java:142)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.concreteIndexNames(IndexNameExpressionResolver.java:102)\\n\\tat org.elasticsearch.snapshots.SnapshotsService$1.execute(SnapshotsService.java:280)\\n\\tat org.elasticsearch.cluster.ClusterStateUpdateTask.execute(ClusterStateUpdateTask.java:47)\\n\\tat org.elasticsearch.cluster.service.MasterService.executeTasks(MasterService.java:687)\\n\\tat org.elasticsearch.cluster.service.MasterService.calculateTaskOutputs(MasterService.java:310)\\n\\tat org.elasticsearch.cluster.service.MasterService.runTasks(MasterService.java:210)\\n\\tat org.elasticsearch.cluster.service.MasterService$Batcher.run(MasterService.java:142)\\n\\tat org.elasticsearch.cluster.service.TaskBatcher.runIfNotProcessed(TaskBatcher.java:150)\\n\\tat org.elasticsearch.cluster.service.TaskBatcher$BatchedTask.run(TaskBatcher.java:188)\\n\\tat org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingRunnable.run(ThreadContext.java:688)\\n\\tat org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.runAndClean(PrioritizedEsThreadPoolExecutor.java:252)\\n\\tat org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.run(PrioritizedEsThreadPoolExecutor.java:215)\\n\\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\\n\\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\\n\\tat java.base/java.lang.Thread.run(Thread.java:834)\\n\"}"
+ } ,
+ "next_execution": "2019-04-24T01:30:00.000Z",
+ "next_execution_millis": 1556048160000
+ }
+}
+--------------------------------------------------
+// TESTRESPONSE[skip:the presence of last_failure and last_success is asynchronous and will be present for users, but is untestable]
+<1> The policy's version has been incremented because it was updated
+<2> The last successfully initiated snapshot information
+
+It is a good idea to test policies using the execute API to ensure they work.
+
+[[slm-api-delete]]
+=== Delete Snapshot Lifecycle Policy API
+
+A policy can be deleted by issuing a delete request with the policy id. Note
+that this prevents any future snapshots from being taken, but does not cancel
+any currently ongoing snapshots or remove any previously taken snapshots.
+
+==== Path Parameters
+
+`policy_id` (optional)::
+ (string) Id of the policy to remove.
+
+==== Example
+
+[source,js]
+--------------------------------------------------
+DELETE /_slm/policy/daily-snapshots
+--------------------------------------------------
+// CONSOLE
+// TEST[continued]
diff --git a/docs/reference/ilm/getting-started-slm.asciidoc b/docs/reference/ilm/getting-started-slm.asciidoc
new file mode 100644
index 0000000000000..5849101ffe6c3
--- /dev/null
+++ b/docs/reference/ilm/getting-started-slm.asciidoc
@@ -0,0 +1,215 @@
+[role="xpack"]
+[testenv="basic"]
+[[getting-started-snapshot-lifecycle-management]]
+== Getting started with snapshot lifecycle management
+
+Let's get started with snapshot lifecycle management (SLM) by working through a
+hands-on scenario. The goal of this example is to automatically back up {es}
+indices using the <> every day at a particular
+time.
+
+[float]
+[[slm-and-security]]
+=== Security and SLM
+Before starting, it's important to understand the privileges that are needed
+when configuring SLM if you are using the security plugin. There are two
+built-in cluster privileges that can be used to assist: `manage_slm` and
+`read_slm`. It's also good to note that the `create_snapshot` permission
+allows taking snapshots even for indices the role may not have access to.
+
+An example of configuring an administrator role for SLM follows:
+
+[source,js]
+-----------------------------------
+POST /_security/role/slm-admin
+{
+ "cluster": ["manage_slm", "create_snapshot"],
+ "indices": [
+ {
+ "names": [".slm-history-*"],
+ "privileges": ["all"]
+ }
+ ]
+}
+-----------------------------------
+// CONSOLE
+// TEST[skip:security is not enabled here]
+
+Or, for a read-only role that can retrieve policies (but not update, execute, or
+delete them), as well as only view the history index:
+
+[source,js]
+-----------------------------------
+POST /_security/role/slm-read-only
+{
+ "cluster": ["read_slm"],
+ "indices": [
+ {
+ "names": [".slm-history-*"],
+ "privileges": ["read"]
+ }
+ ]
+}
+-----------------------------------
+// CONSOLE
+// TEST[skip:security is not enabled here]
+
+[float]
+[[slm-gs-create-policy]]
+=== Setting up a repository
+
+Before we can set up an SLM policy, we'll need to set up a
+<> where the snapshots will be
+stored. Repositories can use {plugins}/repository.html[many different backends],
+including cloud storage providers. You'll probably want to use one of these in
+production, but for this example we'll use a shared file system repository:
+
+[source,js]
+-----------------------------------
+PUT /_snapshot/my_repository
+{
+ "type": "fs",
+ "settings": {
+ "location": "my_backup_location"
+ }
+}
+-----------------------------------
+// CONSOLE
+// TEST
+
+[float]
+=== Setting up a policy
+
+Now that we have a repository in place, we can create a policy to automatically
+take snapshots. Policies are written in JSON and will define when to take
+snapshots, what the snapshots should be named, and which indices should be
+included, among other things. We'll use the <> API
+to create the policy.
+
+[source,js]
+--------------------------------------------------
+PUT /_slm/policy/nightly-snapshots
+{
+ "schedule": "0 30 1 * * ?", <1>
+ "name": "", <2>
+ "repository": "my_repository", <3>
+ "config": { <4>
+ "indices": ["*"] <5>
+ }
+}
+--------------------------------------------------
+// CONSOLE
+// TEST[continued]
+<1> when the snapshot should be taken, using
+ {xpack-ref}/trigger-schedule.html#schedule-cron[Cron syntax], in this
+ case at 1:30AM each day
+<2> whe name each snapshot should be given, using
+ <> to include the current date in the name
+ of the snapshot
+<3> the repository the snapshot should be stored in
+<4> the configuration to be used for the snapshot requests (see below)
+<5> which indices should be included in the snapshot, in this case, every index
+
+This policy will take a snapshot of every index each day at 1:30AM UTC.
+Snapshots are incremental, allowing frequent snapshots to be stored efficiently,
+so don't be afraid to configure a policy to take frequent snapshots.
+
+In addition to specifying the indices that should be included in the snapshot,
+the `config` field can be used to customize other aspects of the snapshot. You
+can use any option allowed in <>, so you can specify, for example, whether the snapshot should fail in
+special cases, such as if one of the specified indices cannot be found.
+
+[float]
+=== Making sure the policy works
+
+While snapshots taken by SLM policies can be viewed through the standard snapshot
+API, SLM also keeps track of policy successes and failures in ways that are a bit
+easier to use to make sure the policy is working. Once a policy has executed at
+least once, when you view the policy using the <>,
+some metadata will be returned indicating whether the snapshot was sucessfully
+initiated or not.
+
+Instead of waiting for our policy to run, let's tell SLM to take a snapshot
+as using the configuration from our policy right now instead of waiting for
+1:30AM.
+
+[source,js]
+--------------------------------------------------
+PUT /_slm/policy/nightly-snapshots/_execute
+--------------------------------------------------
+// CONSOLE
+// TEST[skip:we can't easily handle snapshots from docs tests]
+
+This request will kick off a snapshot for our policy right now, regardless of
+the schedule in the policy. This is useful for taking snapshots before making
+a configuration change, upgrading, or for our purposes, making sure our policy
+is going to work successfully. The policy will continue to run on its configured
+schedule after this execution of the policy.
+
+[source,js]
+--------------------------------------------------
+GET /_slm/policy/nightly-snapshots?human
+--------------------------------------------------
+// CONSOLE
+// TEST[continued]
+
+This request will return a response that includes the policy, as well as
+information about the last time the policy succeeded and failed, as well as the
+next time the policy will be executed.
+
+[source,js]
+--------------------------------------------------
+{
+ "nightly-snapshots" : {
+ "version": 1,
+ "modified_date": "2019-04-23T01:30:00.000Z",
+ "modified_date_millis": 1556048137314,
+ "policy" : {
+ "schedule": "0 30 1 * * ?",
+ "name": "",
+ "repository": "my_repository",
+ "config": {
+ "indices": ["*"],
+ }
+ },
+ "last_success": { <1>
+ "snapshot_name": "nightly-snap-2019.04.24-tmtnyjtrsxkhbrrdcgg18a", <2>
+ "time_string": "2019-04-24T16:43:49.316Z",
+ "time": 1556124229316
+ } ,
+ "last_failure": { <3>
+ "snapshot_name": "nightly-snap-2019.04.02-lohisb5ith2n8hxacaq3mw",
+ "time_string": "2019-04-02T01:30:00.000Z",
+ "time": 1556042030000,
+ "details": "{\"type\":\"index_not_found_exception\",\"reason\":\"no such index [important]\",\"resource.type\":\"index_or_alias\",\"resource.id\":\"important\",\"index_uuid\":\"_na_\",\"index\":\"important\",\"stack_trace\":\"[important] IndexNotFoundException[no such index [important]]\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver$WildcardExpressionResolver.indexNotFoundException(IndexNameExpressionResolver.java:762)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver$WildcardExpressionResolver.innerResolve(IndexNameExpressionResolver.java:714)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver$WildcardExpressionResolver.resolve(IndexNameExpressionResolver.java:670)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.concreteIndices(IndexNameExpressionResolver.java:163)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.concreteIndexNames(IndexNameExpressionResolver.java:142)\\n\\tat org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.concreteIndexNames(IndexNameExpressionResolver.java:102)\\n\\tat org.elasticsearch.snapshots.SnapshotsService$1.execute(SnapshotsService.java:280)\\n\\tat org.elasticsearch.cluster.ClusterStateUpdateTask.execute(ClusterStateUpdateTask.java:47)\\n\\tat org.elasticsearch.cluster.service.MasterService.executeTasks(MasterService.java:687)\\n\\tat org.elasticsearch.cluster.service.MasterService.calculateTaskOutputs(MasterService.java:310)\\n\\tat org.elasticsearch.cluster.service.MasterService.runTasks(MasterService.java:210)\\n\\tat org.elasticsearch.cluster.service.MasterService$Batcher.run(MasterService.java:142)\\n\\tat org.elasticsearch.cluster.service.TaskBatcher.runIfNotProcessed(TaskBatcher.java:150)\\n\\tat org.elasticsearch.cluster.service.TaskBatcher$BatchedTask.run(TaskBatcher.java:188)\\n\\tat org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingRunnable.run(ThreadContext.java:688)\\n\\tat org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.runAndClean(PrioritizedEsThreadPoolExecutor.java:252)\\n\\tat org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.run(PrioritizedEsThreadPoolExecutor.java:215)\\n\\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\\n\\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\\n\\tat java.base/java.lang.Thread.run(Thread.java:834)\\n\"}"
+ } ,
+ "next_execution": "2019-04-24T01:30:00.000Z", <4>
+ "next_execution_millis": 1556048160000
+ }
+}
+--------------------------------------------------
+// TESTRESPONSE[skip:the presence of last_failure and last_success is asynchronous and will be present for users, but is untestable]
+<1> information about the last time the policy successfully initated a snapshot
+<2> the name of the snapshot that was successfully initiated
+<3> information about the last time the policy failed to initiate a snapshot
+<4> the is the next time the policy will execute
+
+NOTE: This metadata only indicates whether the request to initiate the snapshot was
+made successfully or not - after the snapshot has been successfully started, it
+is possible for the snapshot to fail if, for example, the connection to a remote
+repository is lost while copying files.
+
+If you're following along, the returned SLM policy shouldn't have a `last_failure`
+field - it's included above only as an example. You should, however, see a
+`last_success` field and a snapshot name. If you do, you've successfully taken
+your first snapshot using SLM!
+
+While only the most recent sucess and failure are available through the Get Policy
+API, all policy executions are recorded to a history index, which may be queried
+by searching the index pattern `.slm-history*`.
+
+That's it! We have our first SLM policy set up to periodically take snapshots
+so that our backups are always up to date. You can read more details in the
+<> and the
+<>
diff --git a/docs/reference/ilm/index.asciidoc b/docs/reference/ilm/index.asciidoc
index b906f9ade4447..50d2e5f6dac22 100644
--- a/docs/reference/ilm/index.asciidoc
+++ b/docs/reference/ilm/index.asciidoc
@@ -47,6 +47,16 @@ to a single shard.
hardware.
. Delete the index once the required 30 day retention period is reached.
+*Snapshot Lifecycle Management*
+
+ILM itself does allow managing indices, however, managing snapshots for a set of
+indices is outside of the scope of an index-level policy. Instead, there are
+separate APIs for managing snapshot lifecycles. Please see the
+<>
+documentation for information about configuring snapshots.
+
+See <>.
+
[IMPORTANT]
===========================
{ilm} does not support mixed-version cluster usage. Although it
@@ -73,3 +83,5 @@ include::error-handling.asciidoc[]
include::ilm-and-snapshots.asciidoc[]
include::start-stop-ilm.asciidoc[]
+
+include::getting-started-slm.asciidoc[]
diff --git a/docs/reference/ilm/start-stop-ilm.asciidoc b/docs/reference/ilm/start-stop-ilm.asciidoc
index 22ca0ae48fd98..fd1ab654ab6cc 100644
--- a/docs/reference/ilm/start-stop-ilm.asciidoc
+++ b/docs/reference/ilm/start-stop-ilm.asciidoc
@@ -10,6 +10,10 @@ maybe there are scheduled maintenance windows when cluster topology
changes are desired that may impact running ILM actions. For this reason,
ILM has two ways to disable operations.
+When stopping ILM, snapshot lifecycle management operations are also stopped,
+this means that no scheduled snapshots are created (currently ongoing snapshots
+are unaffected).
+
Normally, ILM will be running by default.
To see the current operating status of ILM, use the <>
to see the current state of ILM.
diff --git a/docs/reference/modules/snapshots.asciidoc b/docs/reference/modules/snapshots.asciidoc
index 582991a13cffd..481212aa61084 100644
--- a/docs/reference/modules/snapshots.asciidoc
+++ b/docs/reference/modules/snapshots.asciidoc
@@ -70,6 +70,7 @@ recommend testing the reindex from remote process with a subset of your data to
understand the time requirements before proceeding.
[float]
+[[snapshots-repositories]]
=== Repositories
You must register a snapshot repository before you can perform snapshot and
@@ -329,6 +330,7 @@ POST /_snapshot/my_unverified_backup/_verify
It returns a list of nodes where repository was successfully verified or an error message if verification process failed.
[float]
+[[snapshots-take-snapshot]]
=== Snapshot
A repository can contain multiple snapshots of the same cluster. Snapshots are identified by unique names within the
diff --git a/docs/reference/redirects.asciidoc b/docs/reference/redirects.asciidoc
index 97d2fab69a99b..1985229f06558 100644
--- a/docs/reference/redirects.asciidoc
+++ b/docs/reference/redirects.asciidoc
@@ -626,3 +626,11 @@ See <> and
See <> and
{stack-ov}/ml-calendars.html[Calendars and scheduled events].
+[role="exclude",id="_repositories"]
+=== Snapshot repositories
+See <>.
+
+[role="exclude",id="_snapshot"]
+=== Snapshot
+See <>.
+
diff --git a/docs/reference/rest-api/index.asciidoc b/docs/reference/rest-api/index.asciidoc
index 8bb7053ecfe93..78aa0f7e7d45d 100644
--- a/docs/reference/rest-api/index.asciidoc
+++ b/docs/reference/rest-api/index.asciidoc
@@ -17,6 +17,7 @@ not be included yet.
* <>
* <>
* <>
+* <>
* <>
* <>
* <>
@@ -31,6 +32,7 @@ include::{es-repo-dir}/ccr/apis/ccr-apis.asciidoc[]
include::{es-repo-dir}/data-frames/apis/index.asciidoc[]
include::{es-repo-dir}/graph/explore.asciidoc[]
include::{es-repo-dir}/ilm/apis/ilm-api.asciidoc[]
+include::{es-repo-dir}/ilm/apis/slm-api.asciidoc[]
include::{es-repo-dir}/indices/apis/index.asciidoc[]
include::{es-repo-dir}/licensing/index.asciidoc[]
include::{es-repo-dir}/migration/migration.asciidoc[]
diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java
index a72120e328b00..a64e15d545399 100644
--- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java
+++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java
@@ -36,7 +36,6 @@
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -164,7 +163,7 @@ public ActionRequestValidationException validate() {
return validationException;
}
- private static int metadataSize(Map userMetadata) {
+ public static int metadataSize(Map userMetadata) {
if (userMetadata == null) {
return 0;
}
@@ -431,8 +430,8 @@ public CreateSnapshotRequest source(Map source) {
if (name.equals("indices")) {
if (entry.getValue() instanceof String) {
indices(Strings.splitStringByCommaToArray((String) entry.getValue()));
- } else if (entry.getValue() instanceof ArrayList) {
- indices((ArrayList) entry.getValue());
+ } else if (entry.getValue() instanceof List) {
+ indices((List) entry.getValue());
} else {
throw new IllegalArgumentException("malformed indices section, should be an array of strings");
}
diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java
index 19c6d31ccc82a..00afc064f609a 100644
--- a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java
+++ b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java
@@ -577,7 +577,7 @@ boolean isPatternMatchingAllIndices(MetaData metaData, String[] indicesOrAliases
return false;
}
- static final class Context {
+ public static class Context {
private final ClusterState state;
private final IndicesOptions options;
@@ -597,7 +597,8 @@ static final class Context {
this(state, options, startTime, false, false);
}
- Context(ClusterState state, IndicesOptions options, long startTime, boolean preserveAliases, boolean resolveToWriteIndex) {
+ protected Context(ClusterState state, IndicesOptions options, long startTime,
+ boolean preserveAliases, boolean resolveToWriteIndex) {
this.state = state;
this.options = options;
this.startTime = startTime;
@@ -855,7 +856,7 @@ private static List resolveEmptyOrTrivialWildcard(IndicesOptions options
}
}
- static final class DateMathExpressionResolver implements ExpressionResolver {
+ public static final class DateMathExpressionResolver implements ExpressionResolver {
private static final DateFormatter DEFAULT_DATE_FORMATTER = DateFormatter.forPattern("uuuu.MM.dd");
private static final String EXPRESSION_LEFT_BOUND = "<";
diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java
index 4d03d14a1b2e2..f432dc2a374c6 100644
--- a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java
+++ b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java
@@ -538,7 +538,7 @@ private void wipeCluster() throws Exception {
* the snapshots intact in the repository.
* @return Map of repository name to list of snapshots found in unfinished state
*/
- private Map>> wipeSnapshots() throws IOException {
+ protected Map>> wipeSnapshots() throws IOException {
final Map>> inProgressSnapshots = new HashMap<>();
for (Map.Entry repo : entityAsMap(adminClient.performRequest(new Request("GET", "/_snapshot/_all"))).entrySet()) {
String repoName = repo.getKey();
diff --git a/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc b/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc
index 0ace996f96d71..211ffefa3a069 100644
--- a/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc
+++ b/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc
@@ -72,6 +72,7 @@ A successful call returns an object with "cluster" and "index" fields.
"manage_rollup",
"manage_saml",
"manage_security",
+ "manage_slm",
"manage_token",
"manage_watcher",
"monitor",
@@ -82,6 +83,7 @@ A successful call returns an object with "cluster" and "index" fields.
"none",
"read_ccr",
"read_ilm",
+ "read_slm",
"transport_client"
],
"index" : [
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
index 138f8cac48df5..75d3228c20981 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
@@ -218,6 +218,11 @@
import org.elasticsearch.xpack.core.watcher.transport.actions.put.PutWatchAction;
import org.elasticsearch.xpack.core.watcher.transport.actions.service.WatcherServiceAction;
import org.elasticsearch.xpack.core.watcher.transport.actions.stats.WatcherStatsAction;
+import org.elasticsearch.xpack.core.snapshotlifecycle.SnapshotLifecycleMetadata;
+import org.elasticsearch.xpack.core.snapshotlifecycle.action.DeleteSnapshotLifecycleAction;
+import org.elasticsearch.xpack.core.snapshotlifecycle.action.ExecuteSnapshotLifecycleAction;
+import org.elasticsearch.xpack.core.snapshotlifecycle.action.GetSnapshotLifecycleAction;
+import org.elasticsearch.xpack.core.snapshotlifecycle.action.PutSnapshotLifecycleAction;
import java.util.ArrayList;
import java.util.Arrays;
@@ -402,6 +407,11 @@ public List> getClientActions() {
RemoveIndexLifecyclePolicyAction.INSTANCE,
MoveToStepAction.INSTANCE,
RetryAction.INSTANCE,
+ PutSnapshotLifecycleAction.INSTANCE,
+ GetSnapshotLifecycleAction.INSTANCE,
+ DeleteSnapshotLifecycleAction.INSTANCE,
+ ExecuteSnapshotLifecycleAction.INSTANCE,
+ // Freeze
TransportFreezeIndexAction.FreezeIndexAction.INSTANCE,
// Data Frame
PutDataFrameTransformAction.INSTANCE,
@@ -498,6 +508,9 @@ public List getNamedWriteables() {
new NamedWriteableRegistry.Entry(MetaData.Custom.class, IndexLifecycleMetadata.TYPE, IndexLifecycleMetadata::new),
new NamedWriteableRegistry.Entry(NamedDiff.class, IndexLifecycleMetadata.TYPE,
IndexLifecycleMetadata.IndexLifecycleMetadataDiff::new),
+ new NamedWriteableRegistry.Entry(MetaData.Custom.class, SnapshotLifecycleMetadata.TYPE, SnapshotLifecycleMetadata::new),
+ new NamedWriteableRegistry.Entry(NamedDiff.class, SnapshotLifecycleMetadata.TYPE,
+ SnapshotLifecycleMetadata.SnapshotLifecycleMetadataDiff::new),
// ILM - LifecycleTypes
new NamedWriteableRegistry.Entry(LifecycleType.class, TimeseriesLifecycleType.TYPE,
(in) -> TimeseriesLifecycleType.INSTANCE),
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecycleSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecycleSettings.java
index 9d6002f685692..17ee111cbac78 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecycleSettings.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecycleSettings.java
@@ -15,6 +15,7 @@ public class LifecycleSettings {
public static final String LIFECYCLE_POLL_INTERVAL = "indices.lifecycle.poll_interval";
public static final String LIFECYCLE_NAME = "index.lifecycle.name";
public static final String LIFECYCLE_INDEXING_COMPLETE = "index.lifecycle.indexing_complete";
+ public static final String SLM_HISTORY_INDEX_ENABLED = "slm.history_index_enabled";
public static final Setting LIFECYCLE_POLL_INTERVAL_SETTING = Setting.positiveTimeSetting(LIFECYCLE_POLL_INTERVAL,
TimeValue.timeValueMinutes(10), Setting.Property.Dynamic, Setting.Property.NodeScope);
@@ -22,4 +23,7 @@ public class LifecycleSettings {
Setting.Property.Dynamic, Setting.Property.IndexScope);
public static final Setting LIFECYCLE_INDEXING_COMPLETE_SETTING = Setting.boolSetting(LIFECYCLE_INDEXING_COMPLETE, false,
Setting.Property.Dynamic, Setting.Property.IndexScope);
+
+ public static final Setting SLM_HISTORY_INDEX_ENABLED_SETTING = Setting.boolSetting(SLM_HISTORY_INDEX_ENABLED, true,
+ Setting.Property.NodeScope);
}
diff --git a/x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/job/CronSchedule.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/scheduler/CronSchedule.java
similarity index 75%
rename from x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/job/CronSchedule.java
rename to x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/scheduler/CronSchedule.java
index 0a093742cdc29..b2c763e47f39c 100644
--- a/x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/job/CronSchedule.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/scheduler/CronSchedule.java
@@ -3,15 +3,12 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-package org.elasticsearch.xpack.rollup.job;
-
-import org.elasticsearch.xpack.core.scheduler.Cron;
-import org.elasticsearch.xpack.core.scheduler.SchedulerEngine;
+package org.elasticsearch.xpack.core.scheduler;
public class CronSchedule implements SchedulerEngine.Schedule {
private final Cron cron;
- CronSchedule(String cronExpression) {
+ public CronSchedule(String cronExpression) {
this.cron = new Cron(cronExpression);
}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/scheduler/SchedulerEngine.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/scheduler/SchedulerEngine.java
index 95dca09661978..1c2a9538c25a9 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/scheduler/SchedulerEngine.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/scheduler/SchedulerEngine.java
@@ -17,9 +17,12 @@
import java.time.Clock;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -136,6 +139,10 @@ public void stop() {
}
}
+ public Set scheduledJobIds() {
+ return Collections.unmodifiableSet(new HashSet<>(schedules.keySet()));
+ }
+
public void add(Job job) {
ActiveSchedule schedule = new ActiveSchedule(job.getId(), job.getSchedule(), clock.millis());
schedules.compute(schedule.name, (name, previousSchedule) -> {
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java
index c58f83dcd2f1a..6c8d1e5299535 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java
@@ -15,10 +15,13 @@
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.xpack.core.indexlifecycle.action.GetLifecycleAction;
import org.elasticsearch.xpack.core.indexlifecycle.action.GetStatusAction;
+import org.elasticsearch.xpack.core.indexlifecycle.action.StartILMAction;
+import org.elasticsearch.xpack.core.indexlifecycle.action.StopILMAction;
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction;
import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction;
import org.elasticsearch.xpack.core.security.support.Automatons;
+import org.elasticsearch.xpack.core.snapshotlifecycle.action.GetSnapshotLifecycleAction;
import java.util.Collections;
import java.util.HashSet;
@@ -61,6 +64,9 @@ public final class ClusterPrivilege extends Privilege {
private static final Automaton READ_CCR_AUTOMATON = patterns(ClusterStateAction.NAME, HasPrivilegesAction.NAME);
private static final Automaton MANAGE_ILM_AUTOMATON = patterns("cluster:admin/ilm/*");
private static final Automaton READ_ILM_AUTOMATON = patterns(GetLifecycleAction.NAME, GetStatusAction.NAME);
+ private static final Automaton MANAGE_SLM_AUTOMATON =
+ patterns("cluster:admin/slm/*", StartILMAction.NAME, StopILMAction.NAME, GetStatusAction.NAME);
+ private static final Automaton READ_SLM_AUTOMATON = patterns(GetSnapshotLifecycleAction.NAME, GetStatusAction.NAME);
public static final ClusterPrivilege NONE = new ClusterPrivilege("none", Automatons.EMPTY);
public static final ClusterPrivilege ALL = new ClusterPrivilege("all", ALL_CLUSTER_AUTOMATON);
@@ -92,6 +98,8 @@ public final class ClusterPrivilege extends Privilege {
public static final ClusterPrivilege CREATE_SNAPSHOT = new ClusterPrivilege("create_snapshot", CREATE_SNAPSHOT_AUTOMATON);
public static final ClusterPrivilege MANAGE_ILM = new ClusterPrivilege("manage_ilm", MANAGE_ILM_AUTOMATON);
public static final ClusterPrivilege READ_ILM = new ClusterPrivilege("read_ilm", READ_ILM_AUTOMATON);
+ public static final ClusterPrivilege MANAGE_SLM = new ClusterPrivilege("manage_slm", MANAGE_SLM_AUTOMATON);
+ public static final ClusterPrivilege READ_SLM = new ClusterPrivilege("read_slm", READ_SLM_AUTOMATON);
public static final Predicate ACTION_MATCHER = ClusterPrivilege.ALL.predicate();
@@ -122,6 +130,8 @@ public final class ClusterPrivilege extends Privilege {
.put("create_snapshot", CREATE_SNAPSHOT)
.put("manage_ilm", MANAGE_ILM)
.put("read_ilm", READ_ILM)
+ .put("manage_slm", MANAGE_SLM)
+ .put("read_slm", READ_SLM)
.immutableMap();
private static final ConcurrentHashMap, ClusterPrivilege> CACHE = new ConcurrentHashMap<>();
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/snapshotlifecycle/SnapshotInvocationRecord.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/snapshotlifecycle/SnapshotInvocationRecord.java
new file mode 100644
index 0000000000000..a39153f991664
--- /dev/null
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/snapshotlifecycle/SnapshotInvocationRecord.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.core.snapshotlifecycle;
+
+import org.elasticsearch.cluster.AbstractDiffable;
+import org.elasticsearch.cluster.Diffable;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Holds information about Snapshots kicked off by Snapshot Lifecycle Management in the cluster state, so that this information can be
+ * presented to the user. This class is used for both successes and failures as the structure of the data is very similar.
+ */
+public class SnapshotInvocationRecord extends AbstractDiffable
+ implements Writeable, ToXContentObject, Diffable {
+
+ static final ParseField SNAPSHOT_NAME = new ParseField("snapshot_name");
+ static final ParseField TIMESTAMP = new ParseField("time");
+ static final ParseField DETAILS = new ParseField("details");
+
+ private String snapshotName;
+ private long timestamp;
+ private String details;
+
+ public static final ConstructingObjectParser PARSER =
+ new ConstructingObjectParser<>("snapshot_policy_invocation_record", true,
+ a -> new SnapshotInvocationRecord((String) a[0], (long) a[1], (String) a[2]));
+
+ static {
+ PARSER.declareString(ConstructingObjectParser.constructorArg(), SNAPSHOT_NAME);
+ PARSER.declareLong(ConstructingObjectParser.constructorArg(), TIMESTAMP);
+ PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), DETAILS);
+ }
+
+ public static SnapshotInvocationRecord parse(XContentParser parser, String name) {
+ return PARSER.apply(parser, name);
+ }
+
+ public SnapshotInvocationRecord(String snapshotName, long timestamp, String details) {
+ this.snapshotName = Objects.requireNonNull(snapshotName, "snapshot name must be provided");
+ this.timestamp = timestamp;
+ this.details = details;
+ }
+
+ public SnapshotInvocationRecord(StreamInput in) throws IOException {
+ this.snapshotName = in.readString();
+ this.timestamp = in.readVLong();
+ this.details = in.readOptionalString();
+ }
+
+ public String getSnapshotName() {
+ return snapshotName;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public String getDetails() {
+ return details;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ out.writeString(snapshotName);
+ out.writeVLong(timestamp);
+ out.writeOptionalString(details);
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ {
+ builder.field(SNAPSHOT_NAME.getPreferredName(), snapshotName);
+ builder.timeField(TIMESTAMP.getPreferredName(), "time_string", timestamp);
+ if (Objects.nonNull(details)) {
+ builder.field(DETAILS.getPreferredName(), details);
+ }
+ }
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SnapshotInvocationRecord that = (SnapshotInvocationRecord) o;
+ return getTimestamp() == that.getTimestamp() &&
+ Objects.equals(getSnapshotName(), that.getSnapshotName()) &&
+ Objects.equals(getDetails(), that.getDetails());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getSnapshotName(), getTimestamp(), getDetails());
+ }
+}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/snapshotlifecycle/SnapshotLifecycleMetadata.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/snapshotlifecycle/SnapshotLifecycleMetadata.java
new file mode 100644
index 0000000000000..542014b46dbe7
--- /dev/null
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/snapshotlifecycle/SnapshotLifecycleMetadata.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.core.snapshotlifecycle;
+
+import org.elasticsearch.Version;
+import org.elasticsearch.cluster.AbstractDiffable;
+import org.elasticsearch.cluster.Diff;
+import org.elasticsearch.cluster.DiffableUtils;
+import org.elasticsearch.cluster.NamedDiff;
+import org.elasticsearch.cluster.metadata.MetaData;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.xpack.core.XPackPlugin.XPackMetaDataCustom;
+import org.elasticsearch.xpack.core.indexlifecycle.OperationMode;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Custom cluster state metadata that stores all the snapshot lifecycle
+ * policies and their associated metadata
+ */
+public class SnapshotLifecycleMetadata implements XPackMetaDataCustom {
+
+ public static final String TYPE = "snapshot_lifecycle";
+ public static final ParseField OPERATION_MODE_FIELD = new ParseField("operation_mode");
+ public static final ParseField POLICIES_FIELD = new ParseField("policies");
+
+ public static final SnapshotLifecycleMetadata EMPTY = new SnapshotLifecycleMetadata(Collections.emptyMap(), OperationMode.RUNNING);
+
+ @SuppressWarnings("unchecked")
+ public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(TYPE,
+ a -> new SnapshotLifecycleMetadata(
+ ((List) a[0]).stream()
+ .collect(Collectors.toMap(m -> m.getPolicy().getId(), Function.identity())),
+ OperationMode.valueOf((String) a[1])));
+
+ static {
+ PARSER.declareNamedObjects(ConstructingObjectParser.constructorArg(), (p, c, n) -> SnapshotLifecyclePolicyMetadata.parse(p, n),
+ v -> {
+ throw new IllegalArgumentException("ordered " + POLICIES_FIELD.getPreferredName() + " are not supported");
+ }, POLICIES_FIELD);
+ }
+
+ private final Map snapshotConfigurations;
+ private final OperationMode operationMode;
+
+ public SnapshotLifecycleMetadata(Map snapshotConfigurations, OperationMode operationMode) {
+ this.snapshotConfigurations = new HashMap<>(snapshotConfigurations);
+ this.operationMode = operationMode;
+ }
+
+ public SnapshotLifecycleMetadata(StreamInput in) throws IOException {
+ this.snapshotConfigurations = in.readMap(StreamInput::readString, SnapshotLifecyclePolicyMetadata::new);
+ this.operationMode = in.readEnum(OperationMode.class);
+ }
+
+ public Map getSnapshotConfigurations() {
+ return Collections.unmodifiableMap(this.snapshotConfigurations);
+ }
+
+ public OperationMode getOperationMode() {
+ return operationMode;
+ }
+
+ @Override
+ public EnumSet context() {
+ return MetaData.ALL_CONTEXTS;
+ }
+
+ @Override
+ public Diff diff(MetaData.Custom previousState) {
+ return new SnapshotLifecycleMetadataDiff((SnapshotLifecycleMetadata) previousState, this);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return TYPE;
+ }
+
+ @Override
+ public Version getMinimalSupportedVersion() {
+ return Version.V_7_4_0;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ out.writeMap(this.snapshotConfigurations, StreamOutput::writeString, (out1, value) -> value.writeTo(out1));
+ out.writeEnum(this.operationMode);
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.field(POLICIES_FIELD.getPreferredName(), this.snapshotConfigurations);
+ builder.field(OPERATION_MODE_FIELD.getPreferredName(), operationMode);
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return Strings.toString(this);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.snapshotConfigurations, this.operationMode);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj.getClass() != getClass()) {
+ return false;
+ }
+ SnapshotLifecycleMetadata other = (SnapshotLifecycleMetadata) obj;
+ return this.snapshotConfigurations.equals(other.snapshotConfigurations) &&
+ this.operationMode.equals(other.operationMode);
+ }
+
+ public static class SnapshotLifecycleMetadataDiff implements NamedDiff {
+
+ final Diff