diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/CcrClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/CcrClient.java index fa0acd762dd..dfb516ae083 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/CcrClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/CcrClient.java @@ -21,6 +21,7 @@ package org.elasticsearch.client; import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.ccr.PauseFollowRequest; +import org.elasticsearch.client.ccr.PutAutoFollowPatternRequest; import org.elasticsearch.client.ccr.PutFollowRequest; import org.elasticsearch.client.ccr.PutFollowResponse; import org.elasticsearch.client.ccr.ResumeFollowRequest; @@ -219,4 +220,46 @@ public final class CcrClient { ); } + /** + * Stores an auto follow pattern. + * + * See + * the docs for more. + * + * @param request 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 putAutoFollowPattern(PutAutoFollowPatternRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity( + request, + CcrRequestConverters::putAutoFollowPattern, + options, + AcknowledgedResponse::fromXContent, + Collections.emptySet() + ); + } + + /** + * Asynchronously stores an auto follow pattern. + * + * See + * the docs for more. + * + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + */ + public void putAutoFollowPatternAsync(PutAutoFollowPatternRequest request, + RequestOptions options, + ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity( + request, + CcrRequestConverters::putAutoFollowPattern, + options, + AcknowledgedResponse::fromXContent, + listener, + Collections.emptySet()); + } + } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/CcrRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/CcrRequestConverters.java index 173c96acc6e..47344ff6c32 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/CcrRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/CcrRequestConverters.java @@ -22,6 +22,7 @@ package org.elasticsearch.client; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.elasticsearch.client.ccr.PauseFollowRequest; +import org.elasticsearch.client.ccr.PutAutoFollowPatternRequest; import org.elasticsearch.client.ccr.PutFollowRequest; import org.elasticsearch.client.ccr.ResumeFollowRequest; import org.elasticsearch.client.ccr.UnfollowRequest; @@ -69,4 +70,14 @@ final class CcrRequestConverters { return new Request(HttpPost.METHOD_NAME, endpoint); } + static Request putAutoFollowPattern(PutAutoFollowPatternRequest putAutoFollowPatternRequest) throws IOException { + String endpoint = new RequestConverters.EndpointBuilder() + .addPathPartAsIs("_ccr", "auto_follow") + .addPathPart(putAutoFollowPatternRequest.getName()) + .build(); + Request request = new Request(HttpPut.METHOD_NAME, endpoint); + request.setEntity(createEntity(putAutoFollowPatternRequest, REQUEST_BODY_CONTENT_TYPE)); + return request; + } + } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ccr/PutAutoFollowPatternRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ccr/PutAutoFollowPatternRequest.java new file mode 100644 index 00000000000..e28d825c4e4 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ccr/PutAutoFollowPatternRequest.java @@ -0,0 +1,102 @@ +/* + * 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.ccr; + +import org.elasticsearch.client.Validatable; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +public final class PutAutoFollowPatternRequest extends FollowConfig implements Validatable, ToXContentObject { + + static final ParseField LEADER_PATTERNS_FIELD = new ParseField("leader_index_patterns"); + static final ParseField FOLLOW_PATTERN_FIELD = new ParseField("follow_index_pattern"); + + private final String name; + private final String remoteCluster; + private final List leaderIndexPatterns; + private String followIndexNamePattern; + + public PutAutoFollowPatternRequest(String name, String remoteCluster, List leaderIndexPatterns) { + this.name = Objects.requireNonNull(name); + this.remoteCluster = Objects.requireNonNull(remoteCluster); + this.leaderIndexPatterns = Objects.requireNonNull(leaderIndexPatterns); + } + + public String getName() { + return name; + } + + public String getRemoteCluster() { + return remoteCluster; + } + + public List getLeaderIndexPatterns() { + return leaderIndexPatterns; + } + + public String getFollowIndexNamePattern() { + return followIndexNamePattern; + } + + public void setFollowIndexNamePattern(String followIndexNamePattern) { + this.followIndexNamePattern = followIndexNamePattern; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(PutFollowRequest.REMOTE_CLUSTER_FIELD.getPreferredName(), remoteCluster); + builder.field(LEADER_PATTERNS_FIELD.getPreferredName(), leaderIndexPatterns); + if (followIndexNamePattern != null) { + builder.field(FOLLOW_PATTERN_FIELD.getPreferredName(), followIndexNamePattern); + } + toXContentFragment(builder, params); + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + PutAutoFollowPatternRequest that = (PutAutoFollowPatternRequest) o; + return Objects.equals(name, that.name) && + Objects.equals(remoteCluster, that.remoteCluster) && + Objects.equals(leaderIndexPatterns, that.leaderIndexPatterns) && + Objects.equals(followIndexNamePattern, that.followIndexNamePattern); + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), + name, + remoteCluster, + leaderIndexPatterns, + followIndexNamePattern + ); + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CCRIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CCRIT.java index 0aa4b68263a..cd817ec7daa 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/CCRIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CCRIT.java @@ -30,6 +30,7 @@ import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.client.ccr.PauseFollowRequest; +import org.elasticsearch.client.ccr.PutAutoFollowPatternRequest; import org.elasticsearch.client.ccr.PutFollowRequest; import org.elasticsearch.client.ccr.PutFollowResponse; import org.elasticsearch.client.ccr.ResumeFollowRequest; @@ -127,6 +128,35 @@ public class CCRIT extends ESRestHighLevelClientTestCase { assertThat(unfollowResponse.isAcknowledged(), is(true)); } + public void testAutoFollowing() throws Exception { + CcrClient ccrClient = highLevelClient().ccr(); + PutAutoFollowPatternRequest putAutoFollowPatternRequest = + new PutAutoFollowPatternRequest("pattern1", "local", Collections.singletonList("logs-*")); + putAutoFollowPatternRequest.setFollowIndexNamePattern("copy-{{leader_index}}"); + AcknowledgedResponse putAutoFollowPatternResponse = + execute(putAutoFollowPatternRequest, ccrClient::putAutoFollowPattern, ccrClient::putAutoFollowPatternAsync); + assertThat(putAutoFollowPatternResponse.isAcknowledged(), is(true)); + + CreateIndexRequest createIndexRequest = new CreateIndexRequest("logs-20200101"); + createIndexRequest.settings(Collections.singletonMap("index.soft_deletes.enabled", true)); + CreateIndexResponse response = highLevelClient().indices().create(createIndexRequest, RequestOptions.DEFAULT); + assertThat(response.isAcknowledged(), is(true)); + + assertBusy(() -> { + assertThat(indexExists("copy-logs-20200101"), is(true)); + }); + + // Cleanup: + // TODO: replace with hlrc delete auto follow pattern when it is available: + final Request deleteAutoFollowPatternRequest = new Request("DELETE", "/_ccr/auto_follow/pattern1"); + Map deleteAutoFollowPatternResponse = toMap(client().performRequest(deleteAutoFollowPatternRequest)); + assertThat(deleteAutoFollowPatternResponse.get("acknowledged"), is(true)); + + PauseFollowRequest pauseFollowRequest = new PauseFollowRequest("copy-logs-20200101"); + AcknowledgedResponse pauseFollowResponse = ccrClient.pauseFollow(pauseFollowRequest, RequestOptions.DEFAULT); + assertThat(pauseFollowResponse.isAcknowledged(), is(true)); + } + private static Map toMap(Response response) throws IOException { return XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false); } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ccr/PutAutoFollowPatternRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ccr/PutAutoFollowPatternRequestTests.java new file mode 100644 index 00000000000..429cc4a9f90 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ccr/PutAutoFollowPatternRequestTests.java @@ -0,0 +1,126 @@ +/* + * 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.ccr; + +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public class PutAutoFollowPatternRequestTests extends AbstractXContentTestCase { + + @SuppressWarnings("unchecked") + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("test_parser", + true, (args) -> new PutAutoFollowPatternRequest("name", (String) args[0], (List) args[1])); + + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), PutFollowRequest.REMOTE_CLUSTER_FIELD); + PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), PutAutoFollowPatternRequest.LEADER_PATTERNS_FIELD); + PARSER.declareString(PutAutoFollowPatternRequest::setFollowIndexNamePattern, PutAutoFollowPatternRequest.FOLLOW_PATTERN_FIELD); + PARSER.declareInt(PutAutoFollowPatternRequest::setMaxReadRequestOperationCount, FollowConfig.MAX_READ_REQUEST_OPERATION_COUNT); + PARSER.declareField( + PutAutoFollowPatternRequest::setMaxReadRequestSize, + (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), FollowConfig.MAX_READ_REQUEST_SIZE.getPreferredName()), + PutFollowRequest.MAX_READ_REQUEST_SIZE, + ObjectParser.ValueType.STRING); + PARSER.declareInt(PutAutoFollowPatternRequest::setMaxOutstandingReadRequests, FollowConfig.MAX_OUTSTANDING_READ_REQUESTS); + PARSER.declareInt(PutAutoFollowPatternRequest::setMaxWriteRequestOperationCount, FollowConfig.MAX_WRITE_REQUEST_OPERATION_COUNT); + PARSER.declareField( + PutAutoFollowPatternRequest::setMaxWriteRequestSize, + (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), FollowConfig.MAX_WRITE_REQUEST_SIZE.getPreferredName()), + PutFollowRequest.MAX_WRITE_REQUEST_SIZE, + ObjectParser.ValueType.STRING); + PARSER.declareInt(PutAutoFollowPatternRequest::setMaxOutstandingWriteRequests, FollowConfig.MAX_OUTSTANDING_WRITE_REQUESTS); + PARSER.declareInt(PutAutoFollowPatternRequest::setMaxWriteBufferCount, FollowConfig.MAX_WRITE_BUFFER_COUNT); + PARSER.declareField( + PutAutoFollowPatternRequest::setMaxWriteBufferSize, + (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), FollowConfig.MAX_WRITE_BUFFER_SIZE.getPreferredName()), + PutFollowRequest.MAX_WRITE_BUFFER_SIZE, + ObjectParser.ValueType.STRING); + PARSER.declareField( + PutAutoFollowPatternRequest::setMaxRetryDelay, + (p, c) -> TimeValue.parseTimeValue(p.text(), FollowConfig.MAX_RETRY_DELAY_FIELD.getPreferredName()), + PutFollowRequest.MAX_RETRY_DELAY_FIELD, + ObjectParser.ValueType.STRING); + PARSER.declareField( + PutAutoFollowPatternRequest::setReadPollTimeout, + (p, c) -> TimeValue.parseTimeValue(p.text(), FollowConfig.READ_POLL_TIMEOUT.getPreferredName()), + PutFollowRequest.READ_POLL_TIMEOUT, + ObjectParser.ValueType.STRING); + } + + @Override + protected PutAutoFollowPatternRequest doParseInstance(XContentParser parser) throws IOException { + return PARSER.apply(parser, null); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } + + @Override + protected PutAutoFollowPatternRequest createTestInstance() { + // Name isn't serialized, because it specified in url path, so no need to randomly generate it here. + PutAutoFollowPatternRequest putAutoFollowPatternRequest = new PutAutoFollowPatternRequest("name", + randomAlphaOfLength(4), Arrays.asList(generateRandomStringArray(4, 4, false))); + if (randomBoolean()) { + putAutoFollowPatternRequest.setFollowIndexNamePattern(randomAlphaOfLength(4)); + } + if (randomBoolean()) { + putAutoFollowPatternRequest.setMaxOutstandingReadRequests(randomIntBetween(0, Integer.MAX_VALUE)); + } + if (randomBoolean()) { + putAutoFollowPatternRequest.setMaxOutstandingWriteRequests(randomIntBetween(0, Integer.MAX_VALUE)); + } + if (randomBoolean()) { + putAutoFollowPatternRequest.setMaxReadRequestOperationCount(randomIntBetween(0, Integer.MAX_VALUE)); + } + if (randomBoolean()) { + putAutoFollowPatternRequest.setMaxReadRequestSize(new ByteSizeValue(randomNonNegativeLong())); + } + if (randomBoolean()) { + putAutoFollowPatternRequest.setMaxWriteBufferCount(randomIntBetween(0, Integer.MAX_VALUE)); + } + if (randomBoolean()) { + putAutoFollowPatternRequest.setMaxWriteBufferSize(new ByteSizeValue(randomNonNegativeLong())); + } + if (randomBoolean()) { + putAutoFollowPatternRequest.setMaxWriteRequestOperationCount(randomIntBetween(0, Integer.MAX_VALUE)); + } + if (randomBoolean()) { + putAutoFollowPatternRequest.setMaxWriteRequestSize(new ByteSizeValue(randomNonNegativeLong())); + } + if (randomBoolean()) { + putAutoFollowPatternRequest.setMaxRetryDelay(new TimeValue(randomNonNegativeLong())); + } + if (randomBoolean()) { + putAutoFollowPatternRequest.setReadPollTimeout(new TimeValue(randomNonNegativeLong())); + } + return putAutoFollowPatternRequest; + } + +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CCRDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CCRDocumentationIT.java index 4dce147925c..bc9375b6d6c 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CCRDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CCRDocumentationIT.java @@ -34,6 +34,7 @@ import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.ccr.PauseFollowRequest; +import org.elasticsearch.client.ccr.PutAutoFollowPatternRequest; import org.elasticsearch.client.ccr.PutFollowRequest; import org.elasticsearch.client.ccr.PutFollowResponse; import org.elasticsearch.client.ccr.ResumeFollowRequest; @@ -44,6 +45,7 @@ import org.elasticsearch.common.xcontent.json.JsonXContent; import org.junit.Before; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -375,6 +377,71 @@ public class CCRDocumentationIT extends ESRestHighLevelClientTestCase { assertTrue(latch.await(30L, TimeUnit.SECONDS)); } + public void testPutAutoFollowPattern() throws Exception { + RestHighLevelClient client = highLevelClient(); + + // tag::ccr-put-auto-follow-pattern-request + PutAutoFollowPatternRequest request = + new PutAutoFollowPatternRequest( + "my_pattern", // <1> + "local", // <2> + Arrays.asList("logs-*", "metrics-*") // <3> + ); + request.setFollowIndexNamePattern("copy-{{leader_index}}"); // <4> + // end::ccr-put-auto-follow-pattern-request + + // tag::ccr-put-auto-follow-pattern-execute + AcknowledgedResponse response = client.ccr() + .putAutoFollowPattern(request, RequestOptions.DEFAULT); + // end::ccr-put-auto-follow-pattern-execute + + // tag::ccr-put-auto-follow-pattern-response + boolean acknowledged = response.isAcknowledged(); // <1> + // end::ccr-put-auto-follow-pattern-response + + // Delete auto follow pattern, so that we can store it again: + { + // TODO: replace with hlrc delete auto follow pattern when it is available: + final Request deleteRequest = new Request("DELETE", "/_ccr/auto_follow/my_pattern"); + Map deleteAutoFollowPatternResponse = toMap(client().performRequest(deleteRequest)); + assertThat(deleteAutoFollowPatternResponse.get("acknowledged"), is(true)); + } + + // tag::ccr-put-auto-follow-pattern-execute-listener + ActionListener listener = + new ActionListener() { + @Override + public void onResponse(AcknowledgedResponse response) { // <1> + boolean acknowledged = response.isAcknowledged(); + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::ccr-put-auto-follow-pattern-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::ccr-put-auto-follow-pattern-execute-async + client.ccr().putAutoFollowPatternAsync(request, + RequestOptions.DEFAULT, listener); // <1> + // end::ccr-put-auto-follow-pattern-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + + // Cleanup: + { + // TODO: replace with hlrc delete auto follow pattern when it is available: + final Request deleteRequest = new Request("DELETE", "/_ccr/auto_follow/my_pattern"); + Map deleteAutoFollowPatternResponse = toMap(client().performRequest(deleteRequest)); + assertThat(deleteAutoFollowPatternResponse.get("acknowledged"), is(true)); + } + } + static Map toMap(Response response) throws IOException { return XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false); } diff --git a/docs/java-rest/high-level/ccr/put_auto_follow_pattern.asciidoc b/docs/java-rest/high-level/ccr/put_auto_follow_pattern.asciidoc new file mode 100644 index 00000000000..e6cc6b89ee8 --- /dev/null +++ b/docs/java-rest/high-level/ccr/put_auto_follow_pattern.asciidoc @@ -0,0 +1,37 @@ +-- +:api: ccr-put-auto-follow-pattern +:request: PutAutoFollowPatternRequest +:response: AcknowledgedResponse +-- + +[id="{upid}-{api}"] +=== Put Auto Follow Pattern API + +[id="{upid}-{api}-request"] +==== Request + +The Put Auto Follow Pattern API allows you to store auto follow patterns in order +to automatically follow leader indices in a remote clusters matching certain +index name patterns. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-request] +-------------------------------------------------- +<1> The name of the auto follow pattern. +<2> The name of the remote cluster. +<3> The leader index patterns. +<4> The pattern used to create the follower index + +[id="{upid}-{api}-response"] +==== Response + +The returned +{response}+ indicates if the put auto follow pattern request was received. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-response] +-------------------------------------------------- +<1> Whether or not the put auto follow pattern request was acknowledged. + +include::../execution.asciidoc[] diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index f4e654bf485..cea713a8dd3 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -458,11 +458,13 @@ The Java High Level REST Client supports the following CCR APIs: * <<{upid}-ccr-pause-follow>> * <<{upid}-ccr-resume-follow>> * <<{upid}-ccr-unfollow>> +* <<{upid}-ccr-put-auto-follow-pattern>> include::ccr/put_follow.asciidoc[] include::ccr/pause_follow.asciidoc[] include::ccr/resume_follow.asciidoc[] include::ccr/unfollow.asciidoc[] +include::ccr/put_auto_follow_pattern.asciidoc[] == Index Lifecycle Management APIs