diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java index e0bba0c78a12..4967d8091c96 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java @@ -29,8 +29,6 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.client.RequestConverters.EndpointBuilder; import org.elasticsearch.client.core.PageParams; import org.elasticsearch.client.ml.CloseJobRequest; -import org.elasticsearch.client.ml.DeleteTrainedModelRequest; -import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest; import org.elasticsearch.client.ml.DeleteCalendarEventRequest; import org.elasticsearch.client.ml.DeleteCalendarJobRequest; import org.elasticsearch.client.ml.DeleteCalendarRequest; @@ -41,7 +39,9 @@ import org.elasticsearch.client.ml.DeleteFilterRequest; import org.elasticsearch.client.ml.DeleteForecastRequest; import org.elasticsearch.client.ml.DeleteJobRequest; import org.elasticsearch.client.ml.DeleteModelSnapshotRequest; +import org.elasticsearch.client.ml.DeleteTrainedModelRequest; import org.elasticsearch.client.ml.EvaluateDataFrameRequest; +import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest; import org.elasticsearch.client.ml.FindFileStructureRequest; import org.elasticsearch.client.ml.FlushJobRequest; import org.elasticsearch.client.ml.ForecastJobRequest; @@ -692,7 +692,16 @@ final class MLRequestConverters { .addPathPartAsIs("_ml", "data_frame", "analytics") .addPathPart(deleteRequest.getId()) .build(); - return new Request(HttpDelete.METHOD_NAME, endpoint); + + Request request = new Request(HttpDelete.METHOD_NAME, endpoint); + + RequestConverters.Params params = new RequestConverters.Params(); + if (deleteRequest.getForce() != null) { + params.putParam("force", Boolean.toString(deleteRequest.getForce())); + } + request.addParameters(params.asMap()); + + return request; } static Request evaluateDataFrame(EvaluateDataFrameRequest evaluateRequest) throws IOException { diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/DeleteDataFrameAnalyticsRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/DeleteDataFrameAnalyticsRequest.java index f03466632304..b7300430b55b 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/DeleteDataFrameAnalyticsRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/DeleteDataFrameAnalyticsRequest.java @@ -31,6 +31,7 @@ import java.util.Optional; public class DeleteDataFrameAnalyticsRequest implements Validatable { private final String id; + private Boolean force; public DeleteDataFrameAnalyticsRequest(String id) { this.id = id; @@ -40,6 +41,20 @@ public class DeleteDataFrameAnalyticsRequest implements Validatable { return id; } + public Boolean getForce() { + return force; + } + + /** + * Used to forcefully delete an job that is not stopped. + * This method is quicker than stopping and deleting the job. + * + * @param force When {@code true} forcefully delete a non stopped job. Defaults to {@code false} + */ + public void setForce(Boolean force) { + this.force = force; + } + @Override public Optional validate() { if (id == null) { @@ -54,11 +69,11 @@ public class DeleteDataFrameAnalyticsRequest implements Validatable { if (o == null || getClass() != o.getClass()) return false; DeleteDataFrameAnalyticsRequest other = (DeleteDataFrameAnalyticsRequest) o; - return Objects.equals(id, other.id); + return Objects.equals(id, other.id) && Objects.equals(force, other.force); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(id, force); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java index cf22ba80c162..475dda254448 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java @@ -25,8 +25,6 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.elasticsearch.client.core.PageParams; import org.elasticsearch.client.ml.CloseJobRequest; -import org.elasticsearch.client.ml.DeleteTrainedModelRequest; -import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest; import org.elasticsearch.client.ml.DeleteCalendarEventRequest; import org.elasticsearch.client.ml.DeleteCalendarJobRequest; import org.elasticsearch.client.ml.DeleteCalendarRequest; @@ -37,8 +35,10 @@ import org.elasticsearch.client.ml.DeleteFilterRequest; import org.elasticsearch.client.ml.DeleteForecastRequest; import org.elasticsearch.client.ml.DeleteJobRequest; import org.elasticsearch.client.ml.DeleteModelSnapshotRequest; +import org.elasticsearch.client.ml.DeleteTrainedModelRequest; import org.elasticsearch.client.ml.EvaluateDataFrameRequest; import org.elasticsearch.client.ml.EvaluateDataFrameRequestTests; +import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest; import org.elasticsearch.client.ml.FindFileStructureRequest; import org.elasticsearch.client.ml.FindFileStructureRequestTests; import org.elasticsearch.client.ml.FlushJobRequest; @@ -778,6 +778,15 @@ public class MLRequestConvertersTests extends ESTestCase { assertEquals(HttpDelete.METHOD_NAME, request.getMethod()); assertEquals("/_ml/data_frame/analytics/" + deleteRequest.getId(), request.getEndpoint()); assertNull(request.getEntity()); + assertThat(request.getParameters().isEmpty(), is(true)); + + deleteRequest = new DeleteDataFrameAnalyticsRequest(randomAlphaOfLength(10)); + deleteRequest.setForce(true); + request = MLRequestConverters.deleteDataFrameAnalytics(deleteRequest); + assertEquals(HttpDelete.METHOD_NAME, request.getMethod()); + assertEquals("/_ml/data_frame/analytics/" + deleteRequest.getId(), request.getEndpoint()); + assertNull(request.getEntity()); + assertEquals(Boolean.toString(true), request.getParameters().get("force")); } public void testEvaluateDataFrame() throws IOException { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java index 443c337d089f..f3b63801987d 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java @@ -1616,8 +1616,11 @@ public class MachineLearningIT extends ESRestHighLevelClientTestCase { machineLearningClient::getDataFrameAnalytics, machineLearningClient::getDataFrameAnalyticsAsync); assertThat(getDataFrameAnalyticsResponse.getAnalytics(), hasSize(1)); - AcknowledgedResponse deleteDataFrameAnalyticsResponse = execute( - new DeleteDataFrameAnalyticsRequest(configId), + DeleteDataFrameAnalyticsRequest deleteRequest = new DeleteDataFrameAnalyticsRequest(configId); + if (randomBoolean()) { + deleteRequest.setForce(randomBoolean()); + } + AcknowledgedResponse deleteDataFrameAnalyticsResponse = execute(deleteRequest, machineLearningClient::deleteDataFrameAnalytics, machineLearningClient::deleteDataFrameAnalyticsAsync); assertTrue(deleteDataFrameAnalyticsResponse.isAcknowledged()); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java index 12fea5f5c565..37ae59e9b992 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java @@ -36,9 +36,6 @@ import org.elasticsearch.client.core.PageParams; import org.elasticsearch.client.indices.CreateIndexRequest; import org.elasticsearch.client.ml.CloseJobRequest; import org.elasticsearch.client.ml.CloseJobResponse; -import org.elasticsearch.client.ml.DeleteTrainedModelRequest; -import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest; -import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsResponse; import org.elasticsearch.client.ml.DeleteCalendarEventRequest; import org.elasticsearch.client.ml.DeleteCalendarJobRequest; import org.elasticsearch.client.ml.DeleteCalendarRequest; @@ -51,8 +48,11 @@ import org.elasticsearch.client.ml.DeleteForecastRequest; import org.elasticsearch.client.ml.DeleteJobRequest; import org.elasticsearch.client.ml.DeleteJobResponse; import org.elasticsearch.client.ml.DeleteModelSnapshotRequest; +import org.elasticsearch.client.ml.DeleteTrainedModelRequest; import org.elasticsearch.client.ml.EvaluateDataFrameRequest; import org.elasticsearch.client.ml.EvaluateDataFrameResponse; +import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest; +import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsResponse; import org.elasticsearch.client.ml.FindFileStructureRequest; import org.elasticsearch.client.ml.FindFileStructureResponse; import org.elasticsearch.client.ml.FlushJobRequest; @@ -3065,6 +3065,10 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase { DeleteDataFrameAnalyticsRequest request = new DeleteDataFrameAnalyticsRequest("my-analytics-config"); // <1> // end::delete-data-frame-analytics-request + //tag::delete-data-frame-analytics-request-force + request.setForce(false); // <1> + //end::delete-data-frame-analytics-request-force + // tag::delete-data-frame-analytics-execute AcknowledgedResponse response = client.machineLearning().deleteDataFrameAnalytics(request, RequestOptions.DEFAULT); // end::delete-data-frame-analytics-execute diff --git a/docs/java-rest/high-level/ml/delete-data-frame-analytics.asciidoc b/docs/java-rest/high-level/ml/delete-data-frame-analytics.asciidoc index cb321d95c386..d57b37ff2174 100644 --- a/docs/java-rest/high-level/ml/delete-data-frame-analytics.asciidoc +++ b/docs/java-rest/high-level/ml/delete-data-frame-analytics.asciidoc @@ -21,6 +21,17 @@ include-tagged::{doc-tests-file}[{api}-request] --------------------------------------------------- <1> Constructing a new request referencing an existing {dfanalytics-job}. +==== Optional arguments + +The following arguments are optional: + +["source","java",subs="attributes,callouts,macros"] +--------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-request-force] +--------------------------------------------------- +<1> Use to forcefully delete a job that is not stopped. This method is quicker than stopping +and deleting the job. Defaults to `false`. + include::../execution.asciidoc[] [id="{upid}-{api}-response"] diff --git a/docs/reference/ml/df-analytics/apis/delete-dfanalytics.asciidoc b/docs/reference/ml/df-analytics/apis/delete-dfanalytics.asciidoc index 8d80e3bba878..a7adad79185d 100644 --- a/docs/reference/ml/df-analytics/apis/delete-dfanalytics.asciidoc +++ b/docs/reference/ml/df-analytics/apis/delete-dfanalytics.asciidoc @@ -21,7 +21,7 @@ experimental[] [[ml-delete-dfanalytics-prereq]] ==== {api-prereq-title} -* You must have `machine_learning_admin` built-in role to use this API. For more +* You must have `machine_learning_admin` built-in role to use this API. For more information, see <> and <>. @@ -32,6 +32,13 @@ information, see <> and <>. (Required, string) include::{docdir}/ml/ml-shared.asciidoc[tag=job-id-data-frame-analytics] +[[ml-delete-dfanalytics-query-params]] +==== {api-query-parms-title} + +`force`:: + (Optional, boolean) If `true`, it deletes a job that is not stopped; this method is + quicker than stopping and deleting the job. + [[ml-delete-dfanalytics-example]] ==== {api-examples-title} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteDataFrameAnalyticsAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteDataFrameAnalyticsAction.java index c9c16025bb69..40d70d979be9 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteDataFrameAnalyticsAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteDataFrameAnalyticsAction.java @@ -5,16 +5,16 @@ */ package org.elasticsearch.xpack.core.ml.action; +import org.elasticsearch.Version; 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.action.support.master.MasterNodeOperationRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.xcontent.ToXContentFragment; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsConfig; import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; @@ -30,13 +30,21 @@ public class DeleteDataFrameAnalyticsAction extends ActionType implements ToXContentFragment { + public static class Request extends AcknowledgedRequest { + + public static final ParseField FORCE = new ParseField("force"); private String id; + private boolean force; public Request(StreamInput in) throws IOException { super(in); id = in.readString(); + if (in.getVersion().onOrAfter(Version.CURRENT)) { + force = in.readBoolean(); + } else { + force = false; + } } public Request() {} @@ -49,15 +57,17 @@ public class DeleteDataFrameAnalyticsAction extends ActionType { super(NAME, KillProcessAction.Response::new); } - static class RequestBuilder extends ActionRequestBuilder { - - RequestBuilder(ElasticsearchClient client, KillProcessAction action) { - super(client, action, new Request()); - } - } - public static class Request extends JobTaskRequest { public Request(String jobId) { diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteDataFrameAnalyticsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteDataFrameAnalyticsAction.java index d42cf9684cca..47f2a216f2a8 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteDataFrameAnalyticsAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteDataFrameAnalyticsAction.java @@ -38,6 +38,7 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.ml.MlTasks; import org.elasticsearch.xpack.core.ml.action.DeleteDataFrameAnalyticsAction; +import org.elasticsearch.xpack.core.ml.action.StopDataFrameAnalyticsAction; import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsConfig; import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsState; import org.elasticsearch.xpack.core.ml.job.messages.Messages; @@ -100,6 +101,32 @@ public class TransportDeleteDataFrameAnalyticsAction protected void masterOperation(Task task, DeleteDataFrameAnalyticsAction.Request request, ClusterState state, ActionListener listener) { String id = request.getId(); + TaskId taskId = new TaskId(clusterService.localNode().getId(), task.getId()); + ParentTaskAssigningClient parentTaskClient = new ParentTaskAssigningClient(client, taskId); + + if (request.isForce()) { + forceDelete(parentTaskClient, id, listener); + } else { + normalDelete(parentTaskClient, state, id, listener); + } + } + + private void forceDelete(ParentTaskAssigningClient parentTaskClient, String id, + ActionListener listener) { + logger.debug("[{}] Force deleting data frame analytics job", id); + + ActionListener stopListener = ActionListener.wrap( + stopResponse -> normalDelete(parentTaskClient, clusterService.state(), id, listener), + listener::onFailure + ); + + StopDataFrameAnalyticsAction.Request request = new StopDataFrameAnalyticsAction.Request(id); + request.setForce(true); + executeAsyncWithOrigin(parentTaskClient, ML_ORIGIN, StopDataFrameAnalyticsAction.INSTANCE, request, stopListener); + } + + private void normalDelete(ParentTaskAssigningClient parentTaskClient, ClusterState state, String id, + ActionListener listener) { PersistentTasksCustomMetaData tasks = state.getMetaData().custom(PersistentTasksCustomMetaData.TYPE); DataFrameAnalyticsState taskState = MlTasks.getDataFrameAnalyticsState(id, tasks); if (taskState != DataFrameAnalyticsState.STOPPED) { @@ -108,9 +135,6 @@ public class TransportDeleteDataFrameAnalyticsAction return; } - TaskId taskId = new TaskId(clusterService.localNode().getId(), task.getId()); - ParentTaskAssigningClient parentTaskClient = new ParentTaskAssigningClient(client, taskId); - // We clean up the memory tracker on delete because there is no stop; the task stops by itself memoryTracker.removeDataFrameAnalyticsJob(id); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/dataframe/RestDeleteDataFrameAnalyticsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/dataframe/RestDeleteDataFrameAnalyticsAction.java index 26edf340b2b1..9e78a7cd9f21 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/dataframe/RestDeleteDataFrameAnalyticsAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/dataframe/RestDeleteDataFrameAnalyticsAction.java @@ -32,6 +32,7 @@ public class RestDeleteDataFrameAnalyticsAction extends BaseRestHandler { protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException { String id = restRequest.param(DataFrameAnalyticsConfig.ID.getPreferredName()); DeleteDataFrameAnalyticsAction.Request request = new DeleteDataFrameAnalyticsAction.Request(id); + request.setForce(restRequest.paramAsBoolean(DeleteDataFrameAnalyticsAction.Request.FORCE.getPreferredName(), request.isForce())); return channel -> client.execute(DeleteDataFrameAnalyticsAction.INSTANCE, request, new RestToXContentListener<>(channel)); } } diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/api/ml.delete_data_frame_analytics.json b/x-pack/plugin/src/test/resources/rest-api-spec/api/ml.delete_data_frame_analytics.json index bb2101765c75..98f6a3eedaba 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/api/ml.delete_data_frame_analytics.json +++ b/x-pack/plugin/src/test/resources/rest-api-spec/api/ml.delete_data_frame_analytics.json @@ -19,6 +19,13 @@ } } ] + }, + "params":{ + "force":{ + "type":"boolean", + "description":"True if the job should be forcefully deleted", + "default":false + } } } }