[7.x] Add template simulation API for simulating template composition (#56842) (#56924)

This commit is contained in:
Lee Hinman 2020-05-19 08:12:21 -06:00 committed by GitHub
parent 342e713e2a
commit e208925465
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 861 additions and 52 deletions

View File

@ -803,7 +803,8 @@ public class RestHighLevelClientTests extends ESTestCase {
"scripts_painless_execute",
"indices.create_data_stream",
"indices.get_data_stream",
"indices.delete_data_stream"
"indices.delete_data_stream",
"indices.simulate_template"
};
//These API are not required for high-level client feature completeness
String[] notRequiredApi = new String[] {

View File

@ -96,7 +96,7 @@ PUT _index_template/template_1
[source,console]
--------------------------------------------------
DELETE _index_template/template_*
DELETE _index_template/*
DELETE _component_template/*
--------------------------------------------------
// TEARDOWN
@ -291,6 +291,135 @@ PUT /_index_template/template_1
In this case, an index matching `t*` will have three primary shards. If the order of composed
templates were reversed, the index would have two primary shards.
[[simulating-templates]]
===== Simulating template composition
Since templates can be composed not only of multiple component templates, but also the index
template itself, there are two simulation APIs to determine what the resulting index settings will
be.
To simulate the settings that would be applied to a matching index name:
[source,console]
--------------------------------------------------
POST /_index_template/_simulate_index/myindex
--------------------------------------------------
To simulate the settings that would be applied from a particular template:
[source,console]
--------------------------------------------------
POST /_index_template/_simulate/template_1
POST /_index_template/_simulate
{
"index_patterns": ["foo"],
"template": {
"settings": {
"number_of_replicas": 0
}
}
}
--------------------------------------------------
Here's an example demonstrating simulating both an index name and template name:
[source,console]
--------------------------------------------------
PUT /_component_template/ct1 <1>
{
"template": {
"settings": {
"index.number_of_shards": 2
}
}
}
PUT /_component_template/ct2 <2>
{
"template": {
"settings": {
"index.number_of_replicas": 0
},
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
}
}
}
}
}
PUT /_index_template/final-template <3>
{
"index_patterns": ["logdata-*"],
"composed_of": ["ct1", "ct2"],
"priority": 5
}
POST /_index_template/_simulate_index/logdata-2019-02-01 <4>
POST /_index_template/_simulate/final-template <5>
POST /_index_template/_simulate <6>
{
"index_patterns": ["logdata-*"],
"composed_of": ["ct2"],
"priority": 10,
"template": {
"settings": {
"index.number_of_replicas": 1
}
}
}
--------------------------------------------------
<1> Creating a component template (ct1) setting the number of shards to two
<2> Creating another component template (ct2) setting the number of replicas to zero with mappings
<3> Creating an index template called "final" template using ct1 and ct2
<4> Simulate the settings that would be applied for a new index "logdata-2019-02-01"
<5> Simulate the settings composed using the "final-template" index template
<6> Simulate the settings composed using a custom specified template
The output of the simulate API from the last example call looks like:
[source,console-result]
---------------------------------------------------------
{
"template" : {
"settings" : {
"index" : {
"number_of_replicas" : "1" <1>
}
},
"mappings" : {
"properties" : {
"@timestamp" : { <2>
"type" : "date"
}
}
},
"aliases" : { }
},
"overlapping" : [ <3>
{
"name" : "final-template",
"index_patterns" : [
"logdata-*"
]
}
]
}
---------------------------------------------------------
<1> The number of replicas from the simulated template body
<2> The `@timestamp` field inherited from the "ct2" component template
<3> Any overlapping templates that would have matched, but have lower priority
When simulating a template and specifying a template in the body of the request, the simulated
template is not added to the existing templates, it is only used for the simulation.
===== Index template with index aliases
You can include <<indices-aliases,index aliases>> in an index template.

View File

@ -0,0 +1,51 @@
{
"indices.simulate_template":{
"documentation":{
"url":"https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html",
"description": "Simulate resolving the given template name or body"
},
"stability":"stable",
"url":{
"paths":[
{
"path":"/_index_template/_simulate",
"methods":[
"POST"
]
},
{
"path":"/_index_template/_simulate/{name}",
"methods":[
"POST"
],
"parts":{
"name":{
"type":"string",
"description":"The name of the index template"
}
}
}
]
},
"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"
}
},
"body":{
"description":"New index template definition to be simulated, if no index template name is specified",
"required":false
}
}
}

View File

@ -1,8 +1,8 @@
---
"Simulate index template without new template in the body":
- skip:
version: " - 7.7.99"
reason: "simulate index template API unavailable before 7.8"
version: " - 7.8.99"
reason: "simulate index template API format changed in 7.9 to drop _doc"
features: ["default_shards"]
- do:
@ -25,14 +25,14 @@
- match: {template.settings.index.number_of_shards: "1"}
- match: {template.settings.index.number_of_replicas: "0"}
- match: {template.mappings._doc.properties.field.type: "keyword"}
- match: {template.mappings.properties.field.type: "keyword"}
- match: {overlapping: []}
---
"Simulate index template specifying a new template":
- skip:
version: " - 7.7.99"
reason: "simulate index template API unavailable before 7.8"
version: " - 7.8.99"
reason: "simulate index template API format changed in 7.9 to drop _doc"
features: ["default_shards"]
- do:
@ -77,7 +77,7 @@
- match: {template.settings.index.blocks.write: "true"}
- match: {template.settings.index.number_of_replicas: "2"}
- match: {template.mappings._doc.properties.ct_field.type: "keyword"}
- match: {template.mappings.properties.ct_field.type: "keyword"}
- match: {overlapping.0.name: existing_test}
- match: {overlapping.0.index_patterns: ["te*"]}
- length: {template.aliases: 1}
@ -86,8 +86,8 @@
---
"Simulate index template with index not matching any template":
- skip:
version: " - 7.7.99"
reason: "simulate index template API unavailable before 7.8"
version: " - 7.8.99"
reason: "simulate index template API format changed in 7.9 to drop _doc"
features: allowed_warnings
- do:
@ -116,8 +116,8 @@
---
"Simulate index matches overlapping V1 and V2 templates":
- skip:
version: " - 7.7.99"
reason: "simulate index template API unavailable before 7.8"
version: " - 7.8.99"
reason: "simulate index template API format changed in 7.9 to drop _doc"
features: ["allowed_warnings", "default_shards"]
- do:
@ -170,7 +170,7 @@
- match: {template.settings.index.number_of_shards: "1"}
- match: {template.settings.index.number_of_replicas: "0"}
- match: {template.mappings._doc.properties.field.type: "keyword"}
- match: {template.mappings.properties.field.type: "keyword"}
- match: {overlapping.0.name: v1_template}
- match: {overlapping.0.index_patterns: ["t*", "t1*"]}
- match: {overlapping.1.name: v2_template}

View File

@ -0,0 +1,146 @@
---
"Simulate template without a template in the body":
- skip:
version: " - 7.99.99"
reason: "not yet backported"
features: ["default_shards"]
- do:
indices.put_index_template:
name: my-template
body:
index_patterns: other
template:
settings:
number_of_shards: 1
number_of_replicas: 0
mappings:
properties:
field:
type: keyword
- do:
indices.simulate_template:
name: my-template
- match: {template.settings.index.number_of_shards: "1"}
- match: {template.settings.index.number_of_replicas: "0"}
- match: {template.mappings.properties.field.type: "keyword"}
- match: {overlapping: []}
---
"Simulate index template specifying a new template":
- skip:
version: " - 7.99.99"
reason: "not yet backported"
features: ["default_shards"]
- do:
indices.put_index_template:
name: existing_test
body:
index_patterns: te*
priority: 10
template:
settings:
number_of_shards: 1
number_of_replicas: 0
mappings:
properties:
field:
type: keyword
- do:
cluster.put_component_template:
name: ct
body:
template:
settings:
index.number_of_replicas: 2
mappings:
properties:
ct_field:
type: keyword
- do:
indices.simulate_template:
body:
index_patterns: te*
priority: 15
template:
settings:
index.blocks.write: true
aliases:
test_alias: {}
composed_of: ["ct"]
- match: {template.settings.index.blocks.write: "true"}
- match: {template.settings.index.number_of_replicas: "2"}
- match: {template.mappings.properties.ct_field.type: "keyword"}
- match: {overlapping.0.name: existing_test}
- match: {overlapping.0.index_patterns: ["te*"]}
- length: {template.aliases: 1}
- is_true: template.aliases.test_alias
---
"Simulate template matches overlapping V1 and V2 templates":
- skip:
version: " - 7.99.99"
reason: "not yet backported"
features: ["allowed_warnings", "default_shards"]
- do:
indices.put_template:
name: v1_template
body:
index_patterns: [t*, t1*]
settings:
number_of_shards: 5
- do:
allowed_warnings:
- "index template [v2_template] has index patterns [te*] matching patterns from existing older templates [v1_template] with patterns
(v1_template => [t*, t1*]); this template [v2_template] will take precedence during new index creation"
indices.put_index_template:
name: v2_template
body:
index_patterns: te*
priority: 10
template:
settings:
number_of_shards: 10
number_of_replicas: 2
mappings:
properties:
field:
type: text
- do:
allowed_warnings:
- "index template [winning_v2_template] has index patterns [te*] matching patterns from existing older templates [v1_template] with patterns
(v1_template => [t*, t1*]); this template [winning_v2_template] will take precedence during new index creation"
indices.put_index_template:
name: winning_v2_template
body:
index_patterns: te*
priority: 20
template:
settings:
number_of_shards: 1
number_of_replicas: 0
mappings:
properties:
field:
type: keyword
- do:
indices.simulate_template:
name: winning_v2_template
- match: {template.settings.index.number_of_shards: "1"}
- match: {template.settings.index.number_of_replicas: "0"}
- match: {template.mappings.properties.field.type: "keyword"}
- match: {overlapping.0.name: v1_template}
- match: {overlapping.0.index_patterns: ["t*", "t1*"]}
- match: {overlapping.1.name: v2_template}
- match: {overlapping.1.index_patterns: ["te*"]}

View File

@ -165,7 +165,9 @@ import org.elasticsearch.action.admin.indices.template.get.TransportGetComponent
import org.elasticsearch.action.admin.indices.template.get.TransportGetIndexTemplateV2Action;
import org.elasticsearch.action.admin.indices.template.get.TransportGetIndexTemplatesAction;
import org.elasticsearch.action.admin.indices.template.post.SimulateIndexTemplateAction;
import org.elasticsearch.action.admin.indices.template.post.SimulateTemplateAction;
import org.elasticsearch.action.admin.indices.template.post.TransportSimulateIndexTemplateAction;
import org.elasticsearch.action.admin.indices.template.post.TransportSimulateTemplateAction;
import org.elasticsearch.action.admin.indices.template.put.PutComponentTemplateAction;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateV2Action;
@ -314,7 +316,6 @@ import org.elasticsearch.rest.action.admin.indices.RestIndicesSegmentsAction;
import org.elasticsearch.rest.action.admin.indices.RestIndicesShardStoresAction;
import org.elasticsearch.rest.action.admin.indices.RestIndicesStatsAction;
import org.elasticsearch.rest.action.admin.indices.RestOpenIndexAction;
import org.elasticsearch.rest.action.admin.indices.RestSimulateIndexTemplateAction;
import org.elasticsearch.rest.action.admin.indices.RestPutComponentTemplateAction;
import org.elasticsearch.rest.action.admin.indices.RestPutIndexTemplateAction;
import org.elasticsearch.rest.action.admin.indices.RestPutIndexTemplateV2Action;
@ -323,6 +324,8 @@ import org.elasticsearch.rest.action.admin.indices.RestRecoveryAction;
import org.elasticsearch.rest.action.admin.indices.RestRefreshAction;
import org.elasticsearch.rest.action.admin.indices.RestResizeHandler;
import org.elasticsearch.rest.action.admin.indices.RestRolloverIndexAction;
import org.elasticsearch.rest.action.admin.indices.RestSimulateIndexTemplateAction;
import org.elasticsearch.rest.action.admin.indices.RestSimulateTemplateAction;
import org.elasticsearch.rest.action.admin.indices.RestSyncedFlushAction;
import org.elasticsearch.rest.action.admin.indices.RestUpdateSettingsAction;
import org.elasticsearch.rest.action.admin.indices.RestUpgradeAction;
@ -550,6 +553,7 @@ public class ActionModule extends AbstractModule {
actions.register(GetIndexTemplateV2Action.INSTANCE, TransportGetIndexTemplateV2Action.class);
actions.register(DeleteIndexTemplateV2Action.INSTANCE, TransportDeleteIndexTemplateV2Action.class);
actions.register(SimulateIndexTemplateAction.INSTANCE, TransportSimulateIndexTemplateAction.class);
actions.register(SimulateTemplateAction.INSTANCE, TransportSimulateTemplateAction.class);
actions.register(ValidateQueryAction.INSTANCE, TransportValidateQueryAction.class);
actions.register(RefreshAction.INSTANCE, TransportRefreshAction.class);
actions.register(FlushAction.INSTANCE, TransportFlushAction.class);
@ -692,6 +696,7 @@ public class ActionModule extends AbstractModule {
registerHandler.accept(new RestGetIndexTemplateV2Action());
registerHandler.accept(new RestDeleteIndexTemplateV2Action());
registerHandler.accept(new RestSimulateIndexTemplateAction());
registerHandler.accept(new RestSimulateTemplateAction());
registerHandler.accept(new RestPutMappingAction());
registerHandler.accept(new RestGetMappingAction());

View File

@ -0,0 +1,136 @@
/*
* 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.action.admin.indices.template.post;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.ValidateActions;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateV2Action;
import org.elasticsearch.action.support.master.MasterNodeReadRequest;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import java.util.Objects;
/**
* An action for simulating the complete composed settings of the specified
* index template name, or index template configuration
*/
public class SimulateTemplateAction extends ActionType<SimulateIndexTemplateResponse> {
public static final SimulateTemplateAction INSTANCE = new SimulateTemplateAction();
public static final String NAME = "indices:admin/index_template/simulate";
private SimulateTemplateAction() {
super(NAME, SimulateIndexTemplateResponse::new);
}
public static class Request extends MasterNodeReadRequest<Request> {
@Nullable
private String templateName;
@Nullable
private PutIndexTemplateV2Action.Request indexTemplateRequest;
public Request() { }
public Request(String templateName) {
if (templateName == null) {
throw new IllegalArgumentException("template name cannot be null");
}
this.templateName = templateName;
}
public Request(PutIndexTemplateV2Action.Request indexTemplateRequest) {
if (indexTemplateRequest == null) {
throw new IllegalArgumentException("index template body must be present");
}
this.indexTemplateRequest = indexTemplateRequest;
}
public Request(StreamInput in) throws IOException {
super(in);
templateName = in.readOptionalString();
indexTemplateRequest = in.readOptionalWriteable(PutIndexTemplateV2Action.Request::new);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeOptionalString(templateName);
out.writeOptionalWriteable(indexTemplateRequest);
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (indexTemplateRequest != null) {
validationException = indexTemplateRequest.validateIndexTemplate(validationException);
}
if (templateName == null && indexTemplateRequest == null) {
validationException =
ValidateActions.addValidationError("either index name or index template body must be specified for simulation",
validationException);
}
return validationException;
}
@Nullable
public String getTemplateName() {
return templateName;
}
@Nullable
public PutIndexTemplateV2Action.Request getIndexTemplateRequest() {
return indexTemplateRequest;
}
public Request templateName(String templateName) {
this.templateName = templateName;
return this;
}
public Request indexTemplateRequest(PutIndexTemplateV2Action.Request indexTemplateRequest) {
this.indexTemplateRequest = indexTemplateRequest;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Request that = (Request) o;
return templateName.equals(that.templateName) &&
Objects.equals(indexTemplateRequest, that.indexTemplateRequest);
}
@Override
public int hashCode() {
return Objects.hash(templateName, indexTemplateRequest);
}
}
}

View File

@ -98,29 +98,88 @@ public class TransportSimulateIndexTemplateAction
@Override
protected void masterOperation(SimulateIndexTemplateRequest request, ClusterState state,
ActionListener<SimulateIndexTemplateResponse> listener) throws Exception {
ClusterState simulateOnClusterState = state;
final ClusterState stateWithTemplate;
if (request.getIndexTemplateRequest() != null) {
// we'll "locally" add the template defined by the user in the cluster state (as if it existed in the system)
String simulateTemplateToAdd = "simulate_new_template_" + UUIDs.randomBase64UUID().toLowerCase(Locale.ROOT);
simulateOnClusterState = indexTemplateService.addIndexTemplateV2(state, request.getIndexTemplateRequest().create(),
String simulateTemplateToAdd = "simulate_index_template_" + UUIDs.randomBase64UUID().toLowerCase(Locale.ROOT);
// Perform validation for things like typos in component template names
MetadataIndexTemplateService.validateV2TemplateRequest(state.metadata(), simulateTemplateToAdd,
request.getIndexTemplateRequest().indexTemplate());
stateWithTemplate = indexTemplateService.addIndexTemplateV2(state, request.getIndexTemplateRequest().create(),
simulateTemplateToAdd, request.getIndexTemplateRequest().indexTemplate());
} else {
stateWithTemplate = state;
}
String matchingTemplate = findV2Template(simulateOnClusterState.metadata(), request.getIndexName(), false);
String matchingTemplate = findV2Template(stateWithTemplate.metadata(), request.getIndexName(), false);
if (matchingTemplate == null) {
listener.onResponse(new SimulateIndexTemplateResponse(null, null));
return;
}
Settings settings = resolveSettings(simulateOnClusterState.metadata(), matchingTemplate);
final ClusterState tempClusterState = resolveTemporaryState(matchingTemplate, request.getIndexName(), stateWithTemplate);
IndexTemplateV2 templateV2 = tempClusterState.metadata().templatesV2().get(matchingTemplate);
assert templateV2 != null : "the matched template must exist";
final Template template = resolveTemplate(matchingTemplate, request.getIndexName(), stateWithTemplate,
xContentRegistry, indicesService, aliasValidator);
final Map<String, List<String>> overlapping = new HashMap<>();
overlapping.putAll(findConflictingV1Templates(tempClusterState, matchingTemplate, templateV2.indexPatterns()));
overlapping.putAll(findConflictingV2Templates(tempClusterState, matchingTemplate, templateV2.indexPatterns()));
listener.onResponse(new SimulateIndexTemplateResponse(template, overlapping));
}
@Override
protected ClusterBlockException checkBlock(SimulateIndexTemplateRequest request, ClusterState state) {
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ);
}
/**
* Return a temporary cluster state with an index that exists using the
* matched template's settings
*/
public static ClusterState resolveTemporaryState(final String matchingTemplate, final String indexName,
final ClusterState simulatedState) {
Settings settings = resolveSettings(simulatedState.metadata(), matchingTemplate);
// create the index with dummy settings in the cluster state so we can parse and validate the aliases
Settings dummySettings = Settings.builder()
.put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT)
.put(settings)
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
.put(IndexMetadata.SETTING_INDEX_UUID, UUIDs.randomBase64UUID())
.build();
final IndexMetadata indexMetadata = IndexMetadata.builder(indexName).settings(dummySettings).build();
return ClusterState.builder(simulatedState)
.metadata(Metadata.builder(simulatedState.metadata())
.put(indexMetadata, true)
.build())
.build();
}
/**
* Take a template and index name as well as state where the template exists, and return a final
* {@link Template} that represents all the resolved Settings, Mappings, and Aliases
*/
public static Template resolveTemplate(final String matchingTemplate, final String indexName,
final ClusterState simulatedState,
final NamedXContentRegistry xContentRegistry,
final IndicesService indicesService,
final AliasValidator aliasValidator) throws Exception {
Settings settings = resolveSettings(simulatedState.metadata(), matchingTemplate);
// empty request mapping as the user can't specify any explicit mappings via the simulate api
Map<String, Map<String, Object>> mappings = resolveV2Mappings("{}", simulateOnClusterState, matchingTemplate, xContentRegistry);
String mappingsJson = Strings.toString(XContentFactory.jsonBuilder()
.startObject()
.field(MapperService.SINGLE_MAPPING_NAME, mappings)
.endObject());
Map<String, Map<String, Object>> mappings = resolveV2Mappings("{}", simulatedState, matchingTemplate, xContentRegistry);
assert mappings.size() == 1 : "expected always to have 1 mapping type but there were " + mappings.size();
@SuppressWarnings("unchecked")
Map<String, Object> docMappings = (Map<String, Object>) mappings.get(MapperService.SINGLE_MAPPING_NAME);
String mappingsJson = Strings.toString(XContentFactory.jsonBuilder().map(docMappings));
List<Map<String, AliasMetadata>> resolvedAliases = MetadataIndexTemplateService.resolveAliases(simulateOnClusterState.metadata(),
List<Map<String, AliasMetadata>> resolvedAliases = MetadataIndexTemplateService.resolveAliases(simulatedState.metadata(),
matchingTemplate);
// create the index with dummy settings in the cluster state so we can parse and validate the aliases
@ -131,34 +190,21 @@ public class TransportSimulateIndexTemplateAction
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
.put(IndexMetadata.SETTING_INDEX_UUID, UUIDs.randomBase64UUID())
.build();
final IndexMetadata indexMetadata = IndexMetadata.builder(request.getIndexName()).settings(dummySettings).build();
final IndexMetadata indexMetadata = IndexMetadata.builder(indexName).settings(dummySettings).build();
final ClusterState tempClusterState = ClusterState.builder(simulateOnClusterState)
.metadata(Metadata.builder(simulateOnClusterState.metadata())
.put(indexMetadata, true)
.build())
.build();
final ClusterState tempClusterState = ClusterState.builder(simulatedState)
.metadata(Metadata.builder(simulatedState.metadata())
.put(indexMetadata, true)
.build())
.build();
List<AliasMetadata> aliases = indicesService.withTempIndexService(indexMetadata, tempIndexService ->
MetadataCreateIndexService.resolveAndValidateAliases(request.getIndexName(), Collections.emptySet(),
resolvedAliases, tempClusterState.metadata(), aliasValidator, xContentRegistry,
// the context is only used for validation so it's fine to pass fake values for the
// shard id and the current timestamp
tempIndexService.newQueryShardContext(0, null, () -> 0L, null)));
MetadataCreateIndexService.resolveAndValidateAliases(indexName, Collections.emptySet(),
resolvedAliases, tempClusterState.metadata(), aliasValidator, xContentRegistry,
// the context is only used for validation so it's fine to pass fake values for the
// shard id and the current timestamp
tempIndexService.newQueryShardContext(0, null, () -> 0L, null)));
IndexTemplateV2 templateV2 = tempClusterState.metadata().templatesV2().get(matchingTemplate);
assert templateV2 != null : "the matched template must exist";
Map<String, List<String>> overlapping = new HashMap<>();
overlapping.putAll(findConflictingV1Templates(tempClusterState, matchingTemplate, templateV2.indexPatterns()));
overlapping.putAll(findConflictingV2Templates(tempClusterState, matchingTemplate, templateV2.indexPatterns()));
Template template = new Template(settings, mappingsJson == null ? null : new CompressedXContent(mappingsJson),
aliases.stream().collect(Collectors.toMap(AliasMetadata::getAlias, Function.identity())));
listener.onResponse(new SimulateIndexTemplateResponse(template, overlapping));
}
@Override
protected ClusterBlockException checkBlock(SimulateIndexTemplateRequest request, ClusterState state) {
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ);
return new Template(settings, mappingsJson == null ? null : new CompressedXContent(mappingsJson),
aliases.stream().collect(Collectors.toMap(AliasMetadata::getAlias, Function.identity())));
}
}

