Преглед изворни кода

Support specifying multiple templates names in delete component template api (#70314)

Add support to delete component templates api to specify multiple template
names separated by a comma.

Change the cleanup template logic for rest tests to remove all component templates via a single delete component template request. This to optimize the cleanup logic. After each rest test we delete all templates. So deleting templates this via a single api call (and thus single cluster state update) saves a lot of time considering the number of rest tests.

Older versions don't support component / composable index templates
and/or data streams. Yet the test base class tries to remove objects
after each test, which adds a significant number of lines to the
log files (which slows the tests down). The ESRestTestCase will
now check whether all nodes have a specific version and then decide
whether data streams and component / composable index templates will
be deleted.

Also ensured that the logstash-index-template and security-index-template
aren't deleted between tests, these templates are builtin templates that
ES will install if missing. So if tests remove these templates between tests
then ES will add these template back almost immediately. These causes
many log lines and a lot of cluster state updates, which slow tests down.

Relates to #69973

Co-authored-by: Lee Hinman <dakrone@users.noreply.github.com>
Martijn van Groningen пре 4 година
родитељ
комит
715eb90fea

+ 5 - 2
docs/reference/indices/delete-component-template.asciidoc

@@ -26,6 +26,9 @@ PUT _component_template/template_1
 DELETE _component_template/template_1
 --------------------------------------------------
 
+The provided <component-template> may contain multiple template names separated by a comma.
+If multiple template names are specified then there is no wildcard support and the
+provided names should match completely with existing component templates.
 
 [[delete-component-template-api-request]]
 ==== {api-request-title}
@@ -43,8 +46,8 @@ privilege>> to use this API.
 ==== {api-description-title}
 
 Use the delete component template API to delete one or more component templates
-Component templates are building blocks for constructing <<index-templates,index templates>>  
-that specify index mappings, settings, and aliases. 
+Component templates are building blocks for constructing <<index-templates,index templates>>
+that specify index mappings, settings, and aliases.
 
 [[delete-component-template-api-path-params]]
 ==== {api-path-parms-title}

+ 0 - 1
docs/reference/indices/simulate-template.asciidoc

@@ -47,7 +47,6 @@ PUT _index_template/template_1
 [source,console]
 --------------------------------------------------
 DELETE _index_template/*
-DELETE _component_template/*
 --------------------------------------------------
 // TEARDOWN
 ////

+ 68 - 0
rest-api-spec/src/main/resources/rest-api-spec/test/cluster.component_template/10_basic.yml

@@ -45,3 +45,71 @@
         name: test
 
   - is_false: test
+
+---
+"Delete multiple templates":
+  - skip:
+      version: " - 7.99.99"
+      reason: "not yet backported"
+
+  - do:
+      cluster.put_component_template:
+        name: foo
+        body:
+          template:
+            settings:
+              number_of_shards:   1
+              number_of_replicas: 0
+
+  - do:
+      cluster.put_component_template:
+        name: bar
+        body:
+          template:
+            settings:
+              number_of_shards:   1
+              number_of_replicas: 0
+
+  - do:
+      cluster.put_component_template:
+        name: baz
+        body:
+          template:
+            settings:
+              number_of_shards:   1
+              number_of_replicas: 0
+
+  - do:
+      cluster.get_component_template:
+        name: 'bar'
+  - match: {component_templates.0.name: bar}
+
+  - do:
+      cluster.get_component_template:
+        name: 'baz'
+  - match: {component_templates.0.name: baz}
+
+  - do:
+      cluster.get_component_template:
+        name: 'foo'
+  - match: {component_templates.0.name: foo}
+
+  - do:
+      cluster.delete_component_template:
+        name: foo,bar
+
+  - do:
+      catch: missing
+      cluster.get_component_template:
+        name: foo
+
+  - do:
+      catch: missing
+      cluster.get_component_template:
+        name: bar
+
+  - do:
+      cluster.get_component_template:
+        name: baz
+
+  - match: {component_templates.0.name: baz}

+ 78 - 0
rest-api-spec/src/main/resources/rest-api-spec/test/indices.put_index_template/10_basic.yml

@@ -119,3 +119,81 @@
       indices.put_index_template:
         name: test
         body: {}
+
+---
+"Delete multiple templates":
+  - skip:
+      version: " - 7.99.99"
+      reason: "not yet backported"
+      features: allowed_warnings
+
+  - do:
+      allowed_warnings:
+        - "index template [foo] has index patterns [foo-*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [foo] will take precedence during new index creation"
+      indices.put_index_template:
+        name: foo
+        body:
+          index_patterns: foo-*
+          template:
+            settings:
+              number_of_shards:   1
+              number_of_replicas: 0
+
+  - do:
+      allowed_warnings:
+        - "index template [bar] has index patterns [bar-*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [bar] will take precedence during new index creation"
+      indices.put_index_template:
+        name: bar
+        body:
+          index_patterns: bar-*
+          template:
+            settings:
+              number_of_shards:   1
+              number_of_replicas: 0
+
+  - do:
+      allowed_warnings:
+        - "index template [baz] has index patterns [baz-*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [baz] will take precedence during new index creation"
+      indices.put_index_template:
+        name: baz
+        body:
+          index_patterns: baz-*
+          template:
+            settings:
+              number_of_shards:   1
+              number_of_replicas: 0
+
+  - do:
+      indices.get_index_template:
+        name: 'bar'
+  - match: {index_templates.0.name: "bar"}
+
+  - do:
+      indices.get_index_template:
+        name: 'baz'
+  - match: {index_templates.0.name: "baz"}
+
+  - do:
+      indices.get_index_template:
+        name: 'foo'
+  - match: {index_templates.0.name: "foo"}
+
+  - do:
+      indices.delete_index_template:
+        name: foo,bar
+
+  - do:
+      catch: missing
+      indices.get_index_template:
+        name: foo
+
+  - do:
+      catch: missing
+      indices.get_index_template:
+        name: bar
+
+  - do:
+      indices.get_index_template:
+        name: baz
+
+  - match: {index_templates.0.name: baz}

+ 22 - 20
server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteComponentTemplateAction.java

@@ -8,14 +8,18 @@
 
 package org.elasticsearch.action.admin.indices.template.delete;
 
+import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.ActionType;
 import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.action.support.master.MasterNodeRequest;
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.Objects;
 
 import static org.elasticsearch.action.ValidateActions.addValidationError;
 
@@ -30,50 +34,48 @@ public class DeleteComponentTemplateAction extends ActionType<AcknowledgedRespon
 
     public static class Request extends MasterNodeRequest<Request> {
 
-        private String name;
+        private final String[] names;
 
         public Request(StreamInput in) throws IOException {
             super(in);
-            name = in.readString();
+            if (in.getVersion().onOrAfter(Version.V_8_0_0)) {
+                names = in.readStringArray();
+            } else {
+                names = new String[] {in.readString()};
+            }
         }
 
-        public Request() { }
-
         /**
          * Constructs a new delete index request for the specified name.
          */
-        public Request(String name) {
-            this.name = name;
-        }
-
-        /**
-         * Set the index template name to delete.
-         */
-        public Request name(String name) {
-            this.name = name;
-            return this;
+        public Request(String... names) {
+            this.names = Objects.requireNonNull(names, "component templates to delete must not be null");
         }
 
         @Override
         public ActionRequestValidationException validate() {
             ActionRequestValidationException validationException = null;
-            if (name == null) {
-                validationException = addValidationError("name is missing", validationException);
+            if (Arrays.stream(names).anyMatch(Strings::hasLength) == false) {
+                validationException = addValidationError("no component template names specified", validationException);
             }
             return validationException;
         }
 
         /**
-         * The index template name to delete.
+         * The index template names to delete.
          */
-        public String name() {
-            return name;
+        public String[] names() {
+            return names;
         }
 
         @Override
         public void writeTo(StreamOutput out) throws IOException {
             super.writeTo(out);
-            out.writeString(name);
+            if (out.getVersion().onOrAfter(Version.V_8_0_0)) {
+                out.writeStringArray(names);
+            } else {
+                out.writeString(names[0]);
+            }
         }
     }
 }

+ 2 - 2
server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteComposableIndexTemplateAction.java

@@ -56,13 +56,13 @@ public class DeleteComposableIndexTemplateAction extends ActionType<Acknowledged
         public ActionRequestValidationException validate() {
             ActionRequestValidationException validationException = null;
             if (Arrays.stream(names).anyMatch(Strings::hasLength) == false) {
-                validationException = addValidationError("name is missing", validationException);
+                validationException = addValidationError("no template names specified", validationException);
             }
             return validationException;
         }
 
         /**
-         * The index template name to delete.
+         * The index template names to delete.
          */
         public String[] names() {
             return names;

+ 1 - 1
server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/TransportDeleteComponentTemplateAction.java

@@ -44,6 +44,6 @@ public class TransportDeleteComponentTemplateAction extends AcknowledgedTranspor
     @Override
     protected void masterOperation(Task task, final DeleteComponentTemplateAction.Request request, final ClusterState state,
                                    final ActionListener<AcknowledgedResponse> listener) {
-        indexTemplateService.removeComponentTemplate(request.name(), request.masterNodeTimeout(), listener);
+        indexTemplateService.removeComponentTemplate(request.names(), request.masterNodeTimeout(), state, listener);
     }
 }

+ 60 - 27
server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java

@@ -14,6 +14,7 @@ import org.apache.logging.log4j.Logger;
 import org.apache.lucene.util.CollectionUtil;
 import org.apache.lucene.util.automaton.Automaton;
 import org.apache.lucene.util.automaton.Operations;
+import org.elasticsearch.ResourceNotFoundException;
 import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.admin.indices.alias.Alias;
@@ -53,6 +54,7 @@ import org.elasticsearch.indices.InvalidIndexTemplateException;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -300,10 +302,10 @@ public class MetadataIndexTemplateService {
      * Remove the given component template from the cluster state. The component template name
      * supports simple regex wildcards for removing multiple component templates at a time.
      */
-    public void removeComponentTemplate(final String name, final TimeValue masterTimeout,
-                                        final ActionListener<AcknowledgedResponse> listener) {
-        validateNotInUse(clusterService.state().metadata(), name);
-        clusterService.submitStateUpdateTask("remove-component-template [" + name + "]",
+    public void removeComponentTemplate(final String[] names, final TimeValue masterTimeout,
+                                        ClusterState state, final ActionListener<AcknowledgedResponse> listener) {
+        validateNotInUse(state.metadata(), names);
+        clusterService.submitStateUpdateTask("remove-component-template [" + String.join(",", names) + "]",
             new ClusterStateUpdateTask(Priority.URGENT, masterTimeout) {
 
                 @Override
@@ -313,27 +315,7 @@ public class MetadataIndexTemplateService {
 
                 @Override
                 public ClusterState execute(ClusterState currentState) {
-                    Set<String> templateNames = new HashSet<>();
-                    for (String templateName : currentState.metadata().componentTemplates().keySet()) {
-                        if (Regex.simpleMatch(name, templateName)) {
-                            templateNames.add(templateName);
-                        }
-                    }
-                    if (templateNames.isEmpty()) {
-                        // if its a match all pattern, and no templates are found (we have none), don't
-                        // fail with index missing...
-                        if (Regex.isMatchAllPattern(name)) {
-                            return currentState;
-                        }
-                        // TODO: perhaps introduce a ComponentTemplateMissingException?
-                        throw new IndexTemplateMissingException(name);
-                    }
-                    Metadata.Builder metadata = Metadata.builder(currentState.metadata());
-                    for (String templateName : templateNames) {
-                        logger.info("removing component template [{}]", templateName);
-                        metadata.removeComponentTemplate(templateName);
-                    }
-                    return ClusterState.builder(currentState).metadata(metadata).build();
+                    return innerRemoveComponentTemplate(currentState, names);
                 }
 
                 @Override
@@ -343,13 +325,64 @@ public class MetadataIndexTemplateService {
             });
     }
 
+    static ClusterState innerRemoveComponentTemplate(ClusterState currentState, String... names) {
+        validateNotInUse(currentState.metadata(), names);
+
+        final Set<String> templateNames = new HashSet<>();
+        if (names.length > 1) {
+            Set<String> missingNames = null;
+            for (String name : names) {
+                if (currentState.metadata().componentTemplates().containsKey(name)) {
+                    templateNames.add(name);
+                } else {
+                    // wildcards are not supported, so if a name with a wildcard is specified then
+                    // the else clause gets executed, because template names can't contain a wildcard.
+                    if (missingNames == null) {
+                        missingNames = new LinkedHashSet<>();
+                    }
+                    missingNames.add(name);
+                }
+            }
+
+            if (missingNames != null) {
+                throw new ResourceNotFoundException(String.join(",", missingNames));
+            }
+        } else {
+            for (String templateName : currentState.metadata().componentTemplates().keySet()) {
+                if (Regex.simpleMatch(names[0], templateName)) {
+                    templateNames.add(templateName);
+                }
+            }
+            if (templateNames.isEmpty()) {
+                // if its a match all pattern, and no templates are found (we have none), don't
+                // fail with index missing...
+                if (Regex.isMatchAllPattern(names[0])) {
+                    return currentState;
+                }
+                throw new ResourceNotFoundException(names[0]);
+            }
+        }
+        Metadata.Builder metadata = Metadata.builder(currentState.metadata());
+        for (String templateName : templateNames) {
+            logger.info("removing component template [{}]", templateName);
+            metadata.removeComponentTemplate(templateName);
+        }
+        return ClusterState.builder(currentState).metadata(metadata).build();
+    }
+
     /**
      * Validates that the given component template is not used by any index
      * templates, throwing an error if it is still in use
      */
-    static void validateNotInUse(Metadata metadata, String templateNameOrWildcard) {
+    static void validateNotInUse(Metadata metadata, String... templateNameOrWildcard) {
+        final Predicate<String> predicate;
+        if (templateNameOrWildcard.length > 1) {
+            predicate = name -> Arrays.stream(templateNameOrWildcard).anyMatch(s -> Objects.equals(s, name));
+        } else {
+            predicate = name -> Regex.simpleMatch(templateNameOrWildcard[0], name);
+        }
         final Set<String> matchingComponentTemplates = metadata.componentTemplates().keySet().stream()
-            .filter(name -> Regex.simpleMatch(templateNameOrWildcard, name))
+            .filter(predicate)
             .collect(Collectors.toSet());
         final Set<String> componentsBeingUsed = new HashSet<>();
         final List<String> templatesStillUsing = metadata.templatesV2().entrySet().stream()

+ 3 - 2
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestDeleteComponentTemplateAction.java

@@ -10,6 +10,7 @@ package org.elasticsearch.rest.action.admin.indices;
 
 import org.elasticsearch.action.admin.indices.template.delete.DeleteComponentTemplateAction;
 import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.action.RestToXContentListener;
@@ -33,8 +34,8 @@ public class RestDeleteComponentTemplateAction extends BaseRestHandler {
 
     @Override
     public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
-
-        DeleteComponentTemplateAction.Request deleteReq = new DeleteComponentTemplateAction.Request(request.param("name"));
+        String[] names = Strings.splitStringByCommaToArray(request.param("name"));
+        DeleteComponentTemplateAction.Request deleteReq = new DeleteComponentTemplateAction.Request(names);
         deleteReq.masterNodeTimeout(request.paramAsTime("master_timeout", deleteReq.masterNodeTimeout()));
 
         return channel -> client.execute(DeleteComponentTemplateAction.INSTANCE, deleteReq, new RestToXContentListener<>(channel));

+ 42 - 24
server/src/test/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateServiceTests.java

@@ -10,6 +10,7 @@ package org.elasticsearch.cluster.metadata;
 
 import com.fasterxml.jackson.core.JsonParseException;
 import org.apache.lucene.search.Query;
+import org.elasticsearch.ResourceNotFoundException;
 import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.admin.indices.alias.Alias;
@@ -64,6 +65,7 @@ import java.util.stream.Collectors;
 
 import static java.util.Collections.singletonList;
 import static org.elasticsearch.cluster.metadata.MetadataIndexTemplateService.DEFAULT_TIMESTAMP_FIELD;
+import static org.elasticsearch.cluster.metadata.MetadataIndexTemplateService.innerRemoveComponentTemplate;
 import static org.elasticsearch.common.settings.Settings.builder;
 import static org.elasticsearch.index.mapper.FieldMapper.Parameter;
 import static org.elasticsearch.indices.ShardLimitValidatorTests.createTestShardLimitService;
@@ -1236,37 +1238,53 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
             "component templates [bad] that do not exist"));
     }
 
+    public void testRemoveComponentTemplate() throws Exception {
+        ComponentTemplate foo = new ComponentTemplate(new Template(null, new CompressedXContent("{}"), null), null, null);
+        ComponentTemplate bar = new ComponentTemplate(new Template(null, new CompressedXContent("{}"), null), null, null);
+        ComponentTemplate baz = new ComponentTemplate(new Template(null, new CompressedXContent("{}"), null), null, null);
+
+        final MetadataIndexTemplateService service = getMetadataIndexTemplateService();
+        ClusterState temp = service.addComponentTemplate(ClusterState.EMPTY_STATE, false, "foo", foo);
+        temp = service.addComponentTemplate(temp, false, "bar", bar);
+        final ClusterState clusterState = service.addComponentTemplate(temp, false, "baz", baz);
+
+        ClusterState result = innerRemoveComponentTemplate(clusterState, "foo");
+        assertThat(result.metadata().componentTemplates().get("foo"), nullValue());
+        assertThat(result.metadata().componentTemplates().get("bar"), equalTo(bar));
+        assertThat(result.metadata().componentTemplates().get("baz"), equalTo(baz));
+
+        result = innerRemoveComponentTemplate(clusterState, "bar", "baz");
+        assertThat(result.metadata().componentTemplates().get("foo"), equalTo(foo));
+        assertThat(result.metadata().componentTemplates().get("bar"), nullValue());
+        assertThat(result.metadata().componentTemplates().get("baz"), nullValue());
+
+        Exception e = expectThrows(ResourceNotFoundException.class, () -> innerRemoveComponentTemplate(clusterState, "foobar"));
+        assertThat(e.getMessage(), equalTo("foobar"));
+        e = expectThrows(ResourceNotFoundException.class, () -> innerRemoveComponentTemplate(clusterState, "foo", "barbaz", "foobar"));
+        assertThat(e.getMessage(), equalTo("barbaz,foobar"));
+
+        result = innerRemoveComponentTemplate(clusterState, "*");
+        assertThat(result.metadata().componentTemplates().size(), equalTo(0));
+
+        result = innerRemoveComponentTemplate(clusterState, "b*");
+        assertThat(result.metadata().componentTemplates().size(), equalTo(1));
+        assertThat(result.metadata().componentTemplates().get("foo"), equalTo(foo));
+
+        e = expectThrows(ResourceNotFoundException.class, () -> innerRemoveComponentTemplate(clusterState, "foo", "b*"));
+        assertThat(e.getMessage(), equalTo("b*"));
+    }
+
     public void testRemoveComponentTemplateInUse() throws Exception {
         ComposableIndexTemplate template = new ComposableIndexTemplate(Collections.singletonList("a"), null,
             Collections.singletonList("ct"), null, null, null);
         ComponentTemplate ct = new ComponentTemplate(new Template(null, new CompressedXContent("{}"), null), null, null);
 
         final MetadataIndexTemplateService service = getMetadataIndexTemplateService();
-        CountDownLatch ctLatch = new CountDownLatch(1);
-        service.putComponentTemplate("api", false, "ct", TimeValue.timeValueSeconds(5), ct,
-            ActionListener.wrap(r -> ctLatch.countDown(), e -> fail("unexpected error")));
-        ctLatch.await(5, TimeUnit.SECONDS);
-
-        CountDownLatch latch = new CountDownLatch(1);
-        service.putIndexTemplateV2("api", false, "template", TimeValue.timeValueSeconds(30), template,
-            ActionListener.wrap(r -> latch.countDown(), e -> fail("unexpected error")));
-        latch.await(5, TimeUnit.SECONDS);
-
-        IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
-            () -> {
-                AtomicReference<Exception> err = new AtomicReference<>();
-                CountDownLatch errLatch = new CountDownLatch(1);
-                service.removeComponentTemplate("c*", TimeValue.timeValueSeconds(30),
-                    ActionListener.wrap(r -> fail("should have failed!"), exception -> {
-                        err.set(exception);
-                        errLatch.countDown();
-                    }));
-                errLatch.await(5, TimeUnit.SECONDS);
-                if (err.get() != null) {
-                    throw err.get();
-                }
-            });
+        ClusterState clusterState = service.addComponentTemplate(ClusterState.EMPTY_STATE, false, "ct", ct);
+        clusterState = service.addIndexTemplateV2(clusterState, false, "template", template);
 
+        final ClusterState cs = clusterState;
+        Exception e = expectThrows(IllegalArgumentException.class, () -> innerRemoveComponentTemplate(cs, "c*"));
         assertThat(e.getMessage(),
             containsString("component templates [ct] cannot be removed as they are still in use by index templates [template]"));
     }

+ 57 - 42
test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java

@@ -571,56 +571,69 @@ public abstract class ESRestTestCase extends ESTestCase {
                  * slows down the test because xpack will just recreate
                  * them.
                  */
-                try {
-                    Request getTemplatesRequest = new Request("GET", "_index_template");
-                    Map<String, Object> composableIndexTemplates = XContentHelper.convertToMap(JsonXContent.jsonXContent,
-                        EntityUtils.toString(adminClient().performRequest(getTemplatesRequest).getEntity()), false);
-                    List<String> names = ((List<?>) composableIndexTemplates.get("index_templates")).stream()
-                        .map(ct -> (String) ((Map<?, ?>) ct).get("name"))
-                        .filter(name -> isXPackTemplate(name) == false)
-                        .collect(Collectors.toList());
-                    // Ideally we would want to check the version of the elected master node and
-                    // send the delete request directly to that node.
-                    if (nodeVersions.stream().allMatch(version -> version.onOrAfter(Version.V_7_13_0))) {
-                        try {
-                            adminClient().performRequest(new Request("DELETE", "_index_template/" + String.join(",", names)));
-                        } catch (ResponseException e) {
-                            logger.debug(new ParameterizedMessage("unable to remove multiple composable index template {}", names), e);
-                        }
-                    } else {
-                        for (String name : names) {
+                // In case of bwc testing, if all nodes are before 7.7.0 then no need to attempt to delete component and composable
+                // index templates, because these were introduced in 7.7.0:
+                if (nodeVersions.stream().allMatch(version -> version.onOrAfter(Version.V_7_7_0))) {
+                    try {
+                        Request getTemplatesRequest = new Request("GET", "_index_template");
+                        Map<String, Object> composableIndexTemplates = XContentHelper.convertToMap(JsonXContent.jsonXContent,
+                            EntityUtils.toString(adminClient().performRequest(getTemplatesRequest).getEntity()), false);
+                        List<String> names = ((List<?>) composableIndexTemplates.get("index_templates")).stream()
+                            .map(ct -> (String) ((Map<?, ?>) ct).get("name"))
+                            .filter(name -> isXPackTemplate(name) == false)
+                            .collect(Collectors.toList());
+                        // Ideally we would want to check the version of the elected master node and
+                        // send the delete request directly to that node.
+                        if (nodeVersions.stream().allMatch(version -> version.onOrAfter(Version.V_7_13_0))) {
                             try {
-                                adminClient().performRequest(new Request("DELETE", "_index_template/" + name));
+                                adminClient().performRequest(new Request("DELETE", "_index_template/" + String.join(",", names)));
                             } catch (ResponseException e) {
-                                logger.debug(new ParameterizedMessage("unable to remove composable index template {}", name), e);
+                                logger.warn(new ParameterizedMessage("unable to remove multiple composable index templates {}", names), e);
+                            }
+                        } else {
+                            for (String name : names) {
+                                try {
+                                    adminClient().performRequest(new Request("DELETE", "_index_template/" + name));
+                                } catch (ResponseException e) {
+                                    logger.warn(new ParameterizedMessage("unable to remove composable index template {}", name), e);
+                                }
                             }
                         }
+                    } catch (Exception e) {
+                        logger.debug("ignoring exception removing all composable index templates", e);
+                        // We hit a version of ES that doesn't support index templates v2 yet, so it's safe to ignore
                     }
-                } catch (Exception e) {
-                    logger.debug("ignoring exception removing all composable index templates", e);
-                    // We hit a version of ES that doesn't support index templates v2 yet, so it's safe to ignore
-                }
-                try {
-                    Request compReq = new Request("GET", "_component_template");
-                    String componentTemplates = EntityUtils.toString(adminClient().performRequest(compReq).getEntity());
-                    Map<String, Object> cTemplates = XContentHelper.convertToMap(JsonXContent.jsonXContent, componentTemplates, false);
-                    List<String> names = ((List<?>) cTemplates.get("component_templates")).stream()
-                        .map(ct -> (String) ((Map<?, ?>) ct).get("name"))
-                        .collect(Collectors.toList());
-                    for (String componentTemplate : names) {
-                        try {
-                            if (isXPackTemplate(componentTemplate)) {
-                                continue;
+                    try {
+                        Request compReq = new Request("GET", "_component_template");
+                        String componentTemplates = EntityUtils.toString(adminClient().performRequest(compReq).getEntity());
+                        Map<String, Object> cTemplates = XContentHelper.convertToMap(JsonXContent.jsonXContent, componentTemplates, false);
+                        List<String> names = ((List<?>) cTemplates.get("component_templates")).stream()
+                            .map(ct -> (String) ((Map<?, ?>) ct).get("name"))
+                            .filter(name -> isXPackTemplate(name) == false)
+                            .collect(Collectors.toList());
+                        // Ideally we would want to check the version of the elected master node and
+                        // send the delete request directly to that node.
+                        if (nodeVersions.stream().allMatch(version -> version.onOrAfter(Version.V_8_0_0))) {
+                            try {
+                                adminClient().performRequest(new Request("DELETE", "_component_template/" + String.join(",", names)));
+                            } catch (ResponseException e) {
+                                logger.warn(new ParameterizedMessage("unable to remove multiple component templates {}", names), e);
+                            }
+                        } else {
+                            for (String componentTemplate : names) {
+                                try {
+                                    adminClient().performRequest(new Request("DELETE", "_component_template/" + componentTemplate));
+                                } catch (ResponseException e) {
+                                    logger.warn(new ParameterizedMessage("unable to remove component template {}", componentTemplate), e);
+                                }
                             }
-                            adminClient().performRequest(new Request("DELETE", "_component_template/" + componentTemplate));
-                        } catch (ResponseException e) {
-                            logger.debug(new ParameterizedMessage("unable to remove component template {}", componentTemplate), e);
                         }
+                    } catch (Exception e) {
+                        logger.debug("ignoring exception removing all component templates", e);
+                        // We hit a version of ES that doesn't support index templates v2 yet, so it's safe to ignore
                     }
-                } catch (Exception e) {
-                    logger.debug("ignoring exception removing all component templates", e);
-                    // We hit a version of ES that doesn't support index templates v2 yet, so it's safe to ignore
                 }
+                // Always check for legacy templates:
                 Request getLegacyTemplatesRequest = new Request("GET", "_template");
                 Map<String, Object> legacyTemplates = XContentHelper.convertToMap(JsonXContent.jsonXContent,
                     EntityUtils.toString(adminClient().performRequest(getLegacyTemplatesRequest).getEntity()), false);
@@ -696,7 +709,7 @@ public abstract class ESRestTestCase extends ESTestCase {
 
     protected static void wipeDataStreams() throws IOException {
         try {
-            if (hasXPack()) {
+            if (hasXPack() && nodeVersions.stream().allMatch(version -> version.onOrAfter(Version.V_7_9_0))) {
                 adminClient().performRequest(new Request("DELETE", "_data_stream/*?expand_wildcards=all"));
             }
         } catch (ResponseException e) {
@@ -1396,6 +1409,8 @@ public abstract class ESRestTestCase extends ESTestCase {
             case ".snapshot-blob-cache":
             case ".deprecation-indexing-template":
             case "ilm-history":
+            case "logstash-index-template":
+            case "security-index-template":
                 return true;
             default:
                 return false;