Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,11 @@ public enum DefaultDriverOption implements DriverOption {
* Whether to resolve the addresses passed to `basic.contact-points`.
*
* <p>Value-type: boolean
*
* @deprecated Contact points are now always kept as unresolved hostnames and expanded to all
* their DNS-mapped IPs lazily at connection time. Setting this option has no effect.
*/
@Deprecated
RESOLVE_CONTACT_POINTS("advanced.resolve-contact-points"),

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,13 @@ public String toString() {
/** The coalescer reschedule interval. */
public static final TypedDriverOption<Duration> COALESCER_INTERVAL =
new TypedDriverOption<>(DefaultDriverOption.COALESCER_INTERVAL, GenericType.DURATION);
/** Whether to resolve the addresses passed to `basic.contact-points`. */
/**
* Whether to resolve the addresses passed to `basic.contact-points`.
*
* @deprecated Contact points are now always kept as unresolved hostnames and expanded to all
* their DNS-mapped IPs lazily at connection time. Setting this option has no effect.
*/
@Deprecated
public static final TypedDriverOption<Boolean> RESOLVE_CONTACT_POINTS =
new TypedDriverOption<>(DefaultDriverOption.RESOLVE_CONTACT_POINTS, GenericType.BOOLEAN);
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -935,11 +935,10 @@ protected final CompletionStage<CqlSession> buildDefaultSessionAsync() {
programmaticArguments = programmaticArgumentsBuilder.build();
}

boolean resolveAddresses =
defaultConfig.getBoolean(DefaultDriverOption.RESOLVE_CONTACT_POINTS, false);

// RESOLVE_CONTACT_POINTS is deprecated: contact points are always kept as unresolved
// hostnames and expanded to all their DNS IPs lazily at connection time.
Set<EndPoint> contactPoints =
ContactPoints.merge(programmaticContactPoints, configContactPoints, resolveAddresses);
ContactPoints.merge(programmaticContactPoints, configContactPoints, false);

if (keyspace == null && defaultConfig.isDefined(DefaultDriverOption.SESSION_KEYSPACE)) {
keyspace =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ public Queue<Node> newQueryPlan(
switch (stateRef.get()) {
case BEFORE_INIT:
case DURING_INIT:
// The contact points are not stored in the metadata yet:
List<Node> nodes = new ArrayList<>(context.getMetadataManager().getContactPoints());
Collections.shuffle(nodes);
return new ConcurrentLinkedQueue<>(nodes);
Expand All @@ -170,6 +169,10 @@ public Queue<Node> newControlReconnectionQueryPlan() {
.getConfig()
.getDefaultProfile()
.getBoolean(DefaultDriverOption.CONTROL_CONNECTION_RECONNECT_CONTACT_POINTS)) {
// Use the original (potentially unresolved) contact-point endpoints so that the control
// connection channel retains the hostname, preserving hostname-based node identity in
// metadata. DNS expansion to all IPs for each hostname is handled by ChannelFactory at
// actual connection time.
Set<DefaultNode> originalNodes = context.getMetadataManager().getContactPoints();
List<Node> contactNodes = new ArrayList<>();
for (DefaultNode node : originalNodes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet;
import edu.umd.cs.findbugs.annotations.NonNull;
import io.netty.util.concurrent.EventExecutor;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -173,6 +176,59 @@ public Set<DefaultNode> getContactPoints() {
return contactPoints;
}

/**
* Returns the contact points expanded to all their DNS-resolved IPs.
*
* <p>For each contact point whose underlying address is an unresolved hostname (i.e. stored as
* {@code InetSocketAddress.createUnresolved(...)} when {@code RESOLVE_CONTACT_POINTS=false}),
* this method calls {@link InetAddress#getAllByName(String)} to obtain every IP the hostname maps
* to and creates a synthetic contact-point {@link DefaultNode} for each IP. This lets the load
* balancing policy iterate over all candidate IPs rather than only the first one, so that a
* non-responsive IP does not block initial connection or control-connection reconnection.
*
* <p>Already-resolved addresses and non-{@link InetSocketAddress} endpoints are returned as-is.
*/
public List<Node> getResolvedContactPoints() {
Set<DefaultNode> nodes = contactPoints;
if (nodes == null) {
return new ArrayList<>();
}
List<Node> result = new ArrayList<>();
for (DefaultNode node : nodes) {
EndPoint endPoint = node.getEndPoint();
if (endPoint instanceof DefaultEndPoint) {
InetSocketAddress address = ((DefaultEndPoint) endPoint).resolve();
if (address.isUnresolved()) {
// Expand hostname to all IPs so callers can try each one in turn.
try {
InetAddress[] all = InetAddress.getAllByName(address.getHostString());
if (all.length > 1) {
LOG.debug(
"[{}] Contact point {} expands to {} addresses",
logPrefix,
address.getHostString(),
all.length);
}
for (InetAddress ip : all) {
InetSocketAddress resolved = new InetSocketAddress(ip, address.getPort());
result.add(DefaultNode.newContactPoint(new DefaultEndPoint(resolved), context));
}
} catch (UnknownHostException e) {
LOG.warn(
"[{}] Could not resolve contact point hostname {}, skipping",
logPrefix,
address.getHostString(),
e);
}
continue;
}
}
// Already resolved or non-InetSocketAddress endpoint — use as-is.
result.add(node);
}
return result;
}

/** Whether the default contact point was used (because none were provided explicitly). */
public boolean wasImplicitContactPoint() {
return wasImplicitContactPoint;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,7 @@ public void should_fetch_control_connection_query_plan_from_policy_after_init()
assertThat(queryPlan.poll()).isEqualTo(node3);
assertThat(queryPlan.poll()).isEqualTo(node2);
assertThat(queryPlan.poll()).isEqualTo(node1);
// Remaining nodes are contact points appended at the end.
// They are new DefaultNode instances created via newContactPoint, so compare by endpoint.
// Remaining nodes are the resolved contact points appended at the end.
Set<EndPoint> remainingEndpoints = new java.util.HashSet<>();
for (Node n : queryPlan) {
remainingEndpoints.add(n.getEndPoint());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,66 @@ public void should_throw_on_registerNode_with_null_hostId() {
.hasMessageContaining("Cannot register node without hostId");
}

@Test
public void should_return_empty_list_when_contact_points_not_yet_set() {
// contactPoints field is null until addContactPoints is called
assertThat(metadataManager.getResolvedContactPoints()).isEmpty();
}

@Test
public void should_return_already_resolved_contact_points_unchanged() {
// Given — a contact point with an already-resolved InetSocketAddress
metadataManager.addContactPoints(ImmutableSet.of(END_POINT2));

// When
List<Node> resolved = metadataManager.getResolvedContactPoints();

// Then — the single node is returned as-is (no expansion needed)
assertThat(resolved).hasSize(1);
assertThat(resolved.get(0).getEndPoint()).isEqualTo(END_POINT2);
}

@Test
public void should_expand_unresolved_hostname_to_all_ips() {
// Given — a contact point with an unresolved hostname (localhost → 127.0.0.1)
EndPoint unresolvedEndPoint =
new DefaultEndPoint(InetSocketAddress.createUnresolved("localhost", 9042));
metadataManager.addContactPoints(ImmutableSet.of(unresolvedEndPoint));

// When
List<Node> resolved = metadataManager.getResolvedContactPoints();

// Then — at least one node is returned, each with a resolved address
assertThat(resolved).isNotEmpty();
for (Node node : resolved) {
InetSocketAddress addr = (InetSocketAddress) node.getEndPoint().resolve();
assertThat(addr.isUnresolved()).isFalse();
assertThat(addr.getPort()).isEqualTo(9042);
}
}

@Test
public void should_expand_multiple_contact_points_independently() {
// Given — two contact points: one already resolved, one unresolved
EndPoint resolvedEndPoint = END_POINT3;
EndPoint unresolvedEndPoint =
new DefaultEndPoint(InetSocketAddress.createUnresolved("localhost", 9042));
metadataManager.addContactPoints(ImmutableSet.of(resolvedEndPoint, unresolvedEndPoint));

// When
List<Node> resolved = metadataManager.getResolvedContactPoints();

// Then — at least 2 nodes: 1 for the resolved + at least 1 for localhost expansion
assertThat(resolved.size()).isGreaterThanOrEqualTo(2);
// The resolved endpoint must appear
assertThat(resolved).anySatisfy(n -> assertThat(n.getEndPoint()).isEqualTo(resolvedEndPoint));
// All returned addresses must be resolved
for (Node node : resolved) {
InetSocketAddress addr = (InetSocketAddress) node.getEndPoint().resolve();
assertThat(addr.isUnresolved()).isFalse();
}
}

private static class TestMetadataManager extends MetadataManager {

private List<MetadataRefresh> refreshes = new CopyOnWriteArrayList<>();
Expand Down
Loading