View File

@ -0,0 +1,148 @@
/*
* 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.action.admin.indices.template.post;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.master.TransportMasterNodeReadAction;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.AliasValidator;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.IndexTemplateV2;
import org.elasticsearch.cluster.metadata.MetadataIndexTemplateService;
import org.elasticsearch.cluster.metadata.Template;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.elasticsearch.cluster.metadata.MetadataIndexTemplateService.findConflictingV1Templates;
import static org.elasticsearch.cluster.metadata.MetadataIndexTemplateService.findConflictingV2Templates;
/**
* Handles simulating an index template either by name (looking it up in the
* cluster state), or by a provided template configuration
*/
public class TransportSimulateTemplateAction
extends TransportMasterNodeReadAction<SimulateTemplateAction.Request, SimulateIndexTemplateResponse> {
private final MetadataIndexTemplateService indexTemplateService;
private final NamedXContentRegistry xContentRegistry;
private final IndicesService indicesService;
private AliasValidator aliasValidator;
@Inject
public TransportSimulateTemplateAction(TransportService transportService, ClusterService clusterService,
ThreadPool threadPool, MetadataIndexTemplateService indexTemplateService,
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
NamedXContentRegistry xContentRegistry, IndicesService indicesService) {
super(SimulateTemplateAction.NAME, transportService, clusterService, threadPool, actionFilters,
SimulateTemplateAction.Request::new, indexNameExpressionResolver);
this.indexTemplateService = indexTemplateService;
this.xContentRegistry = xContentRegistry;
this.indicesService = indicesService;
this.aliasValidator = new AliasValidator();
}
@Override
protected String executor() {
return ThreadPool.Names.SAME;
}
@Override
protected SimulateIndexTemplateResponse read(StreamInput in) throws IOException {
return new SimulateIndexTemplateResponse(in);
}
@Override
protected void masterOperation(SimulateTemplateAction.Request request, ClusterState state,
ActionListener<SimulateIndexTemplateResponse> listener) throws Exception {
String uuid = UUIDs.randomBase64UUID().toLowerCase(Locale.ROOT);
final String temporaryIndexName = "simulate_template_index_" + uuid;
final ClusterState stateWithTemplate;
final String simulateTemplateToAdd;
// First, if a template body was requested, we need to "fake add" that template to the
// cluster state, so it can be used when we resolved settings/etc
if (request.getIndexTemplateRequest() != null) {
// we'll "locally" add the template defined by the user in the cluster state (as if it existed in the system)
simulateTemplateToAdd = "simulate_template_" + uuid;
// Perform validation for things like typos in component template names
MetadataIndexTemplateService.validateV2TemplateRequest(state.metadata(), simulateTemplateToAdd,
request.getIndexTemplateRequest().indexTemplate());
stateWithTemplate = indexTemplateService.addIndexTemplateV2(state, request.getIndexTemplateRequest().create(),
simulateTemplateToAdd, request.getIndexTemplateRequest().indexTemplate());
} else {
simulateTemplateToAdd = null;
stateWithTemplate = state;
}
// We also need the name of the template we're going to resolve, so if they specified a
// name, use that, otherwise use the name of the template that was "fake added" in the previous block
final String matchingTemplate;
if (request.getTemplateName() == null) {
// Automatically match the template that was added
matchingTemplate = simulateTemplateToAdd;
} else {
matchingTemplate = request.getTemplateName();
}
// If they didn't either specify a name that existed or a template body, we cannot simulate anything!
if (matchingTemplate == null) {
// They should have specified either a template name or the body of a template, but neither were specified
listener.onFailure(new IllegalArgumentException("a template name to match or a new template body must be specified"));
return;
} else if (stateWithTemplate.metadata().templatesV2().containsKey(matchingTemplate) == false) {
// They specified a template, but it didn't exist
listener.onFailure(new IllegalArgumentException("unable to simulate template [" + matchingTemplate + "] that does not exist"));
return;
}
final ClusterState tempClusterState =
TransportSimulateIndexTemplateAction.resolveTemporaryState(matchingTemplate, temporaryIndexName, stateWithTemplate);
IndexTemplateV2 templateV2 = tempClusterState.metadata().templatesV2().get(matchingTemplate);
assert templateV2 != null : "the matched template must exist";
Map<String, List<String>> overlapping = new HashMap<>();
overlapping.putAll(findConflictingV1Templates(tempClusterState, matchingTemplate, templateV2.indexPatterns()));
overlapping.putAll(findConflictingV2Templates(tempClusterState, matchingTemplate, templateV2.indexPatterns()));
Template template = TransportSimulateIndexTemplateAction.resolveTemplate(matchingTemplate, temporaryIndexName,
stateWithTemplate, xContentRegistry, indicesService, aliasValidator);
listener.onResponse(new SimulateIndexTemplateResponse(template, overlapping));
}
@Override
protected ClusterBlockException checkBlock(SimulateTemplateAction.Request request, ClusterState state) {
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ);
}
}

