diff --git a/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/BulkIntegrationIT.java b/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/BulkIntegrationIT.java index 88a40f8d6db..a1bf1192212 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/BulkIntegrationIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/BulkIntegrationIT.java @@ -24,6 +24,7 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.admin.indices.alias.Alias; +import org.elasticsearch.action.admin.indices.datastream.DeleteDataStreamAction; import org.elasticsearch.action.admin.indices.datastream.GetDataStreamAction; import org.elasticsearch.action.admin.indices.get.GetIndexRequest; import org.elasticsearch.action.admin.indices.get.GetIndexResponse; @@ -271,6 +272,8 @@ public class BulkIntegrationIT extends ESIntegTestCase { assertThat(getIndexResponse.getIndices(), hasItemInArray("logs-barfoo2")); assertThat(getIndexResponse.getIndices(), hasItemInArray("logs-barfoo3")); + DeleteDataStreamAction.Request deleteDSReq = new DeleteDataStreamAction.Request("*"); + client().execute(DeleteDataStreamAction.INSTANCE, deleteDSReq).actionGet(); DeleteComposableIndexTemplateAction.Request deleteTemplateRequest = new DeleteComposableIndexTemplateAction.Request("*"); client().execute(DeleteComposableIndexTemplateAction.INSTANCE, deleteTemplateRequest).actionGet(); } diff --git a/server/src/internalClusterTest/java/org/elasticsearch/indices/DataStreamIT.java b/server/src/internalClusterTest/java/org/elasticsearch/indices/DataStreamIT.java index be4a3f5dba4..69721a004a0 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/indices/DataStreamIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/indices/DataStreamIT.java @@ -18,6 +18,7 @@ */ package org.elasticsearch.indices; +import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.admin.indices.datastream.CreateDataStreamAction; @@ -61,6 +62,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Optional; import static org.elasticsearch.indices.IndicesOptionsIntegrationIT._flush; import static org.elasticsearch.indices.IndicesOptionsIntegrationIT.clearCache; @@ -87,6 +89,8 @@ public class DataStreamIT extends ESIntegTestCase { @After public void deleteAllComposableTemplates() { + DeleteDataStreamAction.Request deleteDSRequest = new DeleteDataStreamAction.Request("*"); + client().execute(DeleteDataStreamAction.INSTANCE, deleteDSRequest).actionGet(); DeleteComposableIndexTemplateAction.Request deleteTemplateRequest = new DeleteComposableIndexTemplateAction.Request("*"); client().execute(DeleteComposableIndexTemplateAction.INSTANCE, deleteTemplateRequest).actionGet(); } @@ -363,6 +367,29 @@ public class DataStreamIT extends ESIntegTestCase { verifyResolvability(wildcardExpression, client().admin().indices().prepareGetIndex().addIndices(wildcardExpression), false); } + public void testCannotDeleteComposableTemplateUsedByDataStream() throws Exception { + createIndexTemplate("id", "metrics-foobar*", "@timestamp1"); + String dataStreamName = "metrics-foobar-baz"; + CreateDataStreamAction.Request createDataStreamRequest = new CreateDataStreamAction.Request(dataStreamName); + client().admin().indices().createDataStream(createDataStreamRequest).get(); + createDataStreamRequest = new CreateDataStreamAction.Request(dataStreamName + "-eggplant"); + client().admin().indices().createDataStream(createDataStreamRequest).get(); + + DeleteComposableIndexTemplateAction.Request req = new DeleteComposableIndexTemplateAction.Request("id"); + Exception e = expectThrows(Exception.class, () -> client().execute(DeleteComposableIndexTemplateAction.INSTANCE, req).get()); + Optional maybeE = ExceptionsHelper.unwrapCausesAndSuppressed(e, err -> + err.getMessage().contains("unable to remove composable templates [id] " + + "as they are in use by a data streams [metrics-foobar-baz, metrics-foobar-baz-eggplant]")); + assertTrue(maybeE.isPresent()); + + DeleteComposableIndexTemplateAction.Request req2 = new DeleteComposableIndexTemplateAction.Request("i*"); + Exception e2 = expectThrows(Exception.class, () -> client().execute(DeleteComposableIndexTemplateAction.INSTANCE, req2).get()); + maybeE = ExceptionsHelper.unwrapCausesAndSuppressed(e2, err -> + err.getMessage().contains("unable to remove composable templates [id] " + + "as they are in use by a data streams [metrics-foobar-baz, metrics-foobar-baz-eggplant]")); + assertTrue(maybeE.isPresent()); + } + private static void verifyResolvability(String dataStream, ActionRequestBuilder requestBuilder, boolean fail) { verifyResolvability(dataStream, requestBuilder, fail, 0); } diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java index f7ac5a3d598..e9ef244a513 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java @@ -68,6 +68,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.TreeSet; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -611,6 +612,17 @@ public class MetadataIndexTemplateService { } throw new IndexTemplateMissingException(name); } + + Optional> dataStreamsUsingTemplates = templateNames.stream() + .map(templateName -> dataStreamsUsingTemplate(currentState, templateName)) + .reduce(Sets::union); + dataStreamsUsingTemplates.ifPresent(set -> { + if (set.size() > 0) { + throw new IllegalArgumentException("unable to remove composable templates " + new TreeSet<>(templateNames) + + " as they are in use by a data streams " + new TreeSet<>(set)); + } + }); + Metadata.Builder metadata = Metadata.builder(currentState.metadata()); for (String templateName : templateNames) { logger.info("removing index template [{}]", templateName); @@ -619,6 +631,18 @@ public class MetadataIndexTemplateService { return ClusterState.builder(currentState).metadata(metadata).build(); } + static Set dataStreamsUsingTemplate(final ClusterState state, final String templateName) { + final ComposableIndexTemplate template = state.metadata().templatesV2().get(templateName); + if (template == null) { + return Collections.emptySet(); + } + final Set dataStreams = state.metadata().dataStreams().keySet(); + Set matches = new HashSet<>(); + template.indexPatterns().forEach(indexPattern -> + matches.addAll(dataStreams.stream().filter(stream -> Regex.simpleMatch(indexPattern, stream)).collect(Collectors.toList()))); + return matches; + } + public void putTemplate(final PutRequest request, final PutListener listener) { Settings.Builder updatedSettingsBuilder = Settings.builder(); updatedSettingsBuilder.put(request.settings).normalizePrefix(IndexMetadata.INDEX_SETTING_PREFIX);