diff --git a/distribution/archives/integ-test-zip/build.gradle b/distribution/archives/integ-test-zip/build.gradle index ed8f893d7f840..5fb5b36553b91 100644 --- a/distribution/archives/integ-test-zip/build.gradle +++ b/distribution/archives/integ-test-zip/build.gradle @@ -1,3 +1,4 @@ +import org.elasticsearch.gradle.info.BuildParams /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with @@ -32,3 +33,9 @@ integTest.runner { systemProperty 'tests.logfile', '--external--' } } + +testClusters.integTest { + if (BuildParams.isSnapshotBuild() == false) { + systemProperty 'es.itv2_feature_flag_registered', 'true' + } +} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.delete_component_template.json b/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.delete_component_template.json new file mode 100644 index 0000000000000..2734fa652e5d6 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.delete_component_template.json @@ -0,0 +1,35 @@ +{ + "cluster.delete_template":{ + "documentation":{ + "url":"https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-component-templates.html", + "description":"Deletes a component template" + }, + "stability":"stable", + "url":{ + "paths":[ + { + "path":"/_component_template/{name}", + "methods":[ + "DELETE" + ], + "parts":{ + "name":{ + "type":"string", + "description":"The name of the template" + } + } + } + ] + }, + "params":{ + "timeout":{ + "type":"time", + "description":"Explicit operation timeout" + }, + "master_timeout":{ + "type":"time", + "description":"Specify timeout for connection to master" + } + } + } +} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.get_component_template.json b/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.get_component_template.json new file mode 100644 index 0000000000000..27bd093e620f0 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.get_component_template.json @@ -0,0 +1,41 @@ +{ + "cluster.get_component_template":{ + "documentation":{ + "url":"https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-component-templates.html", + "description":"Returns one or more component templates" + }, + "stability":"stable", + "url":{ + "paths":[ + { + "path":"/_component_template", + "methods":[ + "GET" + ] + }, + { + "path":"/_component_template/{name}", + "methods":[ + "GET" + ], + "parts":{ + "name":{ + "type":"list", + "description":"The comma separated names of the component templates" + } + } + } + ] + }, + "params":{ + "master_timeout":{ + "type":"time", + "description":"Explicit operation timeout for connection to master node" + }, + "local":{ + "type":"boolean", + "description":"Return local information, do not retrieve the state from master node (default: false)" + } + } + } +} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.put_component_template.json b/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.put_component_template.json new file mode 100644 index 0000000000000..1adf7f1071dad --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.put_component_template.json @@ -0,0 +1,45 @@ +{ + "cluster.put_template":{ + "documentation":{ + "url":"https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-component-templates.html", + "description":"Creates or updates a component template" + }, + "stability":"stable", + "url":{ + "paths":[ + { + "path":"/_component_template/{name}", + "methods":[ + "PUT", + "POST" + ], + "parts":{ + "name":{ + "type":"string", + "description":"The name of the template" + } + } + } + ] + }, + "params":{ + "create":{ + "type":"boolean", + "description":"Whether the index template should only be added if new or can also replace an existing one", + "default":false + }, + "timeout":{ + "type":"time", + "description":"Explicit operation timeout" + }, + "master_timeout":{ + "type":"time", + "description":"Specify timeout for connection to master" + } + }, + "body":{ + "description":"The template definition", + "required":true + } + } +} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.component_template/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.component_template/10_basic.yml new file mode 100644 index 0000000000000..04ad1c6fc73da --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.component_template/10_basic.yml @@ -0,0 +1,40 @@ +--- +"Basic CRUD" + - do: + cluster.put_component_template: + name: test + body: + template: + settings: + number_of_shards: 1 + number_of_replicas: 0 + mappings: + properties: + field: + type: keyword + aliases: + aliasname: {} + version: 2 + _meta: + foo: bar + baz: + eggplant: true + + - do: + cluster.get_component_template: + name: test + + - match: {test.version: 2} + - match: {test._meta: {foo: bar, baz: {eggplant: true}}} + - match: {test.template.settings: {index.number_of_shards: '1', index.number_of_replicas: '0'}} + - match: {test.template.mappings: {properties: {field: {type: keyword}}}} + - match: {test.template.aliases: {}} + + - do: + cluster.delete_component_template: + name: test + + - do: + catch: /missing/ + cluster.get_component_template: + name: test diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index affbb7a41dd31..aa8d92129f577 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -21,6 +21,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.elasticsearch.Build; import org.elasticsearch.action.admin.cluster.allocation.ClusterAllocationExplainAction; import org.elasticsearch.action.admin.cluster.allocation.TransportClusterAllocationExplainAction; import org.elasticsearch.action.admin.cluster.configuration.AddVotingConfigExclusionsAction; @@ -142,11 +143,17 @@ import org.elasticsearch.action.admin.indices.shrink.TransportResizeAction; import org.elasticsearch.action.admin.indices.stats.IndicesStatsAction; import org.elasticsearch.action.admin.indices.stats.TransportIndicesStatsAction; +import org.elasticsearch.action.admin.indices.template.delete.DeleteComponentTemplateAction; import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateAction; +import org.elasticsearch.action.admin.indices.template.delete.TransportDeleteComponentTemplateAction; import org.elasticsearch.action.admin.indices.template.delete.TransportDeleteIndexTemplateAction; +import org.elasticsearch.action.admin.indices.template.get.GetComponentTemplateAction; import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesAction; +import org.elasticsearch.action.admin.indices.template.get.TransportGetComponentTemplateAction; import org.elasticsearch.action.admin.indices.template.get.TransportGetIndexTemplatesAction; +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.TransportPutComponentTemplateAction; import org.elasticsearch.action.admin.indices.template.put.TransportPutIndexTemplateAction; import org.elasticsearch.action.admin.indices.upgrade.get.TransportUpgradeStatusAction; import org.elasticsearch.action.admin.indices.upgrade.get.UpgradeStatusAction; @@ -269,11 +276,13 @@ import org.elasticsearch.rest.action.admin.indices.RestClearIndicesCacheAction; import org.elasticsearch.rest.action.admin.indices.RestCloseIndexAction; import org.elasticsearch.rest.action.admin.indices.RestCreateIndexAction; +import org.elasticsearch.rest.action.admin.indices.RestDeleteComponentTemplateAction; import org.elasticsearch.rest.action.admin.indices.RestDeleteIndexAction; import org.elasticsearch.rest.action.admin.indices.RestDeleteIndexTemplateAction; import org.elasticsearch.rest.action.admin.indices.RestFlushAction; import org.elasticsearch.rest.action.admin.indices.RestForceMergeAction; import org.elasticsearch.rest.action.admin.indices.RestGetAliasesAction; +import org.elasticsearch.rest.action.admin.indices.RestGetComponentTemplateAction; import org.elasticsearch.rest.action.admin.indices.RestGetFieldMappingAction; import org.elasticsearch.rest.action.admin.indices.RestGetIndexTemplateAction; import org.elasticsearch.rest.action.admin.indices.RestGetIndicesAction; @@ -286,6 +295,7 @@ 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.RestPutComponentTemplateAction; import org.elasticsearch.rest.action.admin.indices.RestPutIndexTemplateAction; import org.elasticsearch.rest.action.admin.indices.RestPutMappingAction; import org.elasticsearch.rest.action.admin.indices.RestRecoveryAction; @@ -361,6 +371,23 @@ public class ActionModule extends AbstractModule { private static final Logger logger = LogManager.getLogger(ActionModule.class); + private static final boolean ITV2_FEATURE_FLAG_REGISTERED; + + static { + final String property = System.getProperty("es.itv2_feature_flag_registered"); + if (Build.CURRENT.isSnapshot() && property != null) { + throw new IllegalArgumentException("es.itv2_feature_flag_registered is only supported in non-snapshot builds"); + } + if (Build.CURRENT.isSnapshot() || "true".equals(property)) { + ITV2_FEATURE_FLAG_REGISTERED = true; + } else if ("false".equals(property) || property == null) { + ITV2_FEATURE_FLAG_REGISTERED = false; + } else { + throw new IllegalArgumentException("expected es.itv2_feature_flag_registered to be unset, true, or false but was [" + + property + "]"); + } + } + private final Settings settings; private final IndexNameExpressionResolver indexNameExpressionResolver; private final IndexScopedSettings indexScopedSettings; @@ -486,6 +513,11 @@ public void reg actions.register(PutIndexTemplateAction.INSTANCE, TransportPutIndexTemplateAction.class); actions.register(GetIndexTemplatesAction.INSTANCE, TransportGetIndexTemplatesAction.class); actions.register(DeleteIndexTemplateAction.INSTANCE, TransportDeleteIndexTemplateAction.class); + if (ITV2_FEATURE_FLAG_REGISTERED) { + actions.register(PutComponentTemplateAction.INSTANCE, TransportPutComponentTemplateAction.class); + actions.register(GetComponentTemplateAction.INSTANCE, TransportGetComponentTemplateAction.class); + actions.register(DeleteComponentTemplateAction.INSTANCE, TransportDeleteComponentTemplateAction.class); + } actions.register(ValidateQueryAction.INSTANCE, TransportValidateQueryAction.class); actions.register(RefreshAction.INSTANCE, TransportRefreshAction.class); actions.register(FlushAction.INSTANCE, TransportFlushAction.class); @@ -621,6 +653,11 @@ public void initRestHandlers(Supplier nodesInCluster) { registerHandler.accept(new RestGetIndexTemplateAction()); registerHandler.accept(new RestPutIndexTemplateAction()); registerHandler.accept(new RestDeleteIndexTemplateAction()); + if (ITV2_FEATURE_FLAG_REGISTERED) { + registerHandler.accept(new RestPutComponentTemplateAction()); + registerHandler.accept(new RestGetComponentTemplateAction()); + registerHandler.accept(new RestDeleteComponentTemplateAction()); + } registerHandler.accept(new RestPutMappingAction()); registerHandler.accept(new RestGetMappingAction()); diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteComponentTemplateAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteComponentTemplateAction.java new file mode 100644 index 0000000000000..bccfee1338865 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteComponentTemplateAction.java @@ -0,0 +1,34 @@ +/* + * 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.delete; + +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.master.AcknowledgedResponse; + +public class DeleteComponentTemplateAction extends ActionType { + + public static final DeleteComponentTemplateAction INSTANCE = new DeleteComponentTemplateAction(); + public static final String NAME = "cluster:admin/component_template/delete"; + + private DeleteComponentTemplateAction() { + super(NAME, AcknowledgedResponse::new); + } + +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteComponentTemplateRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteComponentTemplateRequest.java new file mode 100644 index 0000000000000..a32db5fe9cf66 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/DeleteComponentTemplateRequest.java @@ -0,0 +1,79 @@ +/* + * 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.delete; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; + +import static org.elasticsearch.action.ValidateActions.addValidationError; + +public class DeleteComponentTemplateRequest extends MasterNodeRequest { + + private String name; + + public DeleteComponentTemplateRequest(StreamInput in) throws IOException { + super(in); + name = in.readString(); + } + + public DeleteComponentTemplateRequest() { + } + + /** + * Constructs a new delete index request for the specified name. + */ + public DeleteComponentTemplateRequest(String name) { + this.name = name; + } + + /** + * Set the index template name to delete. + */ + public DeleteComponentTemplateRequest name(String name) { + this.name = name; + return this; + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = null; + if (name == null) { + validationException = addValidationError("name is missing", validationException); + } + return validationException; + } + + /** + * The index template name to delete. + */ + public String name() { + return name; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(name); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/TransportDeleteComponentTemplateAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/TransportDeleteComponentTemplateAction.java new file mode 100644 index 0000000000000..9e65ffe3c72b7 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/delete/TransportDeleteComponentTemplateAction.java @@ -0,0 +1,79 @@ +/* + * 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.delete; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.TransportMasterNodeAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.metadata.MetaDataIndexTemplateService; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +import java.io.IOException; + +public class TransportDeleteComponentTemplateAction + extends TransportMasterNodeAction { + + private static final Logger logger = LogManager.getLogger(TransportDeleteComponentTemplateAction.class); + + private final MetaDataIndexTemplateService indexTemplateService; + + @Inject + public TransportDeleteComponentTemplateAction(TransportService transportService, ClusterService clusterService, + ThreadPool threadPool, MetaDataIndexTemplateService indexTemplateService, + ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { + super(DeleteComponentTemplateAction.NAME, transportService, clusterService, threadPool, actionFilters, + DeleteComponentTemplateRequest::new, indexNameExpressionResolver); + this.indexTemplateService = indexTemplateService; + } + + @Override + protected String executor() { + // we go async right away + return ThreadPool.Names.SAME; + } + + @Override + protected AcknowledgedResponse read(StreamInput in) throws IOException { + return new AcknowledgedResponse(in); + } + + @Override + protected ClusterBlockException checkBlock(DeleteComponentTemplateRequest request, ClusterState state) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); + } + + @Override + protected void masterOperation(Task task, final DeleteComponentTemplateRequest request, final ClusterState state, + final ActionListener listener) { + indexTemplateService.removeComponentTemplate(request.name(), request.masterNodeTimeout(), listener); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/GetComponentTemplateAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/GetComponentTemplateAction.java new file mode 100644 index 0000000000000..2e1c435b35ba4 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/GetComponentTemplateAction.java @@ -0,0 +1,33 @@ +/* + * 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.get; + +import org.elasticsearch.action.ActionType; + +public class GetComponentTemplateAction extends ActionType { + + public static final GetComponentTemplateAction INSTANCE = new GetComponentTemplateAction(); + public static final String NAME = "cluster:admin/component_template/get"; + + private GetComponentTemplateAction() { + super(NAME, GetComponentTemplateResponse::new); + } + +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/GetComponentTemplateRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/GetComponentTemplateRequest.java new file mode 100644 index 0000000000000..782b7fe0e3d02 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/GetComponentTemplateRequest.java @@ -0,0 +1,85 @@ +/* + * 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.get; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.support.master.MasterNodeReadRequest; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; + +import static org.elasticsearch.action.ValidateActions.addValidationError; + +/** + * Request that to retrieve one or more component templates + */ +public class GetComponentTemplateRequest extends MasterNodeReadRequest { + + private String[] names; + + public GetComponentTemplateRequest() { } + + public GetComponentTemplateRequest(String... names) { + this.names = names; + } + + public GetComponentTemplateRequest(StreamInput in) throws IOException { + super(in); + names = in.readStringArray(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeStringArray(names); + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = null; + if (names == null) { + validationException = addValidationError("names is null or empty", validationException); + } else { + for (String name : names) { + if (name == null || !Strings.hasText(name)) { + validationException = addValidationError("name is missing", validationException); + } + } + } + return validationException; + } + + /** + * Sets the names of the component templates. + */ + public GetComponentTemplateRequest names(String... names) { + this.names = names; + return this; + } + + /** + * The names of the component templates. + */ + public String[] names() { + return this.names; + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/GetComponentTemplateResponse.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/GetComponentTemplateResponse.java new file mode 100644 index 0000000000000..9bf101cd1586a --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/GetComponentTemplateResponse.java @@ -0,0 +1,87 @@ +/* + * 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.get; + +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class GetComponentTemplateResponse extends ActionResponse implements ToXContentObject { + + private final Map componentTemplates; + + public GetComponentTemplateResponse(StreamInput in) throws IOException { + super(in); + int size = in.readVInt(); + componentTemplates = new HashMap<>(); + for (int i = 0 ; i < size ; i++) { + componentTemplates.put(in.readString(), new ComponentTemplate(in)); + } + } + + public GetComponentTemplateResponse(Map componentTemplates) { + this.componentTemplates = componentTemplates; + } + + public Map getComponentTemplates() { + return componentTemplates; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(componentTemplates.size()); + for (Map.Entry componentTemplate : componentTemplates.entrySet()) { + out.writeString(componentTemplate.getKey()); + componentTemplate.getValue().writeTo(out); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GetComponentTemplateResponse that = (GetComponentTemplateResponse) o; + return Objects.equals(componentTemplates, that.componentTemplates); + } + + @Override + public int hashCode() { + return Objects.hash(componentTemplates); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + for (Map.Entry componentTemplate : this.componentTemplates.entrySet()) { + builder.field(componentTemplate.getKey(), componentTemplate.getValue()); + } + builder.endObject(); + return builder; + } + +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/TransportGetComponentTemplateAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/TransportGetComponentTemplateAction.java new file mode 100644 index 0000000000000..05b0baaac5beb --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/get/TransportGetComponentTemplateAction.java @@ -0,0 +1,95 @@ +/* + * 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.get; + +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.ComponentTemplate; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.regex.Regex; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class TransportGetComponentTemplateAction extends + TransportMasterNodeReadAction { + + @Inject + public TransportGetComponentTemplateAction(TransportService transportService, ClusterService clusterService, + ThreadPool threadPool, ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver) { + super(GetComponentTemplateAction.NAME, transportService, clusterService, threadPool, actionFilters, + GetComponentTemplateRequest::new, indexNameExpressionResolver); + } + + @Override + protected String executor() { + return ThreadPool.Names.SAME; + } + + @Override + protected GetComponentTemplateResponse read(StreamInput in) throws IOException { + return new GetComponentTemplateResponse(in); + } + + @Override + protected ClusterBlockException checkBlock(GetComponentTemplateRequest request, ClusterState state) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ); + } + + @Override + protected void masterOperation(Task task, GetComponentTemplateRequest request, ClusterState state, + ActionListener listener) { + final Map results; + + // If we did not ask for a specific name, then we return all templates + if (request.names().length == 0) { + results = new HashMap<>(state.metaData().componentTemplates()); + } else { + results = new HashMap<>(); + } + + Map allTemplates = state.metaData().componentTemplates(); + for (String name : request.names()) { + if (Regex.isSimpleMatchPattern(name)) { + for (Map.Entry entry : allTemplates.entrySet()) { + if (Regex.simpleMatch(name, entry.getKey())) { + results.put(entry.getKey(), entry.getValue()); + } + } + } else if (allTemplates.containsKey(name)) { + results.put(name, allTemplates.get(name)); + } + } + + listener.onResponse(new GetComponentTemplateResponse(results)); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutComponentTemplateAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutComponentTemplateAction.java new file mode 100644 index 0000000000000..44cc083fa61bc --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutComponentTemplateAction.java @@ -0,0 +1,34 @@ +/* + * 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.put; + +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.master.AcknowledgedResponse; + +public class PutComponentTemplateAction extends ActionType { + + public static final PutComponentTemplateAction INSTANCE = new PutComponentTemplateAction(); + public static final String NAME = "cluster:admin/component_template/put"; + + private PutComponentTemplateAction() { + super(NAME, AcknowledgedResponse::new); + } + +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutComponentTemplateRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutComponentTemplateRequest.java new file mode 100644 index 0000000000000..e20248cb07a48 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutComponentTemplateRequest.java @@ -0,0 +1,134 @@ +/* + * 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.put; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; + +import static org.elasticsearch.action.ValidateActions.addValidationError; + +/** + * A request for putting a single component template into the cluster state + */ +public class PutComponentTemplateRequest extends MasterNodeRequest { + private final String name; + @Nullable + private String cause; + private boolean create; + private ComponentTemplate componentTemplate; + + public PutComponentTemplateRequest(StreamInput in) throws IOException { + super(in); + this.name = in.readString(); + this.cause = in.readOptionalString(); + this.create = in.readBoolean(); + this.componentTemplate = new ComponentTemplate(in); + } + + /** + * Constructs a new put component template request with the provided name. + */ + public PutComponentTemplateRequest(String name) { + this.name = name; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(name); + out.writeOptionalString(cause); + out.writeBoolean(create); + this.componentTemplate.writeTo(out); + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = null; + if (name == null) { + validationException = addValidationError("name is missing", validationException); + } + if (componentTemplate == null) { + validationException = addValidationError("a component template is required", validationException); + } + return validationException; + } + + /** + * The name of the index template. + */ + public String name() { + return this.name; + } + + /** + * Set to {@code true} to force only creation, not an update of an index template. If it already + * exists, it will fail with an {@link IllegalArgumentException}. + */ + public PutComponentTemplateRequest create(boolean create) { + this.create = create; + return this; + } + + public boolean create() { + return create; + } + + /** + * The cause for this index template creation. + */ + public PutComponentTemplateRequest cause(@Nullable String cause) { + this.cause = cause; + return this; + } + + @Nullable + public String cause() { + return this.cause; + } + + /** + * The component template that will be inserted into the cluster state + */ + public PutComponentTemplateRequest componentTemplate(ComponentTemplate template) { + this.componentTemplate = template; + return this; + } + + public ComponentTemplate componentTemplate() { + return this.componentTemplate; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("PutComponentRequest["); + sb.append("name=").append(name); + sb.append(", cause=").append(cause); + sb.append(", create=").append(create); + sb.append(", component_template=").append(componentTemplate); + sb.append("]"); + return sb.toString(); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutComponentTemplateAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutComponentTemplateAction.java new file mode 100644 index 0000000000000..eb91115b1a06e --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutComponentTemplateAction.java @@ -0,0 +1,87 @@ +/* + * 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.put; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.TransportMasterNodeAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.metadata.MetaDataIndexTemplateService; +import org.elasticsearch.cluster.metadata.Template; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +import java.io.IOException; + +public class TransportPutComponentTemplateAction extends TransportMasterNodeAction { + + private final MetaDataIndexTemplateService indexTemplateService; + + @Inject + public TransportPutComponentTemplateAction(TransportService transportService, ClusterService clusterService, + ThreadPool threadPool, MetaDataIndexTemplateService indexTemplateService, + ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { + super(PutComponentTemplateAction.NAME, transportService, clusterService, threadPool, actionFilters, + PutComponentTemplateRequest::new, indexNameExpressionResolver); + this.indexTemplateService = indexTemplateService; + } + + @Override + protected String executor() { + // we go async right away + return ThreadPool.Names.SAME; + } + + @Override + protected AcknowledgedResponse read(StreamInput in) throws IOException { + return new AcknowledgedResponse(in); + } + + @Override + protected ClusterBlockException checkBlock(PutComponentTemplateRequest request, ClusterState state) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); + } + + @Override + protected void masterOperation(Task task, final PutComponentTemplateRequest request, final ClusterState state, + final ActionListener listener) { + ComponentTemplate componentTemplate = request.componentTemplate(); + Template template = componentTemplate.template(); + // Normalize the index settings if necessary + if (template.settings() != null) { + Settings.Builder settings = Settings.builder().put(template.settings()).normalizePrefix(IndexMetaData.INDEX_SETTING_PREFIX); + template = new Template(settings.build(), template.mappings(), template.aliases()); + componentTemplate = new ComponentTemplate(template, componentTemplate.version(), componentTemplate.metadata()); + } + indexTemplateService.putComponentTemplate(request.cause(), request.create(), request.name(), request.masterNodeTimeout(), + componentTemplate, listener); + } +} diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/ComponentTemplate.java b/server/src/main/java/org/elasticsearch/cluster/metadata/ComponentTemplate.java index 2b160b1b2fb11..0cc8c8f8a940d 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/ComponentTemplate.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/ComponentTemplate.java @@ -24,26 +24,19 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.bytes.BytesArray; -import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.XContentType; import java.io.IOException; -import java.util.HashMap; import java.util.Map; import java.util.Objects; /** - * A component template is a re-usable template as well as metadata about the template. Each + * A component template is a re-usable {@link Template} as well as metadata about the template. Each * component template is expected to be valid on its own. For example, if a component template * contains a field "foo", it's expected to contain all the necessary settings/mappings/etc for the * "foo" field. These component templates make up the individual pieces composing an index template. @@ -157,144 +150,4 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.endObject(); return builder; } - - static class Template extends AbstractDiffable