Pārlūkot izejas kodu

Add support for ccr follow info api to HLRC. (#39115)

This API was introduces after #33824 was closed.
Martijn van Groningen 6 gadi atpakaļ
vecāks
revīzija
1516132897

+ 44 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/CcrClient.java

@@ -23,6 +23,8 @@ import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.client.ccr.CcrStatsRequest;
 import org.elasticsearch.client.ccr.CcrStatsResponse;
 import org.elasticsearch.client.ccr.DeleteAutoFollowPatternRequest;
+import org.elasticsearch.client.ccr.FollowInfoRequest;
+import org.elasticsearch.client.ccr.FollowInfoResponse;
 import org.elasticsearch.client.ccr.FollowStatsRequest;
 import org.elasticsearch.client.ccr.FollowStatsResponse;
 import org.elasticsearch.client.ccr.GetAutoFollowPatternRequest;
@@ -452,4 +454,46 @@ public final class CcrClient {
         );
     }
 
+    /**
+     * Gets follow info for specific indices.
+     *
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-follow-info.html">
+     * the docs</a> 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 FollowInfoResponse getFollowInfo(FollowInfoRequest request, RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(
+            request,
+            CcrRequestConverters::getFollowInfo,
+            options,
+            FollowInfoResponse::fromXContent,
+            Collections.emptySet()
+        );
+    }
+
+    /**
+     * Asynchronously gets follow info for specific indices.
+     *
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-follow-info.html">
+     * the docs</a> 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 getFollowInfoAsync(FollowInfoRequest request,
+                                   RequestOptions options,
+                                   ActionListener<FollowInfoResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(
+            request,
+            CcrRequestConverters::getFollowInfo,
+            options,
+            FollowInfoResponse::fromXContent,
+            listener,
+            Collections.emptySet()
+        );
+    }
 }

+ 9 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/CcrRequestConverters.java

@@ -25,6 +25,7 @@ import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
 import org.elasticsearch.client.ccr.CcrStatsRequest;
 import org.elasticsearch.client.ccr.DeleteAutoFollowPatternRequest;
+import org.elasticsearch.client.ccr.FollowInfoRequest;
 import org.elasticsearch.client.ccr.FollowStatsRequest;
 import org.elasticsearch.client.ccr.GetAutoFollowPatternRequest;
 import org.elasticsearch.client.ccr.PauseFollowRequest;
@@ -119,4 +120,12 @@ final class CcrRequestConverters {
         return new Request(HttpGet.METHOD_NAME, endpoint);
     }
 
+    static Request getFollowInfo(FollowInfoRequest followInfoRequest) {
+        String endpoint = new RequestConverters.EndpointBuilder()
+            .addPathPart(followInfoRequest.getFollowerIndex())
+            .addPathPartAsIs("_ccr", "info")
+            .build();
+        return new Request(HttpGet.METHOD_NAME, endpoint);
+    }
+
 }

+ 40 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ccr/FollowConfig.java

@@ -22,8 +22,10 @@ package org.elasticsearch.client.ccr;
 import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.unit.ByteSizeValue;
 import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
 import java.util.Objects;
@@ -41,6 +43,44 @@ public class FollowConfig {
     static final ParseField MAX_RETRY_DELAY_FIELD = new ParseField("max_retry_delay");
     static final ParseField READ_POLL_TIMEOUT = new ParseField("read_poll_timeout");
 
+    private static final ObjectParser<FollowConfig, Void> PARSER = new ObjectParser<>(
+        "follow_config",
+        true,
+        FollowConfig::new);
+
+    static {
+        PARSER.declareInt(FollowConfig::setMaxReadRequestOperationCount, MAX_READ_REQUEST_OPERATION_COUNT);
+        PARSER.declareInt(FollowConfig::setMaxOutstandingReadRequests, MAX_OUTSTANDING_READ_REQUESTS);
+        PARSER.declareField(
+            FollowConfig::setMaxReadRequestSize,
+            (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), MAX_READ_REQUEST_SIZE.getPreferredName()),
+            MAX_READ_REQUEST_SIZE,
+            ObjectParser.ValueType.STRING);
+        PARSER.declareInt(FollowConfig::setMaxWriteRequestOperationCount, MAX_WRITE_REQUEST_OPERATION_COUNT);
+        PARSER.declareField(
+            FollowConfig::setMaxWriteRequestSize,
+            (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), MAX_WRITE_REQUEST_SIZE.getPreferredName()),
+            MAX_WRITE_REQUEST_SIZE,
+            ObjectParser.ValueType.STRING);
+        PARSER.declareInt(FollowConfig::setMaxOutstandingWriteRequests, MAX_OUTSTANDING_WRITE_REQUESTS);
+        PARSER.declareInt(FollowConfig::setMaxWriteBufferCount, MAX_WRITE_BUFFER_COUNT);
+        PARSER.declareField(
+            FollowConfig::setMaxWriteBufferSize,
+            (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), MAX_WRITE_BUFFER_SIZE.getPreferredName()),
+            MAX_WRITE_BUFFER_SIZE,
+            ObjectParser.ValueType.STRING);
+        PARSER.declareField(FollowConfig::setMaxRetryDelay,
+            (p, c) -> TimeValue.parseTimeValue(p.text(), MAX_RETRY_DELAY_FIELD.getPreferredName()),
+            MAX_RETRY_DELAY_FIELD, ObjectParser.ValueType.STRING);
+        PARSER.declareField(FollowConfig::setReadPollTimeout,
+            (p, c) -> TimeValue.parseTimeValue(p.text(), READ_POLL_TIMEOUT.getPreferredName()),
+            READ_POLL_TIMEOUT, ObjectParser.ValueType.STRING);
+    }
+
+    static FollowConfig fromXContent(XContentParser parser) {
+        return PARSER.apply(parser, null);
+    }
+
     private Integer maxReadRequestOperationCount;
     private Integer maxOutstandingReadRequests;
     private ByteSizeValue maxReadRequestSize;

+ 37 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ccr/FollowInfoRequest.java

@@ -0,0 +1,37 @@
+/*
+ * 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 java.util.Objects;
+
+public final class FollowInfoRequest implements Validatable {
+
+    private final String followerIndex;
+
+    public FollowInfoRequest(String followerIndex) {
+        this.followerIndex = Objects.requireNonNull(followerIndex);
+    }
+
+    public String getFollowerIndex() {
+        return followerIndex;
+    }
+}

+ 178 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ccr/FollowInfoResponse.java

@@ -0,0 +1,178 @@
+/*
+ * 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.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.util.List;
+import java.util.Objects;
+
+public final class FollowInfoResponse {
+
+    static final ParseField FOLLOWER_INDICES_FIELD = new ParseField("follower_indices");
+
+    private static final ConstructingObjectParser<FollowInfoResponse, Void> PARSER = new ConstructingObjectParser<>(
+        "indices",
+        true,
+        args -> {
+            @SuppressWarnings("unchecked")
+            List<FollowerInfo> infos = (List<FollowerInfo>) args[0];
+            return new FollowInfoResponse(infos);
+        });
+
+    static {
+        PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(), FollowerInfo.PARSER, FOLLOWER_INDICES_FIELD);
+    }
+
+    public static FollowInfoResponse fromXContent(XContentParser parser) {
+        return PARSER.apply(parser, null);
+    }
+
+    private final List<FollowerInfo> infos;
+
+    FollowInfoResponse(List<FollowerInfo> infos) {
+        this.infos = infos;
+    }
+
+    public List<FollowerInfo> getInfos() {
+        return infos;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        FollowInfoResponse that = (FollowInfoResponse) o;
+        return infos.equals(that.infos);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(infos);
+    }
+
+    public static final class FollowerInfo {
+
+        static final ParseField FOLLOWER_INDEX_FIELD = new ParseField("follower_index");
+        static final ParseField REMOTE_CLUSTER_FIELD = new ParseField("remote_cluster");
+        static final ParseField LEADER_INDEX_FIELD = new ParseField("leader_index");
+        static final ParseField STATUS_FIELD = new ParseField("status");
+        static final ParseField PARAMETERS_FIELD = new ParseField("parameters");
+
+        private static final ConstructingObjectParser<FollowerInfo, Void> PARSER = new ConstructingObjectParser<>(
+            "follower_info",
+            true,
+            args -> {
+                return new FollowerInfo((String) args[0], (String) args[1], (String) args[2],
+                    Status.fromString((String) args[3]), (FollowConfig) args[4]);
+            });
+
+        static {
+            PARSER.declareString(ConstructingObjectParser.constructorArg(), FOLLOWER_INDEX_FIELD);
+            PARSER.declareString(ConstructingObjectParser.constructorArg(), REMOTE_CLUSTER_FIELD);
+            PARSER.declareString(ConstructingObjectParser.constructorArg(), LEADER_INDEX_FIELD);
+            PARSER.declareString(ConstructingObjectParser.constructorArg(), STATUS_FIELD);
+            PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(),
+                (p, c) -> FollowConfig.fromXContent(p), PARAMETERS_FIELD);
+        }
+
+        private final String followerIndex;
+        private final String remoteCluster;
+        private final String leaderIndex;
+        private final Status status;
+        private final FollowConfig parameters;
+
+        FollowerInfo(String followerIndex, String remoteCluster, String leaderIndex, Status status,
+                            FollowConfig parameters) {
+            this.followerIndex = followerIndex;
+            this.remoteCluster = remoteCluster;
+            this.leaderIndex = leaderIndex;
+            this.status = status;
+            this.parameters = parameters;
+        }
+
+        public String getFollowerIndex() {
+            return followerIndex;
+        }
+
+        public String getRemoteCluster() {
+            return remoteCluster;
+        }
+
+        public String getLeaderIndex() {
+            return leaderIndex;
+        }
+
+        public Status getStatus() {
+            return status;
+        }
+
+        public FollowConfig getParameters() {
+            return parameters;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            FollowerInfo that = (FollowerInfo) o;
+            return Objects.equals(followerIndex, that.followerIndex) &&
+                Objects.equals(remoteCluster, that.remoteCluster) &&
+                Objects.equals(leaderIndex, that.leaderIndex) &&
+                status == that.status &&
+                Objects.equals(parameters, that.parameters);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(followerIndex, remoteCluster, leaderIndex, status, parameters);
+        }
+
+    }
+
+    public enum Status {
+
+        ACTIVE("active"),
+        PAUSED("paused");
+
+        private final String name;
+
+        Status(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public static Status fromString(String value) {
+            switch (value) {
+                case "active":
+                    return Status.ACTIVE;
+                case "paused":
+                    return Status.PAUSED;
+                default:
+                    throw new IllegalArgumentException("unexpected status value [" + value + "]");
+            }
+        }
+    }
+}

+ 22 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/CCRIT.java

@@ -32,6 +32,8 @@ import org.elasticsearch.action.support.WriteRequest;
 import org.elasticsearch.client.ccr.CcrStatsRequest;
 import org.elasticsearch.client.ccr.CcrStatsResponse;
 import org.elasticsearch.client.ccr.DeleteAutoFollowPatternRequest;
+import org.elasticsearch.client.ccr.FollowInfoRequest;
+import org.elasticsearch.client.ccr.FollowInfoResponse;
 import org.elasticsearch.client.ccr.FollowStatsRequest;
 import org.elasticsearch.client.ccr.FollowStatsResponse;
 import org.elasticsearch.client.ccr.GetAutoFollowPatternRequest;
@@ -113,6 +115,15 @@ public class CCRIT extends ESRestHighLevelClientTestCase {
 
         try {
             assertBusy(() -> {
+                FollowInfoRequest followInfoRequest = new FollowInfoRequest("follower");
+                FollowInfoResponse followInfoResponse =
+                    execute(followInfoRequest, ccrClient::getFollowInfo, ccrClient::getFollowInfoAsync);
+                assertThat(followInfoResponse.getInfos().size(), equalTo(1));
+                assertThat(followInfoResponse.getInfos().get(0).getFollowerIndex(), equalTo("follower"));
+                assertThat(followInfoResponse.getInfos().get(0).getLeaderIndex(), equalTo("leader"));
+                assertThat(followInfoResponse.getInfos().get(0).getRemoteCluster(), equalTo("local_cluster"));
+                assertThat(followInfoResponse.getInfos().get(0).getStatus(), equalTo(FollowInfoResponse.Status.ACTIVE));
+
                 FollowStatsRequest followStatsRequest = new FollowStatsRequest("follower");
                 FollowStatsResponse followStatsResponse =
                     execute(followStatsRequest, ccrClient::getFollowStats, ccrClient::getFollowStatsAsync);
@@ -170,6 +181,17 @@ public class CCRIT extends ESRestHighLevelClientTestCase {
         pauseFollowResponse = execute(pauseFollowRequest, ccrClient::pauseFollow, ccrClient::pauseFollowAsync);
         assertThat(pauseFollowResponse.isAcknowledged(), is(true));
 
+        assertBusy(() -> {
+            FollowInfoRequest followInfoRequest = new FollowInfoRequest("follower");
+            FollowInfoResponse followInfoResponse =
+                execute(followInfoRequest, ccrClient::getFollowInfo, ccrClient::getFollowInfoAsync);
+            assertThat(followInfoResponse.getInfos().size(), equalTo(1));
+            assertThat(followInfoResponse.getInfos().get(0).getFollowerIndex(), equalTo("follower"));
+            assertThat(followInfoResponse.getInfos().get(0).getLeaderIndex(), equalTo("leader"));
+            assertThat(followInfoResponse.getInfos().get(0).getRemoteCluster(), equalTo("local_cluster"));
+            assertThat(followInfoResponse.getInfos().get(0).getStatus(), equalTo(FollowInfoResponse.Status.PAUSED));
+        });
+
         // Need to close index prior to unfollowing it:
         CloseIndexRequest closeIndexRequest = new CloseIndexRequest("follower");
         org.elasticsearch.action.support.master.AcknowledgedResponse closeIndexReponse =

+ 80 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ccr/FollowConfigTests.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.ccr;
+
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+
+import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester;
+
+public class FollowConfigTests extends ESTestCase {
+
+    public void testFromXContent() throws IOException {
+        xContentTester(this::createParser,
+            FollowConfigTests::createTestInstance,
+            (followConfig, xContentBuilder) -> {
+                xContentBuilder.startObject();
+                followConfig.toXContentFragment(xContentBuilder, ToXContent.EMPTY_PARAMS);
+                xContentBuilder.endObject();
+            },
+            FollowConfig::fromXContent)
+            .supportsUnknownFields(true)
+            .test();
+    }
+
+    static FollowConfig createTestInstance() {
+        FollowConfig followConfig = new FollowConfig();
+        if (randomBoolean()) {
+            followConfig.setMaxOutstandingReadRequests(randomIntBetween(0, Integer.MAX_VALUE));
+        }
+        if (randomBoolean()) {
+            followConfig.setMaxOutstandingWriteRequests(randomIntBetween(0, Integer.MAX_VALUE));
+        }
+        if (randomBoolean()) {
+            followConfig.setMaxReadRequestOperationCount(randomIntBetween(0, Integer.MAX_VALUE));
+        }
+        if (randomBoolean()) {
+            followConfig.setMaxReadRequestSize(new ByteSizeValue(randomNonNegativeLong()));
+        }
+        if (randomBoolean()) {
+            followConfig.setMaxWriteBufferCount(randomIntBetween(0, Integer.MAX_VALUE));
+        }
+        if (randomBoolean()) {
+            followConfig.setMaxWriteBufferSize(new ByteSizeValue(randomNonNegativeLong()));
+        }
+        if (randomBoolean()) {
+            followConfig.setMaxWriteRequestOperationCount(randomIntBetween(0, Integer.MAX_VALUE));
+        }
+        if (randomBoolean()) {
+            followConfig.setMaxWriteRequestSize(new ByteSizeValue(randomNonNegativeLong()));
+        }
+        if (randomBoolean()) {
+            followConfig.setMaxRetryDelay(new TimeValue(randomNonNegativeLong()));
+        }
+        if (randomBoolean()) {
+            followConfig.setReadPollTimeout(new TimeValue(randomNonNegativeLong()));
+        }
+        return followConfig;
+    }
+}

+ 77 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ccr/FollowInfoResponseTests.java

@@ -0,0 +1,77 @@
+/*
+ * 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.ccr.FollowInfoResponse.FollowerInfo;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester;
+
+public class FollowInfoResponseTests extends ESTestCase {
+
+    public void testFromXContent() throws IOException {
+        xContentTester(this::createParser,
+            FollowInfoResponseTests::createTestInstance,
+            FollowInfoResponseTests::toXContent,
+            FollowInfoResponse::fromXContent)
+            .supportsUnknownFields(true)
+            .test();
+    }
+
+    private static void toXContent(FollowInfoResponse response, XContentBuilder builder) throws IOException {
+        builder.startObject();
+        builder.startArray(FollowInfoResponse.FOLLOWER_INDICES_FIELD.getPreferredName());
+        for (FollowerInfo info : response.getInfos()) {
+            builder.startObject();
+            builder.field(FollowerInfo.FOLLOWER_INDEX_FIELD.getPreferredName(), info.getFollowerIndex());
+            builder.field(FollowerInfo.REMOTE_CLUSTER_FIELD.getPreferredName(), info.getRemoteCluster());
+            builder.field(FollowerInfo.LEADER_INDEX_FIELD.getPreferredName(), info.getLeaderIndex());
+            builder.field(FollowerInfo.STATUS_FIELD.getPreferredName(), info.getStatus().getName());
+            if (info.getParameters() != null) {
+                builder.startObject(FollowerInfo.PARAMETERS_FIELD.getPreferredName());
+                {
+                    info.getParameters().toXContentFragment(builder, ToXContent.EMPTY_PARAMS);
+                }
+                builder.endObject();
+            }
+            builder.endObject();
+        }
+        builder.endArray();
+        builder.endObject();
+    }
+
+    private static FollowInfoResponse createTestInstance() {
+        int numInfos = randomIntBetween(0, 64);
+        List<FollowerInfo> infos = new ArrayList<>(numInfos);
+        for (int i = 0; i < numInfos; i++) {
+            FollowInfoResponse.Status status = randomFrom(FollowInfoResponse.Status.values());
+            FollowConfig followConfig = randomBoolean() ? FollowConfigTests.createTestInstance() : null;
+            infos.add(new FollowerInfo(randomAlphaOfLength(4), randomAlphaOfLength(4), randomAlphaOfLength(4), status, followConfig));
+        }
+        return new FollowInfoResponse(infos);
+    }
+
+}

+ 71 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CCRDocumentationIT.java

@@ -36,6 +36,8 @@ import org.elasticsearch.client.ccr.AutoFollowStats;
 import org.elasticsearch.client.ccr.CcrStatsRequest;
 import org.elasticsearch.client.ccr.CcrStatsResponse;
 import org.elasticsearch.client.ccr.DeleteAutoFollowPatternRequest;
+import org.elasticsearch.client.ccr.FollowInfoRequest;
+import org.elasticsearch.client.ccr.FollowInfoResponse;
 import org.elasticsearch.client.ccr.FollowStatsRequest;
 import org.elasticsearch.client.ccr.FollowStatsResponse;
 import org.elasticsearch.client.ccr.GetAutoFollowPatternRequest;
@@ -58,6 +60,7 @@ import org.junit.Before;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -697,6 +700,74 @@ public class CCRDocumentationIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    public void testGetFollowInfos() throws Exception {
+        RestHighLevelClient client = highLevelClient();
+
+        {
+            // Create leader index:
+            CreateIndexRequest createIndexRequest = new CreateIndexRequest("leader");
+            createIndexRequest.settings(Collections.singletonMap("index.soft_deletes.enabled", true));
+            CreateIndexResponse response = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
+            assertThat(response.isAcknowledged(), is(true));
+        }
+        {
+            // Follow index, so that we can query for follow stats:
+            PutFollowRequest putFollowRequest = new PutFollowRequest("local", "leader", "follower", ActiveShardCount.ONE);
+            PutFollowResponse putFollowResponse = client.ccr().putFollow(putFollowRequest, RequestOptions.DEFAULT);
+            assertThat(putFollowResponse.isFollowIndexCreated(), is(true));
+            assertThat(putFollowResponse.isFollowIndexShardsAcked(), is(true));
+            assertThat(putFollowResponse.isIndexFollowingStarted(), is(true));
+        }
+
+        // tag::ccr-get-follow-info-request
+        FollowInfoRequest request =
+            new FollowInfoRequest("follower"); // <1>
+        // end::ccr-get-follow-info-request
+
+        // tag::ccr-get-follow-info-execute
+        FollowInfoResponse response = client.ccr()
+            .getFollowInfo(request, RequestOptions.DEFAULT);
+        // end::ccr-get-follow-info-execute
+
+        // tag::ccr-get-follow-info-response
+        List<FollowInfoResponse.FollowerInfo> infos =
+            response.getInfos(); // <1>
+        // end::ccr-get-follow-info-response
+
+        // tag::ccr-get-follow-info-execute-listener
+        ActionListener<FollowInfoResponse> listener =
+            new ActionListener<FollowInfoResponse>() {
+                @Override
+                public void onResponse(FollowInfoResponse response) { // <1>
+                    List<FollowInfoResponse.FollowerInfo> infos =
+                        response.getInfos();
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            };
+        // end::ccr-get-follow-info-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-get-follow-info-execute-async
+        client.ccr().getFollowInfoAsync(request,
+            RequestOptions.DEFAULT, listener); // <1>
+        // end::ccr-get-follow-info-execute-async
+
+        assertTrue(latch.await(30L, TimeUnit.SECONDS));
+
+        {
+            PauseFollowRequest pauseFollowRequest = new PauseFollowRequest("follower");
+            AcknowledgedResponse pauseFollowResponse =  client.ccr().pauseFollow(pauseFollowRequest, RequestOptions.DEFAULT);
+            assertThat(pauseFollowResponse.isAcknowledged(), is(true));
+        }
+    }
+
     static Map<String, Object> toMap(Response response) throws IOException {
         return XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false);
     }

+ 35 - 0
docs/java-rest/high-level/ccr/get_follow_info.asciidoc

@@ -0,0 +1,35 @@
+--
+:api: ccr-get-follow-info
+:request: FollowInfoRequest
+:response: FollowInfoResponse
+--
+
+[id="{upid}-{api}"]
+=== Get Follow Info API
+
+
+[id="{upid}-{api}-request"]
+==== Request
+
+The Get Follow Info API allows you to get follow information (parameters and status) for specific follower indices.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+<1> The follower index to get follow information for.
+
+[id="{upid}-{api}-response"]
+==== Response
+
+The returned +{response}+ includes follow information for the specified follower indices
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> The follow information for specified follower indices.
+
+include::../execution.asciidoc[]
+
+

+ 2 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -507,6 +507,7 @@ The Java High Level REST Client supports the following CCR APIs:
 * <<{upid}-ccr-get-auto-follow-pattern>>
 * <<{upid}-ccr-get-stats>>
 * <<{upid}-ccr-get-follow-stats>>
+* <<{upid}-ccr-get-follow-info>>
 
 include::ccr/put_follow.asciidoc[]
 include::ccr/pause_follow.asciidoc[]
@@ -517,6 +518,7 @@ include::ccr/delete_auto_follow_pattern.asciidoc[]
 include::ccr/get_auto_follow_pattern.asciidoc[]
 include::ccr/get_stats.asciidoc[]
 include::ccr/get_follow_stats.asciidoc[]
+include::ccr/get_follow_info.asciidoc[]
 
 == Index Lifecycle Management APIs