Skip to content

Commit fe5d469

Browse files
authored
Add Resolver interface and implementations (#332)
* Add CcmBridge#getNodeIpAddress(int) Adds tiny utility method to CcmBridge. * Add Resolver interface and implementations Adds a middleman between driver and all direct calls to InetAddress. This is meant to help with testing in case there is a need to substitute name resolution with resolution by specific ip address without modifying system environment or providing custom DNS server. Default implementation redirects all calls to InetAddress class which should not introduce change in behaviour. One introduced downside is that if the methods are to be customizable then the interface cannot have them `static`. Adds also implementation of MockResolver that has its own mapping of hostnames to addresses. Adds MockResolverIT.
1 parent eacf91d commit fe5d469

File tree

17 files changed

+496
-4
lines changed

17 files changed

+496
-4
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
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.resolver.Resolver;
23+
import com.datastax.oss.driver.internal.core.resolver.ResolverProvider;
2224
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet;
2325
import com.datastax.oss.driver.shaded.guava.common.collect.Sets;
2426
import java.net.InetAddress;
@@ -35,6 +37,7 @@
3537
/** Utility class to handle the initial contact points passed to the driver. */
3638
public class ContactPoints {
3739
private static final Logger LOG = LoggerFactory.getLogger(ContactPoints.class);
40+
private static Resolver RESOLVER = ResolverProvider.getResolver(ContactPoints.class);
3841

3942
public static Set<EndPoint> merge(
4043
Set<EndPoint> programmaticContactPoints, List<String> configContactPoints, boolean resolve) {
@@ -72,7 +75,7 @@ private static Set<InetSocketAddress> extract(String spec, boolean resolve) {
7275
return ImmutableSet.of(InetSocketAddress.createUnresolved(host, port));
7376
} else {
7477
try {
75-
InetAddress[] inetAddresses = InetAddress.getAllByName(host);
78+
InetAddress[] inetAddresses = RESOLVER.getAllByName(host);
7679
if (inetAddresses.length > 1) {
7780
LOG.info(
7881
"Contact point {} resolves to multiple addresses, will use them all ({})",

core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Ec2MultiRegionAddressTranslator.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator;
2121
import com.datastax.oss.driver.api.core.context.DriverContext;
22+
import com.datastax.oss.driver.internal.core.resolver.Resolver;
23+
import com.datastax.oss.driver.internal.core.resolver.ResolverProvider;
2224
import com.datastax.oss.driver.internal.core.util.Loggers;
2325
import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting;
2426
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -57,6 +59,8 @@
5759
public class Ec2MultiRegionAddressTranslator implements AddressTranslator {
5860

5961
private static final Logger LOG = LoggerFactory.getLogger(Ec2MultiRegionAddressTranslator.class);
62+
private static Resolver RESOLVER =
63+
ResolverProvider.getResolver(Ec2MultiRegionAddressTranslator.class);
6064

6165
private final DirContext ctx;
6266
private final String logPrefix;
@@ -94,7 +98,7 @@ public InetSocketAddress translate(@NonNull InetSocketAddress socketAddress) {
9498
return socketAddress;
9599
}
96100

97-
InetAddress translatedAddress = InetAddress.getByName(domainName);
101+
InetAddress translatedAddress = RESOLVER.getByName(domainName);
98102
LOG.debug("[{}] Resolved {} to {}", logPrefix, address, translatedAddress);
99103
return new InetSocketAddress(translatedAddress, socketAddress.getPort());
100104
} catch (Exception e) {

core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater;
4343
import com.datastax.oss.driver.internal.core.protocol.FrameDecoder;
4444
import com.datastax.oss.driver.internal.core.protocol.FrameEncoder;
45+
import com.datastax.oss.driver.internal.core.resolver.Resolver;
46+
import com.datastax.oss.driver.internal.core.resolver.ResolverProvider;
4547
import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting;
4648
import com.datastax.oss.driver.shaded.guava.common.base.Preconditions;
4749
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap;
@@ -68,6 +70,8 @@ public class ChannelFactory {
6870

6971
private static final Logger LOG = LoggerFactory.getLogger(ChannelFactory.class);
7072

73+
private static Resolver RESOLVER = ResolverProvider.getResolver(ChannelFactory.class);
74+
7175
/**
7276
* A value for {@link #productType} that indicates that we are connected to DataStax Cloud. This
7377
* value matches the one defined at DSE DB server side at {@code ProductType.java}.
@@ -204,7 +208,7 @@ private void connect(
204208

205209
nettyOptions.afterBootstrapInitialized(bootstrap);
206210

207-
ChannelFuture connectFuture = bootstrap.connect(endPoint.resolve());
211+
ChannelFuture connectFuture = bootstrap.connect(RESOLVER.resolve(endPoint.resolve()));
208212

209213
connectFuture.addListener(
210214
cf -> {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
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.internal.core.resolver.Resolver;
22+
import com.datastax.oss.driver.internal.core.resolver.ResolverProvider;
2123
import com.datastax.oss.driver.shaded.guava.common.primitives.UnsignedBytes;
2224
import edu.umd.cs.findbugs.annotations.NonNull;
2325
import java.net.InetAddress;
@@ -30,6 +32,7 @@
3032

3133
public class SniEndPoint implements EndPoint {
3234
private static final AtomicLong OFFSET = new AtomicLong();
35+
private static Resolver RESOLVER = ResolverProvider.getResolver(SniEndPoint.class);
3336

3437
private final InetSocketAddress proxyAddress;
3538
private final String serverName;
@@ -55,7 +58,7 @@ public String getServerName() {
5558
@Override
5659
public InetSocketAddress resolve() {
5760
try {
58-
InetAddress[] aRecords = InetAddress.getAllByName(proxyAddress.getHostName());
61+
InetAddress[] aRecords = RESOLVER.getAllByName(proxyAddress.getHostName());
5962
if (aRecords.length == 0) {
6063
// Probably never happens, but the JDK docs don't explicitly say so
6164
throw new IllegalArgumentException(
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.datastax.oss.driver.internal.core.resolver;
2+
3+
public interface AbstractResolverFactory {
4+
public Resolver getResolver(Class<?> clazz);
5+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.datastax.oss.driver.internal.core.resolver;
2+
3+
import com.datastax.oss.driver.internal.core.resolver.defaultResolver.DefaultResolver;
4+
import java.net.InetAddress;
5+
import java.net.SocketAddress;
6+
import java.net.UnknownHostException;
7+
8+
/**
9+
* Resolver is a "middleman" class that is meant to serve as a hook point for testing.
10+
* Any time core driver tries to resolve a host defined by hostname, such attempt will
11+
* go through the Resolver first. At that moment Resolver may return custom result it
12+
* was configured to return or simply forward the call to the corresponding InetAddress class
13+
* method.
14+
*
15+
* Some classes are exempt from running through the Resolver and they still call InetAddress directly
16+
* (e.g. {@link com.datastax.dse.driver.internal.core.insights.InsightsClient}).
17+
* By default, driver should use {@link DefaultResolver}
18+
* which should introduce no change in behaviour and redirect all calls to {@link InetAddress).
19+
*/
20+
public interface Resolver {
21+
22+
/**
23+
* Replaces calls to {@link InetAddress#getByName(String)}.
24+
*
25+
* @param host host to resolve.
26+
* @return an IP address for the given host name.
27+
* @throws UnknownHostException
28+
*/
29+
InetAddress getByName(String host) throws UnknownHostException;
30+
31+
/**
32+
* Replaces calls to {@link InetAddress#getAllByName(String)}
33+
*
34+
* @param host host to resolve.
35+
* @return an array of all the IP addresses for a given host name.
36+
* @throws UnknownHostException
37+
*/
38+
InetAddress[] getAllByName(String host) throws UnknownHostException;
39+
40+
/**
41+
* Resolves {@link SocketAddress} returning {@link SocketAddress} To be called just before passing
42+
* {@link SocketAddress}, that may be unresolved, to an another library that is going resolve it.
43+
*
44+
* @param addr SocketAddress with host details
45+
* @return SocketAddress instance with possibly modified contents
46+
*/
47+
SocketAddress resolve(SocketAddress addr);
48+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.datastax.oss.driver.internal.core.resolver;
2+
3+
import com.datastax.oss.driver.internal.core.resolver.defaultResolver.DefaultResolverFactory;
4+
5+
/**
6+
* Entry point for driver components to getting the {@link Resolver} instances. By default returns
7+
* instances of {@link DefaultResolverFactory}.
8+
*/
9+
public class ResolverProvider {
10+
11+
private static AbstractResolverFactory defaultResolverFactoryImpl = new DefaultResolverFactory();
12+
13+
/**
14+
* Asks factory for new {@link Resolver}.
15+
*
16+
* @param clazz Class that is requesting the {@link Resolver}.
17+
* @return new {@link Resolver}.
18+
*/
19+
public static Resolver getResolver(Class<?> clazz) {
20+
return defaultResolverFactoryImpl.getResolver(clazz);
21+
}
22+
23+
/**
24+
* Replaces resolver factory with another, possibly producing different implementation of {@link
25+
* Resolver}.
26+
*
27+
* @param resolverFactoryImpl new {@link Resolver} factory.
28+
*/
29+
public static void setDefaultResolverFactory(AbstractResolverFactory resolverFactoryImpl) {
30+
defaultResolverFactoryImpl = resolverFactoryImpl;
31+
}
32+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.datastax.oss.driver.internal.core.resolver.defaultResolver;
2+
3+
import com.datastax.oss.driver.internal.core.resolver.Resolver;
4+
import java.net.InetAddress;
5+
import java.net.SocketAddress;
6+
import java.net.UnknownHostException;
7+
8+
/**
9+
* Resolver implementation that forwards all calls to {@link InetAddress} or returns the arguments
10+
* unmodified.
11+
*/
12+
public class DefaultResolver implements Resolver {
13+
14+
DefaultResolver() {}
15+
16+
/**
17+
* Equivalent to {@link InetAddress#getByName(String)}.
18+
*
19+
* @throws UnknownHostException
20+
*/
21+
@Override
22+
public InetAddress getByName(String host) throws UnknownHostException {
23+
return InetAddress.getByName(host);
24+
}
25+
26+
/**
27+
* Equivalent to {@link InetAddress#getAllByName(String)}.
28+
*
29+
* @throws UnknownHostException
30+
*/
31+
@Override
32+
public InetAddress[] getAllByName(String host) throws UnknownHostException {
33+
return InetAddress.getAllByName(host);
34+
}
35+
36+
/**
37+
* Returns {@link SocketAddress} as is. No reason to resolve it here, it is supposed to be done by
38+
* a library that is going to consume the result.
39+
*
40+
* @param addr {@link SocketAddress}.
41+
* @return the very same addr.
42+
*/
43+
@Override
44+
public SocketAddress resolve(SocketAddress addr) {
45+
return addr;
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.datastax.oss.driver.internal.core.resolver.defaultResolver;
2+
3+
import com.datastax.oss.driver.internal.core.resolver.AbstractResolverFactory;
4+
5+
/**
6+
* Produces instances of {@link
7+
* com.datastax.oss.driver.internal.core.resolver.defaultResolver.DefaultResolver}.
8+
*/
9+
public class DefaultResolverFactory implements AbstractResolverFactory {
10+
11+
/**
12+
* Returns new {@link
13+
* com.datastax.oss.driver.internal.core.resolver.defaultResolver.DefaultResolver} instance.
14+
*
15+
* @param clazz Class for which the instance is being created.
16+
* @return new {@link
17+
* com.datastax.oss.driver.internal.core.resolver.defaultResolver.DefaultResolver} instance.
18+
*/
19+
@Override
20+
public DefaultResolver getResolver(Class<?> clazz) {
21+
return new DefaultResolver();
22+
}
23+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.datastax.oss.driver.internal.core.resolver.mockResolver;
2+
3+
import com.datastax.oss.driver.internal.core.resolver.Resolver;
4+
import java.net.InetAddress;
5+
import java.net.InetSocketAddress;
6+
import java.net.SocketAddress;
7+
import java.net.UnknownHostException;
8+
9+
/** Resolver implementation that allows returning custom results. */
10+
public class MockResolver implements Resolver {
11+
private final MockResolverResultSource resultSource;
12+
13+
MockResolver(MockResolverResultSource resultSource) {
14+
this.resultSource = resultSource;
15+
}
16+
17+
/**
18+
* Returns first {@link InetAddress} from overrided results {@link Response} if one is present.
19+
* Otherwise forwards the call to {@link InetAddress#getByName(String)}
20+
*
21+
* @param host host to resolve.
22+
* @return address.
23+
* @throws UnknownHostException
24+
*/
25+
@Override
26+
public InetAddress getByName(String host) throws UnknownHostException {
27+
Response response = this.resultSource.getResponse(host);
28+
if (response == null) {
29+
return InetAddress.getByName(host);
30+
}
31+
return response.result()[0];
32+
}
33+
34+
/**
35+
* Returns overrided results for provided {@code host}, if one is present, otherwise the call to
36+
* Otherwise forwards the call to {@link InetAddress#getAllByName(String)}
37+
*
38+
* @param host host to resolve.
39+
* @return address.
40+
* @throws UnknownHostException
41+
*/
42+
@Override
43+
public InetAddress[] getAllByName(String host) throws UnknownHostException {
44+
Response response = this.resultSource.getResponse(host);
45+
if (response == null) {
46+
return InetAddress.getAllByName(host);
47+
}
48+
return response.result();
49+
}
50+
51+
/**
52+
* If {@code addr} is an resolved {@link InetSocketAddress}, or there is no response overriding
53+
* for the it's host, or {@code addr} is something else, it returns {@code addr} as is. Otherwise
54+
* it returns overrided response, if overrided response throws {@link UnknownHostException}, it
55+
* emulates it by returning unresolved {@link InetSocketAddress} with bogus host.
56+
*
57+
* @param addr SocketAddress to modify.
58+
* @return possibly new SocketAddress.
59+
*/
60+
@Override
61+
public SocketAddress resolve(SocketAddress addr) {
62+
if (!(addr instanceof InetSocketAddress)) {
63+
return addr;
64+
}
65+
InetSocketAddress inetSockAddr = ((InetSocketAddress) addr);
66+
if (!inetSockAddr.isUnresolved()) {
67+
return addr;
68+
}
69+
String hostname = inetSockAddr.getHostName();
70+
int port = inetSockAddr.getPort();
71+
Response response = this.resultSource.getResponse(hostname);
72+
if (response == null) {
73+
return addr;
74+
}
75+
try {
76+
return new InetSocketAddress(response.result()[0], port);
77+
} catch (UnknownHostException e) {
78+
return InetSocketAddress.createUnresolved(hostname + ".bad.host.115t87", port);
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)