diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndexLifecycleClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndexLifecycleClient.java index 80cee2c420ef3..302570af07ed9 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndexLifecycleClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndexLifecycleClient.java @@ -37,6 +37,7 @@ import org.elasticsearch.client.slm.DeleteSnapshotLifecyclePolicyRequest; import org.elasticsearch.client.slm.ExecuteSnapshotLifecyclePolicyRequest; import org.elasticsearch.client.slm.ExecuteSnapshotLifecyclePolicyResponse; +import org.elasticsearch.client.slm.ExecuteSnapshotLifecycleRetentionRequest; import org.elasticsearch.client.slm.GetSnapshotLifecyclePolicyRequest; import org.elasticsearch.client.slm.GetSnapshotLifecyclePolicyResponse; import org.elasticsearch.client.slm.GetSnapshotLifecycleStatsRequest; @@ -467,6 +468,44 @@ public Cancellable executeSnapshotLifecyclePolicyAsync( options, ExecuteSnapshotLifecyclePolicyResponse::fromXContent, listener, emptySet()); } + /** + * Execute snapshot lifecycle retention + * See
+ * https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/ + * java-rest-high-ilm-slm-execute-snapshot-lifecycle-retention.html + *+ * 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 executeSnapshotLifecycleRetention(ExecuteSnapshotLifecycleRetentionRequest request, + RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, IndexLifecycleRequestConverters::executeSnapshotLifecycleRetention, + options, AcknowledgedResponse::fromXContent, emptySet()); + } + + /** + * Asynchronously execute snapshot lifecycle retention + * See
+ * https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/ + * java-rest-high-ilm-slm-execute-snapshot-lifecycle-retention.html + *+ * 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 + * @return cancellable that may be used to cancel the request + */ + public Cancellable executeSnapshotLifecycleRetentionAsync( + ExecuteSnapshotLifecycleRetentionRequest request, RequestOptions options, + ActionListener
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 e6105a54bb264..0715b3759605b 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
@@ -34,6 +34,7 @@
import org.elasticsearch.client.ilm.StopILMRequest;
import org.elasticsearch.client.slm.DeleteSnapshotLifecyclePolicyRequest;
import org.elasticsearch.client.slm.ExecuteSnapshotLifecyclePolicyRequest;
+import org.elasticsearch.client.slm.ExecuteSnapshotLifecycleRetentionRequest;
import org.elasticsearch.client.slm.GetSnapshotLifecyclePolicyRequest;
import org.elasticsearch.client.slm.GetSnapshotLifecycleStatsRequest;
import org.elasticsearch.client.slm.PutSnapshotLifecyclePolicyRequest;
@@ -217,6 +218,18 @@ static Request executeSnapshotLifecyclePolicy(ExecuteSnapshotLifecyclePolicyRequ
return request;
}
+ static Request executeSnapshotLifecycleRetention(ExecuteSnapshotLifecycleRetentionRequest executeSnapshotLifecycleRetentionRequest) {
+ Request request = new Request(HttpPost.METHOD_NAME,
+ new RequestConverters.EndpointBuilder()
+ .addPathPartAsIs("_slm/_execute_retention")
+ .build());
+ RequestConverters.Params params = new RequestConverters.Params();
+ params.withMasterTimeout(executeSnapshotLifecycleRetentionRequest.masterNodeTimeout());
+ params.withTimeout(executeSnapshotLifecycleRetentionRequest.timeout());
+ request.addParameters(params.asMap());
+ return request;
+ }
+
static Request getSnapshotLifecycleStats(GetSnapshotLifecycleStatsRequest getSnapshotLifecycleStatsRequest) {
String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_slm/stats").build();
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/slm/ExecuteSnapshotLifecycleRetentionRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/slm/ExecuteSnapshotLifecycleRetentionRequest.java
new file mode 100644
index 0000000000000..14c02ecb92f25
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/slm/ExecuteSnapshotLifecycleRetentionRequest.java
@@ -0,0 +1,25 @@
+/*
+ * 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.slm;
+
+import org.elasticsearch.client.TimedRequest;
+
+public class ExecuteSnapshotLifecycleRetentionRequest extends TimedRequest {
+}
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 59d0df9a82740..3cd10f5c69070 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
@@ -57,6 +57,7 @@
import org.elasticsearch.client.slm.DeleteSnapshotLifecyclePolicyRequest;
import org.elasticsearch.client.slm.ExecuteSnapshotLifecyclePolicyRequest;
import org.elasticsearch.client.slm.ExecuteSnapshotLifecyclePolicyResponse;
+import org.elasticsearch.client.slm.ExecuteSnapshotLifecycleRetentionRequest;
import org.elasticsearch.client.slm.GetSnapshotLifecyclePolicyRequest;
import org.elasticsearch.client.slm.GetSnapshotLifecyclePolicyResponse;
import org.elasticsearch.client.slm.GetSnapshotLifecycleStatsRequest;
@@ -987,6 +988,44 @@ public void onFailure(Exception e) {
// end::slm-delete-snapshot-lifecycle-policy-execute-async
assertTrue(deleteResp.isAcknowledged());
+
+ //////// EXECUTE RETENTION
+ // tag::slm-execute-snapshot-lifecycle-retention
+ ExecuteSnapshotLifecycleRetentionRequest req =
+ new ExecuteSnapshotLifecycleRetentionRequest();
+ // end::slm-execute-snapshot-lifecycle-retention
+
+ // tag::slm-execute-snapshot-lifecycle-retention-execute
+ AcknowledgedResponse retentionResp =
+ client.indexLifecycle()
+ .executeSnapshotLifecycleRetention(req,
+ RequestOptions.DEFAULT);
+ // end::slm-execute-snapshot-lifecycle-retention-execute
+
+ // tag::slm-execute-snapshot-lifecycle-retention-response
+ final boolean acked = retentionResp.isAcknowledged();
+ // end::slm-execute-snapshot-lifecycle-retention-response
+
+ // tag::slm-execute-snapshot-lifecycle-policy-execute-listener
+ ActionListener retentionListener =
+ new ActionListener<>() {
+ @Override
+ public void onResponse(AcknowledgedResponse r) {
+ assert r.isAcknowledged(); // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::slm-execute-snapshot-lifecycle-retention-execute-listener
+
+ // tag::slm-execute-snapshot-lifecycle-retention-execute-async
+ client.indexLifecycle()
+ .executeSnapshotLifecycleRetentionAsync(req,
+ RequestOptions.DEFAULT, retentionListener);
+ // end::slm-execute-snapshot-lifecycle-retention-execute-async
}
private void assertSnapshotExists(final RestHighLevelClient client, final String repo, final String snapshotName) throws Exception {
diff --git a/docs/java-rest/high-level/ilm/execute_snapshot_lifecycle_retention.asciidoc b/docs/java-rest/high-level/ilm/execute_snapshot_lifecycle_retention.asciidoc
new file mode 100644
index 0000000000000..190f7be209212
--- /dev/null
+++ b/docs/java-rest/high-level/ilm/execute_snapshot_lifecycle_retention.asciidoc
@@ -0,0 +1,35 @@
+--
+:api: slm-execute-snapshot-lifecycle-retention
+:request: ExecuteSnapshotLifecycleRetentionRequest
+:response: AcknowledgedResponse
+--
+[role="xpack"]
+[id="{upid}-{api}"]
+=== Execute Snapshot Lifecycle Retention API
+
+
+[id="{upid}-{api}-request"]
+==== Request
+
+The Execute Snapshot Lifecycle Retention API allows you to execute Snapshot Lifecycle Management
+Retention immediately, rather than waiting for its regularly scheduled execution.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+[id="{upid}-{api}-response"]
+==== Response
+
+The returned +{response}+ contains a boolean for whether the request was
+acknowledged by the master node.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+
+include::../execution.asciidoc[]
+
+
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/slm/action/ExecuteSnapshotRetentionAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/slm/action/ExecuteSnapshotRetentionAction.java
new file mode 100644
index 0000000000000..314e7ba3fc530
--- /dev/null
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/slm/action/ExecuteSnapshotRetentionAction.java
@@ -0,0 +1,63 @@
+/*
+ * 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.slm.action;
+
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.ActionType;
+import org.elasticsearch.action.support.master.AcknowledgedRequest;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+public class ExecuteSnapshotRetentionAction extends ActionType {
+ public static final ExecuteSnapshotRetentionAction INSTANCE = new ExecuteSnapshotRetentionAction();
+ public static final String NAME = "cluster:admin/slm/execute-retention";
+
+ protected ExecuteSnapshotRetentionAction() {
+ super(NAME, AcknowledgedResponse::new);
+ }
+
+ public static class Request extends AcknowledgedRequest implements ToXContentObject {
+
+ public Request() { }
+
+ public Request(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public ActionRequestValidationException validate() {
+ return null;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj.getClass() != getClass()) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycle.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycle.java
index 39e27f2a5afe5..f3cb037756144 100644
--- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycle.java
+++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycle.java
@@ -65,6 +65,7 @@
import org.elasticsearch.xpack.core.slm.SnapshotLifecycleMetadata;
import org.elasticsearch.xpack.core.slm.action.DeleteSnapshotLifecycleAction;
import org.elasticsearch.xpack.core.slm.action.ExecuteSnapshotLifecycleAction;
+import org.elasticsearch.xpack.core.slm.action.ExecuteSnapshotRetentionAction;
import org.elasticsearch.xpack.core.slm.action.GetSnapshotLifecycleAction;
import org.elasticsearch.xpack.core.slm.action.GetSnapshotLifecycleStatsAction;
import org.elasticsearch.xpack.core.slm.action.PutSnapshotLifecycleAction;
@@ -96,11 +97,13 @@
import org.elasticsearch.xpack.slm.SnapshotRetentionTask;
import org.elasticsearch.xpack.slm.action.RestDeleteSnapshotLifecycleAction;
import org.elasticsearch.xpack.slm.action.RestExecuteSnapshotLifecycleAction;
+import org.elasticsearch.xpack.slm.action.RestExecuteSnapshotRetentionAction;
import org.elasticsearch.xpack.slm.action.RestGetSnapshotLifecycleAction;
import org.elasticsearch.xpack.slm.action.RestGetSnapshotLifecycleStatsAction;
import org.elasticsearch.xpack.slm.action.RestPutSnapshotLifecycleAction;
import org.elasticsearch.xpack.slm.action.TransportDeleteSnapshotLifecycleAction;
import org.elasticsearch.xpack.slm.action.TransportExecuteSnapshotLifecycleAction;
+import org.elasticsearch.xpack.slm.action.TransportExecuteSnapshotRetentionAction;
import org.elasticsearch.xpack.slm.action.TransportGetSnapshotLifecycleAction;
import org.elasticsearch.xpack.slm.action.TransportGetSnapshotLifecycleStatsAction;
import org.elasticsearch.xpack.slm.action.TransportPutSnapshotLifecycleAction;
@@ -230,7 +233,8 @@ public List getRestHandlers(Settings settings, RestController restC
new RestDeleteSnapshotLifecycleAction(restController),
new RestGetSnapshotLifecycleAction(restController),
new RestExecuteSnapshotLifecycleAction(restController),
- new RestGetSnapshotLifecycleStatsAction(restController)
+ new RestGetSnapshotLifecycleStatsAction(restController),
+ new RestExecuteSnapshotRetentionAction(restController)
));
}
return handlers;
@@ -265,7 +269,8 @@ public List getRestHandlers(Settings settings, RestController restC
new ActionHandler<>(DeleteSnapshotLifecycleAction.INSTANCE, TransportDeleteSnapshotLifecycleAction.class),
new ActionHandler<>(GetSnapshotLifecycleAction.INSTANCE, TransportGetSnapshotLifecycleAction.class),
new ActionHandler<>(ExecuteSnapshotLifecycleAction.INSTANCE, TransportExecuteSnapshotLifecycleAction.class),
- new ActionHandler<>(GetSnapshotLifecycleStatsAction.INSTANCE, TransportGetSnapshotLifecycleStatsAction.class)
+ new ActionHandler<>(GetSnapshotLifecycleStatsAction.INSTANCE, TransportGetSnapshotLifecycleStatsAction.class),
+ new ActionHandler<>(ExecuteSnapshotRetentionAction.INSTANCE, TransportExecuteSnapshotRetentionAction.class)
));
}
return actions;
diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionService.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionService.java
index 36a60ffdf9365..235df846b4897 100644
--- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionService.java
+++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionService.java
@@ -31,10 +31,13 @@
public class SnapshotRetentionService implements LocalNodeMasterListener, Closeable {
static final String SLM_RETENTION_JOB_ID = "slm-retention-job";
+ static final String SLM_RETENTION_MANUAL_JOB_ID = "slm-execute-manual-retention-job";
private static final Logger logger = LogManager.getLogger(SnapshotRetentionService.class);
private final SchedulerEngine scheduler;
+ private final SnapshotRetentionTask retentionTask;
+ private final Clock clock;
private volatile String slmRetentionSchedule;
private volatile boolean isMaster = false;
@@ -43,8 +46,10 @@ public SnapshotRetentionService(Settings settings,
Supplier taskSupplier,
ClusterService clusterService,
Clock clock) {
+ this.clock = clock;
this.scheduler = new SchedulerEngine(settings, clock);
- this.scheduler.register(taskSupplier.get());
+ this.retentionTask = taskSupplier.get();
+ this.scheduler.register(this.retentionTask);
this.slmRetentionSchedule = LifecycleSettings.SLM_RETENTION_SCHEDULE_SETTING.get(settings);
clusterService.addLocalNodeMasterListener(this);
clusterService.getClusterSettings().addSettingsUpdateConsumer(LifecycleSettings.SLM_RETENTION_SCHEDULE_SETTING,
@@ -91,6 +96,16 @@ private void cancelRetentionJob() {
this.scheduler.scheduledJobIds().forEach(this.scheduler::remove);
}
+ /**
+ * Manually trigger snapshot retention
+ */
+ public void triggerRetention() {
+ if (this.isMaster) {
+ long now = clock.millis();
+ this.retentionTask.triggered(new SchedulerEngine.Event(SLM_RETENTION_MANUAL_JOB_ID, now, now));
+ }
+ }
+
@Override
public String executorName() {
return ThreadPool.Names.SNAPSHOT;
diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java
index d19707a2d8bda..a9e67c41db198 100644
--- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java
+++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java
@@ -84,8 +84,10 @@ public SnapshotRetentionTask(Client client, ClusterService clusterService, LongS
@Override
public void triggered(SchedulerEngine.Event event) {
- assert event.getJobName().equals(SnapshotRetentionService.SLM_RETENTION_JOB_ID) :
- "expected id to be " + SnapshotRetentionService.SLM_RETENTION_JOB_ID + " but it was " + event.getJobName();
+ assert event.getJobName().equals(SnapshotRetentionService.SLM_RETENTION_JOB_ID) ||
+ event.getJobName().equals(SnapshotRetentionService.SLM_RETENTION_MANUAL_JOB_ID):
+ "expected id to be " + SnapshotRetentionService.SLM_RETENTION_JOB_ID + " or " +
+ SnapshotRetentionService.SLM_RETENTION_MANUAL_JOB_ID + " but it was " + event.getJobName();
final ClusterState state = clusterService.state();
if (SnapshotLifecycleService.ilmStoppedOrStopping(state)) {
diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/RestExecuteSnapshotRetentionAction.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/RestExecuteSnapshotRetentionAction.java
new file mode 100644
index 0000000000000..8bf2e3e870e1d
--- /dev/null
+++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/RestExecuteSnapshotRetentionAction.java
@@ -0,0 +1,34 @@
+/*
+ * 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.slm.action;
+
+import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestController;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.action.RestToXContentListener;
+import org.elasticsearch.xpack.core.slm.action.ExecuteSnapshotRetentionAction;
+
+public class RestExecuteSnapshotRetentionAction extends BaseRestHandler {
+
+ public RestExecuteSnapshotRetentionAction(RestController controller) {
+ controller.registerHandler(RestRequest.Method.POST, "/_slm/_execute_retention", this);
+ }
+
+ @Override
+ public String getName() {
+ return "slm_execute_retention";
+ }
+
+ @Override
+ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) {
+ ExecuteSnapshotRetentionAction.Request req = new ExecuteSnapshotRetentionAction.Request();
+ req.timeout(request.paramAsTime("timeout", req.timeout()));
+ req.masterNodeTimeout(request.paramAsTime("master_timeout", req.masterNodeTimeout()));
+ return channel -> client.execute(ExecuteSnapshotRetentionAction.INSTANCE, req, new RestToXContentListener<>(channel));
+ }
+}
diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/TransportExecuteSnapshotRetentionAction.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/TransportExecuteSnapshotRetentionAction.java
new file mode 100644
index 0000000000000..ba01381e2c665
--- /dev/null
+++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/TransportExecuteSnapshotRetentionAction.java
@@ -0,0 +1,73 @@
+/*
+ * 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.slm.action;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
+import org.elasticsearch.action.support.master.TransportMasterNodeAction;
+import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.block.ClusterBlockException;
+import org.elasticsearch.cluster.block.ClusterBlockLevel;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.tasks.Task;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.transport.TransportService;
+import org.elasticsearch.xpack.core.slm.action.ExecuteSnapshotRetentionAction;
+import org.elasticsearch.xpack.slm.SnapshotRetentionService;
+
+import java.io.IOException;
+
+public class TransportExecuteSnapshotRetentionAction
+ extends TransportMasterNodeAction {
+
+ private static final Logger logger = LogManager.getLogger(TransportExecuteSnapshotRetentionAction.class);
+
+ private final SnapshotRetentionService retentionService;
+
+ @Inject
+ public TransportExecuteSnapshotRetentionAction(TransportService transportService, ClusterService clusterService, ThreadPool threadPool,
+ ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
+ SnapshotRetentionService retentionService) {
+ super(ExecuteSnapshotRetentionAction.NAME, transportService, clusterService, threadPool, actionFilters,
+ ExecuteSnapshotRetentionAction.Request::new, indexNameExpressionResolver);
+ this.retentionService = retentionService;
+ }
+ @Override
+ protected String executor() {
+ return ThreadPool.Names.GENERIC;
+ }
+
+ @Override
+ protected AcknowledgedResponse read(StreamInput in) throws IOException {
+ return new AcknowledgedResponse(in);
+ }
+
+ @Override
+ protected void masterOperation(final Task task, final ExecuteSnapshotRetentionAction.Request request,
+ final ClusterState state,
+ final ActionListener listener) {
+ try {
+ logger.info("manually triggering SLM snapshot retention");
+ this.retentionService.triggerRetention();
+ listener.onResponse(new AcknowledgedResponse(true));
+ } catch (Exception e) {
+ listener.onFailure(new ElasticsearchException("failed to execute snapshot lifecycle retention", e));
+ }
+ }
+
+ @Override
+ protected ClusterBlockException checkBlock(ExecuteSnapshotRetentionAction.Request request, ClusterState state) {
+ return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ);
+ }
+}
diff --git a/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java b/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java
index f5e463852f48e..1d066a0d7f0c1 100644
--- a/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java
+++ b/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java
@@ -8,22 +8,26 @@
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotStatus;
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusResponse;
+import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.SnapshotsInProgress;
+import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.repositories.RepositoriesService;
+import org.elasticsearch.snapshots.ConcurrentSnapshotExecutionException;
import org.elasticsearch.snapshots.SnapshotMissingException;
import org.elasticsearch.snapshots.mockstore.MockRepository;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.junit.annotations.TestLogging;
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
-import org.elasticsearch.xpack.core.ilm.LifecycleSettings;
import org.elasticsearch.xpack.core.slm.SnapshotLifecyclePolicy;
import org.elasticsearch.xpack.core.slm.SnapshotLifecyclePolicyItem;
import org.elasticsearch.xpack.core.slm.SnapshotRetentionConfiguration;
import org.elasticsearch.xpack.core.slm.action.ExecuteSnapshotLifecycleAction;
+import org.elasticsearch.xpack.core.slm.action.ExecuteSnapshotRetentionAction;
import org.elasticsearch.xpack.core.slm.action.GetSnapshotLifecycleAction;
import org.elasticsearch.xpack.core.slm.action.PutSnapshotLifecycleAction;
import org.elasticsearch.xpack.ilm.IndexLifecycle;
@@ -50,16 +54,9 @@ public class SLMSnapshotBlockingIntegTests extends ESIntegTestCase {
@After
public void resetSLMSettings() throws Exception {
- // unset retention settings
- client().admin().cluster().prepareUpdateSettings()
- .setTransientSettings(Settings.builder()
- .put(LifecycleSettings.SLM_RETENTION_SCHEDULE, (String) null)
- .build())
- .get();
-
// Cancel/delete all snapshots
assertBusy(() -> {
- logger.info("--> wiping all snapshots");
+ logger.info("--> wiping all snapshots after test");
client().admin().cluster().prepareGetSnapshots(REPO).get().getSnapshots(REPO)
.forEach(snapshotInfo -> {
try {
@@ -124,7 +121,6 @@ public void testSnapshotInProgress() throws Exception {
}
}
- @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/46508")
public void testRetentionWhileSnapshotInProgress() throws Exception {
final String indexName = "test";
final String policyId = "slm-policy";
@@ -165,53 +161,74 @@ public void testRetentionWhileSnapshotInProgress() throws Exception {
// Take another snapshot, but before doing that, block it from completing
logger.info("--> blocking nodes from completing snapshot");
- blockAllDataNodes(REPO);
- final String secondSnapName = executePolicy(policyId);
-
- // Check that the executed snapshot shows up in the SLM output as in_progress
- assertBusy(() -> {
- GetSnapshotLifecycleAction.Response getResp =
- client().execute(GetSnapshotLifecycleAction.INSTANCE, new GetSnapshotLifecycleAction.Request(policyId)).get();
- logger.info("--> checking for in progress snapshot...");
-
- assertThat(getResp.getPolicies().size(), greaterThan(0));
- SnapshotLifecyclePolicyItem item = getResp.getPolicies().get(0);
- assertNotNull(item.getSnapshotInProgress());
- SnapshotLifecyclePolicyItem.SnapshotInProgress inProgress = item.getSnapshotInProgress();
- assertThat(inProgress.getSnapshotId().getName(), equalTo(secondSnapName));
- assertThat(inProgress.getStartTime(), greaterThan(0L));
- assertThat(inProgress.getState(), anyOf(equalTo(SnapshotsInProgress.State.INIT), equalTo(SnapshotsInProgress.State.STARTED)));
- assertNull(inProgress.getFailure());
- });
-
- // Run retention every second
- client().admin().cluster().prepareUpdateSettings()
- .setTransientSettings(Settings.builder()
- .put(LifecycleSettings.SLM_RETENTION_SCHEDULE, "*/1 * * * * ?")
- .build())
- .get();
- // Guarantee that retention gets a chance to run before unblocking, I know sleeps are not
- // ideal, but we don't currently have a way to force retention to run, so waiting at least
- // a second is the best we can do for now.
- Thread.sleep(1500);
-
- logger.info("--> unblocking snapshots");
- unblockRepo(REPO);
- unblockAllDataNodes(REPO);
-
- // Check that the snapshot created by the policy has been removed by retention
- assertBusy(() -> {
- // Trigger a cluster state update so that it re-checks for a snapshot in progress
- client().admin().cluster().prepareReroute().get();
- logger.info("--> waiting for snapshot to be deleted");
- try {
- SnapshotsStatusResponse s =
- client().admin().cluster().prepareSnapshotStatus(REPO).setSnapshots(completedSnapshotName).get();
- assertNull("expected no snapshot but one was returned", s.getSnapshots().get(0));
- } catch (SnapshotMissingException e) {
- // Great, we wanted it to be deleted!
- }
- });
+ try {
+ blockAllDataNodes(REPO);
+ final String secondSnapName = executePolicy(policyId);
+
+ // Check that the executed snapshot shows up in the SLM output as in_progress
+ assertBusy(() -> {
+ GetSnapshotLifecycleAction.Response getResp =
+ client().execute(GetSnapshotLifecycleAction.INSTANCE, new GetSnapshotLifecycleAction.Request(policyId)).get();
+ logger.info("--> checking for in progress snapshot...");
+
+ assertThat(getResp.getPolicies().size(), greaterThan(0));
+ SnapshotLifecyclePolicyItem item = getResp.getPolicies().get(0);
+ assertNotNull(item.getSnapshotInProgress());
+ SnapshotLifecyclePolicyItem.SnapshotInProgress inProgress = item.getSnapshotInProgress();
+ assertThat(inProgress.getSnapshotId().getName(), equalTo(secondSnapName));
+ assertThat(inProgress.getStartTime(), greaterThan(0L));
+ assertThat(inProgress.getState(), anyOf(equalTo(SnapshotsInProgress.State.INIT),
+ equalTo(SnapshotsInProgress.State.STARTED)));
+ assertNull(inProgress.getFailure());
+ });
+
+ // Run retention
+ logger.info("--> triggering retention");
+ assertTrue(client().execute(ExecuteSnapshotRetentionAction.INSTANCE,
+ new ExecuteSnapshotRetentionAction.Request()).get().isAcknowledged());
+
+ logger.info("--> unblocking snapshots");
+ unblockRepo(REPO);
+ unblockAllDataNodes(REPO);
+
+ // Check that the snapshot created by the policy has been removed by retention
+ assertBusy(() -> {
+ // Trigger a cluster state update so that it re-checks for a snapshot in progress
+ client().admin().cluster().prepareReroute().get();
+ logger.info("--> waiting for snapshot to be deleted");
+ try {
+ SnapshotsStatusResponse s =
+ client().admin().cluster().prepareSnapshotStatus(REPO).setSnapshots(completedSnapshotName).get();
+ assertNull("expected no snapshot but one was returned", s.getSnapshots().get(0));
+ } catch (SnapshotMissingException e) {
+ // Great, we wanted it to be deleted!
+ }
+ });
+
+ // Cancel the ongoing snapshot to cancel it
+ assertBusy(() -> {
+ try {
+ logger.info("--> cancelling snapshot {}", secondSnapName);
+ client().admin().cluster().prepareDeleteSnapshot(REPO, secondSnapName).get();
+ } catch (ConcurrentSnapshotExecutionException e) {
+ logger.info("--> attempted to stop second snapshot", e);
+ // just wait and retry
+ fail("attempted to stop second snapshot but a snapshot or delete was in progress");
+ }
+ });
+
+ // Assert that the history document has been written for taking the snapshot and deleting it
+ assertBusy(() -> {
+ SearchResponse resp = client().prepareSearch(".slm-history*")
+ .setQuery(QueryBuilders.matchQuery("snapshot_name", completedSnapshotName)).get();
+ logger.info("--> checking history written for {}, got: {}",
+ completedSnapshotName, Strings.arrayToCommaDelimitedString(resp.getHits().getHits()));
+ assertThat(resp.getHits().getTotalHits().value, equalTo(2L));
+ });
+ } finally {
+ unblockRepo(REPO);
+ unblockAllDataNodes(REPO);
+ }
}
private void initializeRepo(String repoName) {
diff --git a/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionServiceTests.java b/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionServiceTests.java
index 972f0d57db453..15ed96665f544 100644
--- a/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionServiceTests.java
+++ b/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionServiceTests.java
@@ -26,6 +26,8 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
@@ -67,14 +69,51 @@ public void testJobsAreScheduled() {
}
}
+ public void testManualTriggering() {
+ final DiscoveryNode discoveryNode = new DiscoveryNode("node", ESTestCase.buildNewFakeTransportAddress(),
+ Collections.emptyMap(), DiscoveryNodeRole.BUILT_IN_ROLES, Version.CURRENT);
+ ClockMock clock = new ClockMock();
+ AtomicInteger invoked = new AtomicInteger(0);
+
+ try (ThreadPool threadPool = new TestThreadPool("test");
+ ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool, discoveryNode, clusterSettings);
+ SnapshotRetentionService service = new SnapshotRetentionService(Settings.EMPTY,
+ () -> new FakeRetentionTask(event -> {
+ assertThat(event.getJobName(), equalTo(SnapshotRetentionService.SLM_RETENTION_MANUAL_JOB_ID));
+ invoked.incrementAndGet();
+ }), clusterService, clock)) {
+
+ service.onMaster();
+ service.triggerRetention();
+ assertThat(invoked.get(), equalTo(1));
+
+ service.offMaster();
+ service.triggerRetention();
+ assertThat(invoked.get(), equalTo(1));
+
+ service.onMaster();
+ service.triggerRetention();
+ assertThat(invoked.get(), equalTo(2));
+
+ threadPool.shutdownNow();
+ }
+ }
+
private static class FakeRetentionTask extends SnapshotRetentionTask {
+ private final Consumer onTrigger;
+
FakeRetentionTask() {
+ this(evt -> {});
+ }
+
+ FakeRetentionTask(Consumer onTrigger) {
super(mock(Client.class), null, System::nanoTime, mock(SnapshotHistoryStore.class), mock(ThreadPool.class));
+ this.onTrigger = onTrigger;
}
@Override
public void triggered(SchedulerEngine.Event event) {
- super.triggered(event);
+ onTrigger.accept(event);
}
}
}
diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/api/slm.execute_retention.json b/x-pack/plugin/src/test/resources/rest-api-spec/api/slm.execute_retention.json
new file mode 100644
index 0000000000000..e6d21db0716d1
--- /dev/null
+++ b/x-pack/plugin/src/test/resources/rest-api-spec/api/slm.execute_retention.json
@@ -0,0 +1,19 @@
+{
+ "slm.execute_retention":{
+ "documentation":{
+ "url":"https://www.elastic.co/guide/en/elasticsearch/reference/current/slm-api-execute-retention.html"
+ },
+ "stability":"stable",
+ "url":{
+ "paths":[
+ {
+ "path":"/_slm/_execute_retention",
+ "methods":[
+ "POST"
+ ]
+ }
+ ]
+ },
+ "params":{}
+ }
+}