Browse Source

Add remote info to the HLRC (#49657)

Relates to #47678
j-bean 5 years ago
parent
commit
fa1a7c57b8
23 changed files with 770 additions and 74 deletions
  1. 31 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java
  2. 5 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterRequestConverters.java
  3. 75 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/cluster/ProxyModeInfo.java
  4. 137 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/cluster/RemoteConnectionInfo.java
  5. 28 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/cluster/RemoteInfoRequest.java
  6. 59 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/cluster/RemoteInfoResponse.java
  7. 76 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/cluster/SniffModeInfo.java
  8. 1 30
      client/rest-high-level/src/test/java/org/elasticsearch/client/CCRIT.java
  9. 45 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java
  10. 10 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterRequestConvertersTests.java
  11. 37 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java
  12. 4 6
      client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java
  13. 111 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/cluster/RemoteInfoResponseTests.java
  14. 2 24
      client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CCRDocumentationIT.java
  15. 60 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java
  16. 0 8
      client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java
  17. 32 0
      docs/java-rest/high-level/cluster/remote_info.asciidoc
  18. 2 0
      docs/java-rest/high-level/supported-apis.asciidoc
  19. 1 1
      server/src/main/java/org/elasticsearch/action/admin/cluster/remote/RemoteInfoResponse.java
  20. 14 2
      server/src/main/java/org/elasticsearch/transport/ProxyConnectionStrategy.java
  21. 13 1
      server/src/main/java/org/elasticsearch/transport/RemoteConnectionInfo.java
  22. 14 2
      server/src/main/java/org/elasticsearch/transport/SniffConnectionStrategy.java
  23. 13 0
      test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java

+ 31 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java

@@ -26,6 +26,8 @@ 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.client.cluster.RemoteInfoRequest;
+import org.elasticsearch.client.cluster.RemoteInfoResponse;
 import org.elasticsearch.rest.RestStatus;
 
 import java.io.IOException;
@@ -138,4 +140,33 @@ public final class ClusterClient {
         return restHighLevelClient.performRequestAsyncAndParseEntity(healthRequest, ClusterRequestConverters::clusterHealth, options,
                 ClusterHealthResponse::fromXContent, listener, singleton(RestStatus.REQUEST_TIMEOUT.getStatus()));
     }
+
+    /**
+     * Get the remote cluster information using the Remote cluster info API.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-remote-info.html"> Remote cluster info
+     * API on elastic.co</a>
+     * @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 RemoteInfoResponse remoteInfo(RemoteInfoRequest request, RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request, ClusterRequestConverters::remoteInfo, options,
+                RemoteInfoResponse::fromXContent, singleton(RestStatus.REQUEST_TIMEOUT.getStatus()));
+    }
+
+    /**
+     * Asynchronously get remote cluster information using the Remote cluster info API.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-remote-info.html"> Remote cluster info
+     * API on elastic.co</a>
+     * @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 remoteInfoAsync(RemoteInfoRequest request, RequestOptions options,
+                                       ActionListener<RemoteInfoResponse> listener) {
+        return restHighLevelClient.performRequestAsyncAndParseEntity(request, ClusterRequestConverters::remoteInfo, options,
+                RemoteInfoResponse::fromXContent, listener, singleton(RestStatus.REQUEST_TIMEOUT.getStatus()));
+    }
 }

+ 5 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterRequestConverters.java

@@ -25,6 +25,7 @@ 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.common.Strings;
 
 import java.io.IOException;
@@ -76,4 +77,8 @@ final class ClusterRequestConverters {
         request.addParameters(params.asMap());
         return request;
     }
+
+    static Request remoteInfo(RemoteInfoRequest remoteInfoRequest) {
+        return new Request(HttpGet.METHOD_NAME, "/_remote/info");
+    }
 }

+ 75 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/cluster/ProxyModeInfo.java

@@ -0,0 +1,75 @@
+/*
+ * 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.cluster;
+
+import java.util.Objects;
+
+public class ProxyModeInfo implements RemoteConnectionInfo.ModeInfo {
+    static final String NAME = "proxy";
+    static final String ADDRESS = "address";
+    static final String NUM_SOCKETS_CONNECTED = "num_sockets_connected";
+    static final String MAX_SOCKET_CONNECTIONS = "max_socket_connections";
+    private final String address;
+    private final int maxSocketConnections;
+    private final int numSocketsConnected;
+
+    ProxyModeInfo(String address, int maxSocketConnections, int numSocketsConnected) {
+        this.address = address;
+        this.maxSocketConnections = maxSocketConnections;
+        this.numSocketsConnected = numSocketsConnected;
+    }
+
+    @Override
+    public boolean isConnected() {
+        return numSocketsConnected > 0;
+    }
+
+    @Override
+    public String modeName() {
+        return NAME;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public int getMaxSocketConnections() {
+        return maxSocketConnections;
+    }
+
+    public int getNumSocketsConnected() {
+        return numSocketsConnected;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ProxyModeInfo otherProxy = (ProxyModeInfo) o;
+        return maxSocketConnections == otherProxy.maxSocketConnections &&
+                numSocketsConnected == otherProxy.numSocketsConnected &&
+                Objects.equals(address, otherProxy.address);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(address, maxSocketConnections, numSocketsConnected);
+    }
+}

+ 137 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/cluster/RemoteConnectionInfo.java

@@ -0,0 +1,137 @@
+/*
+ * 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.cluster;
+
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
+
+/**
+ * This class encapsulates all remote cluster information to be rendered on
+ * {@code _remote/info} requests.
+ */
+public final class RemoteConnectionInfo {
+    private static final String CONNECTED = "connected";
+    private static final String MODE = "mode";
+    private static final String INITIAL_CONNECT_TIMEOUT = "initial_connect_timeout";
+    private static final String SKIP_UNAVAILABLE = "skip_unavailable";
+
+    @SuppressWarnings("unchecked")
+    private static final ConstructingObjectParser<RemoteConnectionInfo, String> PARSER = new ConstructingObjectParser<>(
+            "RemoteConnectionInfoObjectParser",
+            false,
+            (args, clusterAlias) -> {
+                String mode = (String) args[1];
+                ModeInfo modeInfo;
+                if (mode.equals(ProxyModeInfo.NAME)) {
+                    modeInfo = new ProxyModeInfo((String) args[4], (int) args[5], (int) args[6]);
+                } else if (mode.equals(SniffModeInfo.NAME)) {
+                    modeInfo = new SniffModeInfo((List<String>) args[7], (int) args[8], (int) args[9]);
+                } else {
+                    throw new IllegalArgumentException("mode cannot be " + mode);
+                }
+                return new RemoteConnectionInfo(clusterAlias,
+                        modeInfo,
+                        TimeValue.parseTimeValue((String) args[2], INITIAL_CONNECT_TIMEOUT),
+                        (boolean) args[3]);
+            });
+
+    static {
+        PARSER.declareBoolean(constructorArg(), new ParseField(CONNECTED));
+        PARSER.declareString(constructorArg(), new ParseField(MODE));
+        PARSER.declareString(constructorArg(), new ParseField(INITIAL_CONNECT_TIMEOUT));
+        PARSER.declareBoolean(constructorArg(), new ParseField(SKIP_UNAVAILABLE));
+
+        PARSER.declareString(optionalConstructorArg(), new ParseField(ProxyModeInfo.ADDRESS));
+        PARSER.declareInt(optionalConstructorArg(), new ParseField(ProxyModeInfo.MAX_SOCKET_CONNECTIONS));
+        PARSER.declareInt(optionalConstructorArg(), new ParseField(ProxyModeInfo.NUM_SOCKETS_CONNECTED));
+
+        PARSER.declareStringArray(optionalConstructorArg(), new ParseField(SniffModeInfo.SEEDS));
+        PARSER.declareInt(optionalConstructorArg(), new ParseField(SniffModeInfo.MAX_CONNECTIONS_PER_CLUSTER));
+        PARSER.declareInt(optionalConstructorArg(), new ParseField(SniffModeInfo.NUM_NODES_CONNECTED));
+    }
+
+    final ModeInfo modeInfo;
+    final TimeValue initialConnectionTimeout;
+    final String clusterAlias;
+    final boolean skipUnavailable;
+
+    RemoteConnectionInfo(String clusterAlias, ModeInfo modeInfo, TimeValue initialConnectionTimeout, boolean skipUnavailable) {
+        this.clusterAlias = clusterAlias;
+        this.modeInfo = modeInfo;
+        this.initialConnectionTimeout = initialConnectionTimeout;
+        this.skipUnavailable = skipUnavailable;
+    }
+
+    public boolean isConnected() {
+        return modeInfo.isConnected();
+    }
+
+    public String getClusterAlias() {
+        return clusterAlias;
+    }
+
+    public ModeInfo getModeInfo() {
+        return modeInfo;
+    }
+
+    public TimeValue getInitialConnectionTimeout() {
+        return initialConnectionTimeout;
+    }
+
+    public boolean isSkipUnavailable() {
+        return skipUnavailable;
+    }
+
+    public static RemoteConnectionInfo fromXContent(XContentParser parser, String clusterAlias) throws IOException {
+        return PARSER.parse(parser, clusterAlias);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        RemoteConnectionInfo that = (RemoteConnectionInfo) o;
+        return skipUnavailable == that.skipUnavailable &&
+                Objects.equals(modeInfo, that.modeInfo) &&
+                Objects.equals(initialConnectionTimeout, that.initialConnectionTimeout) &&
+                Objects.equals(clusterAlias, that.clusterAlias);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(modeInfo, initialConnectionTimeout, clusterAlias, skipUnavailable);
+    }
+
+    public interface ModeInfo {
+
+        boolean isConnected();
+
+        String modeName();
+    }
+}

+ 28 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/cluster/RemoteInfoRequest.java

@@ -0,0 +1,28 @@
+/*
+ * 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.cluster;
+
+import org.elasticsearch.client.Validatable;
+
+/**
+ * The request object used by the Remote cluster info API.
+ */
+public final class RemoteInfoRequest implements Validatable {
+
+}

+ 59 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/cluster/RemoteInfoResponse.java

@@ -0,0 +1,59 @@
+/*
+ * 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.cluster;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+
+/**
+ * A response to _remote/info API request.
+ */
+public final class RemoteInfoResponse {
+
+    private List<RemoteConnectionInfo> infos;
+
+    RemoteInfoResponse(Collection<RemoteConnectionInfo> infos) {
+        this.infos = List.copyOf(infos);
+    }
+
+    public List<RemoteConnectionInfo> getInfos() {
+        return infos;
+    }
+
+    public static RemoteInfoResponse fromXContent(XContentParser parser) throws IOException {
+        ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
+
+        List<RemoteConnectionInfo> infos = new ArrayList<>();
+
+        XContentParser.Token token;
+        while ((token = parser.nextToken()) == XContentParser.Token.FIELD_NAME) {
+            String clusterAlias = parser.currentName();
+            RemoteConnectionInfo info = RemoteConnectionInfo.fromXContent(parser, clusterAlias);
+            infos.add(info);
+        }
+        ensureExpectedToken(XContentParser.Token.END_OBJECT, token, parser::getTokenLocation);
+        return new RemoteInfoResponse(infos);
+    }
+}

+ 76 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/cluster/SniffModeInfo.java

@@ -0,0 +1,76 @@
+/*
+ * 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.cluster;
+
+import java.util.List;
+import java.util.Objects;
+
+public class SniffModeInfo implements RemoteConnectionInfo.ModeInfo {
+    public static final String NAME = "sniff";
+    static final String SEEDS = "seeds";
+    static final String NUM_NODES_CONNECTED = "num_nodes_connected";
+    static final String MAX_CONNECTIONS_PER_CLUSTER = "max_connections_per_cluster";
+    final List<String> seedNodes;
+    final int maxConnectionsPerCluster;
+    final int numNodesConnected;
+
+    SniffModeInfo(List<String> seedNodes, int maxConnectionsPerCluster, int numNodesConnected) {
+        this.seedNodes = seedNodes;
+        this.maxConnectionsPerCluster = maxConnectionsPerCluster;
+        this.numNodesConnected = numNodesConnected;
+    }
+
+    @Override
+    public boolean isConnected() {
+        return numNodesConnected > 0;
+    }
+
+    @Override
+    public String modeName() {
+        return NAME;
+    }
+
+    public List<String> getSeedNodes() {
+        return seedNodes;
+    }
+
+    public int getMaxConnectionsPerCluster() {
+        return maxConnectionsPerCluster;
+    }
+
+    public int getNumNodesConnected() {
+        return numNodesConnected;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SniffModeInfo sniff = (SniffModeInfo) o;
+        return maxConnectionsPerCluster == sniff.maxConnectionsPerCluster &&
+                numNodesConnected == sniff.numNodesConnected &&
+                Objects.equals(seedNodes, sniff.seedNodes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(seedNodes, maxConnectionsPerCluster, numNodesConnected);
+    }
+}

+ 1 - 30
client/rest-high-level/src/test/java/org/elasticsearch/client/CCRIT.java

@@ -19,10 +19,7 @@
 
 package org.elasticsearch.client;
 
-import org.apache.http.util.EntityUtils;
 import org.apache.logging.log4j.message.ParameterizedMessage;
-import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
-import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse;
 import org.elasticsearch.action.index.IndexRequest;
 import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.action.search.SearchResponse;
@@ -51,9 +48,7 @@ import org.elasticsearch.client.core.BroadcastResponse;
 import org.elasticsearch.client.indices.CloseIndexRequest;
 import org.elasticsearch.client.indices.CreateIndexRequest;
 import org.elasticsearch.client.indices.CreateIndexResponse;
-import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.common.xcontent.XContentType;
-import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.index.seqno.ReplicationTracker;
 import org.elasticsearch.test.rest.yaml.ObjectPath;
 import org.junit.Before;
@@ -74,27 +69,7 @@ public class CCRIT extends ESRestHighLevelClientTestCase {
 
     @Before
     public void setupRemoteClusterConfig() throws Exception {
-        // Configure local cluster as remote cluster:
-        // TODO: replace with nodes info highlevel rest client code when it is available:
-        final Request request = new Request("GET", "/_nodes");
-        Map<?, ?> nodesResponse = (Map<?, ?>) toMap(client().performRequest(request)).get("nodes");
-        // Select node info of first node (we don't know the node id):
-        nodesResponse = (Map<?, ?>) nodesResponse.get(nodesResponse.keySet().iterator().next());
-        String transportAddress = (String) nodesResponse.get("transport_address");
-
-        ClusterUpdateSettingsRequest updateSettingsRequest = new ClusterUpdateSettingsRequest();
-        updateSettingsRequest.transientSettings(Collections.singletonMap("cluster.remote.local_cluster.seeds", transportAddress));
-        ClusterUpdateSettingsResponse updateSettingsResponse =
-            highLevelClient().cluster().putSettings(updateSettingsRequest, RequestOptions.DEFAULT);
-        assertThat(updateSettingsResponse.isAcknowledged(), is(true));
-
-        assertBusy(() -> {
-            Map<?, ?> localConnection = (Map<?, ?>) toMap(client()
-                .performRequest(new Request("GET", "/_remote/info")))
-                .get("local_cluster");
-            assertThat(localConnection, notNullValue());
-            assertThat(localConnection.get("connected"), is(true));
-        });
+        setupRemoteClusterConfig("local_cluster");
     }
 
     public void testIndexFollowing() throws Exception {
@@ -311,8 +286,4 @@ public class CCRIT extends ESRestHighLevelClientTestCase {
         assertThat(pauseFollowResponse.isAcknowledged(), is(true));
     }
 
-    private static Map<String, Object> toMap(Response response) throws IOException {
-        return XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false);
-    }
-
 }

+ 45 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java

@@ -27,19 +27,27 @@ 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.client.cluster.RemoteConnectionInfo;
+import org.elasticsearch.client.cluster.RemoteInfoRequest;
+import org.elasticsearch.client.cluster.RemoteInfoResponse;
+import org.elasticsearch.client.cluster.SniffModeInfo;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.cluster.health.ClusterIndexHealth;
 import org.elasticsearch.cluster.health.ClusterShardHealth;
 import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.ByteSizeUnit;
+import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
 import org.elasticsearch.indices.recovery.RecoverySettings;
 import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.transport.RemoteClusterService;
+import org.elasticsearch.transport.SniffConnectionStrategy;
 
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import static java.util.Collections.emptyMap;
@@ -297,4 +305,41 @@ public class ClusterClientIT extends ESRestHighLevelClientTestCase {
         assertNoIndices(response);
     }
 
+    public void testRemoteInfo() throws Exception {
+        String clusterAlias = "local_cluster";
+        setupRemoteClusterConfig(clusterAlias);
+
+        ClusterGetSettingsRequest settingsRequest = new ClusterGetSettingsRequest();
+        settingsRequest.includeDefaults(true);
+        ClusterGetSettingsResponse settingsResponse = highLevelClient().cluster().getSettings(settingsRequest, RequestOptions.DEFAULT);
+
+        List<String> seeds = SniffConnectionStrategy.REMOTE_CLUSTER_SEEDS
+                .getConcreteSettingForNamespace(clusterAlias)
+                .get(settingsResponse.getTransientSettings());
+        int connectionsPerCluster = SniffConnectionStrategy.REMOTE_CONNECTIONS_PER_CLUSTER
+                .get(settingsResponse.getDefaultSettings());
+        TimeValue initialConnectionTimeout = RemoteClusterService.REMOTE_INITIAL_CONNECTION_TIMEOUT_SETTING
+                .get(settingsResponse.getDefaultSettings());
+        boolean skipUnavailable = RemoteClusterService.REMOTE_CLUSTER_SKIP_UNAVAILABLE
+                .getConcreteSettingForNamespace(clusterAlias)
+                .get(settingsResponse.getDefaultSettings());
+
+        RemoteInfoRequest request = new RemoteInfoRequest();
+        RemoteInfoResponse response = execute(request, highLevelClient().cluster()::remoteInfo,
+                highLevelClient().cluster()::remoteInfoAsync);
+
+        assertThat(response, notNullValue());
+        assertThat(response.getInfos().size(), equalTo(1));
+        RemoteConnectionInfo info = response.getInfos().get(0);
+        assertThat(info.getClusterAlias(), equalTo(clusterAlias));
+        assertThat(info.getInitialConnectionTimeout(), equalTo(initialConnectionTimeout));
+        assertThat(info.isSkipUnavailable(), equalTo(skipUnavailable));
+        assertThat(info.getModeInfo().modeName(), equalTo(SniffModeInfo.NAME));
+        assertThat(info.getModeInfo().isConnected(), equalTo(true));
+        SniffModeInfo sniffModeInfo = (SniffModeInfo) info.getModeInfo();
+        assertThat(sniffModeInfo.getMaxConnectionsPerCluster(), equalTo(connectionsPerCluster));
+        assertThat(sniffModeInfo.getNumNodesConnected(), equalTo(1));
+        assertThat(sniffModeInfo.getSeedNodes(), equalTo(seeds));
+    }
+
 }

+ 10 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterRequestConvertersTests.java

@@ -26,6 +26,7 @@ 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.action.support.master.AcknowledgedRequest;
+import org.elasticsearch.client.cluster.RemoteInfoRequest;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.common.Priority;
 import org.elasticsearch.test.ESTestCase;
@@ -37,6 +38,7 @@ import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
 
+import static java.util.Collections.emptyMap;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.Matchers.nullValue;
 
@@ -147,4 +149,12 @@ public class ClusterRequestConvertersTests extends ESTestCase {
         }
         Assert.assertThat(request.getParameters(), equalTo(expectedParams));
     }
+
+    public void testRemoteInfo() {
+        RemoteInfoRequest request = new RemoteInfoRequest();
+        Request expectedRequest = ClusterRequestConverters.remoteInfo(request);
+        assertEquals("/_remote/info", expectedRequest.getEndpoint());
+        assertEquals(HttpGet.METHOD_NAME, expectedRequest.getMethod());
+        assertEquals(emptyMap(), expectedRequest.getParameters());
+    }
 }

+ 37 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java

@@ -19,19 +19,25 @@
 
 package org.elasticsearch.client;
 
+import org.apache.http.util.EntityUtils;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
+import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse;
 import org.elasticsearch.action.ingest.PutPipelineRequest;
 import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.support.PlainActionFuture;
+import org.elasticsearch.client.cluster.RemoteInfoRequest;
+import org.elasticsearch.client.cluster.RemoteInfoResponse;
 import org.elasticsearch.client.indices.CreateIndexRequest;
 import org.elasticsearch.common.Booleans;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.concurrent.ThreadContext;
 import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.core.internal.io.IOUtils;
 import org.elasticsearch.ingest.Pipeline;
 import org.elasticsearch.search.SearchHit;
@@ -45,10 +51,15 @@ import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Collections;
+import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
+import static java.util.Collections.singletonMap;
 import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
 
 public abstract class ESRestHighLevelClientTestCase extends ESRestTestCase {
 
@@ -243,4 +254,30 @@ public abstract class ESRestHighLevelClientTestCase extends ESRestTestCase {
         );
         highLevelClient().indices().create(indexRequest, RequestOptions.DEFAULT);
     }
+
+    protected static void setupRemoteClusterConfig(String remoteClusterName) throws Exception {
+        // Configure local cluster as remote cluster:
+        // TODO: replace with nodes info highlevel rest client code when it is available:
+        final Request request = new Request("GET", "/_nodes");
+        Map<?, ?> nodesResponse = (Map<?, ?>) toMap(client().performRequest(request)).get("nodes");
+        // Select node info of first node (we don't know the node id):
+        nodesResponse = (Map<?, ?>) nodesResponse.get(nodesResponse.keySet().iterator().next());
+        String transportAddress = (String) nodesResponse.get("transport_address");
+
+        ClusterUpdateSettingsRequest updateSettingsRequest = new ClusterUpdateSettingsRequest();
+        updateSettingsRequest.transientSettings(singletonMap("cluster.remote." + remoteClusterName + ".seeds", transportAddress));
+        ClusterUpdateSettingsResponse updateSettingsResponse =
+                restHighLevelClient.cluster().putSettings(updateSettingsRequest, RequestOptions.DEFAULT);
+        assertThat(updateSettingsResponse.isAcknowledged(), is(true));
+
+        assertBusy(() -> {
+            RemoteInfoResponse response = highLevelClient().cluster().remoteInfo(new RemoteInfoRequest(), RequestOptions.DEFAULT);
+            assertThat(response, notNullValue());
+            assertThat(response.getInfos().size(), greaterThan(0));
+        });
+    }
+
+    protected static Map<String, Object> toMap(Response response) throws IOException {
+        return XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false);
+    }
 }

+ 4 - 6
client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java

@@ -59,8 +59,8 @@ import org.elasticsearch.client.ml.dataframe.DataFrameAnalysis;
 import org.elasticsearch.client.ml.dataframe.OutlierDetection;
 import org.elasticsearch.client.ml.dataframe.evaluation.classification.AccuracyMetric;
 import org.elasticsearch.client.ml.dataframe.evaluation.classification.Classification;
-import org.elasticsearch.client.ml.dataframe.evaluation.regression.MeanSquaredErrorMetric;
 import org.elasticsearch.client.ml.dataframe.evaluation.classification.MulticlassConfusionMatrixMetric;
+import org.elasticsearch.client.ml.dataframe.evaluation.regression.MeanSquaredErrorMetric;
 import org.elasticsearch.client.ml.dataframe.evaluation.regression.RSquaredMetric;
 import org.elasticsearch.client.ml.dataframe.evaluation.regression.Regression;
 import org.elasticsearch.client.ml.dataframe.evaluation.softclassification.AucRocMetric;
@@ -68,14 +68,14 @@ import org.elasticsearch.client.ml.dataframe.evaluation.softclassification.Binar
 import org.elasticsearch.client.ml.dataframe.evaluation.softclassification.ConfusionMatrixMetric;
 import org.elasticsearch.client.ml.dataframe.evaluation.softclassification.PrecisionMetric;
 import org.elasticsearch.client.ml.dataframe.evaluation.softclassification.RecallMetric;
+import org.elasticsearch.client.ml.inference.preprocessing.FrequencyEncoding;
+import org.elasticsearch.client.ml.inference.preprocessing.OneHotEncoding;
+import org.elasticsearch.client.ml.inference.preprocessing.TargetMeanEncoding;
 import org.elasticsearch.client.ml.inference.trainedmodel.ensemble.Ensemble;
 import org.elasticsearch.client.ml.inference.trainedmodel.ensemble.LogisticRegression;
 import org.elasticsearch.client.ml.inference.trainedmodel.ensemble.WeightedMode;
 import org.elasticsearch.client.ml.inference.trainedmodel.ensemble.WeightedSum;
 import org.elasticsearch.client.ml.inference.trainedmodel.tree.Tree;
-import org.elasticsearch.client.ml.inference.preprocessing.FrequencyEncoding;
-import org.elasticsearch.client.ml.inference.preprocessing.OneHotEncoding;
-import org.elasticsearch.client.ml.inference.preprocessing.TargetMeanEncoding;
 import org.elasticsearch.client.transform.transforms.SyncConfig;
 import org.elasticsearch.client.transform.transforms.TimeSyncConfig;
 import org.elasticsearch.common.CheckedFunction;
@@ -106,7 +106,6 @@ import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.test.InternalAggregationTestCase;
 import org.elasticsearch.test.rest.yaml.restspec.ClientYamlSuiteRestApi;
 import org.elasticsearch.test.rest.yaml.restspec.ClientYamlSuiteRestSpec;
-
 import org.hamcrest.Matchers;
 import org.junit.Before;
 
@@ -773,7 +772,6 @@ public class RestHighLevelClientTests extends ESTestCase {
     public void testApiNamingConventions() throws Exception {
         //this list should be empty once the high-level client is feature complete
         String[] notYetSupportedApi = new String[]{
-            "cluster.remote_info",
             "create",
             "get_script_context",
             "get_script_languages",

+ 111 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/cluster/RemoteInfoResponseTests.java

@@ -0,0 +1,111 @@
+/*
+ * 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.cluster;
+
+import org.elasticsearch.client.AbstractResponseTestCase;
+import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.transport.ProxyConnectionStrategy;
+import org.elasticsearch.transport.RemoteConnectionInfo;
+import org.elasticsearch.transport.SniffConnectionStrategy;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+import static org.hamcrest.Matchers.equalTo;
+
+public class RemoteInfoResponseTests extends AbstractResponseTestCase<org.elasticsearch.action.admin.cluster.remote.RemoteInfoResponse,
+        RemoteInfoResponse> {
+
+    @Override
+    protected org.elasticsearch.action.admin.cluster.remote.RemoteInfoResponse createServerTestInstance(XContentType xContentType) {
+        int numRemoteInfos = randomIntBetween(0, 8);
+        List<RemoteConnectionInfo> remoteInfos = new ArrayList<>();
+        for (int i = 0; i < numRemoteInfos; i++) {
+            remoteInfos.add(createRandomRemoteConnectionInfo());
+        }
+        return new org.elasticsearch.action.admin.cluster.remote.RemoteInfoResponse(remoteInfos);
+    }
+
+    @Override
+    protected RemoteInfoResponse doParseToClientInstance(XContentParser parser) throws IOException {
+        return RemoteInfoResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected void assertInstances(org.elasticsearch.action.admin.cluster.remote.RemoteInfoResponse serverTestInstance,
+                                   RemoteInfoResponse clientInstance) {
+        assertThat(clientInstance.getInfos().size(), equalTo(serverTestInstance.getInfos().size()));
+        Map<String, RemoteConnectionInfo> serverInfos = serverTestInstance.getInfos().stream()
+                .collect(toMap(RemoteConnectionInfo::getClusterAlias, identity()));
+        for (org.elasticsearch.client.cluster.RemoteConnectionInfo clientRemoteInfo : clientInstance.getInfos()) {
+            RemoteConnectionInfo serverRemoteInfo = serverInfos.get(clientRemoteInfo.getClusterAlias());
+            assertThat(clientRemoteInfo.getClusterAlias(), equalTo(serverRemoteInfo.getClusterAlias()));
+            assertThat(clientRemoteInfo.getInitialConnectionTimeout(), equalTo(serverRemoteInfo.getInitialConnectionTimeout()));
+            assertThat(clientRemoteInfo.isConnected(), equalTo(serverRemoteInfo.isConnected()));
+            assertThat(clientRemoteInfo.isSkipUnavailable(), equalTo(serverRemoteInfo.isSkipUnavailable()));
+            assertThat(clientRemoteInfo.getModeInfo().isConnected(), equalTo(serverRemoteInfo.getModeInfo().isConnected()));
+            assertThat(clientRemoteInfo.getModeInfo().modeName(), equalTo(serverRemoteInfo.getModeInfo().modeName()));
+            if (clientRemoteInfo.getModeInfo().modeName().equals(SniffModeInfo.NAME)) {
+                SniffModeInfo clientModeInfo =
+                        (SniffModeInfo) clientRemoteInfo.getModeInfo();
+                SniffConnectionStrategy.SniffModeInfo serverModeInfo =
+                        (SniffConnectionStrategy.SniffModeInfo) serverRemoteInfo.getModeInfo();
+                assertThat(clientModeInfo.getMaxConnectionsPerCluster(), equalTo(serverModeInfo.getMaxConnectionsPerCluster()));
+                assertThat(clientModeInfo.getNumNodesConnected(), equalTo(serverModeInfo.getNumNodesConnected()));
+                assertThat(clientModeInfo.getSeedNodes(), equalTo(serverModeInfo.getSeedNodes()));
+            } else if (clientRemoteInfo.getModeInfo().modeName().equals(ProxyModeInfo.NAME)) {
+                ProxyModeInfo clientModeInfo =
+                        (ProxyModeInfo) clientRemoteInfo.getModeInfo();
+                ProxyConnectionStrategy.ProxyModeInfo serverModeInfo =
+                        (ProxyConnectionStrategy.ProxyModeInfo) serverRemoteInfo.getModeInfo();
+                assertThat(clientModeInfo.getAddress(), equalTo(serverModeInfo.getAddress()));
+                assertThat(clientModeInfo.getMaxSocketConnections(), equalTo(serverModeInfo.getMaxSocketConnections()));
+                assertThat(clientModeInfo.getNumSocketsConnected(), equalTo(serverModeInfo.getNumSocketsConnected()));
+            } else {
+                fail("impossible case");
+            }
+        }
+    }
+
+    private static RemoteConnectionInfo createRandomRemoteConnectionInfo() {
+        RemoteConnectionInfo.ModeInfo modeInfo;
+        if (randomBoolean()) {
+            String address = randomAlphaOfLength(8);
+            int maxSocketConnections = randomInt(5);
+            int numSocketsConnected = randomInt(5);
+            modeInfo = new ProxyConnectionStrategy.ProxyModeInfo(address, maxSocketConnections, numSocketsConnected);
+        } else {
+            List<String> seedNodes = randomList(randomInt(8), () -> randomAlphaOfLength(8));
+            int maxConnectionsPerCluster = randomInt(5);
+            int numNodesConnected = randomInt(5);
+            modeInfo = new SniffConnectionStrategy.SniffModeInfo(seedNodes, maxConnectionsPerCluster, numNodesConnected);
+        }
+        String clusterAlias = randomAlphaOfLength(8);
+        TimeValue initialConnectionTimeout = TimeValue.parseTimeValue(randomTimeValue(), "randomInitialConnectionTimeout");
+        boolean skipUnavailable = randomBoolean();
+        return new RemoteConnectionInfo(clusterAlias, modeInfo, initialConnectionTimeout, skipUnavailable);
+    }
+}

+ 2 - 24
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CCRDocumentationIT.java

@@ -19,11 +19,8 @@
 
 package org.elasticsearch.client.documentation;
 
-import org.apache.http.util.EntityUtils;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.LatchedActionListener;
-import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
-import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
 import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.client.ESRestHighLevelClientTestCase;
@@ -57,8 +54,6 @@ import org.elasticsearch.client.core.BroadcastResponse;
 import org.elasticsearch.client.indices.CloseIndexRequest;
 import org.elasticsearch.client.indices.CreateIndexRequest;
 import org.elasticsearch.client.indices.CreateIndexResponse;
-import org.elasticsearch.common.xcontent.XContentHelper;
-import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.test.rest.yaml.ObjectPath;
 import org.junit.Before;
 
@@ -76,21 +71,8 @@ import static org.hamcrest.Matchers.is;
 public class CCRDocumentationIT extends ESRestHighLevelClientTestCase {
 
     @Before
-    public void setupRemoteClusterConfig() throws IOException {
-        RestHighLevelClient client = highLevelClient();
-        // Configure local cluster as remote cluster:
-        // TODO: replace with nodes info highlevel rest client code when it is available:
-        final Request request = new Request("GET", "/_nodes");
-        Map<?, ?> nodesResponse = (Map<?, ?>) toMap(client().performRequest(request)).get("nodes");
-        // Select node info of first node (we don't know the node id):
-        nodesResponse = (Map<?, ?>) nodesResponse.get(nodesResponse.keySet().iterator().next());
-        String transportAddress = (String) nodesResponse.get("transport_address");
-
-        ClusterUpdateSettingsRequest updateSettingsRequest = new ClusterUpdateSettingsRequest();
-        updateSettingsRequest.transientSettings(Collections.singletonMap("cluster.remote.local.seeds", transportAddress));
-        ClusterUpdateSettingsResponse updateSettingsResponse =
-            client.cluster().putSettings(updateSettingsRequest, RequestOptions.DEFAULT);
-        assertThat(updateSettingsResponse.isAcknowledged(), is(true));
+    public void setupRemoteClusterConfig() throws Exception {
+        setupRemoteClusterConfig("local");
     }
 
     public void testPutFollow() throws Exception {
@@ -985,8 +967,4 @@ public class CCRDocumentationIT extends ESRestHighLevelClientTestCase {
         }
     }
 
-    static Map<String, Object> toMap(Response response) throws IOException {
-        return XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false);
-    }
-
 }

+ 60 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java

@@ -31,6 +31,9 @@ import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.client.ESRestHighLevelClientTestCase;
 import org.elasticsearch.client.RequestOptions;
 import org.elasticsearch.client.RestHighLevelClient;
+import org.elasticsearch.client.cluster.RemoteConnectionInfo;
+import org.elasticsearch.client.cluster.RemoteInfoRequest;
+import org.elasticsearch.client.cluster.RemoteInfoResponse;
 import org.elasticsearch.client.indices.CreateIndexRequest;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.cluster.health.ClusterIndexHealth;
@@ -46,6 +49,7 @@ import org.elasticsearch.rest.RestStatus;
 
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -415,4 +419,60 @@ public class ClusterClientDocumentationIT extends ESRestHighLevelClientTestCase
             assertTrue(latch.await(30L, TimeUnit.SECONDS));
         }
     }
+
+    public void testRemoteInfo() throws Exception {
+        setupRemoteClusterConfig("local_cluster");
+
+        RestHighLevelClient client = highLevelClient();
+
+        // tag::remote-info-request
+        RemoteInfoRequest request = new RemoteInfoRequest();
+        // end::remote-info-request
+
+        // tag::remote-info-execute
+        RemoteInfoResponse response = client.cluster().remoteInfo(request, RequestOptions.DEFAULT); // <1>
+        // end::remote-info-execute
+
+        // tag::remote-info-response
+        List<RemoteConnectionInfo> infos = response.getInfos();
+        // end::remote-info-response
+
+        assertThat(infos.size(), greaterThan(0));
+    }
+
+    public void testRemoteInfoAsync() throws Exception {
+        setupRemoteClusterConfig("local_cluster");
+
+        RestHighLevelClient client = highLevelClient();
+
+        // tag::remote-info-request
+        RemoteInfoRequest request = new RemoteInfoRequest();
+        // end::remote-info-request
+
+
+        // tag::remote-info-execute-listener
+            ActionListener<RemoteInfoResponse> listener =
+                new ActionListener<>() {
+                    @Override
+                    public void onResponse(RemoteInfoResponse response) {
+                        // <1>
+                    }
+
+                    @Override
+                    public void onFailure(Exception e) {
+                        // <2>
+                    }
+                };
+            // end::remote-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::health-execute-async
+            client.cluster().remoteInfoAsync(request, RequestOptions.DEFAULT, listener); // <1>
+        // end::health-execute-async
+
+        assertTrue(latch.await(30L, TimeUnit.SECONDS));
+    }
 }

+ 0 - 8
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java

@@ -19,7 +19,6 @@
 
 package org.elasticsearch.client.documentation;
 
-import org.apache.http.util.EntityUtils;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.LatchedActionListener;
 import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
@@ -28,7 +27,6 @@ import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse
 import org.elasticsearch.action.admin.indices.alias.Alias;
 import org.elasticsearch.client.ESRestHighLevelClientTestCase;
 import org.elasticsearch.client.RequestOptions;
-import org.elasticsearch.client.Response;
 import org.elasticsearch.client.RestHighLevelClient;
 import org.elasticsearch.client.core.AcknowledgedResponse;
 import org.elasticsearch.client.ilm.DeleteAction;
@@ -78,8 +76,6 @@ import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.ByteSizeUnit;
 import org.elasticsearch.common.unit.ByteSizeValue;
 import org.elasticsearch.common.unit.TimeValue;
-import org.elasticsearch.common.xcontent.XContentHelper;
-import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.repositories.fs.FsRepository;
 import org.elasticsearch.snapshots.SnapshotInfo;
 import org.elasticsearch.snapshots.SnapshotState;
@@ -1210,8 +1206,4 @@ public class ILMDocumentationIT extends ESRestHighLevelClientTestCase {
         assertTrue(latch.await(30L, TimeUnit.SECONDS));
     }
 
-    static Map<String, Object> toMap(Response response) throws IOException {
-        return XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false);
-    }
-
 }

+ 32 - 0
docs/java-rest/high-level/cluster/remote_info.asciidoc

@@ -0,0 +1,32 @@
+--
+:api: remote-info
+:request: RemoteInfoRequest
+:response: RemoteInfoResponse
+--
+
+[id="{upid}-{api}"]
+=== Remote Cluster Info API
+
+The Remote cluster info API allows to get all of the configured remote cluster information.
+
+[id="{upid}-{api}-request"]
+==== Remote Cluster Info Request
+
+A +{request}+:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+There are no required parameters.
+
+==== Remote Cluster Info Response
+
+The returned +{response}+ allows to retrieve remote cluster information.
+It returns connection and endpoint information keyed by the configured remote cluster alias.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------

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

@@ -168,12 +168,14 @@ The Java High Level REST Client supports the following Cluster APIs:
 * <<java-rest-high-cluster-put-settings>>
 * <<java-rest-high-cluster-get-settings>>
 * <<java-rest-high-cluster-health>>
+* <<java-rest-high-cluster-remote-info>>
 
 :upid: {mainid}-cluster
 :doc-tests-file: {doc-tests}/ClusterClientDocumentationIT.java
 include::cluster/put_settings.asciidoc[]
 include::cluster/get_settings.asciidoc[]
 include::cluster/health.asciidoc[]
+include::cluster/remote_info.asciidoc[]
 
 == Ingest APIs
 The Java High Level REST Client supports the following Ingest APIs:

+ 1 - 1
server/src/main/java/org/elasticsearch/action/admin/cluster/remote/RemoteInfoResponse.java

@@ -39,7 +39,7 @@ public final class RemoteInfoResponse extends ActionResponse implements ToXConte
         infos = in.readList(RemoteConnectionInfo::new);
     }
 
-    RemoteInfoResponse(Collection<RemoteConnectionInfo> infos) {
+    public RemoteInfoResponse(Collection<RemoteConnectionInfo> infos) {
         this.infos = List.copyOf(infos);
     }
 

+ 14 - 2
server/src/main/java/org/elasticsearch/transport/ProxyConnectionStrategy.java

@@ -268,13 +268,13 @@ public class ProxyConnectionStrategy extends RemoteConnectionStrategy {
         return new TransportAddress(parseConfiguredAddress(address));
     }
 
-    static class ProxyModeInfo implements RemoteConnectionInfo.ModeInfo {
+    public static class ProxyModeInfo implements RemoteConnectionInfo.ModeInfo {
 
         private final String address;
         private final int maxSocketConnections;
         private final int numSocketsConnected;
 
-        ProxyModeInfo(String address, int maxSocketConnections, int numSocketsConnected) {
+        public ProxyModeInfo(String address, int maxSocketConnections, int numSocketsConnected) {
             this.address = address;
             this.maxSocketConnections = maxSocketConnections;
             this.numSocketsConnected = numSocketsConnected;
@@ -311,6 +311,18 @@ public class ProxyConnectionStrategy extends RemoteConnectionStrategy {
             return "proxy";
         }
 
+        public String getAddress() {
+            return address;
+        }
+
+        public int getMaxSocketConnections() {
+            return maxSocketConnections;
+        }
+
+        public int getNumSocketsConnected() {
+            return numSocketsConnected;
+        }
+
         @Override
         public RemoteConnectionStrategy.ConnectionStrategy modeType() {
             return RemoteConnectionStrategy.ConnectionStrategy.PROXY;

+ 13 - 1
server/src/main/java/org/elasticsearch/transport/RemoteConnectionInfo.java

@@ -43,7 +43,7 @@ public final class RemoteConnectionInfo implements ToXContentFragment, Writeable
     final String clusterAlias;
     final boolean skipUnavailable;
 
-    RemoteConnectionInfo(String clusterAlias, ModeInfo modeInfo, TimeValue initialConnectionTimeout, boolean skipUnavailable) {
+    public RemoteConnectionInfo(String clusterAlias, ModeInfo modeInfo, TimeValue initialConnectionTimeout, boolean skipUnavailable) {
         this.clusterAlias = clusterAlias;
         this.modeInfo = modeInfo;
         this.initialConnectionTimeout = initialConnectionTimeout;
@@ -77,6 +77,18 @@ public final class RemoteConnectionInfo implements ToXContentFragment, Writeable
         return clusterAlias;
     }
 
+    public ModeInfo getModeInfo() {
+        return modeInfo;
+    }
+
+    public TimeValue getInitialConnectionTimeout() {
+        return initialConnectionTimeout;
+    }
+
+    public boolean isSkipUnavailable() {
+        return skipUnavailable;
+    }
+
     @Override
     public void writeTo(StreamOutput out) throws IOException {
         // TODO: Change to 7.6 after backport

+ 14 - 2
server/src/main/java/org/elasticsearch/transport/SniffConnectionStrategy.java

@@ -465,13 +465,13 @@ public class SniffConnectionStrategy extends RemoteConnectionStrategy {
         return Objects.equals(oldProxy, newProxy) == false;
     }
 
-    static class SniffModeInfo implements RemoteConnectionInfo.ModeInfo {
+    public static class SniffModeInfo implements RemoteConnectionInfo.ModeInfo {
 
         final List<String> seedNodes;
         final int maxConnectionsPerCluster;
         final int numNodesConnected;
 
-        SniffModeInfo(List<String> seedNodes, int maxConnectionsPerCluster, int numNodesConnected) {
+        public SniffModeInfo(List<String> seedNodes, int maxConnectionsPerCluster, int numNodesConnected) {
             this.seedNodes = seedNodes;
             this.maxConnectionsPerCluster = maxConnectionsPerCluster;
             this.numNodesConnected = numNodesConnected;
@@ -512,6 +512,18 @@ public class SniffConnectionStrategy extends RemoteConnectionStrategy {
             return "sniff";
         }
 
+        public List<String> getSeedNodes() {
+            return seedNodes;
+        }
+
+        public int getMaxConnectionsPerCluster() {
+            return maxConnectionsPerCluster;
+        }
+
+        public int getNumNodesConnected() {
+            return numNodesConnected;
+        }
+
         @Override
         public RemoteConnectionStrategy.ConnectionStrategy modeType() {
             return RemoteConnectionStrategy.ConnectionStrategy.SNIFF;

+ 13 - 0
test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java

@@ -755,6 +755,19 @@ public abstract class ESTestCase extends LuceneTestCase {
         return array;
     }
 
+    public static <T> List<T> randomList(int maxListSize, Supplier<T> valueConstructor) {
+        return randomList(0, maxListSize, valueConstructor);
+    }
+
+    public static <T> List<T> randomList(int minListSize, int maxListSize, Supplier<T> valueConstructor) {
+        final int size = randomIntBetween(minListSize, maxListSize);
+        List<T> list = new ArrayList<>();
+        for (int i = 0; i < size; i++) {
+            list.add(valueConstructor.get());
+        }
+        return list;
+    }
+
 
     private static final String[] TIME_SUFFIXES = new String[]{"d", "h", "ms", "s", "m", "micros", "nanos"};