[7.x] Add REST API for ComponentTemplate CRUD (#53558) (#53681)

* Add REST API for ComponentTemplate CRUD

This adds the Put/Get/DeleteComponentTemplate APIs that allow inserting, retrieving, and removing
ComponentTemplateMetadata into the cluster state metadata.

These APIs are currently only available behind a feature flag system property -
`es.itv2_feature_flag_registered`.

Relates to #53101

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Lee Hinman 2020-03-17 13:23:28 -06:00 committed by GitHub
parent 3c5437894e
commit 9c0e846db3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1454 additions and 164 deletions

View File

@ -790,7 +790,10 @@ public class RestHighLevelClientTests extends ESTestCase {
"indices.get_upgrade",
"indices.put_alias",
"render_search_template",
"scripts_painless_execute"
"scripts_painless_execute",
"cluster.put_component_template",
"cluster.get_component_template",
"cluster.delete_component_template"
};
//These API are not required for high-level client feature completeness
String[] notRequiredApi = new String[] {

View File

@ -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'
}
}

View File

@ -0,0 +1,35 @@
{
"cluster.delete_component_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"
}
}
}
}

View File

@ -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)"
}
}
}
}

View File

@ -0,0 +1,45 @@
{
"cluster.put_component_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
}
}
}

View File

@ -0,0 +1,47 @@
---
"Basic CRUD":
- skip:
version: " - 7.99.99"
reason: not backported yet
- 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: {component_templates.0.name: test}
- match: {component_templates.0.component_template.version: 2}
- match: {component_templates.0.component_template._meta: {foo: bar, baz: {eggplant: true}}}
- match: {component_templates.0.component_template.template.settings: {index: {number_of_shards: '1', number_of_replicas: '0'}}}
- match: {component_templates.0.component_template.template.mappings: {properties: {field: {type: keyword}}}}
- match: {component_templates.0.component_template.template.aliases: {aliasname: {}}}
- do:
cluster.delete_component_template:
name: test
- do:
catch: missing
cluster.get_component_template:
name: test
- is_false: test

View File

@ -21,6 +21,7 @@ package org.elasticsearch.action;
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;
@ -147,11 +148,17 @@ import org.elasticsearch.action.admin.indices.shrink.ResizeAction;
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;
@ -270,11 +277,13 @@ import org.elasticsearch.rest.action.admin.indices.RestAnalyzeAction;
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;
@ -287,6 +296,7 @@ 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.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;
@ -363,6 +373,24 @@ public class ActionModule extends AbstractModule {
private static final Logger logger = LogManager.getLogger(ActionModule.class);
private final boolean transportClient;
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;
@ -497,6 +525,11 @@ public class ActionModule extends AbstractModule {
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);
@ -624,6 +657,11 @@ public class ActionModule extends AbstractModule {
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());

View File

@ -0,0 +1,90 @@
/*
* 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.ActionType;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
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 DeleteComponentTemplateAction extends ActionType<AcknowledgedResponse> {
public static final DeleteComponentTemplateAction INSTANCE = new DeleteComponentTemplateAction();
public static final String NAME = "cluster:admin/component_template/delete";
private DeleteComponentTemplateAction() {
super(NAME, AcknowledgedResponse::new);
}
public static class Request extends MasterNodeRequest<Request> {
private String name;
public Request(StreamInput in) throws IOException {
super(in);
name = in.readString();
}
public Request() { }
/**
* Constructs a new delete index request for the specified name.
*/
public Request(String name) {
this.name = name;
}
/**
* Set the index template name to delete.
*/
public Request name(String name) {
this.name = name;
return this;
}
@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);
}
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import java.io.IOException;
public class TransportDeleteComponentTemplateAction
extends TransportMasterNodeAction<DeleteComponentTemplateAction.Request, AcknowledgedResponse> {
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,
DeleteComponentTemplateAction.Request::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(DeleteComponentTemplateAction.Request request, ClusterState state) {
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
}
@Override
protected void masterOperation(final DeleteComponentTemplateAction.Request request, final ClusterState state,
final ActionListener<AcknowledgedResponse> listener) {
indexTemplateService.removeComponentTemplate(request.name(), request.masterNodeTimeout(), listener);
}
}

View File

