From 7b1bb9952a4f0e65121dce5bfc7e2104f291a8dd Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Mon, 6 Apr 2020 20:24:23 +0200 Subject: [PATCH] [7.x] HLRC support for Component Templates APIs (#54635) (#54828) * HLRC support for Component Templates APIs (#54635) --- .../elasticsearch/client/ClusterClient.java | 115 +++++++++++++++ .../client/ClusterRequestConverters.java | 59 ++++++++ .../ComponentTemplatesExistRequest.java | 40 ++++++ .../DeleteComponentTemplateRequest.java | 35 +++++ .../indices/GetComponentTemplatesRequest.java | 80 +++++++++++ .../GetComponentTemplatesResponse.java | 108 +++++++++++++++ .../indices/PutComponentTemplateRequest.java | 100 +++++++++++++ .../elasticsearch/client/ClusterClientIT.java | 56 ++++++++ .../client/RestHighLevelClientTests.java | 3 - .../GetComponentTemplatesResponseTests.java | 131 ++++++++++++++++++ .../cluster.exists_component_template.json | 35 +++++ .../cluster/metadata/ComponentTemplate.java | 2 +- 12 files changed, 760 insertions(+), 4 deletions(-) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/indices/ComponentTemplatesExistRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/indices/DeleteComponentTemplateRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetComponentTemplatesRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetComponentTemplatesResponse.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/indices/PutComponentTemplateRequest.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/indices/GetComponentTemplatesResponseTests.java create mode 100644 rest-api-spec/src/main/resources/rest-api-spec/api/cluster.exists_component_template.json diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java index 14fbf3e1f6d..bebb71d9b99 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java @@ -26,8 +26,14 @@ import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsResponse; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; +import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.client.cluster.RemoteInfoRequest; import org.elasticsearch.client.cluster.RemoteInfoResponse; +import org.elasticsearch.client.indices.ComponentTemplatesExistRequest; +import org.elasticsearch.client.indices.DeleteComponentTemplateRequest; +import org.elasticsearch.client.indices.GetComponentTemplatesRequest; +import org.elasticsearch.client.indices.GetComponentTemplatesResponse; +import org.elasticsearch.client.indices.PutComponentTemplateRequest; import org.elasticsearch.rest.RestStatus; import java.io.IOException; @@ -169,4 +175,113 @@ public final class ClusterClient { return restHighLevelClient.performRequestAsyncAndParseEntity(request, ClusterRequestConverters::remoteInfo, options, RemoteInfoResponse::fromXContent, listener, singleton(RestStatus.REQUEST_TIMEOUT.getStatus())); } + + /** + * Delete a component template using the Component Templates API + * + * @param req the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public AcknowledgedResponse deleteComponentTemplate(DeleteComponentTemplateRequest req, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(req, ClusterRequestConverters::deleteComponentTemplate, + options, AcknowledgedResponse::fromXContent, emptySet()); + } + + /** + * Asynchronously delete a component template using the Component Templates API + * + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + * @return cancellable that may be used to cancel the request + */ + public Cancellable deleteComponentTemplateAsync(DeleteComponentTemplateRequest request, RequestOptions options, + ActionListener listener) { + return restHighLevelClient.performRequestAsyncAndParseEntity(request, ClusterRequestConverters::deleteComponentTemplate, + options, AcknowledgedResponse::fromXContent, listener, emptySet()); + } + + /** + * Puts a component template using the Component Templates API. + * + * @param putComponentTemplateRequest the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public AcknowledgedResponse putComponentTemplate(PutComponentTemplateRequest putComponentTemplateRequest, + RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(putComponentTemplateRequest, ClusterRequestConverters::putComponentTemplate, + options, AcknowledgedResponse::fromXContent, emptySet()); + } + + /** + * Asynchronously puts a component template using the Component Templates API. + * + * @param putComponentTemplateRequest the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + * @return cancellable that may be used to cancel the request + */ + public Cancellable putComponentTemplateAsync(PutComponentTemplateRequest putComponentTemplateRequest, + RequestOptions options, ActionListener listener) { + return restHighLevelClient.performRequestAsyncAndParseEntity(putComponentTemplateRequest, + ClusterRequestConverters::putComponentTemplate, options, AcknowledgedResponse::fromXContent, listener, emptySet()); + } + + /** + * Gets component templates using the Components Templates API + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param getComponentTemplatesRequest the request + * @return the response + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public GetComponentTemplatesResponse getComponentTemplate(GetComponentTemplatesRequest getComponentTemplatesRequest, + RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(getComponentTemplatesRequest, + ClusterRequestConverters::getComponentTemplates, options, GetComponentTemplatesResponse::fromXContent, emptySet()); + } + + /** + * Asynchronously gets component templates using the Components Templates API + * @param getComponentTemplatesRequest the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + * @return cancellable that may be used to cancel the request + */ + public Cancellable getComponentTemplateAsync(GetComponentTemplatesRequest getComponentTemplatesRequest, RequestOptions options, + ActionListener listener) { + return restHighLevelClient.performRequestAsyncAndParseEntity(getComponentTemplatesRequest, + ClusterRequestConverters::getComponentTemplates, options, GetComponentTemplatesResponse::fromXContent, listener, emptySet()); + } + + /** + * Uses the Component Templates API to determine if component templates exist + * + * @param componentTemplatesRequest the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return true if any index templates in the request exist, false otherwise + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public boolean existsComponentTemplate(ComponentTemplatesExistRequest componentTemplatesRequest, + RequestOptions options) throws IOException { + return restHighLevelClient.performRequest(componentTemplatesRequest, + ClusterRequestConverters::componentTemplatesExist, options, RestHighLevelClient::convertExistsResponse, emptySet()); + } + + /** + * Uses the Index Templates API to determine if index templates exist + * @param componentTemplatesRequest the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion. The listener will be called with the value {@code true} + * @return cancellable that may be used to cancel the request + */ + public Cancellable existsComponentTemplateAsync(ComponentTemplatesExistRequest componentTemplatesRequest, + RequestOptions options, + ActionListener listener) { + + return restHighLevelClient.performRequestAsync(componentTemplatesRequest, + ClusterRequestConverters::componentTemplatesExist, options, RestHighLevelClient::convertExistsResponse, listener, emptySet()); + } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterRequestConverters.java index 74b2c3b7c6a..e4f8f541caa 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterRequestConverters.java @@ -19,13 +19,19 @@ package org.elasticsearch.client; +import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPut; import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.client.cluster.RemoteInfoRequest; +import org.elasticsearch.client.indices.ComponentTemplatesExistRequest; +import org.elasticsearch.client.indices.DeleteComponentTemplateRequest; +import org.elasticsearch.client.indices.GetComponentTemplatesRequest; +import org.elasticsearch.client.indices.PutComponentTemplateRequest; import org.elasticsearch.common.Strings; import java.io.IOException; @@ -81,4 +87,57 @@ final class ClusterRequestConverters { static Request remoteInfo(RemoteInfoRequest remoteInfoRequest) { return new Request(HttpGet.METHOD_NAME, "/_remote/info"); } + + static Request putComponentTemplate(PutComponentTemplateRequest putComponentTemplateRequest) throws IOException { + String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_component_template") + .addPathPart(putComponentTemplateRequest.name()).build(); + Request request = new Request(HttpPut.METHOD_NAME, endpoint); + RequestConverters.Params params = new RequestConverters.Params(); + params.withMasterTimeout(putComponentTemplateRequest.masterNodeTimeout()); + if (putComponentTemplateRequest.create()) { + params.putParam("create", Boolean.TRUE.toString()); + } + if (Strings.hasText(putComponentTemplateRequest.cause())) { + params.putParam("cause", putComponentTemplateRequest.cause()); + } + request.addParameters(params.asMap()); + request.setEntity(RequestConverters.createEntity(putComponentTemplateRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE)); + return request; + } + + static Request getComponentTemplates(GetComponentTemplatesRequest getComponentTemplatesRequest){ + final String endpoint = new RequestConverters.EndpointBuilder() + .addPathPartAsIs("_component_template") + .addPathPart(getComponentTemplatesRequest.name()) + .build(); + final Request request = new Request(HttpGet.METHOD_NAME, endpoint); + final RequestConverters.Params params = new RequestConverters.Params(); + params.withLocal(getComponentTemplatesRequest.isLocal()); + params.withMasterTimeout(getComponentTemplatesRequest.getMasterNodeTimeout()); + request.addParameters(params.asMap()); + return request; + } + + static Request componentTemplatesExist(ComponentTemplatesExistRequest componentTemplatesRequest) { + final String endpoint = new RequestConverters.EndpointBuilder() + .addPathPartAsIs("_component_template") + .addPathPart(componentTemplatesRequest.name()) + .build(); + final Request request = new Request(HttpHead.METHOD_NAME, endpoint); + final RequestConverters.Params params = new RequestConverters.Params(); + params.withLocal(componentTemplatesRequest.isLocal()); + params.withMasterTimeout(componentTemplatesRequest.getMasterNodeTimeout()); + request.addParameters(params.asMap()); + return request; + } + + static Request deleteComponentTemplate(DeleteComponentTemplateRequest deleteComponentTemplateRequest) { + String name = deleteComponentTemplateRequest.getName(); + String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_component_template").addPathPart(name).build(); + Request request = new Request(HttpDelete.METHOD_NAME, endpoint); + RequestConverters.Params params = new RequestConverters.Params(); + params.withMasterTimeout(deleteComponentTemplateRequest.masterNodeTimeout()); + request.addParameters(params.asMap()); + return request; + } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/ComponentTemplatesExistRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/ComponentTemplatesExistRequest.java new file mode 100644 index 00000000000..ae2bbed7d04 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/ComponentTemplatesExistRequest.java @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.client.indices; + +import org.elasticsearch.common.Strings; + +/** + * A request to check for the existence of component templates + */ +public class ComponentTemplatesExistRequest extends GetComponentTemplatesRequest { + + /** + * Create a request to check for the existence of component template. Name must be provided + * + * @param name the name of template to check for the existence of + */ + public ComponentTemplatesExistRequest(String name) { + super(name); + if (Strings.isNullOrEmpty(name)) { + throw new IllegalArgumentException("must provide component template name"); + } + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/DeleteComponentTemplateRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/DeleteComponentTemplateRequest.java new file mode 100644 index 00000000000..e96c2e35459 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/DeleteComponentTemplateRequest.java @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.client.indices; + +import org.elasticsearch.client.TimedRequest; + +public class DeleteComponentTemplateRequest extends TimedRequest { + + private final String name; + + public DeleteComponentTemplateRequest(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetComponentTemplatesRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetComponentTemplatesRequest.java new file mode 100644 index 00000000000..c7a537d4018 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetComponentTemplatesRequest.java @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.client.indices; + +import org.elasticsearch.client.TimedRequest; +import org.elasticsearch.client.Validatable; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.unit.TimeValue; + +/** + * A request to read the content of component templates + */ +public class GetComponentTemplatesRequest implements Validatable { + + private final String name; + + private TimeValue masterNodeTimeout = TimedRequest.DEFAULT_MASTER_NODE_TIMEOUT; + private boolean local = false; + + /** + * Create a request to read the content of component template. If no template name is provided, all templates will + * be read + * + * @param name the name of template to read + */ + public GetComponentTemplatesRequest(String name) { + this.name = name; + } + + /** + * @return the name of component template this request is requesting + */ + public String name() { + return name; + } + + /** + * @return the timeout for waiting for the master node to respond + */ + public TimeValue getMasterNodeTimeout() { + return masterNodeTimeout; + } + + public void setMasterNodeTimeout(@Nullable TimeValue masterNodeTimeout) { + this.masterNodeTimeout = masterNodeTimeout; + } + + public void setMasterNodeTimeout(String masterNodeTimeout) { + final TimeValue timeValue = TimeValue.parseTimeValue(masterNodeTimeout, getClass().getSimpleName() + ".masterNodeTimeout"); + setMasterNodeTimeout(timeValue); + } + + /** + * @return true if this request is to read from the local cluster state, rather than the master node - false otherwise + */ + public boolean isLocal() { + return local; + } + + public void setLocal(boolean local) { + this.local = local; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetComponentTemplatesResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetComponentTemplatesResponse.java new file mode 100644 index 00000000000..6d870c7eaa1 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetComponentTemplatesResponse.java @@ -0,0 +1,108 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.indices; + +import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + + +public class GetComponentTemplatesResponse { + + 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"); + + @SuppressWarnings("unchecked") + private static final ConstructingObjectParser, Void> PARSER = + new ConstructingObjectParser<>("component_templates", false, + a -> ((List) a[0]).stream().collect(Collectors.toMap(n -> n.name, n -> n.componentTemplate, + (n1, n2) -> n1, LinkedHashMap::new))); + + private static final ConstructingObjectParser INNER_PARSER = + new ConstructingObjectParser<>("named_component_template", false, + a -> new NamedComponentTemplate((String) a[0], (ComponentTemplate) a[1])); + + static { + INNER_PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME); + INNER_PARSER.declareObject(ConstructingObjectParser.constructorArg(), ComponentTemplate.PARSER, COMPONENT_TEMPLATE); + PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(), INNER_PARSER, COMPONENT_TEMPLATES); + } + + private static class NamedComponentTemplate { + String name; + ComponentTemplate componentTemplate; + + private NamedComponentTemplate(String name, ComponentTemplate componentTemplate) { + this.name = name; + this.componentTemplate = componentTemplate; + } + } + + @Override + public String toString() { + return "GetIndexTemplatesResponse [indexTemplates=" + componentTemplates + "]"; + } + + private final Map componentTemplates; + + GetComponentTemplatesResponse(Map componentTemplates) { + this.componentTemplates = Collections.unmodifiableMap(new LinkedHashMap<>(componentTemplates)); + } + + public Map getComponentTemplates() { + return componentTemplates; + } + + + public static GetComponentTemplatesResponse fromXContent(XContentParser parser) throws IOException { + return new GetComponentTemplatesResponse(PARSER.apply(parser, null)); + } + + @Override + public int hashCode() { + return Objects.hash(componentTemplates); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GetComponentTemplatesResponse other = (GetComponentTemplatesResponse) obj; + return Objects.equals(componentTemplates, other.componentTemplates); + } + + +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/PutComponentTemplateRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/PutComponentTemplateRequest.java new file mode 100644 index 00000000000..e37d44baee8 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/PutComponentTemplateRequest.java @@ -0,0 +1,100 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.indices; + +import org.elasticsearch.client.TimedRequest; +import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; + +/** + * A request to create an component template. + */ +public class PutComponentTemplateRequest extends TimedRequest implements ToXContentObject { + + private String name; + + private String cause = ""; + + private boolean create; + + private ComponentTemplate componentTemplate; + + /** + * Sets the name of the component template. + */ + public PutComponentTemplateRequest name(String name) { + if (Strings.isNullOrEmpty(name)) { + throw new IllegalArgumentException("name cannot be null or empty"); + } + this.name = name; + return this; + } + + /** + * The name of the component template. + */ + public String name() { + return this.name; + } + + /** + * Set to {@code true} to force only creation, not an update of an component 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 component template to create. + */ + public PutComponentTemplateRequest componentTemplate(ComponentTemplate componentTemplate) { + this.componentTemplate = componentTemplate; + return this; + } + + /** + * The cause for this component template creation. + */ + public PutComponentTemplateRequest cause(String cause) { + this.cause = cause; + return this; + } + + public String cause() { + return this.cause; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + if (componentTemplate != null) { + componentTemplate.toXContent(builder, params); + } + return builder; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java index 09c8549d725..0bf0316b72e 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java @@ -21,20 +21,31 @@ package org.elasticsearch.client; import org.apache.http.util.EntityUtils; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsResponse; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; +import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.client.cluster.RemoteConnectionInfo; import org.elasticsearch.client.cluster.RemoteInfoRequest; import org.elasticsearch.client.cluster.RemoteInfoResponse; import org.elasticsearch.client.cluster.SniffModeInfo; +import org.elasticsearch.client.indices.ComponentTemplatesExistRequest; +import org.elasticsearch.client.indices.DeleteComponentTemplateRequest; +import org.elasticsearch.client.indices.GetComponentTemplatesRequest; +import org.elasticsearch.client.indices.GetComponentTemplatesResponse; +import org.elasticsearch.client.indices.PutComponentTemplateRequest; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.cluster.health.ClusterIndexHealth; import org.elasticsearch.cluster.health.ClusterShardHealth; +import org.elasticsearch.cluster.metadata.AliasMetadata; +import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.cluster.metadata.Template; import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; +import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.TimeValue; @@ -46,6 +57,7 @@ import org.elasticsearch.transport.RemoteClusterService; import org.elasticsearch.transport.SniffConnectionStrategy; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -342,4 +354,48 @@ public class ClusterClientIT extends ESRestHighLevelClientTestCase { assertThat(sniffModeInfo.getSeedNodes(), equalTo(seeds)); } + public void testComponentTemplates() throws Exception { + String templateName = "my-template"; + Settings settings = Settings.builder().put("index.number_of_shards", 1).build(); + CompressedXContent mappings = new CompressedXContent("{\"properties\":{\"host_name\":{\"type\":\"keyword\"}}}"); + AliasMetadata alias = AliasMetadata.builder("alias").writeIndex(true).build(); + Template template = new Template(settings, mappings, Collections.singletonMap("alias", alias)); + ComponentTemplate componentTemplate = new ComponentTemplate(template, 1L, new HashMap<>()); + PutComponentTemplateRequest putComponentTemplateRequest = + new PutComponentTemplateRequest().name(templateName).create(true).componentTemplate(componentTemplate); + + AcknowledgedResponse response = execute(putComponentTemplateRequest, + highLevelClient().cluster()::putComponentTemplate, highLevelClient().cluster()::putComponentTemplateAsync); + assertThat(response.isAcknowledged(), equalTo(true)); + + ComponentTemplatesExistRequest componentTemplatesExistRequest = new ComponentTemplatesExistRequest(templateName); + boolean exist = execute(componentTemplatesExistRequest, + highLevelClient().cluster()::existsComponentTemplate, highLevelClient().cluster()::existsComponentTemplateAsync); + + assertTrue(exist); + + GetComponentTemplatesRequest getComponentTemplatesRequest = new GetComponentTemplatesRequest(templateName); + GetComponentTemplatesResponse getResponse = execute(getComponentTemplatesRequest, + highLevelClient().cluster()::getComponentTemplate, highLevelClient().cluster()::getComponentTemplateAsync); + + assertThat(getResponse.getComponentTemplates().size(), equalTo(1)); + assertThat(getResponse.getComponentTemplates().containsKey(templateName), equalTo(true)); + assertThat(getResponse.getComponentTemplates().get(templateName), equalTo(componentTemplate)); + + DeleteComponentTemplateRequest deleteComponentTemplateRequest = new DeleteComponentTemplateRequest(templateName); + response = execute(deleteComponentTemplateRequest, highLevelClient().cluster()::deleteComponentTemplate, + highLevelClient().cluster()::deleteComponentTemplateAsync); + assertThat(response.isAcknowledged(), equalTo(true)); + + ElasticsearchStatusException statusException = expectThrows(ElasticsearchStatusException.class, + () -> execute(getComponentTemplatesRequest, + highLevelClient().cluster()::getComponentTemplate, highLevelClient().cluster()::getComponentTemplateAsync)); + + assertThat(statusException.status(), equalTo(RestStatus.NOT_FOUND)); + + exist = execute(componentTemplatesExistRequest, + highLevelClient().cluster()::existsComponentTemplate, highLevelClient().cluster()::existsComponentTemplateAsync); + + assertFalse(exist); + } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java index 26656753f6d..4b505671ef4 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java @@ -858,9 +858,6 @@ public class RestHighLevelClientTests extends ESTestCase { "indices.put_alias", "render_search_template", "scripts_painless_execute", - "cluster.put_component_template", - "cluster.get_component_template", - "cluster.delete_component_template", "indices.create_data_stream", "indices.get_data_streams", "indices.delete_data_stream", diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/indices/GetComponentTemplatesResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/indices/GetComponentTemplatesResponseTests.java new file mode 100644 index 00000000000..1f4acce23a6 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/indices/GetComponentTemplatesResponseTests.java @@ -0,0 +1,131 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.client.indices; + +import org.elasticsearch.cluster.metadata.AliasMetadata; +import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.cluster.metadata.Template; +import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester; + +public class GetComponentTemplatesResponseTests extends ESTestCase { + + public void testFromXContent() throws Exception { + xContentTester( + this::createParser, + GetComponentTemplatesResponseTests::createTestInstance, + GetComponentTemplatesResponseTests::toXContent, + GetComponentTemplatesResponse::fromXContent) + .supportsUnknownFields(true) + .randomFieldsExcludeFilter(a -> true) + .test(); + } + + private static GetComponentTemplatesResponse createTestInstance() { + Map templates = new HashMap<>(); + if (randomBoolean()) { + int count = randomInt(10); + for (int i = 0; i < count; i++) { + templates.put(randomAlphaOfLength(10), randomTemplate()); + } + } + return new GetComponentTemplatesResponse(templates); + } + + private static void toXContent(GetComponentTemplatesResponse response, XContentBuilder builder) throws IOException { + builder.startObject(); + builder.startArray("component_templates"); + for (Map.Entry e : response.getComponentTemplates().entrySet()) { + builder.startObject(); + builder.field("name", e.getKey()); + builder.field("component_template"); + e.getValue().toXContent(builder, null); + builder.endObject(); + } + builder.endArray(); + builder.endObject(); + } + + private static ComponentTemplate randomTemplate() { + Settings settings = null; + CompressedXContent mappings = null; + Map aliases = null; + if (randomBoolean()) { + settings = randomSettings(); + } + if (randomBoolean()) { + mappings = randomMappings(); + } + if (randomBoolean()) { + aliases = randomAliases(); + } + Template template = new Template(settings, mappings, aliases); + + Map meta = null; + if (randomBoolean()) { + meta = randomMeta(); + } + return new ComponentTemplate(template, randomBoolean() ? null : randomNonNegativeLong(), meta); + } + + private static Map randomAliases() { + String aliasName = randomAlphaOfLength(5); + AliasMetadata aliasMeta = AliasMetadata.builder(aliasName) + .filter(Collections.singletonMap(randomAlphaOfLength(2), randomAlphaOfLength(2))) + .routing(randomBoolean() ? null : randomAlphaOfLength(3)) + .isHidden(randomBoolean() ? null : randomBoolean()) + .writeIndex(randomBoolean() ? null : randomBoolean()) + .build(); + return Collections.singletonMap(aliasName, aliasMeta); + } + + private static CompressedXContent randomMappings() { + try { + return new CompressedXContent("{\"" + randomAlphaOfLength(3) + "\":\"" + randomAlphaOfLength(7) + "\"}"); + } catch (IOException e) { + fail("got an IO exception creating fake mappings: " + e); + return null; + } + } + + private static Settings randomSettings() { + return Settings.builder() + .put(randomAlphaOfLength(4), randomAlphaOfLength(10)) + .build(); + } + + private static Map randomMeta() { + if (randomBoolean()) { + return Collections.singletonMap(randomAlphaOfLength(4), randomAlphaOfLength(4)); + } else { + return Collections.singletonMap(randomAlphaOfLength(5), + Collections.singletonMap(randomAlphaOfLength(4), randomAlphaOfLength(4))); + } + } +} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.exists_component_template.json b/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.exists_component_template.json new file mode 100644 index 00000000000..23c918d6056 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/cluster.exists_component_template.json @@ -0,0 +1,35 @@ +{ + "cluster.exists_component_template":{ + "documentation":{ + "url":"https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-component-templates.html", + "description":"Returns information about whether a particular component template exist" + }, + "stability":"stable", + "url":{ + "paths":[ + { + "path":"/_component_template/{name}", + "methods":[ + "HEAD" + ], + "parts":{ + "name":{ + "type":"string", + "description":"The name of the template" + } + } + } + ] + }, + "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/server/src/main/java/org/elasticsearch/cluster/metadata/ComponentTemplate.java b/server/src/main/java/org/elasticsearch/cluster/metadata/ComponentTemplate.java index 0cc8c8f8a94..24638caddc0 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/ComponentTemplate.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/ComponentTemplate.java @@ -47,7 +47,7 @@ public class ComponentTemplate extends AbstractDiffable imple private static final ParseField METADATA = new ParseField("_meta"); @SuppressWarnings("unchecked") - private static final ConstructingObjectParser PARSER = + public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("component_template", false, a -> new ComponentTemplate((Template) a[0], (Long) a[1], (Map) a[2]));