diff --git a/docs/reference/ml/df-analytics/apis/delete-trained-models-aliases.asciidoc b/docs/reference/ml/df-analytics/apis/delete-trained-models-aliases.asciidoc
new file mode 100644
index 0000000000000..f3c8602219181
--- /dev/null
+++ b/docs/reference/ml/df-analytics/apis/delete-trained-models-aliases.asciidoc
@@ -0,0 +1,62 @@
+[role="xpack"]
+[testenv="platinum"]
+[[delete-trained-models-aliases]]
+= Delete Trained Model Aliases API
+[subs="attributes"]
+++++
+Delete Trained Model Aliases
+++++
+
+Deletes a trained model alias.
+
+beta::[]
+
+[[ml-delete-trained-models-aliases-request]]
+== {api-request-title}
+
+`DELETE _ml/trained_models//model_aliases/`
+
+
+[[ml-delete-trained-models-aliases-prereq]]
+== {api-prereq-title}
+
+If the {es} {security-features} are enabled, you must have the following
+built-in roles and privileges:
+
+* `machine_learning_admin`
+
+For more information, see <>, <>, and
+{ml-docs-setup-privileges}.
+
+[[ml-delete-trained-models-aliases-desc]]
+== {api-description-title}
+
+This API deletes an existing model alias that refers to a trained model.
+
+If the model alias is missing or refers to a model other than the one identified by
+the `model_id`, this API will return an error.
+
+[[ml-delete-trained-models-aliases-path-params]]
+== {api-path-parms-title}
+
+`model_id`::
+(Required, string)
+The trained model ID to which the model alias refers.
+
+`model_alias`::
+(Required, string)
+The model alias to delete.
+
+[[ml-delete-trained-models-aliases-example]]
+== {api-examples-title}
+
+[[ml-delete-trained-models-aliases-example-delete]]
+=== Deleting a model alias
+
+The following example shows how to delete a model alias for a trained model ID.
+
+[source,console]
+--------------------------------------------------
+DELETE _ml/trained_models/flight-delay-prediction-1574775339910/model_aliases/flight_delay_model
+--------------------------------------------------
+// TEST[skip:setup kibana sample data]
diff --git a/docs/reference/ml/df-analytics/apis/index.asciidoc b/docs/reference/ml/df-analytics/apis/index.asciidoc
index 958298f027874..dcf35454f5b3b 100644
--- a/docs/reference/ml/df-analytics/apis/index.asciidoc
+++ b/docs/reference/ml/df-analytics/apis/index.asciidoc
@@ -8,6 +8,7 @@ include::update-dfanalytics.asciidoc[leveloffset=+2]
//DELETE
include::delete-dfanalytics.asciidoc[leveloffset=+2]
include::delete-trained-models.asciidoc[leveloffset=+2]
+include::delete-trained-models-aliases.asciidoc[leveloffset=+2]
//EVALUATE
include::evaluate-dfanalytics.asciidoc[leveloffset=+2]
//ESTIMATE_MEMORY_USAGE
diff --git a/docs/reference/ml/df-analytics/apis/ml-df-analytics-apis.asciidoc b/docs/reference/ml/df-analytics/apis/ml-df-analytics-apis.asciidoc
index 7c485f6c35f48..14e0c8012c89a 100644
--- a/docs/reference/ml/df-analytics/apis/ml-df-analytics-apis.asciidoc
+++ b/docs/reference/ml/df-analytics/apis/ml-df-analytics-apis.asciidoc
@@ -23,6 +23,7 @@ You can use the following APIs to perform {infer} operations.
* <>
* <>
* <>
+* <>
You can deploy a trained model to make predictions in an ingest pipeline or in
an aggregation. Refer to the following documentation to learn more.
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAliasAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAliasAction.java
new file mode 100644
index 0000000000000..d53ab36ff10ae
--- /dev/null
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAliasAction.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.core.ml.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.io.stream.StreamOutput;
+import org.elasticsearch.xpack.core.ml.inference.TrainedModelConfig;
+import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
+
+import java.io.IOException;
+import java.util.Objects;
+
+
+public class DeleteTrainedModelAliasAction extends ActionType {
+
+ public static final DeleteTrainedModelAliasAction INSTANCE = new DeleteTrainedModelAliasAction();
+ public static final String NAME = "cluster:admin/xpack/ml/inference/model_aliases/delete";
+
+ private DeleteTrainedModelAliasAction() {
+ super(NAME, AcknowledgedResponse::readFrom);
+ }
+
+ public static class Request extends AcknowledgedRequest {
+
+ public static final String MODEL_ALIAS = "model_alias";
+
+ private final String modelAlias;
+ private final String modelId;
+
+ public Request(String modelAlias, String modelId) {
+ this.modelAlias = ExceptionsHelper.requireNonNull(modelAlias, MODEL_ALIAS);
+ this.modelId = ExceptionsHelper.requireNonNull(modelId, TrainedModelConfig.MODEL_ID);
+ }
+
+ public Request(StreamInput in) throws IOException {
+ super(in);
+ this.modelAlias = in.readString();
+ this.modelId = in.readString();
+ }
+
+ public String getModelAlias() {
+ return modelAlias;
+ }
+
+ public String getModelId() {
+ return modelId;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ super.writeTo(out);
+ out.writeString(modelAlias);
+ out.writeString(modelId);
+ }
+
+ @Override
+ public ActionRequestValidationException validate() {
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Request request = (Request) o;
+ return Objects.equals(modelAlias, request.modelAlias)
+ && Objects.equals(modelId, request.modelId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(modelAlias, modelId);
+ }
+
+ }
+}
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAliasActionRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAliasActionRequestTests.java
new file mode 100644
index 0000000000000..cf45cde927c9b
--- /dev/null
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/DeleteTrainedModelAliasActionRequestTests.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+package org.elasticsearch.xpack.core.ml.action;
+
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.test.AbstractWireSerializingTestCase;
+import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAliasAction.Request;
+
+
+public class DeleteTrainedModelAliasActionRequestTests extends AbstractWireSerializingTestCase {
+
+ @Override
+ protected Request createTestInstance() {
+ return new Request(randomAlphaOfLength(10), randomAlphaOfLength(10));
+ }
+
+ @Override
+ protected Writeable.Reader instanceReader() {
+ return Request::new;
+ }
+
+ public void testCtor() {
+ expectThrows(Exception.class, () -> new Request(null, randomAlphaOfLength(10)));
+ expectThrows(Exception.class, () -> new Request(randomAlphaOfLength(10), null));
+ }
+
+}
diff --git a/x-pack/plugin/ml/qa/ml-with-security/build.gradle b/x-pack/plugin/ml/qa/ml-with-security/build.gradle
index e26ab4bbd5792..dcaea1e2aae51 100644
--- a/x-pack/plugin/ml/qa/ml-with-security/build.gradle
+++ b/x-pack/plugin/ml/qa/ml-with-security/build.gradle
@@ -149,6 +149,8 @@ tasks.named("yamlRestTest").configure {
'ml/inference_crud/Test update model alias with bad alias',
'ml/inference_crud/Test update model alias where alias exists but old model id is different inference type',
'ml/inference_crud/Test update model alias where alias exists but reassign is false',
+ 'ml/inference_crud/Test delete model alias with missing alias',
+ 'ml/inference_crud/Test delete model alias where alias points to different model',
'ml/inference_processor/Test create processor with missing mandatory fields',
'ml/inference_stats_crud/Test get stats given missing trained model',
'ml/inference_stats_crud/Test get stats given expression without matches and allow_no_match is false',
diff --git a/x-pack/plugin/ml/qa/single-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/InferenceProcessorIT.java b/x-pack/plugin/ml/qa/single-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/InferenceProcessorIT.java
index 960514b6e1cc0..593017aca99e8 100644
--- a/x-pack/plugin/ml/qa/single-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/InferenceProcessorIT.java
+++ b/x-pack/plugin/ml/qa/single-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/InferenceProcessorIT.java
@@ -226,6 +226,27 @@ public void testDeleteModelWhileAliasReferencedByPipeline() throws Exception {
waitForStats();
}
+ public void testDeleteModelAliasWhileAliasReferencedByPipeline() throws Exception {
+ putRegressionModel();
+ putModelAlias("regression_to_delete", MODEL_ID);
+ createdPipelines.add("first_pipeline");
+ putPipeline("regression_to_delete", "first_pipeline");
+ Exception ex = expectThrows(Exception.class,
+ () -> client().performRequest(
+ new Request(
+ "DELETE",
+ "_ml/trained_models/" + MODEL_ID + "/model_aliases/regression_to_delete"
+ )
+ ));
+ assertThat(
+ ex.getMessage(),
+ containsString("Cannot delete model_alias [regression_to_delete] as it is still referenced by ingest processors")
+ );
+ infer("first_pipeline");
+ deletePipeline("first_pipeline");
+ waitForStats();
+ }
+
public void testDeleteModelWhileReferencedByPipeline() throws Exception {
putRegressionModel();
createdPipelines.add("first_pipeline");
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java
index 468ad6b114b2e..43498fc91e7c3 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java
@@ -83,6 +83,7 @@
import org.elasticsearch.xpack.core.ml.action.DeleteJobAction;
import org.elasticsearch.xpack.core.ml.action.DeleteModelSnapshotAction;
import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAction;
+import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAliasAction;
import org.elasticsearch.xpack.core.ml.action.EstimateModelMemoryAction;
import org.elasticsearch.xpack.core.ml.action.EvaluateDataFrameAction;
import org.elasticsearch.xpack.core.ml.action.ExplainDataFrameAnalyticsAction;
@@ -157,6 +158,7 @@
import org.elasticsearch.xpack.ml.action.TransportDeleteJobAction;
import org.elasticsearch.xpack.ml.action.TransportDeleteModelSnapshotAction;
import org.elasticsearch.xpack.ml.action.TransportDeleteTrainedModelAction;
+import org.elasticsearch.xpack.ml.action.TransportDeleteTrainedModelAliasAction;
import org.elasticsearch.xpack.ml.action.TransportEstimateModelMemoryAction;
import org.elasticsearch.xpack.ml.action.TransportEvaluateDataFrameAction;
import org.elasticsearch.xpack.ml.action.TransportExplainDataFrameAnalyticsAction;
@@ -299,6 +301,7 @@
import org.elasticsearch.xpack.ml.rest.filter.RestPutFilterAction;
import org.elasticsearch.xpack.ml.rest.filter.RestUpdateFilterAction;
import org.elasticsearch.xpack.ml.rest.inference.RestDeleteTrainedModelAction;
+import org.elasticsearch.xpack.ml.rest.inference.RestDeleteTrainedModelAliasAction;
import org.elasticsearch.xpack.ml.rest.inference.RestGetTrainedModelsAction;
import org.elasticsearch.xpack.ml.rest.inference.RestGetTrainedModelsStatsAction;
import org.elasticsearch.xpack.ml.rest.inference.RestPutTrainedModelAction;
@@ -932,6 +935,7 @@ public List getRestHandlers(Settings settings, RestController restC
new RestPutTrainedModelAction(),
new RestUpgradeJobModelSnapshotAction(),
new RestPutTrainedModelAliasAction(),
+ new RestDeleteTrainedModelAliasAction(),
// CAT Handlers
new RestCatJobsAction(),
new RestCatTrainedModelsAction(),
@@ -1011,7 +1015,8 @@ public List getRestHandlers(Settings settings, RestController restC
new ActionHandler<>(GetTrainedModelsStatsAction.INSTANCE, TransportGetTrainedModelsStatsAction.class),
new ActionHandler<>(PutTrainedModelAction.INSTANCE, TransportPutTrainedModelAction.class),
new ActionHandler<>(UpgradeJobModelSnapshotAction.INSTANCE, TransportUpgradeJobModelSnapshotAction.class),
- new ActionHandler<>(PutTrainedModelAliasAction.INSTANCE, TransportPutTrainedModelAliasAction.class)
+ new ActionHandler<>(PutTrainedModelAliasAction.INSTANCE, TransportPutTrainedModelAliasAction.class),
+ new ActionHandler<>(DeleteTrainedModelAliasAction.INSTANCE, TransportDeleteTrainedModelAliasAction.class)
);
}
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAction.java
index 087ddb95b6ec3..4a37a49d20e47 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAction.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAction.java
@@ -75,7 +75,7 @@ protected void masterOperation(DeleteTrainedModelAction.Request request,
ActionListener listener) {
String id = request.getId();
IngestMetadata currentIngestMetadata = state.metadata().custom(IngestMetadata.TYPE);
- Set referencedModels = getReferencedModelKeys(currentIngestMetadata);
+ Set referencedModels = getReferencedModelKeys(currentIngestMetadata, ingestService);
if (referencedModels.contains(id)) {
listener.onFailure(new ElasticsearchStatusException("Cannot delete model [{}] as it is still referenced by ingest processors",
@@ -140,7 +140,7 @@ public ClusterState execute(final ClusterState currentState) {
});
}
- private Set getReferencedModelKeys(IngestMetadata ingestMetadata) {
+ static Set getReferencedModelKeys(IngestMetadata ingestMetadata, IngestService ingestService) {
Set allReferencedModelKeys = new HashSet<>();
if (ingestMetadata == null) {
return allReferencedModelKeys;
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAliasAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAliasAction.java
new file mode 100644
index 0000000000000..adcd519951642
--- /dev/null
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteTrainedModelAliasAction.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.ml.action;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.elasticsearch.ElasticsearchStatusException;
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
+import org.elasticsearch.action.support.master.AcknowledgedTransportMasterNodeAction;
+import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
+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.metadata.Metadata;
+import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.ingest.IngestMetadata;
+import org.elasticsearch.ingest.IngestService;
+import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.transport.TransportService;
+import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAliasAction;
+import org.elasticsearch.xpack.core.ml.inference.ModelAliasMetadata;
+import org.elasticsearch.xpack.ml.notifications.InferenceAuditor;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import static org.elasticsearch.xpack.ml.action.TransportDeleteTrainedModelAction.getReferencedModelKeys;
+
+public class TransportDeleteTrainedModelAliasAction extends AcknowledgedTransportMasterNodeAction {
+
+ private static final Logger logger = LogManager.getLogger(TransportDeleteTrainedModelAliasAction.class);
+
+ private final InferenceAuditor auditor;
+ private final IngestService ingestService;
+
+ @Inject
+ public TransportDeleteTrainedModelAliasAction(
+ TransportService transportService,
+ ClusterService clusterService,
+ ThreadPool threadPool,
+ ActionFilters actionFilters,
+ InferenceAuditor auditor,
+ IngestService ingestService,
+ IndexNameExpressionResolver indexNameExpressionResolver) {
+ super(
+ DeleteTrainedModelAliasAction.NAME,
+ transportService,
+ clusterService,
+ threadPool,
+ actionFilters,
+ DeleteTrainedModelAliasAction.Request::new,
+ indexNameExpressionResolver,
+ ThreadPool.Names.SAME
+ );
+ this.auditor = auditor;
+ this.ingestService = ingestService;
+ }
+
+ @Override
+ protected void masterOperation(
+ DeleteTrainedModelAliasAction.Request request,
+ ClusterState state,
+ ActionListener listener
+ ) throws Exception {
+ clusterService.submitStateUpdateTask("delete-model-alias", new AckedClusterStateUpdateTask(request, listener) {
+ @Override
+ public ClusterState execute(final ClusterState currentState) {
+ return deleteModelAlias(currentState, ingestService, auditor, request);
+ }
+ });
+ }
+
+ static ClusterState deleteModelAlias(final ClusterState currentState,
+ final IngestService ingestService,
+ final InferenceAuditor inferenceAuditor,
+ final DeleteTrainedModelAliasAction.Request request) {
+ final ModelAliasMetadata currentMetadata = ModelAliasMetadata.fromState(currentState);
+ final String referencedModel = currentMetadata.getModelId(request.getModelAlias());
+ if (referencedModel == null) {
+ throw new ElasticsearchStatusException("model_alias [{}] could not be found", RestStatus.NOT_FOUND, request.getModelAlias());
+ }
+ if (referencedModel.equals(request.getModelId()) == false) {
+ throw new ElasticsearchStatusException(
+ "model_alias [{}] does not refer to provided model_id [{}]",
+ RestStatus.CONFLICT,
+ request.getModelAlias(),
+ request.getModelId()
+ );
+ }
+ IngestMetadata currentIngestMetadata = currentState.metadata().custom(IngestMetadata.TYPE);
+ Set referencedModels = getReferencedModelKeys(currentIngestMetadata, ingestService);
+ if (referencedModels.contains(request.getModelAlias())) {
+ throw new ElasticsearchStatusException(
+ "Cannot delete model_alias [{}] as it is still referenced by ingest processors",
+ RestStatus.CONFLICT,
+ request.getModelAlias()
+ );
+ }
+ final ClusterState.Builder builder = ClusterState.builder(currentState);
+ final Map newMetadata = new HashMap<>(currentMetadata.modelAliases());
+ logger.info("deleting model_alias [{}] that refers to model [{}]", request.getModelAlias(), request.getModelId());
+ inferenceAuditor.info(referencedModel, String.format(Locale.ROOT, "deleting model_alias [%s]", request.getModelAlias()));
+
+ newMetadata.remove(request.getModelAlias());
+ final ModelAliasMetadata modelAliasMetadata = new ModelAliasMetadata(newMetadata);
+ builder.metadata(Metadata.builder(currentState.getMetadata()).putCustom(ModelAliasMetadata.NAME, modelAliasMetadata).build());
+ return builder.build();
+ }
+
+ @Override
+ protected ClusterBlockException checkBlock(DeleteTrainedModelAliasAction.Request request, ClusterState state) {
+ return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
+ }
+}
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/inference/RestDeleteTrainedModelAliasAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/inference/RestDeleteTrainedModelAliasAction.java
new file mode 100644
index 0000000000000..c66ba0e769fcb
--- /dev/null
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/inference/RestDeleteTrainedModelAliasAction.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+package org.elasticsearch.xpack.ml.rest.inference;
+
+import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.action.RestToXContentListener;
+import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAliasAction;
+import org.elasticsearch.xpack.core.ml.inference.TrainedModelConfig;
+import org.elasticsearch.xpack.ml.MachineLearning;
+
+import java.io.IOException;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.elasticsearch.rest.RestRequest.Method.DELETE;
+
+public class RestDeleteTrainedModelAliasAction extends BaseRestHandler {
+
+ @Override
+ public List routes() {
+ return singletonList(
+ new Route(
+ DELETE,
+ MachineLearning.BASE_PATH
+ + "trained_models/{"
+ + TrainedModelConfig.MODEL_ID.getPreferredName()
+ + "}/model_aliases/{"
+ + DeleteTrainedModelAliasAction.Request.MODEL_ALIAS
+ + "}"
+
+ )
+ );
+ }
+
+ @Override
+ public String getName() {
+ return "ml_delete_trained_model_alias_action";
+ }
+
+ @Override
+ protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
+ final String modelId = restRequest.param(TrainedModelConfig.MODEL_ID.getPreferredName());
+ final String modelAlias = restRequest.param(DeleteTrainedModelAliasAction.Request.MODEL_ALIAS);
+ return channel -> client.execute(
+ DeleteTrainedModelAliasAction.INSTANCE,
+ new DeleteTrainedModelAliasAction.Request(modelAlias, modelId),
+ new RestToXContentListener<>(channel)
+ );
+ }
+}
diff --git a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java
index 8d828bd7fd592..525d1f0c3ce89 100644
--- a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java
+++ b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java
@@ -135,6 +135,7 @@ public class Constants {
"cluster:admin/xpack/ml/inference/delete",
"cluster:admin/xpack/ml/inference/put",
"cluster:admin/xpack/ml/inference/model_aliases/put",
+ "cluster:admin/xpack/ml/inference/model_aliases/delete",
"cluster:admin/xpack/ml/job/close",
"cluster:admin/xpack/ml/job/data/post",
"cluster:admin/xpack/ml/job/delete",
diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/api/ml.delete_trained_model_alias.json b/x-pack/plugin/src/test/resources/rest-api-spec/api/ml.delete_trained_model_alias.json
new file mode 100644
index 0000000000000..1e51ceea1aee1
--- /dev/null
+++ b/x-pack/plugin/src/test/resources/rest-api-spec/api/ml.delete_trained_model_alias.json
@@ -0,0 +1,34 @@
+{
+ "ml.delete_trained_model_alias":{
+ "documentation":{
+ "url":"https://www.elastic.co/guide/en/elasticsearch/reference/current/delete-trained-models-aliases.html",
+ "description":"Deletes a model alias that refers to the trained model"
+ },
+ "stability":"beta",
+ "visibility":"public",
+ "headers":{
+ "accept": [ "application/json"],
+ "content_type": ["application/json"]
+ },
+ "url":{
+ "paths":[
+ {
+ "path":"/_ml/trained_models/{model_id}/model_aliases/{model_alias}",
+ "methods":[
+ "DELETE"
+ ],
+ "parts":{
+ "model_alias":{
+ "type":"string",
+ "description":"The trained model alias to delete"
+ },
+ "model_id": {
+ "type": "string",
+ "description": "The trained model where the model alias is assigned"
+ }
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/ml/inference_crud.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/ml/inference_crud.yml
index 0994bdf33319c..dafe223bfd60d 100644
--- a/x-pack/plugin/src/test/resources/rest-api-spec/test/ml/inference_crud.yml
+++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/ml/inference_crud.yml
@@ -913,3 +913,44 @@ setup:
model_alias: "regression-model"
model_id: "a-regression-model-1"
reassign: false
+---
+"Test delete model alias":
+ - do:
+ ml.put_trained_model_alias:
+ model_alias: "regression-model"
+ model_id: "a-regression-model-0"
+ - do:
+ ml.get_trained_models:
+ model_id: "regression-model"
+
+ - match: { count: 1 }
+ - length: { trained_model_configs: 1 }
+ - match: { trained_model_configs.0.model_id: "a-regression-model-0" }
+
+ - do:
+ ml.delete_trained_model_alias:
+ model_alias: "regression-model"
+ model_id: "a-regression-model-0"
+
+ - do:
+ catch: missing
+ ml.get_trained_models:
+ model_id: "regression-model"
+---
+"Test delete model alias with missing alias":
+ - do:
+ catch: missing
+ ml.delete_trained_model_alias:
+ model_alias: "regression-model"
+ model_id: "a-regression-model-0"
+---
+"Test delete model alias where alias points to different model":
+ - do:
+ ml.put_trained_model_alias:
+ model_alias: "regression-model"
+ model_id: "a-regression-model-1"
+ - do:
+ catch: conflict
+ ml.delete_trained_model_alias:
+ model_alias: "regression-model"
+ model_id: "a-regression-model-0"