Skip to content

Commit af0ce42

Browse files
[ML] Implement force deleting a data frame analytics job (#50553)
Adds a `force` parameter to the delete data frame analytics request. When `force` is `true`, the action force-stops the jobs and then proceeds to the deletion. This can be used in order to delete a non-stopped job with a single request. Closes #48124
1 parent 32730cf commit af0ce42

File tree

12 files changed

+130
-36
lines changed

12 files changed

+130
-36
lines changed

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

+12-3
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
import org.elasticsearch.client.RequestConverters.EndpointBuilder;
3030
import org.elasticsearch.client.core.PageParams;
3131
import org.elasticsearch.client.ml.CloseJobRequest;
32-
import org.elasticsearch.client.ml.DeleteTrainedModelRequest;
33-
import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest;
3432
import org.elasticsearch.client.ml.DeleteCalendarEventRequest;
3533
import org.elasticsearch.client.ml.DeleteCalendarJobRequest;
3634
import org.elasticsearch.client.ml.DeleteCalendarRequest;
@@ -41,7 +39,9 @@
4139
import org.elasticsearch.client.ml.DeleteForecastRequest;
4240
import org.elasticsearch.client.ml.DeleteJobRequest;
4341
import org.elasticsearch.client.ml.DeleteModelSnapshotRequest;
42+
import org.elasticsearch.client.ml.DeleteTrainedModelRequest;
4443
import org.elasticsearch.client.ml.EvaluateDataFrameRequest;
44+
import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest;
4545
import org.elasticsearch.client.ml.FindFileStructureRequest;
4646
import org.elasticsearch.client.ml.FlushJobRequest;
4747
import org.elasticsearch.client.ml.ForecastJobRequest;
@@ -692,7 +692,16 @@ static Request deleteDataFrameAnalytics(DeleteDataFrameAnalyticsRequest deleteRe
692692
.addPathPartAsIs("_ml", "data_frame", "analytics")
693693
.addPathPart(deleteRequest.getId())
694694
.build();
695-
return new Request(HttpDelete.METHOD_NAME, endpoint);
695+
696+
Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
697+
698+
RequestConverters.Params params = new RequestConverters.Params();
699+
if (deleteRequest.getForce() != null) {
700+
params.putParam("force", Boolean.toString(deleteRequest.getForce()));
701+
}
702+
request.addParameters(params.asMap());
703+
704+
return request;
696705
}
697706

698707
static Request evaluateDataFrame(EvaluateDataFrameRequest evaluateRequest) throws IOException {

client/rest-high-level/src/main/java/org/elasticsearch/client/ml/DeleteDataFrameAnalyticsRequest.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
public class DeleteDataFrameAnalyticsRequest implements Validatable {
3232

3333
private final String id;
34+
private Boolean force;
3435

3536
public DeleteDataFrameAnalyticsRequest(String id) {
3637
this.id = id;
@@ -40,6 +41,20 @@ public String getId() {
4041
return id;
4142
}
4243

44+
public Boolean getForce() {
45+
return force;
46+
}
47+
48+
/**
49+
* Used to forcefully delete an job that is not stopped.
50+
* This method is quicker than stopping and deleting the job.
51+
*
52+
* @param force When {@code true} forcefully delete a non stopped job. Defaults to {@code false}
53+
*/
54+
public void setForce(Boolean force) {
55+
this.force = force;
56+
}
57+
4358
@Override
4459
public Optional<ValidationException> validate() {
4560
if (id == null) {
@@ -54,11 +69,11 @@ public boolean equals(Object o) {
5469
if (o == null || getClass() != o.getClass()) return false;
5570

5671
DeleteDataFrameAnalyticsRequest other = (DeleteDataFrameAnalyticsRequest) o;
57-
return Objects.equals(id, other.id);
72+
return Objects.equals(id, other.id) && Objects.equals(force, other.force);
5873
}
5974

6075
@Override
6176
public int hashCode() {
62-
return Objects.hash(id);
77+
return Objects.hash(id, force);
6378
}
6479
}

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

+11-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
import org.apache.http.client.methods.HttpPut;
2626
import org.elasticsearch.client.core.PageParams;
2727
import org.elasticsearch.client.ml.CloseJobRequest;
28-
import org.elasticsearch.client.ml.DeleteTrainedModelRequest;
29-
import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest;
3028
import org.elasticsearch.client.ml.DeleteCalendarEventRequest;
3129
import org.elasticsearch.client.ml.DeleteCalendarJobRequest;
3230
import org.elasticsearch.client.ml.DeleteCalendarRequest;
@@ -37,8 +35,10 @@
3735
import org.elasticsearch.client.ml.DeleteForecastRequest;
3836
import org.elasticsearch.client.ml.DeleteJobRequest;
3937
import org.elasticsearch.client.ml.DeleteModelSnapshotRequest;
38+
import org.elasticsearch.client.ml.DeleteTrainedModelRequest;
4039
import org.elasticsearch.client.ml.EvaluateDataFrameRequest;
4140
import org.elasticsearch.client.ml.EvaluateDataFrameRequestTests;
41+
import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest;
4242
import org.elasticsearch.client.ml.FindFileStructureRequest;
4343
import org.elasticsearch.client.ml.FindFileStructureRequestTests;
4444
import org.elasticsearch.client.ml.FlushJobRequest;
@@ -778,6 +778,15 @@ public void testDeleteDataFrameAnalytics() {
778778
assertEquals(HttpDelete.METHOD_NAME, request.getMethod());
779779
assertEquals("/_ml/data_frame/analytics/" + deleteRequest.getId(), request.getEndpoint());
780780
assertNull(request.getEntity());
781+
assertThat(request.getParameters().isEmpty(), is(true));
782+
783+
deleteRequest = new DeleteDataFrameAnalyticsRequest(randomAlphaOfLength(10));
784+
deleteRequest.setForce(true);
785+
request = MLRequestConverters.deleteDataFrameAnalytics(deleteRequest);
786+
assertEquals(HttpDelete.METHOD_NAME, request.getMethod());
787+
assertEquals("/_ml/data_frame/analytics/" + deleteRequest.getId(), request.getEndpoint());
788+
assertNull(request.getEntity());
789+
assertEquals(Boolean.toString(true), request.getParameters().get("force"));
781790
}
782791

783792
public void testEvaluateDataFrame() throws IOException {

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -1616,8 +1616,11 @@ public void testDeleteDataFrameAnalyticsConfig() throws Exception {
16161616
machineLearningClient::getDataFrameAnalytics, machineLearningClient::getDataFrameAnalyticsAsync);
16171617
assertThat(getDataFrameAnalyticsResponse.getAnalytics(), hasSize(1));
16181618

1619-
AcknowledgedResponse deleteDataFrameAnalyticsResponse = execute(
1620-
new DeleteDataFrameAnalyticsRequest(configId),
1619+
DeleteDataFrameAnalyticsRequest deleteRequest = new DeleteDataFrameAnalyticsRequest(configId);
1620+
if (randomBoolean()) {
1621+
deleteRequest.setForce(randomBoolean());
1622+
}
1623+
AcknowledgedResponse deleteDataFrameAnalyticsResponse = execute(deleteRequest,
16211624
machineLearningClient::deleteDataFrameAnalytics, machineLearningClient::deleteDataFrameAnalyticsAsync);
16221625
assertTrue(deleteDataFrameAnalyticsResponse.isAcknowledged());
16231626

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@
3636
import org.elasticsearch.client.indices.CreateIndexRequest;
3737
import org.elasticsearch.client.ml.CloseJobRequest;
3838
import org.elasticsearch.client.ml.CloseJobResponse;
39-
import org.elasticsearch.client.ml.DeleteTrainedModelRequest;
40-
import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest;
41-
import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsResponse;
4239
import org.elasticsearch.client.ml.DeleteCalendarEventRequest;
4340
import org.elasticsearch.client.ml.DeleteCalendarJobRequest;
4441
import org.elasticsearch.client.ml.DeleteCalendarRequest;
@@ -51,8 +48,11 @@
5148
import org.elasticsearch.client.ml.DeleteJobRequest;
5249
import org.elasticsearch.client.ml.DeleteJobResponse;
5350
import org.elasticsearch.client.ml.DeleteModelSnapshotRequest;
51+
import org.elasticsearch.client.ml.DeleteTrainedModelRequest;
5452
import org.elasticsearch.client.ml.EvaluateDataFrameRequest;
5553
import org.elasticsearch.client.ml.EvaluateDataFrameResponse;
54+
import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsRequest;
55+
import org.elasticsearch.client.ml.ExplainDataFrameAnalyticsResponse;
5656
import org.elasticsearch.client.ml.FindFileStructureRequest;
5757
import org.elasticsearch.client.ml.FindFileStructureResponse;
5858
import org.elasticsearch.client.ml.FlushJobRequest;
@@ -3065,6 +3065,10 @@ public void testDeleteDataFrameAnalytics() throws Exception {
30653065
DeleteDataFrameAnalyticsRequest request = new DeleteDataFrameAnalyticsRequest("my-analytics-config"); // <1>
30663066
// end::delete-data-frame-analytics-request
30673067

3068+
//tag::delete-data-frame-analytics-request-force
3069+
request.setForce(false); // <1>
3070+
//end::delete-data-frame-analytics-request-force
3071+
30683072
// tag::delete-data-frame-analytics-execute
30693073
AcknowledgedResponse response = client.machineLearning().deleteDataFrameAnalytics(request, RequestOptions.DEFAULT);
30703074
// end::delete-data-frame-analytics-execute

docs/java-rest/high-level/ml/delete-data-frame-analytics.asciidoc

+11
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ include-tagged::{doc-tests-file}[{api}-request]
2121
---------------------------------------------------
2222
<1> Constructing a new request referencing an existing {dfanalytics-job}.
2323

24+
==== Optional arguments
25+
26+
The following arguments are optional:
27+
28+
["source","java",subs="attributes,callouts,macros"]
29+
---------------------------------------------------
30+
include-tagged::{doc-tests-file}[{api}-request-force]
31+
---------------------------------------------------
32+
<1> Use to forcefully delete a job that is not stopped. This method is quicker than stopping
33+
and deleting the job. Defaults to `false`.
34+
2435
include::../execution.asciidoc[]
2536

2637
[id="{upid}-{api}-response"]

docs/reference/ml/df-analytics/apis/delete-dfanalytics.asciidoc

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ experimental[]
2121
[[ml-delete-dfanalytics-prereq]]
2222
==== {api-prereq-title}
2323

24-
* You must have `machine_learning_admin` built-in role to use this API. For more
24+
* You must have `machine_learning_admin` built-in role to use this API. For more
2525
information, see <<security-privileges>> and <<built-in-roles>>.
2626

2727

@@ -32,6 +32,13 @@ information, see <<security-privileges>> and <<built-in-roles>>.
3232
(Required, string)
3333
include::{docdir}/ml/ml-shared.asciidoc[tag=job-id-data-frame-analytics]
3434

35+
[[ml-delete-dfanalytics-query-params]]
36+
==== {api-query-parms-title}
37+
38+
`force`::
39+
(Optional, boolean) If `true`, it deletes a job that is not stopped; this method is
40+
quicker than stopping and deleting the job.
41+
3542

3643
[[ml-delete-dfanalytics-example]]
3744
==== {api-examples-title}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteDataFrameAnalyticsAction.java

+24-11
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
*/
66
package org.elasticsearch.xpack.core.ml.action;
77

8+
import org.elasticsearch.Version;
89
import org.elasticsearch.action.ActionRequestValidationException;
910
import org.elasticsearch.action.ActionType;
1011
import org.elasticsearch.action.support.master.AcknowledgedRequest;
1112
import org.elasticsearch.action.support.master.AcknowledgedResponse;
1213
import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder;
1314
import org.elasticsearch.client.ElasticsearchClient;
15+
import org.elasticsearch.common.ParseField;
1416
import org.elasticsearch.common.io.stream.StreamInput;
1517
import org.elasticsearch.common.io.stream.StreamOutput;
16-
import org.elasticsearch.common.xcontent.ToXContentFragment;
17-
import org.elasticsearch.common.xcontent.XContentBuilder;
1818
import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsConfig;
1919
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
2020

@@ -30,13 +30,21 @@ private DeleteDataFrameAnalyticsAction() {
3030
super(NAME, AcknowledgedResponse::new);
3131
}
3232

33-
public static class Request extends AcknowledgedRequest<Request> implements ToXContentFragment {
33+
public static class Request extends AcknowledgedRequest<Request> {
34+
35+
public static final ParseField FORCE = new ParseField("force");
3436

3537
private String id;
38+
private boolean force;
3639

3740
public Request(StreamInput in) throws IOException {
3841
super(in);
3942
id = in.readString();
43+
if (in.getVersion().onOrAfter(Version.CURRENT)) {
44+
force = in.readBoolean();
45+
} else {
46+
force = false;
47+
}
4048
}
4149

4250
public Request() {}
@@ -49,34 +57,39 @@ public String getId() {
4957
return id;
5058
}
5159

52-
@Override
53-
public ActionRequestValidationException validate() {
54-
return null;
60+
public boolean isForce() {
61+
return force;
62+
}
63+
64+
public void setForce(boolean force) {
65+
this.force = force;
5566
}
5667

5768
@Override
58-
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
59-
builder.field(DataFrameAnalyticsConfig.ID.getPreferredName(), id);
60-
return builder;
69+
public ActionRequestValidationException validate() {
70+
return null;
6171
}
6272

6373
@Override
6474
public boolean equals(Object o) {
6575
if (this == o) return true;
6676
if (o == null || getClass() != o.getClass()) return false;
6777
DeleteDataFrameAnalyticsAction.Request request = (DeleteDataFrameAnalyticsAction.Request) o;
68-
return Objects.equals(id, request.id);
78+
return Objects.equals(id, request.id) && force == request.force;
6979
}
7080

7181
@Override
7282
public void writeTo(StreamOutput out) throws IOException {
7383
super.writeTo(out);
7484
out.writeString(id);
85+
if (out.getVersion().onOrAfter(Version.CURRENT)) {
86+
out.writeBoolean(force);
87+
}
7588
}
7689

7790
@Override
7891
public int hashCode() {
79-
return Objects.hash(id);
92+
return Objects.hash(id, force);
8093
}
8194
}
8295

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/KillProcessAction.java

-9
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
package org.elasticsearch.xpack.core.ml.action;
77

88
import org.elasticsearch.action.ActionType;
9-
import org.elasticsearch.action.ActionRequestBuilder;
109
import org.elasticsearch.action.support.tasks.BaseTasksResponse;
11-
import org.elasticsearch.client.ElasticsearchClient;
1210
import org.elasticsearch.common.io.stream.StreamInput;
1311
import org.elasticsearch.common.io.stream.StreamOutput;
1412
import org.elasticsearch.common.io.stream.Writeable;
@@ -25,13 +23,6 @@ private KillProcessAction() {
2523
super(NAME, KillProcessAction.Response::new);
2624
}
2725

28-
static class RequestBuilder extends ActionRequestBuilder<Request, Response> {
29-
30-
RequestBuilder(ElasticsearchClient client, KillProcessAction action) {
31-
super(client, action, new Request());
32-
}
33-
}
34-
3526
public static class Request extends JobTaskRequest<Request> {
3627

3728
public Request(String jobId) {

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteDataFrameAnalyticsAction.java

+27-3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.elasticsearch.transport.TransportService;
3939
import org.elasticsearch.xpack.core.ml.MlTasks;
4040
import org.elasticsearch.xpack.core.ml.action.DeleteDataFrameAnalyticsAction;
41+
import org.elasticsearch.xpack.core.ml.action.StopDataFrameAnalyticsAction;
4142
import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsConfig;
4243
import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsState;
4344
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
@@ -100,6 +101,32 @@ protected AcknowledgedResponse read(StreamInput in) throws IOException {
100101
protected void masterOperation(Task task, DeleteDataFrameAnalyticsAction.Request request, ClusterState state,
101102
ActionListener<AcknowledgedResponse> listener) {
102103
String id = request.getId();
104+
TaskId taskId = new TaskId(clusterService.localNode().getId(), task.getId());
105+
ParentTaskAssigningClient parentTaskClient = new ParentTaskAssigningClient(client, taskId);
106+
107+
if (request.isForce()) {
108+
forceDelete(parentTaskClient, id, listener);
109+
} else {
110+
normalDelete(parentTaskClient, state, id, listener);
111+
}
112+
}
113+
114+
private void forceDelete(ParentTaskAssigningClient parentTaskClient, String id,
115+
ActionListener<AcknowledgedResponse> listener) {
116+
logger.debug("[{}] Force deleting data frame analytics job", id);
117+
118+
ActionListener<StopDataFrameAnalyticsAction.Response> stopListener = ActionListener.wrap(
119+
stopResponse -> normalDelete(parentTaskClient, clusterService.state(), id, listener),
120+
listener::onFailure
121+
);
122+
123+
StopDataFrameAnalyticsAction.Request request = new StopDataFrameAnalyticsAction.Request(id);
124+
request.setForce(true);
125+
executeAsyncWithOrigin(parentTaskClient, ML_ORIGIN, StopDataFrameAnalyticsAction.INSTANCE, request, stopListener);
126+
}
127+
128+
private void normalDelete(ParentTaskAssigningClient parentTaskClient, ClusterState state, String id,
129+
ActionListener<AcknowledgedResponse> listener) {
103130
PersistentTasksCustomMetaData tasks = state.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
104131
DataFrameAnalyticsState taskState = MlTasks.getDataFrameAnalyticsState(id, tasks);
105132
if (taskState != DataFrameAnalyticsState.STOPPED) {
@@ -108,9 +135,6 @@ protected void masterOperation(Task task, DeleteDataFrameAnalyticsAction.Request
108135
return;
109136
}
110137

111-
TaskId taskId = new TaskId(clusterService.localNode().getId(), task.getId());
112-
ParentTaskAssigningClient parentTaskClient = new ParentTaskAssigningClient(client, taskId);
113-
114138
// We clean up the memory tracker on delete because there is no stop; the task stops by itself
115139
memoryTracker.removeDataFrameAnalyticsJob(id);
116140

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/dataframe/RestDeleteDataFrameAnalyticsAction.java

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public String getName() {
3232
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
3333
String id = restRequest.param(DataFrameAnalyticsConfig.ID.getPreferredName());
3434
DeleteDataFrameAnalyticsAction.Request request = new DeleteDataFrameAnalyticsAction.Request(id);
35+
request.setForce(restRequest.paramAsBoolean(DeleteDataFrameAnalyticsAction.Request.FORCE.getPreferredName(), request.isForce()));
3536
return channel -> client.execute(DeleteDataFrameAnalyticsAction.INSTANCE, request, new RestToXContentListener<>(channel));
3637
}
3738
}

x-pack/plugin/src/test/resources/rest-api-spec/api/ml.delete_data_frame_analytics.json

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
}
2020
}
2121
]
22+
},
23+
"params":{
24+
"force":{
25+
"type":"boolean",
26+
"description":"True if the job should be forcefully deleted",
27+
"default":false
28+
}
2229
}
2330
}
2431
}

0 commit comments

Comments
 (0)