Skip to content

Commit 23ce185

Browse files
committed
Connect to all DNS records of unresolved endpoint
netty bootstrap.connect uses only first address of unresolved InetSocketAddress. That causes 4.x to not even try to connect to other when it first one fails. This PR makes driver to resolve unresolved endpoint, instead of leaving to to netty. Making it possible to connect to any ip address from DNS contact endpoint.
1 parent d69bdd4 commit 23ce185

File tree

12 files changed

+244
-30
lines changed

12 files changed

+244
-30
lines changed

Diff for: core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderBase.java

+9
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@
2121
import com.datastax.oss.driver.api.core.metadata.EndPoint;
2222
import com.datastax.oss.driver.api.core.session.Session;
2323
import com.datastax.oss.driver.shaded.guava.common.base.Charsets;
24+
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
2425
import edu.umd.cs.findbugs.annotations.NonNull;
2526
import edu.umd.cs.findbugs.annotations.Nullable;
2627
import java.net.InetSocketAddress;
2728
import java.net.SocketAddress;
29+
import java.net.UnknownHostException;
2830
import java.nio.ByteBuffer;
2931
import java.nio.CharBuffer;
3032
import java.nio.charset.StandardCharsets;
3133
import java.util.Arrays;
34+
import java.util.List;
3235
import java.util.Objects;
3336
import net.jcip.annotations.ThreadSafe;
3437
import org.slf4j.Logger;
@@ -171,6 +174,12 @@ public SocketAddress resolve() {
171174
return new InetSocketAddress("127.0.0.1", 9042);
172175
}
173176