@ -0,0 +1,171 @@
/*
* 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.ActionResponse;
import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.support.master.MasterNodeReadRequest;
import org.elasticsearch.cluster.metadata.ComponentTemplate;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
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;
import static org.elasticsearch.action.ValidateActions.addValidationError;
/**
* Action to retrieve one or more component templates
*/
public class GetComponentTemplateAction extends ActionType<GetComponentTemplateAction.Response> {
public static final GetComponentTemplateAction INSTANCE = new GetComponentTemplateAction();
public static final String NAME = "cluster:admin/component_template/get";
private GetComponentTemplateAction() {
super(NAME, GetComponentTemplateAction.Response::new);
}
/**
* Request that to retrieve one or more component templates
*/
public static class Request extends MasterNodeReadRequest<Request> {
private String[] names;
public Request() { }
public Request(String... names) {
this.names = names;
}
public Request(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) == false) {
validationException = addValidationError("name is missing", validationException);
}
}
}
return validationException;
}
/**
* Sets the names of the component templates.
*/
public Request names(String... names) {
this.names = names;
return this;
}
/**
* The names of the component templates.
*/
public String[] names() {
return this.names;
}
}
public static class Response extends ActionResponse implements ToXContentObject {
public static final ParseField NAME = new ParseField("name");
public static final ParseField COMPONENT_TEMPLATES = new ParseField("component_templates");
public static final ParseField COMPONENT_TEMPLATE = new ParseField("component_template");
private final Map<String, ComponentTemplate> componentTemplates;
public Response(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 Response(Map<String, ComponentTemplate> componentTemplates) {
this.componentTemplates = componentTemplates;
}
public Map<String, ComponentTemplate> getComponentTemplates() {
return componentTemplates;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(componentTemplates.size());
for (Map.Entry<String, ComponentTemplate> 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;
Response that = (Response) 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();
builder.startArray(COMPONENT_TEMPLATES.getPreferredName());
for (Map.Entry<String, ComponentTemplate> componentTemplate : this.componentTemplates.entrySet()) {
builder.startObject();
builder.field(NAME.getPreferredName(), componentTemplate.getKey());
builder.field(COMPONENT_TEMPLATE.getPreferredName(), componentTemplate.getValue());
builder.endObject();
}
builder.endArray();
builder.endObject();
return builder;
}
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class TransportGetComponentTemplateAction extends
TransportMasterNodeReadAction<GetComponentTemplateAction.Request, GetComponentTemplateAction.Response> {
@Inject
public TransportGetComponentTemplateAction(TransportService transportService, ClusterService clusterService,
ThreadPool threadPool, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver) {
super(GetComponentTemplateAction.NAME, transportService, clusterService, threadPool, actionFilters,
GetComponentTemplateAction.Request::new, indexNameExpressionResolver);
}
@Override
protected String executor() {
return ThreadPool.Names.SAME;
}
@Override
protected GetComponentTemplateAction.Response read(StreamInput in) throws IOException {
return new GetComponentTemplateAction.Response(in);
}
@Override
protected ClusterBlockException checkBlock(GetComponentTemplateAction.Request request, ClusterState state) {
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ);
}
@Override
protected void masterOperation(GetComponentTemplateAction.Request request, ClusterState state,
ActionListener<GetComponentTemplateAction.Response> listener) {
Map<String, ComponentTemplate> allTemplates = state.metaData().componentTemplates();
// If we did not ask for a specific name, then we return all templates
if (request.names().length == 0) {
listener.onResponse(new GetComponentTemplateAction.Response(allTemplates));
return;
}
final Map<String, ComponentTemplate> results = new HashMap<>();
for (String name : request.names()) {
if (Regex.isSimpleMatchPattern(name)) {
for (Map.Entry<String, ComponentTemplate> 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 GetComponentTemplateAction.Response(results));
}
}

View File

@ -0,0 +1,151 @@
/*
* 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.ActionType;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.support.master.MasterNodeRequest;
import org.elasticsearch.cluster.metadata.ComponentTemplate;
import org.elasticsearch.common.Nullable;
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;
/**
* An action for putting a single component template into the cluster state
*/
public class PutComponentTemplateAction extends ActionType<AcknowledgedResponse> {
public static final PutComponentTemplateAction INSTANCE = new PutComponentTemplateAction();
public static final String NAME = "cluster:admin/component_template/put";
private PutComponentTemplateAction() {
super(NAME, AcknowledgedResponse::new);
}
/**
* A request for putting a single component template into the cluster state
*/
public static class Request extends MasterNodeRequest<Request> {
private final String name;
@Nullable
private String cause;
private boolean create;
private ComponentTemplate componentTemplate;
public Request(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 Request(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 || Strings.hasText(name) == false) {
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 Request create(boolean create) {
this.create = create;
return this;
}
public boolean create() {
return create;
}
/**
* The cause for this index template creation.
*/
public Request 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 Request 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();
}
}
}

View File

@ -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.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import java.io.IOException;
public class TransportPutComponentTemplateAction
extends TransportMasterNodeAction<PutComponentTemplateAction.Request, AcknowledgedResponse> {
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,
PutComponentTemplateAction.Request::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(PutComponentTemplateAction.Request request, ClusterState state) {
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
}
@Override
protected void masterOperation(final PutComponentTemplateAction.Request request, final ClusterState state,
final ActionListener<AcknowledgedResponse> 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);
}
}

View File

@ -24,26 +24,19 @@ import org.elasticsearch.cluster.Diff;
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 class ComponentTemplate extends AbstractDiffable<ComponentTemplate> imple
builder.endObject();
return builder;
}
static class Template extends AbstractDiffable<Template> implements ToXContentObject {
private static final ParseField SETTINGS = new ParseField("settings");
private static final ParseField MAPPINGS = new ParseField("mappings");
private static final ParseField ALIASES = new ParseField("aliases");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<Template, Void> PARSER = new ConstructingObjectParser<>("template", false,
a -> new Template((Settings) a[0], (CompressedXContent) a[1], (Map<String, AliasMetaData>) a[2]));
static {
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> Settings.fromXContent(p), SETTINGS);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) ->
new CompressedXContent(Strings.toString(XContentFactory.jsonBuilder().map(p.mapOrdered()))), MAPPINGS);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> {
Map<String, AliasMetaData> aliasMap = new HashMap<>();
while ((p.nextToken()) != XContentParser.Token.END_OBJECT) {
AliasMetaData alias = AliasMetaData.Builder.fromXContent(p);
aliasMap.put(alias.alias(), alias);
}
return aliasMap;
}, ALIASES);
}
@Nullable
private final Settings settings;
@Nullable
private final CompressedXContent mappings;
@Nullable
private final Map<String, AliasMetaData> aliases;
Template(@Nullable Settings settings, @Nullable CompressedXContent mappings, @Nullable Map<String, AliasMetaData> aliases) {
this.settings = settings;
this.mappings = mappings;
this.aliases = aliases;
}
Template(StreamInput in) throws IOException {
if (in.readBoolean()) {
this.settings = Settings.readSettingsFromStream(in);
} else {
this.settings = null;
}
if (in.readBoolean()) {
this.mappings = CompressedXContent.readCompressedString(in);
} else {
this.mappings = null;
}
if (in.readBoolean()) {
this.aliases = in.readMap(StreamInput::readString, AliasMetaData::new);
} else {
this.aliases = null;
}
}
public Settings settings() {
return settings;
}
public CompressedXContent mappings() {
return mappings;
}
public Map<String, AliasMetaData> aliases() {
return aliases;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
if (this.settings == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
Settings.writeSettingsToStream(this.settings, out);
}
if (this.mappings == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
this.mappings.writeTo(out);
}
if (this.aliases == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
out.writeMap(this.aliases, StreamOutput::writeString, (stream, aliasMetaData) -> aliasMetaData.writeTo(stream));
}
}
@Override
public int hashCode() {
return Objects.hash(settings, mappings, aliases);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != getClass()) {
return false;
}
Template other = (Template) obj;
return Objects.equals(settings, other.settings) &&
Objects.equals(mappings, other.mappings) &&
Objects.equals(aliases, other.aliases);
}
@Override
public String toString() {
return Strings.toString(this);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (this.settings != null) {
builder.startObject(SETTINGS.getPreferredName());
this.settings.toXContent(builder, params);
builder.endObject();
}
if (this.mappings != null) {
Map<String, Object> uncompressedMapping =
XContentHelper.convertToMap(new BytesArray(this.mappings.uncompressed()), true, XContentType.JSON).v2();
if (uncompressedMapping.size() > 0) {
builder.field(MAPPINGS.getPreferredName());
builder.map(uncompressedMapping);
}
}
if (this.aliases != null) {
builder.startObject(ALIASES.getPreferredName());
for (AliasMetaData alias : this.aliases.values()) {
AliasMetaData.Builder.toXContent(alias, builder, params);
}
builder.endObject();
}
builder.endObject();
return builder;
}
}
}

