Skip to content

Commit 0af38bb

Browse files
authored
[ML] add new delete trained model aliases API (#69195)
In addition to creating and re-assigning model aliases, users should be able to delete existing and unused model aliases.
1 parent f2b27c1 commit 0af38bb

File tree

14 files changed

+470
-2
lines changed

14 files changed

+470
-2
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
[role="xpack"]
2+
[testenv="platinum"]
3+
[[delete-trained-models-aliases]]
4+
= Delete Trained Model Aliases API
5+
[subs="attributes"]
6+
++++
7+
<titleabbrev>Delete Trained Model Aliases</titleabbrev>
8+
++++
9+
10+
Deletes a trained model alias.
11+
12+
beta::[]
13+
14+
[[ml-delete-trained-models-aliases-request]]
15+
== {api-request-title}
16+
17+
`DELETE _ml/trained_models/<model_id>/model_aliases/<model_alias>`
18+
19+
20+
[[ml-delete-trained-models-aliases-prereq]]
21+
== {api-prereq-title}
22+
23+
If the {es} {security-features} are enabled, you must have the following
24+
built-in roles and privileges:
25+
26+
* `machine_learning_admin`
27+
28+
For more information, see <<built-in-roles>>, <<security-privileges>>, and
29+
{ml-docs-setup-privileges}.
30+
31+
[[ml-delete-trained-models-aliases-desc]]
32+
== {api-description-title}
33+
34+
This API deletes an existing model alias that refers to a trained model.
35+
36+
If the model alias is missing or refers to a model other than the one identified by
37+
the `model_id`, this API will return an error.
38+
39+
[[ml-delete-trained-models-aliases-path-params]]
40+
== {api-path-parms-title}
41+
42+
`model_id`::
43+
(Required, string)
44+
The trained model ID to which the model alias refers.
45+
46+
`model_alias`::
47+
(Required, string)
48+
The model alias to delete.
49+
50+
[[ml-delete-trained-models-aliases-example]]
51+
== {api-examples-title}
52+
53+
[[ml-delete-trained-models-aliases-example-delete]]
54+
=== Deleting a model alias
55+
56+
The following example shows how to delete a model alias for a trained model ID.
57+
58+
[source,console]
59+
--------------------------------------------------
60+
DELETE _ml/trained_models/flight-delay-prediction-1574775339910/model_aliases/flight_delay_model
61+
--------------------------------------------------
62+
// TEST[skip:setup kibana sample data]

docs/reference/ml/df-analytics/apis/index.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ include::update-dfanalytics.asciidoc[leveloffset=+2]
88
//DELETE
99
include::delete-dfanalytics.asciidoc[leveloffset=+2]
1010
include::delete-trained-models.asciidoc[leveloffset=+2]
11+
include::delete-trained-models-aliases.asciidoc[leveloffset=+2]
1112
//EVALUATE
1213
include::evaluate-dfanalytics.asciidoc[leveloffset=+2]
1314
//ESTIMATE_MEMORY_USAGE

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ You can use the following APIs to perform {infer} operations.
2323
* <<get-trained-models-stats>>
2424
* <<delete-trained-models>>
2525
* <<put-trained-models-aliases>>
26+
* <<delete-trained-models-aliases>>
2627

2728
You can deploy a trained model to make predictions in an ingest pipeline or in
2829
an aggregation. Refer to the following documentation to learn more.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.core.ml.action;
9+
10+
import org.elasticsearch.action.ActionRequestValidationException;
11+
import org.elasticsearch.action.ActionType;
12+
import org.elasticsearch.action.support.master.AcknowledgedRequest;
13+
import org.elasticsearch.action.support.master.AcknowledgedResponse;
14+
import org.elasticsearch.common.io.stream.StreamInput;
15+
import org.elasticsearch.common.io.stream.StreamOutput;
16+
import org.elasticsearch.xpack.core.ml.inference.TrainedModelConfig;
17+
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
18+
19+
import java.io.IOException;
20+
import java.util.Objects;
21+
22+
23+
public class DeleteTrainedModelAliasAction extends ActionType<AcknowledgedResponse> {
24+
25+
public static final DeleteTrainedModelAliasAction INSTANCE = new DeleteTrainedModelAliasAction();
26+
public static final String NAME = "cluster:admin/xpack/ml/inference/model_aliases/delete";
27+
28+
private DeleteTrainedModelAliasAction() {
29+
super(NAME, AcknowledgedResponse::readFrom);
30+
}
31+
32+
public static class Request extends AcknowledgedRequest<Request> {
33+
34+
public static final String MODEL_ALIAS = "model_alias";
35+
36+
private final String modelAlias;
37+
private final String modelId;
38+
39+
public Request(String modelAlias, String modelId) {
40+
this.modelAlias = ExceptionsHelper.requireNonNull(modelAlias, MODEL_ALIAS);
41+
this.modelId = ExceptionsHelper.requireNonNull(modelId, TrainedModelConfig.MODEL_ID);
42+
}
43+
44+
public Request(StreamInput in) throws IOException {
45+
super(in);
46+
this.modelAlias = in.readString();
47+
this.modelId = in.readString();
48+
}
49+
50+
public String getModelAlias() {
51+
return modelAlias;
52+
}
53+
54+
public String getModelId() {
55+
return modelId;
56+
}
57+
58+
@Override
59+
public void writeTo(StreamOutput out) throws IOException {
60+
super.writeTo(out);
61+
out.writeString(modelAlias);
62+
out.writeString(modelId);
63+
}
64+
65+
@Override
66+
public ActionRequestValidationException validate() {
67+
return null;
68+
}
69+
70+
@Override
71+
public boolean equals(Object o) {
72+
if (this == o) return true;
73+
if (o == null || getClass() != o.getClass()) return false;
74+
Request request = (Request) o;
75+
return Objects.equals(modelAlias, request.modelAlias)
76+
&& Objects.equals(modelId, request.modelId);
77+
}
78+
79+
@Override
80+
public int hashCode() {
81+
return Objects.hash(modelAlias, modelId);
82+
}
83+
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
package org.elasticsearch.xpack.core.ml.action;
8+
9+
import org.elasticsearch.common.io.stream.Writeable;
10+
import org.elasticsearch.test.AbstractWireSerializingTestCase;
11+
import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAliasAction.Request;
12+
13+
14+
public class DeleteTrainedModelAliasActionRequestTests extends AbstractWireSerializingTestCase<Request> {
15+
16+
@Override
17+
protected Request createTestInstance() {
18+
return new Request(randomAlphaOfLength(10), randomAlphaOfLength(10));
19+
}
20+
21+
@Override
22+
protected Writeable.Reader<Request> instanceReader() {
23+
return Request::new;
24+
}
25+
26+
public void testCtor() {
27+
expectThrows(Exception.class, () -> new Request(null, randomAlphaOfLength(10)));
28+
expectThrows(Exception.class, () -> new Request(randomAlphaOfLength(10), null));
29+
}
30+
31+
}

x-pack/plugin/ml/qa/ml-with-security/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ tasks.named("yamlRestTest").configure {
149149
'ml/inference_crud/Test update model alias with bad alias',
150150
'ml/inference_crud/Test update model alias where alias exists but old model id is different inference type',
151151
'ml/inference_crud/Test update model alias where alias exists but reassign is false',
152+
'ml/inference_crud/Test delete model alias with missing alias',
153+
'ml/inference_crud/Test delete model alias where alias points to different model',
152154
'ml/inference_processor/Test create processor with missing mandatory fields',
153155
'ml/inference_stats_crud/Test get stats given missing trained model',
154156
'ml/inference_stats_crud/Test get stats given expression without matches and allow_no_match is false',

x-pack/plugin/ml/qa/single-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/InferenceProcessorIT.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,27 @@ public void testDeleteModelWhileAliasReferencedByPipeline() throws Exception {
226226
waitForStats();
227227
}
228228

229+
public void testDeleteModelAliasWhileAliasReferencedByPipeline() throws Exception {
230+
putRegressionModel();
231+
putModelAlias("regression_to_delete", MODEL_ID);
232+
createdPipelines.add("first_pipeline");
233+
putPipeline("regression_to_delete", "first_pipeline");
234+
Exception ex = expectThrows(Exception.class,
235+
() -> client().performRequest(
236+
new Request(
237+
"DELETE",
238+
"_ml/trained_models/" + MODEL_ID + "/model_aliases/regression_to_delete"
239+
)
240+
));
241+
assertThat(
242+
ex.getMessage(),
243+
containsString("Cannot delete model_alias [regression_to_delete] as it is still referenced by ingest processors")
244+
);
245+
infer("first_pipeline");
246+
deletePipeline("first_pipeline");
247+
waitForStats();
248+
}
249+
229250
public void testDeleteModelWhileReferencedByPipeline() throws Exception {
230251
putRegressionModel();
231252
createdPipelines.add("first_pipeline");

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
import org.elasticsearch.xpack.core.ml.action.DeleteJobAction;
9292
import org.elasticsearch.xpack.core.ml.action.DeleteModelSnapshotAction;
9393
import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAction;
94+
import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAliasAction;
9495
import org.elasticsearch.xpack.core.ml.action.EstimateModelMemoryAction;
9596
import org.elasticsearch.xpack.core.ml.action.EvaluateDataFrameAction;
9697
import org.elasticsearch.xpack.core.ml.action.ExplainDataFrameAnalyticsAction;
@@ -170,6 +171,7 @@
170171
import org.elasticsearch.xpack.ml.action.TransportDeleteJobAction;
171172
import org.elasticsearch.xpack.ml.action.TransportDeleteModelSnapshotAction;
172173
import org.elasticsearch.xpack.ml.action.TransportDeleteTrainedModelAction;
174+
import org.elasticsearch.xpack.ml.action.TransportDeleteTrainedModelAliasAction;
173175
import org.elasticsearch.xpack.ml.action.TransportEstimateModelMemoryAction;
174176
import org.elasticsearch.xpack.ml.action.TransportEvaluateDataFrameAction;
175177
import org.elasticsearch.xpack.ml.action.TransportExplainDataFrameAnalyticsAction;
@@ -317,6 +319,7 @@
317319
import org.elasticsearch.xpack.ml.rest.filter.RestPutFilterAction;
318320
import org.elasticsearch.xpack.ml.rest.filter.RestUpdateFilterAction;
319321
import org.elasticsearch.xpack.ml.rest.inference.RestDeleteTrainedModelAction;
322+
import org.elasticsearch.xpack.ml.rest.inference.RestDeleteTrainedModelAliasAction;
320323
import org.elasticsearch.xpack.ml.rest.inference.RestGetTrainedModelsAction;
321324
import org.elasticsearch.xpack.ml.rest.inference.RestGetTrainedModelsStatsAction;
322325
import org.elasticsearch.xpack.ml.rest.inference.RestPutTrainedModelAction;
@@ -939,6 +942,7 @@ public List<RestHandler> getRestHandlers(Settings settings, RestController restC
939942
new RestPutTrainedModelAction(),
940943
new RestUpgradeJobModelSnapshotAction(),
941944
new RestPutTrainedModelAliasAction(),
945+
new RestDeleteTrainedModelAliasAction(),
942946
// CAT Handlers
943947
new RestCatJobsAction(),
944948
new RestCatTrainedModelsAction(),
@@ -1023,6 +1027,7 @@ public List<RestHandler> getRestHandlers(Settings settings, RestController restC
10231027
new ActionHandler<>(PutTrainedModelAction.INSTANCE, TransportPutTrainedModelAction.class),
10241028
new ActionHandler<>(UpgradeJobModelSnapshotAction.INSTANCE, TransportUpgradeJobModelSnapshotAction.class),
10251029
new ActionHandler<>(PutTrainedModelAliasAction.INSTANCE, TransportPutTrainedModelAliasAction.class),
1030+
new ActionHandler<>(DeleteTrainedModelAliasAction.INSTANCE, TransportDeleteTrainedModelAliasAction.class),
10261031
usageAction,
10271032
infoAction);
10281033
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ protected void masterOperation(Task task,
7777
ActionListener<AcknowledgedResponse> listener) {
7878
String id = request.getId();
7979
IngestMetadata currentIngestMetadata = state.metadata().custom(IngestMetadata.TYPE);
80-
Set<String> referencedModels = getReferencedModelKeys(currentIngestMetadata);
80+
Set<String> referencedModels = getReferencedModelKeys(currentIngestMetadata, ingestService);
8181

8282
if (referencedModels.contains(id)) {
8383
listener.onFailure(new ElasticsearchStatusException("Cannot delete model [{}] as it is still referenced by ingest processors",
@@ -142,7 +142,7 @@ public ClusterState execute(final ClusterState currentState) {
142142
});
143143
}
144144

145-
private Set<String> getReferencedModelKeys(IngestMetadata ingestMetadata) {
145+
static Set<String> getReferencedModelKeys(IngestMetadata ingestMetadata, IngestService ingestService) {
146146
Set<String> allReferencedModelKeys = new HashSet<>();
147147
if (ingestMetadata == null) {
148148
return allReferencedModelKeys;

0 commit comments

Comments
 (0)