177+
@NonNull
178+
@Override
179+
public List<EndPoint> resolveAll() throws UnknownHostException {
180+
return ImmutableList.of(this);
181+
}
182+
174183
@NonNull
175184
@Override
176185
public String asMetricPrefix() {

Diff for: core/src/main/java/com/datastax/oss/driver/api/core/metadata/EndPoint.java

+10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import edu.umd.cs.findbugs.annotations.NonNull;
2121
import java.net.InetSocketAddress;
2222
import java.net.SocketAddress;
23+
import java.net.UnknownHostException;
24+
import java.util.List;
2325

2426
/**
2527
* Encapsulates the information needed to open connections to a node.
@@ -40,6 +42,14 @@ public interface EndPoint {
4042
@NonNull
4143
SocketAddress resolve();
4244

45+
/**
46+
* Resolves this instance to a socket address.
47+
*
48+
* <p>This will be called each time the driver opens a new connection to the node. The returned
49+
* address cannot be null.
50+
*/
51+
@NonNull
52+
List<EndPoint> resolveAll() throws UnknownHostException;
4353
/**
4454
* Returns an alternate string representation for use in node-level metric names.
4555
*

Diff for: core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java

+7-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import com.datastax.oss.driver.api.core.metadata.EndPoint;
2121
import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint;
22+
import com.datastax.oss.driver.internal.core.metadata.UnresolvedEndPoint;
2223
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet;
2324
import com.datastax.oss.driver.shaded.guava.common.collect.Sets;
2425
import java.net.InetAddress;
@@ -41,18 +42,17 @@ public static Set<EndPoint> merge(
4142

4243
Set<EndPoint> result = Sets.newHashSet(programmaticContactPoints);
4344
for (String spec : configContactPoints) {
44-
for (InetSocketAddress address : extract(spec, resolve)) {
45-
DefaultEndPoint endPoint = new DefaultEndPoint(address);
45+
for (EndPoint endPoint : extract(spec, resolve)) {
4646
boolean wasNew = result.add(endPoint);
4747
if (!wasNew) {
48-
LOG.warn("Duplicate contact point {}", address);
48+
LOG.warn("Duplicate contact point {}", endPoint);
4949
}
5050
}
5151
}
5252
return ImmutableSet.copyOf(result);
5353
}
5454

55-
private static Set<InetSocketAddress> extract(String spec, boolean resolve) {
55+
private static Set<EndPoint> extract(String spec, boolean resolve) {
5656
int separator = spec.lastIndexOf(':');
5757
if (separator < 0) {
5858
LOG.warn("Ignoring invalid contact point {} (expecting host:port)", spec);
@@ -69,7 +69,7 @@ private static Set<InetSocketAddress> extract(String spec, boolean resolve) {
6969
return Collections.emptySet();
7070
}
7171
if (!resolve) {
72-
return ImmutableSet.of(InetSocketAddress.createUnresolved(host, port));
72+
return ImmutableSet.of(new UnresolvedEndPoint(host, port));
7373
} else {
7474
try {
7575
InetAddress[] inetAddresses = InetAddress.getAllByName(host);
@@ -79,9 +79,9 @@ private static Set<InetSocketAddress> extract(String spec, boolean resolve) {
7979
spec,
8080
Arrays.deepToString(inetAddresses));
8181
}
82-
Set<InetSocketAddress> result = new HashSet<>();
82+
Set<EndPoint> result = new HashSet<>();
8383
for (InetAddress inetAddress : inetAddresses) {
84-
result.add(new InetSocketAddress(inetAddress, port));
84+
result.add(new DefaultEndPoint(new InetSocketAddress(inetAddress, port)));
8585
}
8686
return result;
8787
} catch (UnknownHostException e) {

Diff for: core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultEndPoint.java

+8
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
package com.datastax.oss.driver.internal.core.metadata;
1919

2020
import com.datastax.oss.driver.api.core.metadata.EndPoint;
21+
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
2122
import edu.umd.cs.findbugs.annotations.NonNull;
2223
import java.io.Serializable;
2324
import java.net.InetSocketAddress;
25+
import java.util.List;
2426
import java.util.Objects;
2527

2628
public class DefaultEndPoint implements EndPoint, Serializable {
@@ -41,6 +43,12 @@ public InetSocketAddress resolve() {
4143
return address;
4244
}
4345

46+
@NonNull
47+
@Override
48+
public List<EndPoint> resolveAll() {
49+
return ImmutableList.of(this);
50+
}
51+
4452
@Override
4553
public boolean equals(Object other) {
4654
if (other == this) {

Diff for: core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitialNodeListRefresh.java

+13-13
Original file line numberDiff line numberDiff line change
@@ -72,21 +72,21 @@ public Result compute(
7272
+ "keeping only the first one",
7373
logPrefix,
7474
hostId);
75+
continue;
76+
}
77+
EndPoint endPoint = nodeInfo.getEndPoint();
78+
DefaultNode node = findIn(contactPoints, endPoint);
79+
if (node == null) {
80+
node = new DefaultNode(endPoint, context);
81+
LOG.debug("[{}] Adding new node {}", logPrefix, node);
7582
} else {
76-
EndPoint endPoint = nodeInfo.getEndPoint();
77-
DefaultNode node = findIn(contactPoints, endPoint);
78-
if (node == null) {
79-
node = new DefaultNode(endPoint, context);
80-
LOG.debug("[{}] Adding new node {}", logPrefix, node);
81-
} else {
82-
LOG.debug("[{}] Copying contact point {}", logPrefix, node);
83-
}
84-
if (tokenMapEnabled && tokenFactory == null && nodeInfo.getPartitioner() != null) {
85-
tokenFactory = tokenFactoryRegistry.tokenFactoryFor(nodeInfo.getPartitioner());
86-
}
87-
copyInfos(nodeInfo, node, context);
88-
newNodes.put(hostId, node);
83+
LOG.debug("[{}] Copying contact point {}", logPrefix, node);
84+
}
85+
if (tokenMapEnabled && tokenFactory == null && nodeInfo.getPartitioner() != null) {
86+
tokenFactory = tokenFactoryRegistry.tokenFactoryFor(nodeInfo.getPartitioner());
8987
}
88+
copyInfos(nodeInfo, node, context);
89+
newNodes.put(hostId, node);
9090
}
9191

9292
ImmutableList.Builder<Object> eventsBuilder = ImmutableList.builder();

Diff for: core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java

+38-7
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,16 @@
4848
import edu.umd.cs.findbugs.annotations.NonNull;
4949
import io.netty.util.concurrent.EventExecutor;
5050
import java.net.InetSocketAddress;
51+
import java.net.UnknownHostException;
5152
import java.nio.ByteBuffer;
5253
import java.util.Collections;
5354
import java.util.List;
5455
import java.util.Map;
5556
import java.util.Set;
5657
import java.util.concurrent.CompletableFuture;
5758
import java.util.concurrent.CompletionStage;
59+
import java.util.concurrent.CopyOnWriteArraySet;
60+
import jnr.ffi.annotations.Synchronized;
5861
import net.jcip.annotations.ThreadSafe;
5962
import org.slf4j.Logger;
6063
import org.slf4j.LoggerFactory;
@@ -80,7 +83,8 @@ public class MetadataManager implements AsyncAutoCloseable {
8083
private volatile KeyspaceFilter keyspaceFilter;
8184
private volatile Boolean schemaEnabledProgrammatically;
8285
private volatile boolean tokenMapEnabled;
83-
private volatile Set<DefaultNode> contactPoints;
86+
private volatile Set<EndPoint> contactPoints;
87+
private volatile Set<DefaultNode> resolvedContactPoints;
8488
private volatile boolean wasImplicitContactPoint;
8589
private volatile TypeCodec<TupleValue> tabletPayloadCodec = null;
8690

@@ -102,7 +106,7 @@ protected MetadataManager(InternalDriverContext context, DefaultMetadata initial
102106
DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList());
103107
this.keyspaceFilter = KeyspaceFilter.newInstance(logPrefix, refreshedKeyspaces);
104108
this.tokenMapEnabled = config.getBoolean(DefaultDriverOption.METADATA_TOKEN_MAP_ENABLED);
105-
109+
this.resolvedContactPoints = new CopyOnWriteArraySet<>();
106110
context.getEventBus().register(ConfigChangeEvent.class, this::onConfigChanged);
107111
}
108112

@@ -145,18 +149,19 @@ public void addContactPoints(Set<EndPoint> providedContactPoints) {
145149
// Convert the EndPoints to Nodes, but we can't put them into the Metadata yet, because we
146150
// don't know their host_id. So store them in a volatile field instead, they will get copied
147151
// during the first node refresh.
148-
ImmutableSet.Builder<DefaultNode> contactPointsBuilder = ImmutableSet.builder();
152+
ImmutableSet.Builder<EndPoint> contactPointsBuilder = ImmutableSet.builder();
149153
if (providedContactPoints == null || providedContactPoints.isEmpty()) {
150154
LOG.info(
151155
"[{}] No contact points provided, defaulting to {}", logPrefix, DEFAULT_CONTACT_POINT);
152156
this.wasImplicitContactPoint = true;
153-
contactPointsBuilder.add(new DefaultNode(DEFAULT_CONTACT_POINT, context));
157+
contactPointsBuilder.add(DEFAULT_CONTACT_POINT);
154158
} else {
155159
for (EndPoint endPoint : providedContactPoints) {
156-
contactPointsBuilder.add(new DefaultNode(endPoint, context));
160+
contactPointsBuilder.add(endPoint);
157161
}
158162
}
159163
this.contactPoints = contactPointsBuilder.build();
164+
this.resolveContactPoints();
160165
LOG.debug("[{}] Adding initial contact points {}", logPrefix, contactPoints);
161166
}
162167

@@ -167,7 +172,30 @@ public void addContactPoints(Set<EndPoint> providedContactPoints) {
167172
* @see #wasImplicitContactPoint()
168173
*/
169174
public Set<DefaultNode> getContactPoints() {
170-
return contactPoints;
175+
return resolvedContactPoints;
176+
}
177+
178+
@Synchronized
179+
public void resolveContactPoints() {
180+
ImmutableSet.Builder<EndPoint> resultBuilder = ImmutableSet.builder();
181+
for (EndPoint endPoint : contactPoints) {
182+
try {
183+
resultBuilder.addAll(endPoint.resolveAll());
184+
} catch (UnknownHostException e) {
185+
LOG.error("failed to resolve contact endpoint {}", endPoint, e);
186+
}
187+
}
188+
189+
Set<EndPoint> result = resultBuilder.build();
190+
for (EndPoint endPoint : result) {
191+
if (resolvedContactPoints.stream()
192+
.anyMatch(resolved -> resolved.getEndPoint().equals(endPoint))) {
193+
continue;
194+
}
195+
this.resolvedContactPoints.add(new DefaultNode(endPoint, context));
196+
}
197+
198+
this.resolvedContactPoints.removeIf(endPoint -> !result.contains(endPoint.getEndPoint()));
171199
}
172200

173201
/** Whether the default contact point was used (because none were provided explicitly). */
@@ -337,10 +365,13 @@ private SingleThreaded(InternalDriverContext context, DriverExecutionProfile con
337365
}
338366

339367
private Void refreshNodes(Iterable<NodeInfo> nodeInfos) {
368+
if (!didFirstNodeListRefresh) {
369+
resolveContactPoints();
370+
}
340371
MetadataRefresh refresh =
341372
didFirstNodeListRefresh
342373
? new FullNodeListRefresh(nodeInfos)
343-
: new InitialNodeListRefresh(nodeInfos, contactPoints);
374+
: new InitialNodeListRefresh(nodeInfos, resolvedContactPoints);
344375
didFirstNodeListRefresh = true;
345376
return apply(refresh);
346377
}

Diff for: core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SniEndPoint.java

+8
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
package com.datastax.oss.driver.internal.core.metadata;
1919

2020
import com.datastax.oss.driver.api.core.metadata.EndPoint;
21+
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
2122
import com.datastax.oss.driver.shaded.guava.common.primitives.UnsignedBytes;
2223
import edu.umd.cs.findbugs.annotations.NonNull;
2324
import java.net.InetAddress;
2425
import java.net.InetSocketAddress;
2526
import java.net.UnknownHostException;
2627
import java.util.Arrays;
2728
import java.util.Comparator;
29+
import java.util.List;
2830
import java.util.Objects;
2931
import java.util.concurrent.atomic.AtomicLong;
3032

@@ -72,6 +74,12 @@ public InetSocketAddress resolve() {
7274
}
7375
}
7476

