listener) {
+ return restHighLevelClient.performRequestAsyncAndParseEntity(simulateIndexTemplateRequest,
+ IndicesRequestConverters::simulateIndexTemplate, options, SimulateIndexTemplateResponse::fromXContent, listener, emptySet());
+ }
+
/**
* Validate a potentially expensive query without executing it.
*
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesRequestConverters.java
index a024df14bfcf2..e9e2fa13595c4 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesRequestConverters.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesRequestConverters.java
@@ -45,8 +45,8 @@
import org.elasticsearch.client.indices.FreezeIndexRequest;
import org.elasticsearch.client.indices.GetFieldMappingsRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
-import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
import org.elasticsearch.client.indices.GetIndexTemplateV2Request;
+import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
import org.elasticsearch.client.indices.GetMappingsRequest;
import org.elasticsearch.client.indices.IndexTemplateV2ExistRequest;
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
@@ -55,6 +55,7 @@
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.client.indices.ReloadAnalyzersRequest;
import org.elasticsearch.client.indices.ResizeRequest;
+import org.elasticsearch.client.indices.SimulateIndexTemplateRequest;
import org.elasticsearch.client.indices.UnfreezeIndexRequest;
import org.elasticsearch.client.indices.rollover.RolloverRequest;
import org.elasticsearch.cluster.metadata.IndexMetadata;
@@ -426,6 +427,26 @@ static Request putIndexTemplate(PutIndexTemplateV2Request putIndexTemplateReques
return request;
}
+ static Request simulateIndexTemplate(SimulateIndexTemplateRequest simulateIndexTemplateRequest) throws IOException {
+ String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_index_template", "_simulate_index")
+ .addPathPart(simulateIndexTemplateRequest.indexName()).build();
+ Request request = new Request(HttpPost.METHOD_NAME, endpoint);
+ RequestConverters.Params params = new RequestConverters.Params();
+ params.withMasterTimeout(simulateIndexTemplateRequest.masterNodeTimeout());
+ PutIndexTemplateV2Request putIndexTemplateV2Request = simulateIndexTemplateRequest.indexTemplateV2Request();
+ if (putIndexTemplateV2Request != null) {
+ if (putIndexTemplateV2Request.create()) {
+ params.putParam("create", Boolean.TRUE.toString());
+ }
+ if (Strings.hasText(putIndexTemplateV2Request.cause())) {
+ params.putParam("cause", putIndexTemplateV2Request.cause());
+ }
+ request.setEntity(RequestConverters.createEntity(putIndexTemplateV2Request, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
+ }
+ request.addParameters(params.asMap());
+ return request;
+ }
+
static Request validateQuery(ValidateQueryRequest validateQueryRequest) throws IOException {
String[] indices = validateQueryRequest.indices() == null ? Strings.EMPTY_ARRAY : validateQueryRequest.indices();
String endpoint = RequestConverters.endpoint(indices, "_validate/query");
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/SimulateIndexTemplateRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/SimulateIndexTemplateRequest.java
new file mode 100644
index 0000000000000..c7147f569a6bd
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/SimulateIndexTemplateRequest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.indices;
+
+import org.elasticsearch.client.TimedRequest;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.Strings;
+
+/**
+ * A request to simulate matching a provided index name and an optional new index template against the existing index templates.
+ */
+public class SimulateIndexTemplateRequest extends TimedRequest {
+
+ private String indexName;
+
+ @Nullable
+ private PutIndexTemplateV2Request indexTemplateV2Request;
+
+ public SimulateIndexTemplateRequest(String indexName) {
+ if (Strings.isNullOrEmpty(indexName)) {
+ throw new IllegalArgumentException("index name cannot be null or empty");
+ }
+ this.indexName = indexName;
+ }
+
+ /**
+ * Return the index name for which we simulate the index template matching.
+ */
+ public String indexName() {
+ return indexName;
+ }
+
+ /**
+ * Set the index name to simulate template matching against the index templates in the system.
+ */
+ public SimulateIndexTemplateRequest indexName(String indexName) {
+ if (Strings.isNullOrEmpty(indexName)) {
+ throw new IllegalArgumentException("index name cannot be null or empty");
+ }
+ this.indexName = indexName;
+ return this;
+ }
+
+ /**
+ * An optional new template request will be part of the index template simulation.
+ */
+ @Nullable
+ public PutIndexTemplateV2Request indexTemplateV2Request() {
+ return indexTemplateV2Request;
+ }
+
+ /**
+ * Optionally, define a new template request which will included in the index simulation as if it was an index template stored in the
+ * system. The new template will be validated just as a regular, standalone, live, new index template request.
+ */
+ public SimulateIndexTemplateRequest indexTemplateV2Request(@Nullable PutIndexTemplateV2Request indexTemplateV2Request) {
+ this.indexTemplateV2Request = indexTemplateV2Request;
+ return this;
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/SimulateIndexTemplateResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/SimulateIndexTemplateResponse.java
new file mode 100644
index 0000000000000..161e2f54a8f07
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/SimulateIndexTemplateResponse.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.client.indices;
+
+import org.elasticsearch.cluster.metadata.Template;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class SimulateIndexTemplateResponse {
+
+ private static final ParseField TEMPLATE = new ParseField("template");
+ private static final ParseField OVERLAPPING = new ParseField("overlapping");
+ private static final ParseField NAME = new ParseField("name");
+ private static final ParseField INDEX_PATTERNS = new ParseField("index_patterns");
+
+ @SuppressWarnings("unchecked")
+ private static final ConstructingObjectParser PARSER =
+ new ConstructingObjectParser<>("simulate_index_templates_response", false,
+ a -> new SimulateIndexTemplateResponse(
+ a[0] != null ? (Template) a[0] : null,
+ a[1] != null ?
+ ((List) a[1]).stream()
+ .collect(Collectors.toMap(IndexTemplateAndPatterns::name, IndexTemplateAndPatterns::indexPatterns)) : null
+ )
+ );
+
+ @SuppressWarnings("unchecked")
+ private static final ConstructingObjectParser INNER_PARSER =
+ new ConstructingObjectParser<>("index_template_and_patterns", false,
+ a -> new IndexTemplateAndPatterns((String) a[0], (List) a[1]));
+
+ private static class IndexTemplateAndPatterns {
+ String name;
+ List indexPatterns;
+
+ IndexTemplateAndPatterns(String name, List indexPatterns) {
+ this.name = name;
+ this.indexPatterns = indexPatterns;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public List indexPatterns() {
+ return indexPatterns;
+ }
+ }
+
+ static {
+ PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), Template.PARSER, TEMPLATE);
+ INNER_PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
+ INNER_PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), INDEX_PATTERNS);
+ PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(), INNER_PARSER, OVERLAPPING);
+ }
+
+ @Nullable
+ // the resolved settings, mappings and aliases for the matched templates, if any
+ private Template resolvedTemplate;
+
+ @Nullable
+ // a map of template names and their index patterns that would overlap when matching the given index name
+ private Map> overlappingTemplates;
+
+ SimulateIndexTemplateResponse(@Nullable Template resolvedTemplate, @Nullable Map> overlappingTemplates) {
+ this.resolvedTemplate = resolvedTemplate;
+ this.overlappingTemplates = overlappingTemplates;
+ }
+
+ public static SimulateIndexTemplateResponse fromXContent(XContentParser parser) throws IOException {
+ return PARSER.parse(parser, null);
+ }
+
+ public Template resolvedTemplate() {
+ return resolvedTemplate;
+ }
+
+ public Map> overlappingTemplates() {
+ return overlappingTemplates;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ SimulateIndexTemplateResponse that = (SimulateIndexTemplateResponse) o;
+ return Objects.equals(resolvedTemplate, that.resolvedTemplate)
+ && Objects.deepEquals(overlappingTemplates, that.overlappingTemplates);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(resolvedTemplate, overlappingTemplates);
+ }
+
+ @Override
+ public String toString() {
+ return "SimulateIndexTemplateResponse{" + "resolved template=" + resolvedTemplate + ", overlapping templates="
+ + String.join("|", overlappingTemplates.keySet()) + "}";
+ }
+}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java
index 66d7174d04053..43e49130db69d 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java
@@ -67,9 +67,9 @@
import org.elasticsearch.client.indices.GetFieldMappingsResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
+import org.elasticsearch.client.indices.GetIndexTemplateV2Request;
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
import org.elasticsearch.client.indices.GetIndexTemplatesResponse;
-import org.elasticsearch.client.indices.GetIndexTemplateV2Request;
import org.elasticsearch.client.indices.GetIndexTemplatesV2Response;
import org.elasticsearch.client.indices.GetMappingsRequest;
import org.elasticsearch.client.indices.GetMappingsResponse;
@@ -81,6 +81,8 @@
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.client.indices.ReloadAnalyzersRequest;
import org.elasticsearch.client.indices.ReloadAnalyzersResponse;
+import org.elasticsearch.client.indices.SimulateIndexTemplateRequest;
+import org.elasticsearch.client.indices.SimulateIndexTemplateResponse;
import org.elasticsearch.client.indices.UnfreezeIndexRequest;
import org.elasticsearch.client.indices.rollover.RolloverRequest;
import org.elasticsearch.client.indices.rollover.RolloverResponse;
@@ -1614,4 +1616,40 @@ public void testIndexTemplates() throws Exception {
assertFalse(exist);
}
+
+ public void testSimulateIndexTemplate() throws Exception {
+ String templateName = "my-template";
+ Settings settings = Settings.builder().put("index.number_of_shards", 1).build();
+ CompressedXContent mappings = new CompressedXContent("{\"properties\":{\"host_name\":{\"type\":\"keyword\"}}}");
+ AliasMetadata alias = AliasMetadata.builder("alias").writeIndex(true).build();
+ Template template = new Template(settings, mappings, Map.of("alias", alias));
+ List pattern = List.of("pattern");
+ IndexTemplateV2 indexTemplate = new IndexTemplateV2(pattern, template, Collections.emptyList(), 1L, 1L, new HashMap<>());
+ PutIndexTemplateV2Request putIndexTemplateV2Request =
+ new PutIndexTemplateV2Request().name(templateName).create(true).indexTemplate(indexTemplate);
+
+ AcknowledgedResponse response = execute(putIndexTemplateV2Request,
+ highLevelClient().indices()::putIndexTemplate, highLevelClient().indices()::putIndexTemplateAsync);
+ assertThat(response.isAcknowledged(), equalTo(true));
+
+ SimulateIndexTemplateRequest simulateIndexTemplateRequest = new SimulateIndexTemplateRequest("pattern");
+ AliasMetadata simulationAlias = AliasMetadata.builder("simulation-alias").writeIndex(true).build();
+ IndexTemplateV2 simulationTemplate = new IndexTemplateV2(pattern, new Template(null, null,
+ Map.of("simulation-alias", simulationAlias)), Collections.emptyList(), 2L, 1L, new HashMap<>());
+ PutIndexTemplateV2Request newIndexTemplateReq =
+ new PutIndexTemplateV2Request().name("used-for-simulation").create(true).indexTemplate(indexTemplate);
+ newIndexTemplateReq.indexTemplate(simulationTemplate);
+ simulateIndexTemplateRequest.indexTemplateV2Request(newIndexTemplateReq);
+
+ SimulateIndexTemplateResponse simulateResponse = execute(simulateIndexTemplateRequest,
+ highLevelClient().indices()::simulateIndexTemplate, highLevelClient().indices()::simulateIndexTemplateAsync);
+
+ Map aliases = simulateResponse.resolvedTemplate().aliases();
+ assertThat(aliases, is(notNullValue()));
+ assertThat("the template we provided for the simulation has a higher priority than the one in the system",
+ aliases.get("simulation-alias"), is(notNullValue()));
+ assertThat(aliases.get("simulation-alias").getAlias(), is("simulation-alias"));
+ assertThat("existing template overlaps the higher priority template we provided for the simulation",
+ simulateResponse.overlappingTemplates().get("my-template").get(0), is("pattern"));
+ }
}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java
index c852469126031..4745ee4f11be8 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java
@@ -860,8 +860,7 @@ public void testApiNamingConventions() throws Exception {
"scripts_painless_execute",
"indices.create_data_stream",
"indices.get_data_streams",
- "indices.delete_data_stream",
- "indices.simulate_index_template"
+ "indices.delete_data_stream"
};
//These API are not required for high-level client feature completeness
String[] notRequiredApi = new String[] {
diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/indices.simulate_index_template.json b/rest-api-spec/src/main/resources/rest-api-spec/api/indices.simulate_index_template.json
index c9a966e3fa137..889310e977142 100644
--- a/rest-api-spec/src/main/resources/rest-api-spec/api/indices.simulate_index_template.json
+++ b/rest-api-spec/src/main/resources/rest-api-spec/api/indices.simulate_index_template.json
@@ -22,6 +22,16 @@
]
},
"params":{
+ "create":{
+ "type":"boolean",
+ "description":"Whether the index template we optionally defined in the body should only be dry-run added if new or can also replace an existing one",
+ "default":false
+ },
+ "cause":{
+ "type":"string",
+ "description":"User defined reason for dry-run creating the new template for simulation purposes",
+ "default":false
+ },
"master_timeout":{
"type":"time",
"description":"Specify timeout for connection to master"
diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/Template.java b/server/src/main/java/org/elasticsearch/cluster/metadata/Template.java
index 2497e986c97d9..c4ecaa0e8f376 100644
--- a/server/src/main/java/org/elasticsearch/cluster/metadata/Template.java
+++ b/server/src/main/java/org/elasticsearch/cluster/metadata/Template.java
@@ -53,7 +53,7 @@ public class Template extends AbstractDiffable implements ToXContentOb
private static final ParseField ALIASES = new ParseField("aliases");
@SuppressWarnings("unchecked")
- static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("template", false,
+ public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("template", false,
a -> new Template((Settings) a[0], (CompressedXContent) a[1], (Map) a[2]));
static {
diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestSimulateIndexTemplateAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestSimulateIndexTemplateAction.java
index a73af8019e781..a15256168f683 100644
--- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestSimulateIndexTemplateAction.java
+++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestSimulateIndexTemplateAction.java
@@ -54,6 +54,9 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC
if (request.hasContent()) {
PutIndexTemplateV2Action.Request indexTemplateRequest = new PutIndexTemplateV2Action.Request("simulating_template");
indexTemplateRequest.indexTemplate(IndexTemplateV2.parse(request.contentParser()));
+ indexTemplateRequest.create(request.paramAsBoolean("create", false));
+ indexTemplateRequest.cause(request.param("cause", "api"));
+
simulateIndexTemplateRequest.indexTemplateRequest(indexTemplateRequest);
}