View File

@ -352,7 +352,7 @@ public class MetadataIndexTemplateService {
});
}
static void validateV2TemplateRequest(Metadata metadata, String name, IndexTemplateV2 template) {
public static void validateV2TemplateRequest(Metadata metadata, String name, IndexTemplateV2 template) {
if (template.indexPatterns().stream().anyMatch(Regex::isMatchAllPattern)) {
Settings mergedSettings = resolveSettings(metadata, template);
if (IndexMetadata.INDEX_HIDDEN_SETTING.exists(mergedSettings)) {

View File

@ -0,0 +1,65 @@
/*
* 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.rest.action.admin.indices;
import org.elasticsearch.action.admin.indices.template.post.SimulateTemplateAction;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateV2Action;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.metadata.IndexTemplateV2;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.RestToXContentListener;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import static org.elasticsearch.rest.RestRequest.Method.POST;
public class RestSimulateTemplateAction extends BaseRestHandler {
@Override
public List<Route> routes() {
return Arrays.asList(
new Route(POST, "/_index_template/_simulate"),
new Route(POST, "/_index_template/_simulate/{name}"));
}
@Override
public String getName() {
return "simulate_template_action";
}
@Override
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
SimulateTemplateAction.Request simulateRequest = new SimulateTemplateAction.Request();
simulateRequest.templateName(request.param("name"));
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"));
simulateRequest.indexTemplateRequest(indexTemplateRequest);
}
simulateRequest.masterNodeTimeout(request.paramAsTime("master_timeout", simulateRequest.masterNodeTimeout()));
return channel -> client.execute(SimulateTemplateAction.INSTANCE, simulateRequest, new RestToXContentListener<>(channel));
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.action.admin.indices.template.post;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateV2Action;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.metadata.IndexTemplateV2;
import org.elasticsearch.cluster.metadata.IndexTemplateV2Tests;
import org.elasticsearch.cluster.metadata.Template;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
public class SimulateTemplateRequestTests extends AbstractWireSerializingTestCase<SimulateTemplateAction.Request> {
@Override
protected Writeable.Reader<SimulateTemplateAction.Request> instanceReader() {
return SimulateTemplateAction.Request::new;
}
@Override
protected SimulateTemplateAction.Request createTestInstance() {
SimulateTemplateAction.Request req = new SimulateTemplateAction.Request(randomAlphaOfLength(10));
PutIndexTemplateV2Action.Request newTemplateRequest = new PutIndexTemplateV2Action.Request(randomAlphaOfLength(4));
newTemplateRequest.indexTemplate(IndexTemplateV2Tests.randomInstance());
req.indexTemplateRequest(newTemplateRequest);
return req;
}
@Override
protected SimulateTemplateAction.Request mutateInstance(SimulateTemplateAction.Request instance) throws IOException {
return randomValueOtherThan(instance, this::createTestInstance);
}
public void testIndexNameCannotBeNullOrEmpty() {
expectThrows(IllegalArgumentException.class, () -> new SimulateTemplateAction.Request((String) null));
expectThrows(IllegalArgumentException.class, () -> new SimulateTemplateAction.Request((PutIndexTemplateV2Action.Request) null));
}
public void testAddingGlobalTemplateWithHiddenIndexSettingIsIllegal() {
Template template = new Template(Settings.builder().put(IndexMetadata.SETTING_INDEX_HIDDEN, true).build(), null, null);
IndexTemplateV2 globalTemplate = new IndexTemplateV2(Collections.singletonList("*"), template, null, null, null, null, null);
PutIndexTemplateV2Action.Request request = new PutIndexTemplateV2Action.Request("test");
request.indexTemplate(globalTemplate);
SimulateTemplateAction.Request simulateRequest = new SimulateTemplateAction.Request("testing");
simulateRequest.indexTemplateRequest(request);
ActionRequestValidationException validationException = simulateRequest.validate();
assertThat(validationException, is(notNullValue()));
List<String> validationErrors = validationException.validationErrors();
assertThat(validationErrors.size(), is(1));
String error = validationErrors.get(0);
assertThat(error, is("global V2 templates may not specify the setting " + IndexMetadata.SETTING_INDEX_HIDDEN));
}
}