View File

@ -23,7 +23,9 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.CollectionUtil;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.support.master.MasterNodeRequest;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateUpdateTask;
@ -134,6 +136,104 @@ public class MetaDataIndexTemplateService {
});
}
/**
* Add the given component template to the cluster state. If {@code create} is true, an
* exception will be thrown if the component template already exists
*/
public void putComponentTemplate(final String cause, final boolean create, final String name, final TimeValue masterTimeout,
final ComponentTemplate template, final ActionListener<AcknowledgedResponse> listener) {
clusterService.submitStateUpdateTask("create-component-template [" + name + "], cause [" + cause + "]",
new ClusterStateUpdateTask(Priority.URGENT) {
@Override
public TimeValue timeout() {
return masterTimeout;
}
@Override
public void onFailure(String source, Exception e) {
listener.onFailure(e);
}
@Override
public ClusterState execute(ClusterState currentState) {
return addComponentTemplate(currentState, create, name, template);
}
@Override
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
listener.onResponse(new AcknowledgedResponse(true));
}
});
}
// Package visible for testing
static ClusterState addComponentTemplate(final ClusterState currentState, final boolean create,
final String name, final ComponentTemplate template) {
if (create && currentState.metaData().componentTemplates().containsKey(name)) {
throw new IllegalArgumentException("component template [" + name + "] already exists");
}
// TODO: validation of component template
// validateAndAddTemplate(request, templateBuilder, indicesService, xContentRegistry);
logger.info("adding component template [{}]", name);
return ClusterState.builder(currentState)
.metaData(MetaData.builder(currentState.metaData()).put(name, template))
.build();
}
/**
* Remove the given component template from the cluster state. The component template name
* supports simple regex wildcards for removing multiple component templates at a time.
*/
public void removeComponentTemplate(final String name, final TimeValue masterTimeout,
final ActionListener<AcknowledgedResponse> listener) {
clusterService.submitStateUpdateTask("remove-component-template [" + name + "]",
new ClusterStateUpdateTask(Priority.URGENT) {
@Override
public TimeValue timeout() {
return masterTimeout;
}
@Override
public void onFailure(String source, Exception e) {
listener.onFailure(e);
}
@Override
public ClusterState execute(ClusterState currentState) {
Set<String> templateNames = new HashSet<>();
for (String templateName : currentState.metaData().componentTemplates().keySet()) {
if (Regex.simpleMatch(name, templateName)) {
templateNames.add(templateName);
}
}
if (templateNames.isEmpty()) {
// if its a match all pattern, and no templates are found (we have none), don't
// fail with index missing...
if (Regex.isMatchAllPattern(name)) {
return currentState;
}
// TODO: perhaps introduce a ComponentTemplateMissingException?
throw new IndexTemplateMissingException(name);
}
MetaData.Builder metaData = MetaData.builder(currentState.metaData());
for (String templateName : templateNames) {
logger.info("removing component template [{}]", templateName);
metaData.removeComponentTemplate(templateName);
}
return ClusterState.builder(currentState).metaData(metaData).build();
}
@Override
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
listener.onResponse(new AcknowledgedResponse(true));
}
});
}
public void putTemplate(final PutRequest request, final PutListener listener) {
Settings.Builder updatedSettingsBuilder = Settings.builder();
updatedSettingsBuilder.put(request.settings).normalizePrefix(IndexMetaData.INDEX_SETTING_PREFIX);

View File

@ -0,0 +1,187 @@
/*
* 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.cluster.metadata;
import org.elasticsearch.cluster.AbstractDiffable;
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 template consists of optional settings, mappings, or alias configuration for an index, however,
* it is entirely independent from an index. It's a building block forming part of a regular index
* template and a {@link ComponentTemplate}.
*/
public class Template extends AbstractDiffable<Template> implements ToXContentObject {
private static final ParseField SETTINGS = new ParseField("settings");
private static final ParseField MAPPINGS = new ParseField("mappings");
private static final ParseField ALIASES = new ParseField("aliases");
@SuppressWarnings("unchecked")
static final ConstructingObjectParser<Template, Void> PARSER = new ConstructingObjectParser<>("template", false,
a -> new Template((Settings) a[0], (CompressedXContent) a[1], (Map<String, AliasMetaData>) a[2]));
static {
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> Settings.fromXContent(p), SETTINGS);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) ->
new CompressedXContent(Strings.toString(XContentFactory.jsonBuilder().map(p.mapOrdered()))), MAPPINGS);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> {
Map<String, AliasMetaData> aliasMap = new HashMap<>();
while ((p.nextToken()) != XContentParser.Token.END_OBJECT) {
AliasMetaData alias = AliasMetaData.Builder.fromXContent(p);
aliasMap.put(alias.alias(), alias);
}
return aliasMap;
}, ALIASES);
}
@Nullable
private final Settings settings;
@Nullable
private final CompressedXContent mappings;
@Nullable
private final Map<String, AliasMetaData> aliases;
public Template(@Nullable Settings settings, @Nullable CompressedXContent mappings, @Nullable Map<String, AliasMetaData> aliases) {
this.settings = settings;
this.mappings = mappings;
this.aliases = aliases;
}
Template(StreamInput in) throws IOException {
if (in.readBoolean()) {
this.settings = Settings.readSettingsFromStream(in);
} else {
this.settings = null;
}
if (in.readBoolean()) {
this.mappings = CompressedXContent.readCompressedString(in);
} else {
this.mappings = null;
}
if (in.readBoolean()) {
this.aliases = in.readMap(StreamInput::readString, AliasMetaData::new);
} else {
this.aliases = null;
}
}
public Settings settings() {
return settings;
}
public CompressedXContent mappings() {
return mappings;
}
public Map<String, AliasMetaData> aliases() {
return aliases;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
if (this.settings == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
Settings.writeSettingsToStream(this.settings, out);
}
if (this.mappings == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
this.mappings.writeTo(out);
}
if (this.aliases == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
out.writeMap(this.aliases, StreamOutput::writeString, (stream, aliasMetaData) -> aliasMetaData.writeTo(stream));
}
}
@Override
public int hashCode() {
return Objects.hash(settings, mappings, aliases);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != getClass()) {
return false;
}
Template other = (Template) obj;
return Objects.equals(settings, other.settings) &&
Objects.equals(mappings, other.mappings) &&
Objects.equals(aliases, other.aliases);
}
@Override
public String toString() {
return Strings.toString(this);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (this.settings != null) {
builder.startObject(SETTINGS.getPreferredName());
this.settings.toXContent(builder, params);
builder.endObject();
}
if (this.mappings != null) {
Map<String, Object> uncompressedMapping =
XContentHelper.convertToMap(new BytesArray(this.mappings.uncompressed()), true, XContentType.JSON).v2();
if (uncompressedMapping.size() > 0) {
builder.field(MAPPINGS.getPreferredName());
builder.map(uncompressedMapping);
}
}
if (this.aliases != null) {
builder.startObject(ALIASES.getPreferredName());
for (AliasMetaData alias : this.aliases.values()) {
AliasMetaData.Builder.toXContent(alias, builder, params);
}
builder.endObject();
}
builder.endObject();
return builder;
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.delete.DeleteComponentTemplateAction;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.RestToXContentListener;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import static org.elasticsearch.rest.RestRequest.Method.DELETE;
public class RestDeleteComponentTemplateAction extends BaseRestHandler {
@Override
public List<Route> routes() {
return Collections.singletonList(new Route(DELETE, "/_component_template/{name}"));
}
@Override
public String getName() {
return "delete_component_template_action";
}
@Override
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
DeleteComponentTemplateAction.Request deleteReq = new DeleteComponentTemplateAction.Request(request.param("name"));
deleteReq.masterNodeTimeout(request.paramAsTime("master_timeout", deleteReq.masterNodeTimeout()));
return channel -> client.execute(DeleteComponentTemplateAction.INSTANCE, deleteReq, new RestToXContentListener<>(channel));
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.get.GetComponentTemplateAction;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestToXContentListener;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.HEAD;
import static org.elasticsearch.rest.RestStatus.NOT_FOUND;
import static org.elasticsearch.rest.RestStatus.OK;
public class RestGetComponentTemplateAction extends BaseRestHandler {
@Override
public List<Route> routes() {
return Arrays.asList(
new Route(GET, "/_component_template"),
new Route(GET, "/_component_template/{name}"),
new Route(HEAD, "/_component_template/{name}"));
}
@Override
public String getName() {
return "get_component_template_action";
}
@Override
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
final String[] names = Strings.splitStringByCommaToArray(request.param("name"));
final GetComponentTemplateAction.Request getRequest = new GetComponentTemplateAction.Request(names);
getRequest.local(request.paramAsBoolean("local", getRequest.local()));
getRequest.masterNodeTimeout(request.paramAsTime("master_timeout", getRequest.masterNodeTimeout()));
final boolean implicitAll = getRequest.names().length == 0;
return channel ->
client.execute(GetComponentTemplateAction.INSTANCE, getRequest,
new RestToXContentListener<GetComponentTemplateAction.Response>(channel) {
@Override
protected RestStatus getStatus(final GetComponentTemplateAction.Response response) {
final boolean templateExists = response.getComponentTemplates().isEmpty() == false;
return (templateExists || implicitAll) ? OK : NOT_FOUND;
}
});
}
@Override
protected Set<String> responseParams() {
return Settings.FORMAT_PARAMS;
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.put.PutComponentTemplateAction;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.metadata.ComponentTemplate;
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;
import static org.elasticsearch.rest.RestRequest.Method.PUT;
public class RestPutComponentTemplateAction extends BaseRestHandler {
@Override
public List<Route> routes() {
return Arrays.asList(
new Route(POST, "/_component_template/{name}"),
new Route(PUT, "/_component_template/{name}"));
}
@Override
public String getName() {
return "put_component_template_action";
}
@Override
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
PutComponentTemplateAction.Request putRequest = new PutComponentTemplateAction.Request(request.param("name"));
putRequest.masterNodeTimeout(request.paramAsTime("master_timeout", putRequest.masterNodeTimeout()));
putRequest.create(request.paramAsBoolean("create", false));
putRequest.cause(request.param("cause", "api"));
putRequest.componentTemplate(ComponentTemplate.parse(request.contentParser()));
return channel -> client.execute(PutComponentTemplateAction.INSTANCE, putRequest, new RestToXContentListener<>(channel));
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.cluster.metadata.ComponentTemplate;
import org.elasticsearch.cluster.metadata.ComponentTemplateTests;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class GetComponentTemplateResponseTests extends AbstractWireSerializingTestCase<GetComponentTemplateAction.Response> {
@Override
protected Writeable.Reader<GetComponentTemplateAction.Response> instanceReader() {
return GetComponentTemplateAction.Response::new;
}
@Override
protected GetComponentTemplateAction.Response createTestInstance() {
if (randomBoolean()) {
return new GetComponentTemplateAction.Response(Collections.emptyMap());
}
Map<String, ComponentTemplate> templates = new HashMap<>();
for (int i = 0; i < randomIntBetween(1, 4); i++) {
templates.put(randomAlphaOfLength(4), ComponentTemplateTests.randomInstance());
}
return new GetComponentTemplateAction.Response(templates);
}
@Override
protected GetComponentTemplateAction.Response mutateInstance(GetComponentTemplateAction.Response instance) throws IOException {
return randomValueOtherThan(instance, this::createTestInstance);
}
}

View File

@ -76,7 +76,7 @@ public class ComponentTemplateTests extends AbstractDiffableSerializationTestCas
if (randomBoolean()) {
aliases = randomAliases();
}
ComponentTemplate.Template template = new ComponentTemplate.Template(settings, mappings, aliases);
Template template = new Template(settings, mappings, aliases);
Map<String, Object> meta = null;
if (randomBoolean()) {
@ -130,21 +130,21 @@ public class ComponentTemplateTests extends AbstractDiffableSerializationTestCas
case 0:
switch (randomIntBetween(0, 2)) {
case 0:
ComponentTemplate.Template ot = orig.template();
Template ot = orig.template();
return new ComponentTemplate(
new ComponentTemplate.Template(randomValueOtherThan(ot.settings(), ComponentTemplateTests::randomSettings),
new Template(randomValueOtherThan(ot.settings(), ComponentTemplateTests::randomSettings),
ot.mappings(), ot.aliases()),
orig.version(), orig.metadata());
case 1:
ComponentTemplate.Template ot2 = orig.template();
Template ot2 = orig.template();
return new ComponentTemplate(
new ComponentTemplate.Template(ot2.settings(),
new Template(ot2.settings(),
randomValueOtherThan(ot2.mappings(), ComponentTemplateTests::randomMappings), ot2.aliases()),
orig.version(), orig.metadata());
case 2:
ComponentTemplate.Template ot3 = orig.template();
Template ot3 = orig.template();
return new ComponentTemplate(
new ComponentTemplate.Template(ot3.settings(), ot3.mappings(),
new Template(ot3.settings(), ot3.mappings(),
randomValueOtherThan(ot3.aliases(), ComponentTemplateTests::randomAliases)),
orig.version(), orig.metadata());
default:

View File

@ -17,15 +17,10 @@
* under the License.
*/
package org.elasticsearch.action.admin.indices.template.put;
package org.elasticsearch.cluster.metadata;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.AliasValidator;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
import org.elasticsearch.cluster.metadata.MetaDataCreateIndexService;
import org.elasticsearch.cluster.metadata.MetaDataIndexTemplateService;
import org.elasticsearch.cluster.metadata.MetaDataIndexTemplateService.PutRequest;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Strings;
@ -202,6 +197,23 @@ public class MetaDataIndexTemplateServiceTests extends ESSingleNodeTestCase {
assertThat(errors.get(0).getMessage(), containsString("global templates may not specify the setting index.hidden"));
}
public void testAddComponentTemplate() {
ClusterState state = ClusterState.EMPTY_STATE;
ComponentTemplate template = ComponentTemplateTests.randomInstance();
state = MetaDataIndexTemplateService.addComponentTemplate(state, false, "foo", template);
assertNotNull(state.metaData().componentTemplates().get("foo"));
assertThat(state.metaData().componentTemplates().get("foo"), equalTo(template));
final ClusterState throwState = ClusterState.builder(state).build();
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> MetaDataIndexTemplateService.addComponentTemplate(throwState, true, "foo", template));
assertThat(e.getMessage(), containsString("component template [foo] already exists"));
state = MetaDataIndexTemplateService.addComponentTemplate(state, randomBoolean(), "bar", template);
assertNotNull(state.metaData().componentTemplates().get("bar"));
}
private static List<Throwable> putTemplate(NamedXContentRegistry xContentRegistry, PutRequest request) {
MetaDataCreateIndexService createIndexService = new MetaDataCreateIndexService(
Settings.EMPTY,

View File

@ -124,7 +124,7 @@ public class ToAndFromJsonMetaDataTests extends ESTestCase {
.putAlias(newAliasMetaDataBuilder("alias-bar2").filter("{\"term\":{\"user\":\"kimchy\"}}"))
.putAlias(newAliasMetaDataBuilder("alias-bar3").routing("routing-bar")))
.put("component_template", new ComponentTemplate(
new ComponentTemplate.Template(Settings.builder().put("setting", "value").build(),
new Template(Settings.builder().put("setting", "value").build(),
new CompressedXContent("{\"baz\":\"eggplant\"}"),
Collections.singletonMap("alias", AliasMetaData.builder("alias").build())),
5L, Collections.singletonMap("my_meta", Collections.singletonMap("foo", "bar"))))
@ -320,7 +320,7 @@ public class ToAndFromJsonMetaDataTests extends ESTestCase {
assertThat(parsedMetaData.componentTemplates().get("component_template").metadata(),
equalTo(Collections.singletonMap("my_meta", Collections.singletonMap("foo", "bar"))));
assertThat(parsedMetaData.componentTemplates().get("component_template").template(),
equalTo(new ComponentTemplate.Template(Settings.builder().put("setting", "value").build(),
equalTo(new Template(Settings.builder().put("setting", "value").build(),
new CompressedXContent("{\"baz\":\"eggplant\"}"),
Collections.singletonMap("alias", AliasMetaData.builder("alias").build()))));
}