Skip to content

Commit 3b9965b

Browse files
committed
Add proxy support to RemoteClusterConnection (#33062)
This adds support for connecting to a remote cluster through a tcp proxy. A remote cluster can configured with an additional `search.remote.$clustername.proxy` setting. This proxy will be used to connect to remote nodes for every node connection established. We still try to sniff the remote clsuter and connect to nodes directly through the proxy which has to support some kind of routing to these nodes. Yet, this routing mechanism requires the handshake request to include some kind of information where to route to which is not yet implemented. The effort to use the hostname and an optional node attribute for routing is tracked in #32517 Closes #31840
1 parent e896cbd commit 3b9965b

File tree

10 files changed

+340
-64
lines changed

10 files changed

+340
-64
lines changed

server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java

+1
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ public void apply(Settings value, Settings current, Settings previous) {
274274
ElectMasterService.DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING,
275275
TransportSearchAction.SHARD_COUNT_LIMIT_SETTING,
276276
RemoteClusterAware.REMOTE_CLUSTERS_SEEDS,
277+
RemoteClusterAware.REMOTE_CLUSTERS_PROXY,
277278
RemoteClusterService.REMOTE_CLUSTER_SKIP_UNAVAILABLE,
278279
RemoteClusterService.REMOTE_CONNECTIONS_PER_CLUSTER,
279280
RemoteClusterService.REMOTE_INITIAL_CONNECTION_TIMEOUT_SETTING,

server/src/main/java/org/elasticsearch/common/settings/Setting.java

+4
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,10 @@ public static Setting<String> simpleString(String key, Property... properties) {
10091009
return new Setting<>(key, s -> "", Function.identity(), properties);
10101010
}
10111011

1012+
public static Setting<String> simpleString(String key, Function<String, String> parser, Property... properties) {
1013+
return new Setting<>(key, s -> "", parser, properties);
1014+
}
1015+
10121016
public static Setting<String> simpleString(String key, Setting<String> fallback, Property... properties) {
10131017
return new Setting<>(key, fallback, Function.identity(), properties);
10141018
}

server/src/main/java/org/elasticsearch/transport/RemoteClusterAware.java

+51-10
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@
1818
*/
1919
package org.elasticsearch.transport;
2020

21+
import java.util.EnumSet;
2122
import java.util.function.Supplier;
2223
import org.elasticsearch.Version;
2324
import org.elasticsearch.cluster.metadata.ClusterNameExpressionResolver;
2425
import org.elasticsearch.cluster.node.DiscoveryNode;
26+
import org.elasticsearch.common.Strings;
27+
import org.elasticsearch.common.UUIDs;
28+
import org.elasticsearch.common.collect.Tuple;
2529
import org.elasticsearch.common.component.AbstractComponent;
2630
import org.elasticsearch.common.settings.ClusterSettings;
2731
import org.elasticsearch.common.settings.Setting;
@@ -66,6 +70,22 @@ public abstract class RemoteClusterAware extends AbstractComponent {
6670
public static final char REMOTE_CLUSTER_INDEX_SEPARATOR = ':';
6771
public static final String LOCAL_CLUSTER_GROUP_KEY = "";
6872

73+
/**
74+
* A proxy address for the remote cluster.
75+
* NOTE: this settings is undocumented until we have at last one transport that supports passing
76+
* on the hostname via a mechanism like SNI.
77+
*/
78+
public static final Setting.AffixSetting<String> REMOTE_CLUSTERS_PROXY = Setting.affixKeySetting(
79+
"search.remote.",
80+
"proxy",
81+
key -> Setting.simpleString(key, s -> {
82+
if (Strings.hasLength(s)) {
83+
parsePort(s);
84+
}
85+
return s;
86+
}, Setting.Property.NodeScope, Setting.Property.Dynamic), REMOTE_CLUSTERS_SEEDS);
87+
88+
6989
protected final ClusterNameExpressionResolver clusterNameResolver;
7090

7191
/**
@@ -77,25 +97,42 @@ protected RemoteClusterAware(Settings settings) {
7797
this.clusterNameResolver = new ClusterNameExpressionResolver(settings);
7898
}
7999

80-
protected static Map<String, List<Supplier<DiscoveryNode>>> buildRemoteClustersSeeds(Settings settings) {
100+
/**
101+
* Builds the dynamic per-cluster config from the given settings. This is a map keyed by the cluster alias that points to a tuple
102+
* (ProxyAddresss, [SeedNodeSuppliers]). If a cluster is configured with a proxy address all seed nodes will point to
103+
* {@link TransportAddress#META_ADDRESS} and their configured address will be used as the hostname for the generated discovery node.
104+
*/
105+
protected static Map<String, Tuple<String, List<Supplier<DiscoveryNode>>>> buildRemoteClustersDynamicConfig(Settings settings) {
81106
Stream<Setting<List<String>>> allConcreteSettings = REMOTE_CLUSTERS_SEEDS.getAllConcreteSettings(settings);
82107
return allConcreteSettings.collect(
83108
Collectors.toMap(REMOTE_CLUSTERS_SEEDS::getNamespace, concreteSetting -> {
84109
String clusterName = REMOTE_CLUSTERS_SEEDS.getNamespace(concreteSetting);
85110
List<String> addresses = concreteSetting.get(settings);
111+
final boolean proxyMode = REMOTE_CLUSTERS_PROXY.getConcreteSettingForNamespace(clusterName).exists(settings);
86112
List<Supplier<DiscoveryNode>> nodes = new ArrayList<>(addresses.size());
87113
for (String address : addresses) {
88-
nodes.add(() -> {
89-
TransportAddress transportAddress = new TransportAddress(RemoteClusterAware.parseSeedAddress(address));
90-
return new DiscoveryNode(clusterName + "#" + transportAddress.toString(),
91-
transportAddress,
92-
Version.CURRENT.minimumCompatibilityVersion());
93-
});
114+
nodes.add(() -> buildSeedNode(clusterName, address, proxyMode));
94115
}
95-
return nodes;
116+
return new Tuple<>(REMOTE_CLUSTERS_PROXY.getConcreteSettingForNamespace(clusterName).get(settings), nodes);
96117
}));
97118
}
98119

120+
static DiscoveryNode buildSeedNode(String clusterName, String address, boolean proxyMode) {
121+
if (proxyMode) {
122+
TransportAddress transportAddress = new TransportAddress(TransportAddress.META_ADDRESS, 0);
123+
String hostName = address.substring(0, indexOfPortSeparator(address));
124+
return new DiscoveryNode("", clusterName + "#" + address, UUIDs.randomBase64UUID(), hostName, address,
125+
transportAddress, Collections
126+
.emptyMap(), EnumSet.allOf(DiscoveryNode.Role.class),
127+
Version.CURRENT.minimumCompatibilityVersion());
128+
} else {
129+
TransportAddress transportAddress = new TransportAddress(RemoteClusterAware.parseSeedAddress(address));
130+
return new DiscoveryNode(clusterName + "#" + transportAddress.toString(),
131+
transportAddress,
132+
Version.CURRENT.minimumCompatibilityVersion());
133+
}
134+
}
135+
99136
/**
100137
* Groups indices per cluster by splitting remote cluster-alias, index-name pairs on {@link #REMOTE_CLUSTER_INDEX_SEPARATOR}. All
101138
* indices per cluster are collected as a list in the returned map keyed by the cluster alias. Local indices are grouped under
@@ -138,20 +175,24 @@ public Map<String, List<String>> groupClusterIndices(String[] requestIndices, Pr
138175

139176
protected abstract Set<String> getRemoteClusterNames();
140177

178+
141179
/**
142180
* Subclasses must implement this to receive information about updated cluster aliases. If the given address list is
143181
* empty the cluster alias is unregistered and should be removed.
144182
*/
145-
protected abstract void updateRemoteCluster(String clusterAlias, List<String> addresses);
183+
protected abstract void updateRemoteCluster(String clusterAlias, List<String> addresses, String proxy);
146184

147185
/**
148186
* Registers this instance to listen to updates on the cluster settings.
149187
*/
150188
public void listenForUpdates(ClusterSettings clusterSettings) {
151-
clusterSettings.addAffixUpdateConsumer(RemoteClusterAware.REMOTE_CLUSTERS_SEEDS, this::updateRemoteCluster,
189+
clusterSettings.addAffixUpdateConsumer(RemoteClusterAware.REMOTE_CLUSTERS_PROXY,
190+
RemoteClusterAware.REMOTE_CLUSTERS_SEEDS,
191+
(key, value) -> updateRemoteCluster(key, value.v2(), value.v1()),
152192
(namespace, value) -> {});
153193
}
154194

195+
155196
protected static InetSocketAddress parseSeedAddress(String remoteHost) {
156197
String host = remoteHost.substring(0, indexOfPortSeparator(remoteHost));
157198
InetAddress hostAddress;

server/src/main/java/org/elasticsearch/transport/RemoteClusterConnection.java

+37-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.elasticsearch.transport;
2020

21+
import java.net.InetSocketAddress;
2122
import java.util.function.Supplier;
2223
import org.apache.logging.log4j.message.ParameterizedMessage;
2324
import org.apache.lucene.store.AlreadyClosedException;
@@ -42,6 +43,7 @@
4243
import org.elasticsearch.common.io.stream.StreamInput;
4344
import org.elasticsearch.common.settings.Settings;
4445
import org.elasticsearch.common.transport.TransportAddress;
46+
import org.elasticsearch.common.unit.TimeValue;
4547
import org.elasticsearch.common.util.CancellableThreads;
4648
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
4749
import org.elasticsearch.common.util.concurrent.ThreadContext;
@@ -92,6 +94,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo
9294
private final int maxNumRemoteConnections;
9395
private final Predicate<DiscoveryNode> nodePredicate;
9496
private final ThreadPool threadPool;
97+
private volatile String proxyAddress;
9598
private volatile List<Supplier<DiscoveryNode>> seedNodes;
9699
private volatile boolean skipUnavailable;
97100
private final ConnectHandler connectHandler;
@@ -110,6 +113,13 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo
110113
RemoteClusterConnection(Settings settings, String clusterAlias, List<Supplier<DiscoveryNode>> seedNodes,
111114
TransportService transportService, ConnectionManager connectionManager, int maxNumRemoteConnections,
112115
Predicate<DiscoveryNode> nodePredicate) {
116+
this(settings, clusterAlias, seedNodes, transportService, connectionManager, maxNumRemoteConnections, nodePredicate, null);
117+
}
118+
119+
RemoteClusterConnection(Settings settings, String clusterAlias, List<Supplier<DiscoveryNode>> seedNodes,
120+
TransportService transportService, ConnectionManager connectionManager, int maxNumRemoteConnections, Predicate<DiscoveryNode>
121+
nodePredicate,
122+
String proxyAddress) {
113123
super(settings);
114124
this.transportService = transportService;
115125
this.maxNumRemoteConnections = maxNumRemoteConnections;
@@ -134,13 +144,26 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo
134144
connectionManager.addListener(this);
135145
// we register the transport service here as a listener to make sure we notify handlers on disconnect etc.
136146
connectionManager.addListener(transportService);
147+
this.proxyAddress = proxyAddress;
148+
}
149+
150+
private static DiscoveryNode maybeAddProxyAddress(String proxyAddress, DiscoveryNode node) {
151+
if (proxyAddress == null || proxyAddress.isEmpty()) {
152+
return node;
153+
} else {
154+
// resovle proxy address lazy here
155+
InetSocketAddress proxyInetAddress = RemoteClusterAware.parseSeedAddress(proxyAddress);
156+
return new DiscoveryNode(node.getName(), node.getId(), node.getEphemeralId(), node.getHostName(), node
157+
.getHostAddress(), new TransportAddress(proxyInetAddress), node.getAttributes(), node.getRoles(), node.getVersion());
158+
}
137159
}
138160

139161
/**
140162
* Updates the list of seed nodes for this cluster connection
141163
*/
142-
synchronized void updateSeedNodes(List<Supplier<DiscoveryNode>> seedNodes, ActionListener<Void> connectListener) {
164+
synchronized void updateSeedNodes(String proxyAddress, List<Supplier<DiscoveryNode>> seedNodes, ActionListener<Void> connectListener) {
143165
this.seedNodes = Collections.unmodifiableList(new ArrayList<>(seedNodes));
166+
this.proxyAddress = proxyAddress;
144167
connectHandler.connect(connectListener);
145168
}
146169

@@ -285,6 +308,7 @@ Transport.Connection getConnection(DiscoveryNode remoteClusterNode) {
285308
return new ProxyConnection(connection, remoteClusterNode);
286309
}
287310

311+
288312
static final class ProxyConnection implements Transport.Connection {
289313
private final Transport.Connection proxyConnection;
290314
private final DiscoveryNode targetNode;
@@ -465,7 +489,7 @@ private void collectRemoteNodes(Iterator<Supplier<DiscoveryNode>> seedNodes,
465489
try {
466490
if (seedNodes.hasNext()) {
467491
cancellableThreads.executeIO(() -> {
468-
final DiscoveryNode seedNode = seedNodes.next().get();
492+
final DiscoveryNode seedNode = maybeAddProxyAddress(proxyAddress, seedNodes.next().get());
469493
final TransportService.HandshakeResponse handshakeResponse;
470494
Transport.Connection connection = manager.openConnection(seedNode,
471495
ConnectionProfile.buildSingleChannelProfile(TransportRequestOptions.Type.REG, null, null));
@@ -480,7 +504,7 @@ private void collectRemoteNodes(Iterator<Supplier<DiscoveryNode>> seedNodes,
480504
throw ex;
481505
}
482506

483-
final DiscoveryNode handshakeNode = handshakeResponse.getDiscoveryNode();
507+
final DiscoveryNode handshakeNode = maybeAddProxyAddress(proxyAddress, handshakeResponse.getDiscoveryNode());
484508
if (nodePredicate.test(handshakeNode) && connectedNodes.size() < maxNumRemoteConnections) {
485509
manager.connectToNode(handshakeNode, remoteProfile, transportService.connectionValidator(handshakeNode));
486510
if (remoteClusterName.get() == null) {
@@ -587,7 +611,8 @@ public void handleResponse(ClusterStateResponse response) {
587611
cancellableThreads.executeIO(() -> {
588612
DiscoveryNodes nodes = response.getState().nodes();
589613
Iterable<DiscoveryNode> nodesIter = nodes.getNodes()::valuesIt;
590-
for (DiscoveryNode node : nodesIter) {
614+
for (DiscoveryNode n : nodesIter) {
615+
DiscoveryNode node = maybeAddProxyAddress(proxyAddress, n);
591616
if (nodePredicate.test(node) && connectedNodes.size() < maxNumRemoteConnections) {
592617
try {
593618
connectionManager.connectToNode(node, remoteProfile,
@@ -709,6 +734,14 @@ public String executor() {
709734
}
710735
}
711736

737+
RemoteConnectionInfo getLocalConnectionInfo() { // for tests
738+
List<TransportAddress> seedNodeAddresses = seedNodes.stream().map(node -> node.get().getAddress()).collect
739+
(Collectors.toList());
740+
TimeValue initialConnectionTimeout = RemoteClusterService.REMOTE_INITIAL_CONNECTION_TIMEOUT_SETTING.get(settings);
741+
return new RemoteConnectionInfo(clusterAlias, Collections.emptyList(),
742+
seedNodeAddresses, maxNumRemoteConnections, connectedNodes.size(), initialConnectionTimeout, skipUnavailable);
743+
}
744+
712745
int getNumNodesConnected() {
713746
return connectedNodes.size();
714747
}

0 commit comments

Comments
 (0)