77+
@NonNull
78+
@Override
79+
public List<EndPoint> resolveAll() {
80+
return ImmutableList.of(this);
81+
}
82+
7583
@Override
7684
public boolean equals(Object other) {
7785
if (other == this) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package com.datastax.oss.driver.internal.core.metadata;
19+
20+
import com.datastax.oss.driver.api.core.metadata.EndPoint;
21+
import edu.umd.cs.findbugs.annotations.NonNull;
22+
import java.io.Serializable;
23+
import java.net.InetAddress;
24+
import java.net.InetSocketAddress;
25+
import java.net.SocketAddress;
26+
import java.net.UnknownHostException;
27+
import java.util.ArrayList;
28+
import java.util.HashSet;
29+
import java.util.List;
30+
import java.util.Set;
31+
32+
public class UnresolvedEndPoint implements EndPoint, Serializable {
33+
private final String metricPrefix;
34+
String host;
35+
int port;
36+
37+
public UnresolvedEndPoint(String host, int port) {
38+
this.host = host;
39+
this.port = port;
40+
this.metricPrefix = buildMetricPrefix(host, port);
41+
}
42+
43+
@NonNull
44+
@Override
45+
public SocketAddress resolve() {
46+
throw new RuntimeException(
47+
String.format(
48+
"This endpoint %s should never been resolved, but it happened, it somehow leaked to downstream code.",
49+
this));
50+
}
51+
52+
@NonNull
53+
@Override
54+
public List<EndPoint> resolveAll() throws UnknownHostException {
55+
InetAddress[] inetAddresses = InetAddress.getAllByName(host);
56+
Set<EndPoint> result = new HashSet<>();
57+
for (InetAddress inetAddress : inetAddresses) {
58+
result.add(new DefaultEndPoint(new InetSocketAddress(inetAddress, port)));
59+
}
60+
return new ArrayList<>(result);
61+
}
62+
63+
@Override
64+
public boolean equals(Object other) {
65+
if (other == this) {
66+
return true;
67+
}
68+
if (other instanceof UnresolvedEndPoint) {
69+
UnresolvedEndPoint that = (UnresolvedEndPoint) other;
70+
return this.host.equals(that.host) && this.port == that.port;
71+
}
72+
return false;
73+
}
74+
75+
@Override
76+
public int hashCode() {
77+
return host.toLowerCase().hashCode() + port;
78+
}
79+
80+
@Override
81+
public String toString() {
82+
return host + ":" + port;
83+
}
84+
85+
@NonNull
86+
@Override
87+
public String asMetricPrefix() {
88+
return metricPrefix;
89+
}
90+
91+
private static String buildMetricPrefix(String host, int port) {
92+
// Append the port since Cassandra 4 supports nodes with different ports
93+
return host.replace('.', '_') + ':' + port;
94+
}
95+
}

0 commit comments

Comments
 (0)