Skip to content

Commit f82bb64

Browse files
NETWORKING: Make RemoteClusterConn. Lazy Resolve DNS (#32764)
* Lazy resolve DNS (i.e. `String` to `DiscoveryNode`) to not run into indefinitely caching lookup issues (provided the JVM dns cache is configured correctly as explained in https://www.elastic.co/guide/en/elasticsearch/reference/6.3/networkaddress-cache-ttl.html) * Changed `InetAddress` type to `String` for that higher up the stack * Passed down `Supplier<DiscoveryNode>` instead of outright `DiscoveryNode` from `RemoteClusterAware#buildRemoteClustersSeeds` on to lazy resolve DNS when the `DiscoveryNode` is actually used (could've also passed down the value of `clusterName = REMOTE_CLUSTERS_SEEDS.getNamespace(concreteSetting)` together with the `List<String>` of hosts, but this route seemed to introduce less duplication and resulted in a significantly smaller changeset). * Closes #28858
1 parent 532d552 commit f82bb64

File tree

6 files changed

+153
-102
lines changed

6 files changed

+153
-102
lines changed

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

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.elasticsearch.transport;
2020

21+
import java.util.function.Supplier;
2122
import org.elasticsearch.Version;
2223
import org.elasticsearch.cluster.metadata.ClusterNameExpressionResolver;
2324
import org.elasticsearch.cluster.node.DiscoveryNode;
@@ -48,9 +49,20 @@ public abstract class RemoteClusterAware extends AbstractComponent {
4849
/**
4950
* A list of initial seed nodes to discover eligible nodes from the remote cluster
5051
*/
51-
public static final Setting.AffixSetting<List<InetSocketAddress>> REMOTE_CLUSTERS_SEEDS = Setting.affixKeySetting("search.remote.",
52-
"seeds", (key) -> Setting.listSetting(key, Collections.emptyList(), RemoteClusterAware::parseSeedAddress,
53-
Setting.Property.NodeScope, Setting.Property.Dynamic));
52+
public static final Setting.AffixSetting<List<String>> REMOTE_CLUSTERS_SEEDS = Setting.affixKeySetting(
53+
"search.remote.",
54+
"seeds",
55+
key -> Setting.listSetting(
56+
key, Collections.emptyList(),
57+
s -> {
58+
// validate seed address
59+
parsePort(s);
60+
return s;
61+
},
62+
Setting.Property.NodeScope,
63+
Setting.Property.Dynamic
64+
)
65+
);
5466
public static final char REMOTE_CLUSTER_INDEX_SEPARATOR = ':';
5567
public static final String LOCAL_CLUSTER_GROUP_KEY = "";
5668

@@ -65,18 +77,20 @@ protected RemoteClusterAware(Settings settings) {
6577
this.clusterNameResolver = new ClusterNameExpressionResolver(settings);
6678
}
6779

68-
protected static Map<String, List<DiscoveryNode>> buildRemoteClustersSeeds(Settings settings) {
69-
Stream<Setting<List<InetSocketAddress>>> allConcreteSettings = REMOTE_CLUSTERS_SEEDS.getAllConcreteSettings(settings);
80+
protected static Map<String, List<Supplier<DiscoveryNode>>> buildRemoteClustersSeeds(Settings settings) {
81+
Stream<Setting<List<String>>> allConcreteSettings = REMOTE_CLUSTERS_SEEDS.getAllConcreteSettings(settings);
7082
return allConcreteSettings.collect(
7183
Collectors.toMap(REMOTE_CLUSTERS_SEEDS::getNamespace, concreteSetting -> {
7284
String clusterName = REMOTE_CLUSTERS_SEEDS.getNamespace(concreteSetting);
73-
List<DiscoveryNode> nodes = new ArrayList<>();
74-
for (InetSocketAddress address : concreteSetting.get(settings)) {
75-
TransportAddress transportAddress = new TransportAddress(address);
76-
DiscoveryNode node = new DiscoveryNode(clusterName + "#" + transportAddress.toString(),
77-
transportAddress,
78-
Version.CURRENT.minimumCompatibilityVersion());
79-
nodes.add(node);
85+
List<String> addresses = concreteSetting.get(settings);
86+
List<Supplier<DiscoveryNode>> nodes = new ArrayList<>(addresses.size());
87+
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+
});
8094
}
8195
return nodes;
8296
}));
@@ -128,7 +142,7 @@ public Map<String, List<String>> groupClusterIndices(String[] requestIndices, Pr
128142
* Subclasses must implement this to receive information about updated cluster aliases. If the given address list is
129143
* empty the cluster alias is unregistered and should be removed.
130144
*/
131-
protected abstract void updateRemoteCluster(String clusterAlias, List<InetSocketAddress> addresses);
145+
protected abstract void updateRemoteCluster(String clusterAlias, List<String> addresses);
132146

133147
/**
134148
* Registers this instance to listen to updates on the cluster settings.
@@ -138,27 +152,35 @@ public void listenForUpdates(ClusterSettings clusterSettings) {
138152
(namespace, value) -> {});
139153
}
140154

141-
private static InetSocketAddress parseSeedAddress(String remoteHost) {
142-
int portSeparator = remoteHost.lastIndexOf(':'); // in case we have a IPv6 address ie. [::1]:9300
143-
if (portSeparator == -1 || portSeparator == remoteHost.length()) {
144-
throw new IllegalArgumentException("remote hosts need to be configured as [host:port], found [" + remoteHost + "] instead");
145-
}
146-
String host = remoteHost.substring(0, portSeparator);
155+
protected static InetSocketAddress parseSeedAddress(String remoteHost) {
156+
String host = remoteHost.substring(0, indexOfPortSeparator(remoteHost));
147157
InetAddress hostAddress;
148158
try {
149159
hostAddress = InetAddress.getByName(host);
150160
} catch (UnknownHostException e) {
151161
throw new IllegalArgumentException("unknown host [" + host + "]", e);
152162
}
163+
return new InetSocketAddress(hostAddress, parsePort(remoteHost));
164+
}
165+
166+
private static int parsePort(String remoteHost) {
153167
try {
154-
int port = Integer.valueOf(remoteHost.substring(portSeparator + 1));
168+
int port = Integer.valueOf(remoteHost.substring(indexOfPortSeparator(remoteHost) + 1));
155169
if (port <= 0) {
156170
throw new IllegalArgumentException("port number must be > 0 but was: [" + port + "]");
157171
}
158-
return new InetSocketAddress(hostAddress, port);
172+
return port;
159173
} catch (NumberFormatException e) {
160-
throw new IllegalArgumentException("port must be a number", e);
174+
throw new IllegalArgumentException("failed to parse port", e);
175+
}
176+
}
177+
178+
private static int indexOfPortSeparator(String remoteHost) {
179+
int portSeparator = remoteHost.lastIndexOf(':'); // in case we have a IPv6 address ie. [::1]:9300
180+
if (portSeparator == -1 || portSeparator == remoteHost.length()) {
181+
throw new IllegalArgumentException("remote hosts need to be configured as [host:port], found [" + remoteHost + "] instead");
161182
}
183+
return portSeparator;
162184
}
163185

164186
public static String buildRemoteIndexName(String clusterAlias, String indexName) {

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.elasticsearch.transport;
2020

21+
import java.util.function.Supplier;
2122
import org.apache.logging.log4j.message.ParameterizedMessage;
2223
import org.apache.lucene.store.AlreadyClosedException;
2324
import org.apache.lucene.util.SetOnce;
@@ -84,7 +85,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo
8485
private final String clusterAlias;
8586
private final int maxNumRemoteConnections;
8687
private final Predicate<DiscoveryNode> nodePredicate;
87-
private volatile List<DiscoveryNode> seedNodes;
88+
private volatile List<Supplier<DiscoveryNode>> seedNodes;
8889
private volatile boolean skipUnavailable;
8990
private final ConnectHandler connectHandler;
9091
private SetOnce<ClusterName> remoteClusterName = new SetOnce<>();
@@ -99,7 +100,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo
99100
* @param maxNumRemoteConnections the maximum number of connections to the remote cluster
100101
* @param nodePredicate a predicate to filter eligible remote nodes to connect to
101102
*/
102-
RemoteClusterConnection(Settings settings, String clusterAlias, List<DiscoveryNode> seedNodes,
103+
RemoteClusterConnection(Settings settings, String clusterAlias, List<Supplier<DiscoveryNode>> seedNodes,
103104
TransportService transportService, int maxNumRemoteConnections, Predicate<DiscoveryNode> nodePredicate) {
104105
super(settings);
105106
this.localClusterName = ClusterName.CLUSTER_NAME_SETTING.get(settings);
@@ -127,7 +128,7 @@ final class RemoteClusterConnection extends AbstractComponent implements Transpo
127128
/**
128129
* Updates the list of seed nodes for this cluster connection
129130
*/
130-
synchronized void updateSeedNodes(List<DiscoveryNode> seedNodes, ActionListener<Void> connectListener) {
131+
synchronized void updateSeedNodes(List<Supplier<DiscoveryNode>> seedNodes, ActionListener<Void> connectListener) {
131132
this.seedNodes = Collections.unmodifiableList(new ArrayList<>(seedNodes));
132133
connectHandler.connect(connectListener);
133134
}
@@ -456,15 +457,15 @@ protected void doRun() {
456457
});
457458
}
458459

459-
void collectRemoteNodes(Iterator<DiscoveryNode> seedNodes,
460+
private void collectRemoteNodes(Iterator<Supplier<DiscoveryNode>> seedNodes,
460461
final TransportService transportService, ActionListener<Void> listener) {
461462
if (Thread.currentThread().isInterrupted()) {
462463
listener.onFailure(new InterruptedException("remote connect thread got interrupted"));
463464
}
464465
try {
465466
if (seedNodes.hasNext()) {
466467
cancellableThreads.executeIO(() -> {
467-
final DiscoveryNode seedNode = seedNodes.next();
468+
final DiscoveryNode seedNode = seedNodes.next().get();
468469
final TransportService.HandshakeResponse handshakeResponse;
469470
Transport.Connection connection = transportService.openConnection(seedNode,
470471
ConnectionProfile.buildSingleChannelProfile(TransportRequestOptions.Type.REG, null, null));
@@ -554,11 +555,11 @@ private class SniffClusterStateResponseHandler implements TransportResponseHandl
554555
private final TransportService transportService;
555556
private final Transport.Connection connection;
556557
private final ActionListener<Void> listener;
557-
private final Iterator<DiscoveryNode> seedNodes;
558+
private final Iterator<Supplier<DiscoveryNode>> seedNodes;
558559
private final CancellableThreads cancellableThreads;
559560

560561
SniffClusterStateResponseHandler(TransportService transportService, Transport.Connection connection,
561-
ActionListener<Void> listener, Iterator<DiscoveryNode> seedNodes,
562+
ActionListener<Void> listener, Iterator<Supplier<DiscoveryNode>> seedNodes,
562563
CancellableThreads cancellableThreads) {
563564
this.transportService = transportService;
564565
this.connection = connection;
@@ -651,7 +652,7 @@ void addConnectedNode(DiscoveryNode node) {
651652
* Get the information about remote nodes to be rendered on {@code _remote/info} requests.
652653
*/
653654
public RemoteConnectionInfo getConnectionInfo() {
654-
List<TransportAddress> seedNodeAddresses = seedNodes.stream().map(DiscoveryNode::getAddress).collect(Collectors.toList());
655+
List<TransportAddress> seedNodeAddresses = seedNodes.stream().map(node -> node.get().getAddress()).collect(Collectors.toList());
655656
TimeValue initialConnectionTimeout = RemoteClusterService.REMOTE_INITIAL_CONNECTION_TIMEOUT_SETTING.get(settings);
656657
return new RemoteConnectionInfo(clusterAlias, seedNodeAddresses, maxNumRemoteConnections, connectedNodes.size(),
657658
initialConnectionTimeout, skipUnavailable);

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.elasticsearch.transport;
2020

21+
import java.util.function.Supplier;
2122
import org.elasticsearch.Version;
2223
import org.elasticsearch.action.ActionListener;
2324
import org.elasticsearch.action.OriginalIndices;
@@ -40,7 +41,6 @@
4041

4142
import java.io.Closeable;
4243
import java.io.IOException;
43-
import java.net.InetSocketAddress;
4444
import java.util.Collections;
4545
import java.util.HashMap;
4646
import java.util.List;
@@ -115,7 +115,8 @@ public final class RemoteClusterService extends RemoteClusterAware implements Cl
115115
* @param seeds a cluster alias to discovery node mapping representing the remote clusters seeds nodes
116116
* @param connectionListener a listener invoked once every configured cluster has been connected to
117117
*/
118-
private synchronized void updateRemoteClusters(Map<String, List<DiscoveryNode>> seeds, ActionListener<Void> connectionListener) {
118+
private synchronized void updateRemoteClusters(Map<String, List<Supplier<DiscoveryNode>>> seeds,
119+
ActionListener<Void> connectionListener) {
119120
if (seeds.containsKey(LOCAL_CLUSTER_GROUP_KEY)) {
120121
throw new IllegalArgumentException("remote clusters must not have the empty string as its key");
121122
}
@@ -125,7 +126,7 @@ private synchronized void updateRemoteClusters(Map<String, List<DiscoveryNode>>
125126
} else {
126127
CountDown countDown = new CountDown(seeds.size());
127128
remoteClusters.putAll(this.remoteClusters);
128-
for (Map.Entry<String, List<DiscoveryNode>> entry : seeds.entrySet()) {
129+
for (Map.Entry<String, List<Supplier<DiscoveryNode>>> entry : seeds.entrySet()) {
129130
RemoteClusterConnection remote = this.remoteClusters.get(entry.getKey());
130131
if (entry.getValue().isEmpty()) { // with no seed nodes we just remove the connection
131132
try {
@@ -310,16 +311,17 @@ synchronized void updateSkipUnavailable(String clusterAlias, Boolean skipUnavail
310311
}
311312
}
312313

313-
protected void updateRemoteCluster(String clusterAlias, List<InetSocketAddress> addresses) {
314+
@Override
315+
protected void updateRemoteCluster(String clusterAlias, List<String> addresses) {
314316
updateRemoteCluster(clusterAlias, addresses, ActionListener.wrap((x) -> {}, (x) -> {}));
315317
}
316318

317319
void updateRemoteCluster(
318320
final String clusterAlias,
319-
final List<InetSocketAddress> addresses,
321+
final List<String> addresses,
320322
final ActionListener<Void> connectionListener) {
321-
final List<DiscoveryNode> nodes = addresses.stream().map(address -> {
322-
final TransportAddress transportAddress = new TransportAddress(address);
323+
final List<Supplier<DiscoveryNode>> nodes = addresses.stream().<Supplier<DiscoveryNode>>map(address -> () -> {
324+
final TransportAddress transportAddress = new TransportAddress(RemoteClusterAware.parseSeedAddress(address));
323325
final String id = clusterAlias + "#" + transportAddress.toString();
324326
final Version version = Version.CURRENT.minimumCompatibilityVersion();
325327
return new DiscoveryNode(id, transportAddress, version);
@@ -334,7 +336,7 @@ void updateRemoteCluster(
334336
void initializeRemoteClusters() {
335337
final TimeValue timeValue = REMOTE_INITIAL_CONNECTION_TIMEOUT_SETTING.get(settings);
336338
final PlainActionFuture<Void> future = new PlainActionFuture<>();
337-
Map<String, List<DiscoveryNode>> seeds = RemoteClusterAware.buildRemoteClustersSeeds(settings);
339+
Map<String, List<Supplier<DiscoveryNode>>> seeds = RemoteClusterAware.buildRemoteClustersSeeds(settings);
338340
updateRemoteClusters(seeds, future);
339341
try {
340342
future.get(timeValue.millis(), TimeUnit.MILLISECONDS);

0 commit comments

Comments
 (0)