diff --git a/driver-core/src/main/com/mongodb/ConnectionString.java b/driver-core/src/main/com/mongodb/ConnectionString.java index 210cdbd48e8..cd8f359f35c 100644 --- a/driver-core/src/main/com/mongodb/ConnectionString.java +++ b/driver-core/src/main/com/mongodb/ConnectionString.java @@ -23,6 +23,7 @@ import com.mongodb.internal.diagnostics.logging.Loggers; import com.mongodb.internal.dns.DefaultDnsResolver; import com.mongodb.lang.Nullable; +import com.mongodb.spi.dns.DnsClient; import org.bson.UuidRepresentation; import java.io.UnsupportedEncodingException; @@ -296,6 +297,21 @@ public class ConnectionString { * @since 3.0 */ public ConnectionString(final String connectionString) { + this(connectionString, null); + } + + /** + * Creates a ConnectionString from the given string with the given {@link DnsClient}. + * + *

If setting {@link MongoClientSettings#getDnsClient()} explicitly, care should be taken to call this constructor with the same + * {@link DnsClient}. + * + * @param connectionString the connection string + * @param dnsClient the DNS client with which to resolve TXT record for the mongodb+srv protocol + * @since 4.10 + * @see MongoClientSettings#getDnsClient() + */ + public ConnectionString(final String connectionString, @Nullable final DnsClient dnsClient) { this.connectionString = connectionString; boolean isMongoDBProtocol = connectionString.startsWith(MONGODB_PREFIX); isSrvProtocol = connectionString.startsWith(MONGODB_SRV_PREFIX); @@ -394,7 +410,7 @@ public ConnectionString(final String connectionString) { } String txtRecordsQueryParameters = isSrvProtocol - ? new DefaultDnsResolver().resolveAdditionalQueryParametersFromTxtRecords(unresolvedHosts.get(0)) : ""; + ? new DefaultDnsResolver(dnsClient).resolveAdditionalQueryParametersFromTxtRecords(unresolvedHosts.get(0)) : ""; String connectionStringQueryParameters = unprocessedConnectionString; Map> connectionStringOptionsMap = parseOptions(connectionStringQueryParameters); diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java index 700d4148c8f..cb68ac4c2f0 100644 --- a/driver-core/src/main/com/mongodb/MongoClientSettings.java +++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java @@ -29,6 +29,8 @@ import com.mongodb.connection.StreamFactoryFactory; import com.mongodb.event.CommandListener; import com.mongodb.lang.Nullable; +import com.mongodb.spi.dns.DnsClient; +import com.mongodb.spi.dns.InetAddressResolver; import org.bson.UuidRepresentation; import org.bson.codecs.BsonCodecProvider; import org.bson.codecs.BsonValueCodecProvider; @@ -107,6 +109,8 @@ public final class MongoClientSettings { private final boolean heartbeatConnectTimeoutSetExplicitly; private final ContextProvider contextProvider; + private final DnsClient dnsClient; + private final InetAddressResolver inetAddressResolver; /** * Gets the default codec registry. It includes the following providers: @@ -160,6 +164,39 @@ public static Builder builder(final MongoClientSettings settings) { return new Builder(settings); } + /** + * Gets the {@link DnsClient} to use for resolving DNS queries. + * + *

If set, it will be used to resolve SRV and TXT records for mongodb+srv connections. Otherwise, + * implementations of {@link com.mongodb.spi.dns.DnsClientProvider} will be discovered via {@link java.util.ServiceLoader}. + * If no implementations are discovered, then {@code com.sun.jndi.dns.DnsContextFactory} will be used to resolve these records. + * + *

If applying a connection string to these settings, care must be taken to also pass the same {@link DnsClient} as an argument to + * the {@link ConnectionString} constructor. + * + * @return the DNS client + * @since 4.10 + * @see ConnectionString#ConnectionString(String, DnsClient) + */ + @Nullable + public DnsClient getDnsClient() { + return dnsClient; + } + + /** + * Gets the {@link InetAddressResolver} to use for looking up the {@link java.net.InetAddress} instances for each host. + * + *

If set, it will be used to look up the {@link java.net.InetAddress} for each host, via + * {@link InetAddressResolver#lookupByName(String)}. Otherwise, {@link java.net.InetAddress#getAllByName(String)} will be used. + * + * @return the {@link java.net.InetAddress} resolver + * @since 4.10 + */ + @Nullable + public InetAddressResolver getInetAddressResolver() { + return inetAddressResolver; + } + /** * A builder for {@code MongoClientSettings} so that {@code MongoClientSettings} can be immutable, and to support easier construction * through chaining. @@ -193,6 +230,8 @@ public static final class Builder { private int heartbeatSocketTimeoutMS; private ContextProvider contextProvider; + private DnsClient dnsClient; + private InetAddressResolver inetAddressResolver; private Builder() { } @@ -211,6 +250,8 @@ private Builder(final MongoClientSettings settings) { credential = settings.getCredential(); uuidRepresentation = settings.getUuidRepresentation(); serverApi = settings.getServerApi(); + dnsClient = settings.getDnsClient(); + inetAddressResolver = settings.getInetAddressResolver(); streamFactoryFactory = settings.getStreamFactoryFactory(); autoEncryptionSettings = settings.getAutoEncryptionSettings(); contextProvider = settings.getContextProvider(); @@ -220,6 +261,7 @@ private Builder(final MongoClientSettings settings) { socketSettingsBuilder.applySettings(settings.getSocketSettings()); connectionPoolSettingsBuilder.applySettings(settings.getConnectionPoolSettings()); sslSettingsBuilder.applySettings(settings.getSslSettings()); + if (settings.heartbeatConnectTimeoutSetExplicitly) { heartbeatConnectTimeoutMS = settings.heartbeatSocketSettings.getConnectTimeout(MILLISECONDS); } @@ -273,7 +315,7 @@ public Builder applyConnectionString(final ConnectionString connectionString) { /** * Applies the {@link LoggerSettings.Builder} block and then sets the loggerSettings. * - * @param block the block to apply to the LoggerSettins. + * @param block the block to apply to the LoggerSettings. * @return this * @see MongoClientSettings#getLoggerSettings() * @since 4.9 @@ -580,6 +622,45 @@ public Builder contextProvider(@Nullable final ContextProvider contextProvider) return this; } + /** + * Sets the {@link DnsClient} to use for resolving DNS queries. + * + *

If set, it will be used to resolve SRV and TXT records for mongodb+srv connections. Otherwise, + * implementation of {@link com.mongodb.spi.dns.DnsClientProvider} will be discovered via {@link java.util.ServiceLoader} + * and used to create an instance of {@link DnsClient}. If no implementation is discovered, then + * {@code com.sun.jndi.dns.DnsContextFactory} will be used to resolve these records. + * + *

If {@linkplain #applyConnectionString(ConnectionString) applying a connection string to these settings}, care must be + * taken to also pass the same {@link DnsClient} as an argument to the {@link ConnectionString} constructor. + * + * @param dnsClient the DNS client + * @return the DNS client + * @since 4.10 + * @see ConnectionString#ConnectionString(String, DnsClient) + */ + public Builder dnsClient(@Nullable final DnsClient dnsClient) { + this.dnsClient = dnsClient; + return this; + } + + /** + * Sets the {@link InetAddressResolver} to use for looking up the {@link java.net.InetAddress} instances for each host. + * + *

If set, it will be used to look up the {@link java.net.InetAddress} for each host, via + * {@link InetAddressResolver#lookupByName(String)}. Otherwise, + * an implementation of {@link com.mongodb.spi.dns.InetAddressResolverProvider} will be discovered via + * {@link java.util.ServiceLoader} and used to create an instance of {@link InetAddressResolver}. If no implementation is + * discovered, {@link java.net.InetAddress#getAllByName(String)} will be used to lookup the {@link java.net.InetAddress} + * instances for a host. + * + * @param inetAddressResolver the InetAddress provider + * @return the {@link java.net.InetAddress} resolver + * @since 4.10 + */ + public Builder inetAddressResolver(@Nullable final InetAddressResolver inetAddressResolver) { + this.inetAddressResolver = inetAddressResolver; + return this; + } // Package-private to provide interop with MongoClientOptions Builder heartbeatConnectTimeoutMS(final int heartbeatConnectTimeoutMS) { @@ -905,6 +986,8 @@ public boolean equals(final Object o) { && uuidRepresentation == that.uuidRepresentation && Objects.equals(serverApi, that.serverApi) && Objects.equals(autoEncryptionSettings, that.autoEncryptionSettings) + && Objects.equals(dnsClient, that.dnsClient) + && Objects.equals(inetAddressResolver, that.inetAddressResolver) && Objects.equals(contextProvider, that.contextProvider); } @@ -913,7 +996,8 @@ public int hashCode() { return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, readConcern, credential, streamFactoryFactory, commandListeners, codecRegistry, loggerSettings, clusterSettings, socketSettings, heartbeatSocketSettings, connectionPoolSettings, serverSettings, sslSettings, applicationName, compressorList, uuidRepresentation, serverApi, - autoEncryptionSettings, heartbeatSocketTimeoutSetExplicitly, heartbeatConnectTimeoutSetExplicitly, contextProvider); + autoEncryptionSettings, heartbeatSocketTimeoutSetExplicitly, heartbeatConnectTimeoutSetExplicitly, dnsClient, + inetAddressResolver, contextProvider); } @Override @@ -940,6 +1024,8 @@ public String toString() { + ", uuidRepresentation=" + uuidRepresentation + ", serverApi=" + serverApi + ", autoEncryptionSettings=" + autoEncryptionSettings + + ", dnsClient=" + dnsClient + + ", inetAddressResolver=" + inetAddressResolver + ", contextProvider=" + contextProvider + '}'; } @@ -964,6 +1050,8 @@ private MongoClientSettings(final Builder builder) { compressorList = builder.compressorList; uuidRepresentation = builder.uuidRepresentation; serverApi = builder.serverApi; + dnsClient = builder.dnsClient; + inetAddressResolver = builder.inetAddressResolver; autoEncryptionSettings = builder.autoEncryptionSettings; heartbeatSocketSettings = SocketSettings.builder() .readTimeout(builder.heartbeatSocketTimeoutMS == 0 diff --git a/driver-core/src/main/com/mongodb/MongoSocketException.java b/driver-core/src/main/com/mongodb/MongoSocketException.java index 2641b39abae..820c2cb769f 100644 --- a/driver-core/src/main/com/mongodb/MongoSocketException.java +++ b/driver-core/src/main/com/mongodb/MongoSocketException.java @@ -33,7 +33,7 @@ public class MongoSocketException extends MongoException { * @param msg the message * @param e the cause */ - MongoSocketException(final String msg, final ServerAddress serverAddress, final Throwable e) { + public MongoSocketException(final String msg, final ServerAddress serverAddress, final Throwable e) { super(-2, msg, e); this.serverAddress = serverAddress; } diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterFactory.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterFactory.java index 26c19ee7ec7..d7749ce30c5 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterFactory.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterFactory.java @@ -32,6 +32,8 @@ import com.mongodb.event.ServerListener; import com.mongodb.event.ServerMonitorListener; import com.mongodb.lang.Nullable; +import com.mongodb.spi.dns.DnsClient; +import com.mongodb.spi.dns.InetAddressResolver; import java.util.List; @@ -59,7 +61,8 @@ public Cluster createCluster(final ClusterSettings originalClusterSettings, fina @Nullable final CommandListener commandListener, @Nullable final String applicationName, @Nullable final MongoDriverInformation mongoDriverInformation, - final List compressorList, @Nullable final ServerApi serverApi) { + final List compressorList, @Nullable final ServerApi serverApi, + @Nullable final DnsClient dnsClient, @Nullable final InetAddressResolver inetAddressResolver) { ClusterId clusterId = new ClusterId(applicationName); ClusterSettings clusterSettings; @@ -87,20 +90,20 @@ public Cluster createCluster(final ClusterSettings originalClusterSettings, fina .build(); } - DnsSrvRecordMonitorFactory dnsSrvRecordMonitorFactory = new DefaultDnsSrvRecordMonitorFactory(clusterId, serverSettings); + DnsSrvRecordMonitorFactory dnsSrvRecordMonitorFactory = new DefaultDnsSrvRecordMonitorFactory(clusterId, serverSettings, dnsClient); if (clusterSettings.getMode() == ClusterConnectionMode.LOAD_BALANCED) { ClusterableServerFactory serverFactory = new LoadBalancedClusterableServerFactory(serverSettings, connectionPoolSettings, internalConnectionPoolSettings, streamFactory, credential, loggerSettings, commandListener, applicationName, mongoDriverInformation != null ? mongoDriverInformation : MongoDriverInformation.builder().build(), - compressorList, serverApi); + compressorList, serverApi, inetAddressResolver); return new LoadBalancedCluster(clusterId, clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); } else { ClusterableServerFactory serverFactory = new DefaultClusterableServerFactory(serverSettings, connectionPoolSettings, internalConnectionPoolSettings, streamFactory, heartbeatStreamFactory, credential, loggerSettings, commandListener, applicationName, mongoDriverInformation != null ? mongoDriverInformation : MongoDriverInformation.builder().build(), compressorList, - serverApi); + serverApi, inetAddressResolver); if (clusterSettings.getMode() == ClusterConnectionMode.SINGLE) { return new SingleServerCluster(clusterId, clusterSettings, serverFactory); diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterableServerFactory.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterableServerFactory.java index 2c6d349c58f..c5e66200de1 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterableServerFactory.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterableServerFactory.java @@ -31,6 +31,7 @@ import com.mongodb.event.ServerListener; import com.mongodb.internal.inject.SameObjectProvider; import com.mongodb.lang.Nullable; +import com.mongodb.spi.dns.InetAddressResolver; import java.util.List; @@ -54,6 +55,8 @@ public class DefaultClusterableServerFactory implements ClusterableServerFactory private final List compressorList; @Nullable private final ServerApi serverApi; + @Nullable + private final InetAddressResolver inetAddressResolver; public DefaultClusterableServerFactory( final ServerSettings serverSettings, final ConnectionPoolSettings connectionPoolSettings, @@ -63,7 +66,8 @@ public DefaultClusterableServerFactory( final LoggerSettings loggerSettings, @Nullable final CommandListener commandListener, @Nullable final String applicationName, @Nullable final MongoDriverInformation mongoDriverInformation, - final List compressorList, @Nullable final ServerApi serverApi) { + final List compressorList, @Nullable final ServerApi serverApi, + @Nullable final InetAddressResolver inetAddressResolver) { this.serverSettings = serverSettings; this.connectionPoolSettings = connectionPoolSettings; this.internalConnectionPoolSettings = internalConnectionPoolSettings; @@ -76,6 +80,7 @@ public DefaultClusterableServerFactory( this.mongoDriverInformation = mongoDriverInformation; this.compressorList = compressorList; this.serverApi = serverApi; + this.inetAddressResolver = inetAddressResolver; } @Override @@ -86,11 +91,11 @@ public ClusterableServer create(final Cluster cluster, final ServerAddress serve ServerMonitor serverMonitor = new DefaultServerMonitor(serverId, serverSettings, cluster.getClock(), // no credentials, compressor list, or command listener for the server monitor factory new InternalStreamConnectionFactory(clusterMode, true, heartbeatStreamFactory, null, applicationName, - mongoDriverInformation, emptyList(), loggerSettings, null, serverApi), + mongoDriverInformation, emptyList(), loggerSettings, null, serverApi, inetAddressResolver), clusterMode, serverApi, sdamProvider); ConnectionPool connectionPool = new DefaultConnectionPool(serverId, new InternalStreamConnectionFactory(clusterMode, streamFactory, credential, applicationName, - mongoDriverInformation, compressorList, loggerSettings, commandListener, serverApi), + mongoDriverInformation, compressorList, loggerSettings, commandListener, serverApi, inetAddressResolver), connectionPoolSettings, internalConnectionPoolSettings, sdamProvider); ServerListener serverListener = singleServerListener(serverSettings); SdamServerDescriptionManager sdam = new DefaultSdamServerDescriptionManager(cluster, serverId, serverListener, serverMonitor, diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultDnsSrvRecordMonitorFactory.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultDnsSrvRecordMonitorFactory.java index c584e9547ab..d303acdef99 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultDnsSrvRecordMonitorFactory.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultDnsSrvRecordMonitorFactory.java @@ -19,6 +19,8 @@ import com.mongodb.connection.ClusterId; import com.mongodb.connection.ServerSettings; import com.mongodb.internal.dns.DefaultDnsResolver; +import com.mongodb.lang.Nullable; +import com.mongodb.spi.dns.DnsClient; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -32,15 +34,17 @@ public class DefaultDnsSrvRecordMonitorFactory implements DnsSrvRecordMonitorFac private final ClusterId clusterId; private final long noRecordsRescanFrequency; + private final DnsClient dnsClient; - public DefaultDnsSrvRecordMonitorFactory(final ClusterId clusterId, final ServerSettings serverSettings) { + public DefaultDnsSrvRecordMonitorFactory(final ClusterId clusterId, final ServerSettings serverSettings, @Nullable final DnsClient dnsClient) { this.clusterId = clusterId; this.noRecordsRescanFrequency = serverSettings.getHeartbeatFrequency(MILLISECONDS); + this.dnsClient = dnsClient; } @Override public DnsSrvRecordMonitor create(final String hostName, final String srvServiceName, final DnsSrvRecordInitializer dnsSrvRecordInitializer) { return new DefaultDnsSrvRecordMonitor(hostName, srvServiceName, DEFAULT_RESCAN_FREQUENCY_MILLIS, noRecordsRescanFrequency, - dnsSrvRecordInitializer, clusterId, new DefaultDnsResolver()); + dnsSrvRecordInitializer, clusterId, new DefaultDnsResolver(dnsClient)); } } diff --git a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java index ac9f70943e6..cf331dee4f4 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java +++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java @@ -48,6 +48,7 @@ import com.mongodb.internal.logging.StructuredLogger; import com.mongodb.internal.session.SessionContext; import com.mongodb.lang.Nullable; +import com.mongodb.spi.dns.InetAddressResolver; import org.bson.BsonBinaryReader; import org.bson.BsonDocument; import org.bson.ByteBuf; @@ -118,6 +119,7 @@ public class InternalStreamConnection implements InternalConnection { private final ConnectionGenerationSupplier connectionGenerationSupplier; private final StreamFactory streamFactory; private final InternalConnectionInitializer connectionInitializer; + private final InetAddressResolver inetAddressResolver; private volatile ConnectionDescription description; private volatile ServerDescription initialServerDescription; @@ -149,17 +151,19 @@ static Set getSecuritySensitiveHelloCommands() { public InternalStreamConnection(final ClusterConnectionMode clusterConnectionMode, final ServerId serverId, final ConnectionGenerationSupplier connectionGenerationSupplier, final StreamFactory streamFactory, final List compressorList, - final CommandListener commandListener, final InternalConnectionInitializer connectionInitializer) { + final CommandListener commandListener, final InternalConnectionInitializer connectionInitializer, + @Nullable final InetAddressResolver inetAddressResolver) { this(clusterConnectionMode, false, serverId, connectionGenerationSupplier, streamFactory, compressorList, - LoggerSettings.builder().build(), commandListener, connectionInitializer); + LoggerSettings.builder().build(), commandListener, connectionInitializer, inetAddressResolver); } public InternalStreamConnection(final ClusterConnectionMode clusterConnectionMode, final boolean isMonitoringConnection, - final ServerId serverId, - final ConnectionGenerationSupplier connectionGenerationSupplier, - final StreamFactory streamFactory, final List compressorList, - final LoggerSettings loggerSettings, - final CommandListener commandListener, final InternalConnectionInitializer connectionInitializer) { + final ServerId serverId, + final ConnectionGenerationSupplier connectionGenerationSupplier, + final StreamFactory streamFactory, final List compressorList, + final LoggerSettings loggerSettings, + final CommandListener commandListener, final InternalConnectionInitializer connectionInitializer, + @Nullable final InetAddressResolver inetAddressResolver) { this.clusterConnectionMode = clusterConnectionMode; this.isMonitoringConnection = isMonitoringConnection; this.serverId = notNull("serverId", serverId); @@ -176,6 +180,7 @@ public InternalStreamConnection(final ClusterConnectionMode clusterConnectionMod .type(ServerType.UNKNOWN) .state(ServerConnectionState.CONNECTING) .build(); + this.inetAddressResolver = inetAddressResolver; if (clusterConnectionMode != ClusterConnectionMode.LOAD_BALANCED) { generation = connectionGenerationSupplier.getGeneration(); } @@ -199,7 +204,7 @@ public int getGeneration() { @Override public void open() { isTrue("Open already called", stream == null); - stream = streamFactory.create(serverId.getAddress()); + stream = streamFactory.create(getServerAddressWithResolver()); try { stream.open(); @@ -222,7 +227,7 @@ public void open() { public void openAsync(final SingleResultCallback callback) { isTrue("Open already called", stream == null, callback); try { - stream = streamFactory.create(serverId.getAddress()); + stream = streamFactory.create(getServerAddressWithResolver()); stream.openAsync(new AsyncCompletionHandler() { @Override public void completed(@Nullable final Void aVoid) { @@ -261,6 +266,9 @@ public void failed(final Throwable t) { } } + private ServerAddress getServerAddressWithResolver() { + return new ServerAddressWithResolver(serverId.getAddress(), inetAddressResolver); + } private void initAfterHandshakeStart(final InternalConnectionInitializationDescription initializationDescription) { description = initializationDescription.getConnectionDescription(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionFactory.java b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionFactory.java index 14312edef17..2431a3b800a 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionFactory.java +++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionFactory.java @@ -25,6 +25,7 @@ import com.mongodb.connection.StreamFactory; import com.mongodb.event.CommandListener; import com.mongodb.lang.Nullable; +import com.mongodb.spi.dns.InetAddressResolver; import org.bson.BsonDocument; import java.util.List; @@ -39,28 +40,31 @@ class InternalStreamConnectionFactory implements InternalConnectionFactory { private final StreamFactory streamFactory; private final BsonDocument clientMetadataDocument; private final List compressorList; - private LoggerSettings loggerSettings; + private final LoggerSettings loggerSettings; private final CommandListener commandListener; @Nullable private final ServerApi serverApi; + private final InetAddressResolver inetAddressResolver; private final MongoCredentialWithCache credential; InternalStreamConnectionFactory(final ClusterConnectionMode clusterConnectionMode, final StreamFactory streamFactory, @Nullable final MongoCredentialWithCache credential, - @Nullable final String applicationName, final MongoDriverInformation mongoDriverInformation, + @Nullable final String applicationName, @Nullable final MongoDriverInformation mongoDriverInformation, final List compressorList, - final LoggerSettings loggerSettings, @Nullable final CommandListener commandListener, @Nullable final ServerApi serverApi) { + final LoggerSettings loggerSettings, @Nullable final CommandListener commandListener, @Nullable final ServerApi serverApi, + @Nullable final InetAddressResolver inetAddressResolver) { this(clusterConnectionMode, false, streamFactory, credential, applicationName, mongoDriverInformation, compressorList, - loggerSettings, commandListener, serverApi); + loggerSettings, commandListener, serverApi, inetAddressResolver); } InternalStreamConnectionFactory(final ClusterConnectionMode clusterConnectionMode, final boolean isMonitoringConnection, final StreamFactory streamFactory, @Nullable final MongoCredentialWithCache credential, - @Nullable final String applicationName, final MongoDriverInformation mongoDriverInformation, + @Nullable final String applicationName, @Nullable final MongoDriverInformation mongoDriverInformation, final List compressorList, - final LoggerSettings loggerSettings, @Nullable final CommandListener commandListener, @Nullable final ServerApi serverApi) { + final LoggerSettings loggerSettings, @Nullable final CommandListener commandListener, @Nullable final ServerApi serverApi, + @Nullable final InetAddressResolver inetAddressResolver) { this.clusterConnectionMode = clusterConnectionMode; this.isMonitoringConnection = isMonitoringConnection; this.streamFactory = notNull("streamFactory", streamFactory); @@ -68,6 +72,7 @@ class InternalStreamConnectionFactory implements InternalConnectionFactory { this.loggerSettings = loggerSettings; this.commandListener = commandListener; this.serverApi = serverApi; + this.inetAddressResolver = inetAddressResolver; this.clientMetadataDocument = createClientMetadataDocument(applicationName, mongoDriverInformation); this.credential = credential; } @@ -78,7 +83,7 @@ public InternalConnection create(final ServerId serverId, final ConnectionGenera return new InternalStreamConnection(clusterConnectionMode, isMonitoringConnection, serverId, connectionGenerationSupplier, streamFactory, compressorList, loggerSettings, commandListener, new InternalStreamConnectionInitializer(clusterConnectionMode, authenticator, clientMetadataDocument, compressorList, - serverApi)); + serverApi), inetAddressResolver); } private Authenticator createAuthenticator(final MongoCredentialWithCache credential) { diff --git a/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedClusterableServerFactory.java b/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedClusterableServerFactory.java index 2ff282e9867..54d3aca40e0 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedClusterableServerFactory.java +++ b/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedClusterableServerFactory.java @@ -31,6 +31,7 @@ import com.mongodb.event.CommandListener; import com.mongodb.internal.inject.EmptyProvider; import com.mongodb.lang.Nullable; +import com.mongodb.spi.dns.InetAddressResolver; import java.util.List; @@ -52,15 +53,17 @@ public class LoadBalancedClusterableServerFactory implements ClusterableServerFa private final MongoDriverInformation mongoDriverInformation; private final List compressorList; private final ServerApi serverApi; + private final InetAddressResolver inetAddressResolver; public LoadBalancedClusterableServerFactory(final ServerSettings serverSettings, - final ConnectionPoolSettings connectionPoolSettings, - final InternalConnectionPoolSettings internalConnectionPoolSettings, - final StreamFactory streamFactory, @Nullable final MongoCredential credential, - final LoggerSettings loggerSettings, - @Nullable final CommandListener commandListener, - @Nullable final String applicationName, final MongoDriverInformation mongoDriverInformation, - final List compressorList, @Nullable final ServerApi serverApi) { + final ConnectionPoolSettings connectionPoolSettings, + final InternalConnectionPoolSettings internalConnectionPoolSettings, + final StreamFactory streamFactory, @Nullable final MongoCredential credential, + final LoggerSettings loggerSettings, + @Nullable final CommandListener commandListener, + @Nullable final String applicationName, final MongoDriverInformation mongoDriverInformation, + final List compressorList, @Nullable final ServerApi serverApi, + @Nullable final InetAddressResolver inetAddressResolver) { this.serverSettings = serverSettings; this.connectionPoolSettings = connectionPoolSettings; this.internalConnectionPoolSettings = internalConnectionPoolSettings; @@ -72,13 +75,14 @@ public LoadBalancedClusterableServerFactory(final ServerSettings serverSettings, this.mongoDriverInformation = mongoDriverInformation; this.compressorList = compressorList; this.serverApi = serverApi; + this.inetAddressResolver = inetAddressResolver; } @Override public ClusterableServer create(final Cluster cluster, final ServerAddress serverAddress) { ConnectionPool connectionPool = new DefaultConnectionPool(new ServerId(cluster.getClusterId(), serverAddress), new InternalStreamConnectionFactory(ClusterConnectionMode.LOAD_BALANCED, streamFactory, credential, applicationName, - mongoDriverInformation, compressorList, loggerSettings, commandListener, serverApi), + mongoDriverInformation, compressorList, loggerSettings, commandListener, serverApi, inetAddressResolver), connectionPoolSettings, internalConnectionPoolSettings, EmptyProvider.instance()); connectionPool.ready(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/ServerAddressWithResolver.java b/driver-core/src/main/com/mongodb/internal/connection/ServerAddressWithResolver.java new file mode 100644 index 00000000000..0caa448153c --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/connection/ServerAddressWithResolver.java @@ -0,0 +1,101 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal.connection; + +import com.mongodb.MongoSocketException; +import com.mongodb.ServerAddress; +import com.mongodb.lang.Nullable; +import com.mongodb.spi.dns.InetAddressResolver; +import com.mongodb.spi.dns.InetAddressResolverProvider; + +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +final class ServerAddressWithResolver extends ServerAddress { + private static final long serialVersionUID = 1; + + @Nullable + private static final InetAddressResolver DEFAULT_INET_ADDRESS_RESOLVER; + + static { + DEFAULT_INET_ADDRESS_RESOLVER = StreamSupport.stream(ServiceLoader.load(InetAddressResolverProvider.class).spliterator(), false) + .findFirst() + .map(InetAddressResolverProvider::create) + .orElse(null); + } + + @Nullable + private final transient InetAddressResolver resolver; + + ServerAddressWithResolver(final ServerAddress serverAddress, @Nullable final InetAddressResolver inetAddressResolver) { + super(serverAddress.getHost(), serverAddress.getPort()); + this.resolver = inetAddressResolver == null ? DEFAULT_INET_ADDRESS_RESOLVER : inetAddressResolver; + } + + @Override + public InetSocketAddress getSocketAddress() { + if (resolver == null) { + return super.getSocketAddress(); + } + + return getSocketAddresses().get(0); + } + + @Override + public List getSocketAddresses() { + if (resolver == null || isIpLiteral()) { + return super.getSocketAddresses(); + } + try { + return resolver.lookupByName(getHost()) + .stream() + .map(inetAddress -> new InetSocketAddress(inetAddress, getPort())).collect(Collectors.toList()); + } catch (UnknownHostException e) { + throw new MongoSocketException(e.getMessage(), this, e); + } + } + + // If this returns true, it's either an IP literal or a malformed hostname. But either way, skip lookup via resolver + private boolean isIpLiteral() { + return getHost().charAt(0) == '[' || Character.digit(getHost().charAt(0), 16) != -1 || (getHost().charAt(0) == ':'); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + ServerAddressWithResolver that = (ServerAddressWithResolver) o; + return Objects.equals(resolver, that.resolver); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), resolver); + } +} diff --git a/driver-core/src/main/com/mongodb/internal/dns/DefaultDnsResolver.java b/driver-core/src/main/com/mongodb/internal/dns/DefaultDnsResolver.java index acf98487577..d483e220253 100644 --- a/driver-core/src/main/com/mongodb/internal/dns/DefaultDnsResolver.java +++ b/driver-core/src/main/com/mongodb/internal/dns/DefaultDnsResolver.java @@ -17,6 +17,7 @@ package com.mongodb.internal.dns; import com.mongodb.MongoConfigurationException; +import com.mongodb.lang.Nullable; import com.mongodb.spi.dns.DnsClient; import com.mongodb.spi.dns.DnsClientProvider; import com.mongodb.spi.dns.DnsWithResponseCodeException; @@ -24,6 +25,7 @@ import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; +import java.util.stream.StreamSupport; import static java.lang.String.format; import static java.util.Arrays.asList; @@ -35,21 +37,23 @@ */ public final class DefaultDnsResolver implements DnsResolver { + private static final DnsClient DEFAULT_DNS_CLIENT; + + static { + DEFAULT_DNS_CLIENT = StreamSupport.stream(ServiceLoader.load(DnsClientProvider.class).spliterator(), false) + .findFirst() + .map(DnsClientProvider::create) + .orElse(new JndiDnsClient()); + } + private final DnsClient dnsClient; public DefaultDnsResolver() { - ServiceLoader loader = ServiceLoader.load(DnsClientProvider.class); - DnsClient dnsClientFromServiceLoader = null; - for (DnsClientProvider dnsClientProvider : loader) { - dnsClientFromServiceLoader = dnsClientProvider.create(); - break; - } + this(DEFAULT_DNS_CLIENT); + } - if (dnsClientFromServiceLoader == null) { - dnsClient = new JndiDnsClient(); - } else { - dnsClient = dnsClientFromServiceLoader; - } + public DefaultDnsResolver(@Nullable final DnsClient dnsClient) { + this.dnsClient = dnsClient == null ? DEFAULT_DNS_CLIENT : dnsClient; } /* diff --git a/driver-core/src/main/com/mongodb/spi/dns/DnsClient.java b/driver-core/src/main/com/mongodb/spi/dns/DnsClient.java index 64fd3424d39..482d48ec0a2 100644 --- a/driver-core/src/main/com/mongodb/spi/dns/DnsClient.java +++ b/driver-core/src/main/com/mongodb/spi/dns/DnsClient.java @@ -16,6 +16,9 @@ package com.mongodb.spi.dns; +import com.mongodb.MongoClientSettings; +import com.mongodb.annotations.ThreadSafe; + import java.util.List; @@ -24,7 +27,9 @@ * * @since 4.6 * @see DnsClientProvider + * @see MongoClientSettings.Builder#dnsClient(DnsClient) */ +@ThreadSafe public interface DnsClient { /** * Gets the resource record values for the given name and type. diff --git a/driver-core/src/main/com/mongodb/spi/dns/DnsClientProvider.java b/driver-core/src/main/com/mongodb/spi/dns/DnsClientProvider.java index 3a82835f981..85581ee8eed 100644 --- a/driver-core/src/main/com/mongodb/spi/dns/DnsClientProvider.java +++ b/driver-core/src/main/com/mongodb/spi/dns/DnsClientProvider.java @@ -17,15 +17,21 @@ package com.mongodb.spi.dns; /** - * A provider interface for {@link DnsClient}. + * Service-provider class for {@link DnsClient}. * - *

- * The driver discovers implementations of this interface via {@link java.util.ServiceLoader}. - *

+ *

A resolver provider is a factory for custom implementations of + * {@linkplain DnsClient a DNS client}. A DNS client defines operations for + * looking up DNS records for a given type. + * + *

The driver discovers implementations of this interface via {@link java.util.ServiceLoader}. + * + *

If more fine-grained control is required for multi-tenant applications, an + * {@linkplain DnsClient a DNS client} can be configured via + * {@link com.mongodb.MongoClientSettings.Builder#dnsClient(DnsClient)}. * * @since 4.6 * @see java.util.ServiceLoader - */ +*/ public interface DnsClientProvider { /** * Construct a new instance of a {@link DnsClient}. diff --git a/driver-core/src/main/com/mongodb/spi/dns/InetAddressResolver.java b/driver-core/src/main/com/mongodb/spi/dns/InetAddressResolver.java new file mode 100644 index 00000000000..8a577579ec4 --- /dev/null +++ b/driver-core/src/main/com/mongodb/spi/dns/InetAddressResolver.java @@ -0,0 +1,49 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.spi.dns; + +import com.mongodb.MongoClientSettings; +import com.mongodb.annotations.ThreadSafe; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; + +/** + * This interface defines operations for looking up host names. + * + *

The default resolver for the driver can be customized by deploying an implementation of {@link InetAddressResolverProvider}.

+ * + * @see InetAddressResolverProvider + * @see MongoClientSettings.Builder#inetAddressResolver(InetAddressResolver) + * @since 4.10 + */ +@ThreadSafe +public interface InetAddressResolver { + /** + * Given the name of a host, returns a list of IP addresses of the requested + * address family associated with a provided hostname. + * + *

Implementations are encouraged to implement their own caching policies, as there is + * no guarantee that the caller will implement a cache. + * + * @param host the host + * @return a list of IP addresses for the requested host + * @throws UnknownHostException if no IP addresses for the {@code host} could be found + */ + List lookupByName(String host) throws UnknownHostException; +} diff --git a/driver-core/src/main/com/mongodb/spi/dns/InetAddressResolverProvider.java b/driver-core/src/main/com/mongodb/spi/dns/InetAddressResolverProvider.java new file mode 100644 index 00000000000..0bbb92162a3 --- /dev/null +++ b/driver-core/src/main/com/mongodb/spi/dns/InetAddressResolverProvider.java @@ -0,0 +1,42 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.spi.dns; + +/** + * Service-provider class for {@link InetAddressResolver}. + * + *

A resolver provider is a factory for custom implementations of {@linkplain + * InetAddressResolver InetAddress resolvers}. A resolver defines operations for + * looking up (resolving) host names. + * + *

The driver discovers implementations of this interface via {@link java.util.ServiceLoader}. + * + *

If more fine-grained control is required for multi-tenant applications, an {@linkplain InetAddressResolver InetAddress resolver} + * can be configured via {@link com.mongodb.MongoClientSettings.Builder#inetAddressResolver(InetAddressResolver)}. + * + * @since 4.10 + * @see java.util.ServiceLoader + */ + +public interface InetAddressResolverProvider { + /** + * Construct a new instance of a {@link InetAddressResolver}. + * + * @return a {@link InetAddressResolver} + */ + InetAddressResolver create(); +} diff --git a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java index 945c2aba10d..85b4a9cfeac 100644 --- a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java +++ b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java @@ -379,7 +379,7 @@ private static Cluster createCluster(final MongoCredential credential, final Str ServerSettings.builder().build(), ConnectionPoolSettings.builder().maxSize(1).build(), InternalConnectionPoolSettings.builder().build(), streamFactory, streamFactory, credential, LoggerSettings.builder().build(), null, null, null, - Collections.emptyList(), getServerApi()); + Collections.emptyList(), getServerApi(), null, null); } private static Cluster createCluster(final ConnectionString connectionString, final StreamFactory streamFactory) { @@ -391,7 +391,7 @@ private static Cluster createCluster(final ConnectionString connectionString, fi new SocketStreamFactory(SocketSettings.builder().readTimeout(5, SECONDS).build(), getSslSettings(connectionString)), connectionString.getCredential(), LoggerSettings.builder().build(), null, null, null, - connectionString.getCompressorList(), getServerApi()); + connectionString.getCompressorList(), getServerApi(), null, null); } public static StreamFactory getStreamFactory() { diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy index d58a4dd067b..4f6f360d857 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy @@ -49,7 +49,7 @@ class CommandHelperSpecification extends Specification { def setup() { connection = new InternalStreamConnectionFactory(ClusterConnectionMode.SINGLE, new NettyStreamFactory(SocketSettings.builder().build(), getSslSettings()), - getCredentialWithCache(), null, null, [], LoggerSettings.builder().build(), null, getServerApi()) + getCredentialWithCache(), null, null, [], LoggerSettings.builder().build(), null, getServerApi(), null) .create(new ServerId(new ClusterId(), getPrimary())) connection.open() } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java index 82e83b3b568..348f4fdf47c 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java @@ -54,7 +54,8 @@ public void setUp() { source = System.getProperty("org.mongod.test.source"); password = System.getProperty("org.mongodb.test.password"); internalConnection = new InternalStreamConnectionFactory(ClusterConnectionMode.SINGLE, streamFactory, null, null, - null, Collections.emptyList(), LoggerSettings.builder().build(), null, getServerApi()).create(new ServerId(new ClusterId(), + null, Collections.emptyList(), LoggerSettings.builder().build(), null, getServerApi(), + null).create(new ServerId(new ClusterId(), new ServerAddress(host))); connectionDescription = new ConnectionDescription(new ServerId(new ClusterId(), new ServerAddress())); } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/ServerMonitorSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/ServerMonitorSpecification.groovy index 5f9f940c98b..6d1cb0133db 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ServerMonitorSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ServerMonitorSpecification.groovy @@ -226,7 +226,7 @@ class ServerMonitorSpecification extends OperationFunctionalSpecification { .connectTimeout(500, TimeUnit.MILLISECONDS) .build(), getSslSettings()), getCredentialWithCache(), null, null, [], LoggerSettings.builder().build(), null, - getServerApi()), + getServerApi(), null), getClusterConnectionMode(), getServerApi(), SameObjectProvider.initialized(sdam)) serverMonitor.start() serverMonitor diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java index a94d3319ab1..66fa750803f 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java @@ -70,7 +70,7 @@ private void setUpCluster(final ServerAddress serverAddress) { streamFactory, streamFactory, getCredential(), LoggerSettings.builder().build(), null, null, null, - Collections.emptyList(), getServerApi())); + Collections.emptyList(), getServerApi(), null)); } @After diff --git a/driver-core/src/test/unit/com/mongodb/ConnectionStringSpecification.groovy b/driver-core/src/test/unit/com/mongodb/ConnectionStringSpecification.groovy index be9702b6588..c4057576995 100644 --- a/driver-core/src/test/unit/com/mongodb/ConnectionStringSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/ConnectionStringSpecification.groovy @@ -703,4 +703,15 @@ class ConnectionStringSpecification extends Specification { expect: uri.credential == createCredential('bob', 'otherDB', 'pwd'.toCharArray()) } + + def 'should use DnsClient to resolve TXT record'() { + given: + def dnsClient = { def name, def type -> ['replicaSet=java'] } + + when: + def connectionString = new ConnectionString('mongodb+srv://free-java.mongodb-dev.net', dnsClient); + + then: + connectionString.getRequiredReplicaSetName() == 'java' + } } diff --git a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy index e0b7d6f5c66..db9938fcecf 100644 --- a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy @@ -24,6 +24,8 @@ import com.mongodb.connection.SocketSettings import com.mongodb.connection.SslSettings import com.mongodb.connection.netty.NettyStreamFactoryFactory import com.mongodb.event.CommandListener +import com.mongodb.spi.dns.DnsClient +import com.mongodb.spi.dns.InetAddressResolver import org.bson.UuidRepresentation import org.bson.codecs.configuration.CodecRegistry import spock.lang.Specification @@ -58,6 +60,8 @@ class MongoClientSettingsSpecification extends Specification { settings.credential == null settings.uuidRepresentation == UuidRepresentation.UNSPECIFIED settings.contextProvider == null + settings.dnsClient == null + settings.inetAddressResolver == null } @SuppressWarnings('UnnecessaryObjectReferences') @@ -119,6 +123,8 @@ class MongoClientSettingsSpecification extends Specification { def commandListener = Stub(CommandListener) def clusterSettings = ClusterSettings.builder().hosts([new ServerAddress('localhost')]).requiredReplicaSetName('test').build() def contextProvider = Stub(ContextProvider) + def dnsClient = Stub(DnsClient) + def inetAddressResolver = Stub(InetAddressResolver) when: def settings = MongoClientSettings.builder() @@ -141,6 +147,8 @@ class MongoClientSettingsSpecification extends Specification { .compressorList([MongoCompressor.createZlibCompressor()]) .uuidRepresentation(UuidRepresentation.STANDARD) .contextProvider(contextProvider) + .dnsClient(dnsClient) + .inetAddressResolver(inetAddressResolver) .build() then: @@ -160,6 +168,8 @@ class MongoClientSettingsSpecification extends Specification { settings.getCompressorList() == [MongoCompressor.createZlibCompressor()] settings.getUuidRepresentation() == UuidRepresentation.STANDARD settings.getContextProvider() == contextProvider + settings.getDnsClient() == dnsClient + settings.getInetAddressResolver() == inetAddressResolver } def 'should be easy to create new settings from existing'() { @@ -175,6 +185,8 @@ class MongoClientSettingsSpecification extends Specification { def commandListener = Stub(CommandListener) def compressorList = [MongoCompressor.createZlibCompressor()] def contextProvider = Stub(ContextProvider) + def dnsClient = Stub(DnsClient) + def inetAddressResolver = Stub(InetAddressResolver) settings = MongoClientSettings.builder() .heartbeatConnectTimeoutMS(24000) @@ -197,6 +209,8 @@ class MongoClientSettingsSpecification extends Specification { .codecRegistry(codecRegistry) .compressorList(compressorList) .contextProvider(contextProvider) + .dnsClient(dnsClient) + .inetAddressResolver(inetAddressResolver) .build() then: @@ -464,8 +478,8 @@ class MongoClientSettingsSpecification extends Specification { // A regression test so that if anymore fields are added then the builder(final MongoClientSettings settings) should be updated def actual = MongoClientSettings.Builder.declaredFields.grep { !it.synthetic } *.name.sort() def expected = ['applicationName', 'autoEncryptionSettings', 'clusterSettingsBuilder', 'codecRegistry', 'commandListeners', - 'compressorList', 'connectionPoolSettingsBuilder', 'contextProvider', 'credential', - 'heartbeatConnectTimeoutMS', 'heartbeatSocketTimeoutMS', 'loggerSettingsBuilder', + 'compressorList', 'connectionPoolSettingsBuilder', 'contextProvider', 'credential', 'dnsClient', + 'heartbeatConnectTimeoutMS', 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'loggerSettingsBuilder', 'readConcern', 'readPreference', 'retryReads', 'retryWrites', 'serverApi', 'serverSettingsBuilder', 'socketSettingsBuilder', 'sslSettingsBuilder', 'streamFactoryFactory', 'uuidRepresentation', 'writeConcern'] @@ -481,9 +495,9 @@ class MongoClientSettingsSpecification extends Specification { def expected = ['addCommandListener', 'applicationName', 'applyConnectionString', 'applyToClusterSettings', 'applyToConnectionPoolSettings', 'applyToLoggerSettings', 'applyToServerSettings', 'applyToSocketSettings', 'applyToSslSettings', 'autoEncryptionSettings', 'build', 'codecRegistry', 'commandListenerList', - 'compressorList', 'contextProvider', 'credential', 'heartbeatConnectTimeoutMS', 'heartbeatSocketTimeoutMS', - 'readConcern', 'readPreference', 'retryReads', 'retryWrites', 'serverApi', 'streamFactoryFactory', - 'uuidRepresentation', 'writeConcern'] + 'compressorList', 'contextProvider', 'credential', 'dnsClient', 'heartbeatConnectTimeoutMS', + 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'readConcern', 'readPreference', 'retryReads', 'retryWrites', + 'serverApi', 'streamFactoryFactory', 'uuidRepresentation', 'writeConcern'] then: actual == expected } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java index ed14716c538..bd22307e75f 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java @@ -186,7 +186,8 @@ public void setUp() { Collections.emptyList(), LoggerSettings.builder().build(), new TestCommandListener(), - ClusterFixture.getServerApi()), + ClusterFixture.getServerApi(), + null), settings, internalSettings, sdamProvider)); sdamProvider.initialize(new DefaultSdamServerDescriptionManager(mockedCluster(), serverId, mock(ServerListener.class), mock(ServerMonitor.class), pool, connectionMode)); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy index 28071239a91..1ea53ef4543 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy @@ -107,7 +107,7 @@ class InternalStreamConnectionSpecification extends Specification { def getConnection() { new InternalStreamConnection(SINGLE, SERVER_ID, new TestConnectionGenerationSupplier(), streamFactory, [], commandListener, - initializer) + initializer, null) } def getOpenedConnection() { @@ -172,7 +172,7 @@ class InternalStreamConnectionSpecification extends Specification { startHandshake(_) >> { throw new MongoInternalException('Something went wrong') } } def connection = new InternalStreamConnection(SINGLE, SERVER_ID, new TestConnectionGenerationSupplier(), streamFactory, [], null, - failedInitializer) + failedInitializer, null) when: connection.open() @@ -189,7 +189,7 @@ class InternalStreamConnectionSpecification extends Specification { startHandshakeAsync(_, _) >> { it[1].onResult(null, new MongoInternalException('Something went wrong')) } } def connection = new InternalStreamConnection(SINGLE, SERVER_ID, new TestConnectionGenerationSupplier(), streamFactory, [], null, - failedInitializer) + failedInitializer, null) when: def futureResultCallback = new FutureResultCallback() diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClients.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClients.java index 744a69ec266..569b93083e6 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClients.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClients.java @@ -149,7 +149,7 @@ private static Cluster createCluster(final MongoClientSettings settings, InternalConnectionPoolSettings.builder().prestartAsyncWorkManager(true).build(), streamFactory, heartbeatStreamFactory, settings.getCredential(), settings.getLoggerSettings(), getCommandListener(settings.getCommandListeners()), settings.getApplicationName(), mongoDriverInformation, - settings.getCompressorList(), settings.getServerApi()); + settings.getCompressorList(), settings.getServerApi(), settings.getDnsClient(), settings.getInetAddressResolver()); } private static MongoDriverInformation wrapMongoDriverInformation(@Nullable final MongoDriverInformation mongoDriverInformation) { diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/DnsConfigurationTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/DnsConfigurationTest.java new file mode 100644 index 00000000000..6cb1c951bae --- /dev/null +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/DnsConfigurationTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.reactivestreams.client; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.AbstractDnsConfigurationTest; +import com.mongodb.client.MongoClient; +import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; + +public class DnsConfigurationTest extends AbstractDnsConfigurationTest { + @Override + protected MongoClient createMongoClient(final MongoClientSettings settings) { + return new SyncMongoClient(MongoClients.create(settings)); + } +} diff --git a/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala index 4f461599789..8f1fb8de210 100644 --- a/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala +++ b/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala @@ -55,6 +55,8 @@ class ApiAliasAndCompanionSpec extends BaseSpec { "DocumentToDBRefTransformer", "Function", "FutureResultCallback", + "InetAddressResolver", + "InetAddressResolverProvider", "Jep395RecordCodecProvider", "KerberosSubjectProvider", "KotlinCodecProvider", diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java index 52dc02a41ee..ff71b42633c 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java @@ -229,7 +229,8 @@ private static Cluster createCluster(final MongoClientSettings settings, settings.getConnectionPoolSettings(), InternalConnectionPoolSettings.builder().build(), getStreamFactory(settings, false), getStreamFactory(settings, true), settings.getCredential(), settings.getLoggerSettings(), getCommandListener(settings.getCommandListeners()), - settings.getApplicationName(), mongoDriverInformation, settings.getCompressorList(), settings.getServerApi()); + settings.getApplicationName(), mongoDriverInformation, settings.getCompressorList(), settings.getServerApi(), + settings.getDnsClient(), settings.getInetAddressResolver()); } private static StreamFactory getStreamFactory(final MongoClientSettings settings, final boolean isHeartbeat) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractDnsConfigurationTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractDnsConfigurationTest.java new file mode 100644 index 00000000000..26a90337998 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractDnsConfigurationTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.client; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoException; +import com.mongodb.ServerAddress; +import com.mongodb.connection.ServerDescription; +import com.mongodb.event.ClusterDescriptionChangedEvent; +import com.mongodb.event.ClusterListener; +import com.mongodb.spi.dns.DnsClient; +import com.mongodb.spi.dns.DnsException; +import com.mongodb.spi.dns.InetAddressResolver; +import org.junit.jupiter.api.Test; + +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public abstract class AbstractDnsConfigurationTest { + + protected abstract MongoClient createMongoClient(MongoClientSettings settings); + + @Test + public void testInetAddressResolverConfiguration() throws InterruptedException, ExecutionException, TimeoutException { + UnknownHostException exception = new UnknownHostException(); + InetAddressResolver resolver = host -> { + throw exception; + }; + + CompletableFuture exceptionReceived = new CompletableFuture<>(); + MongoClientSettings settings = MongoClientSettings.builder() + .applyToClusterSettings(builder -> + builder.hosts(Collections.singletonList(new ServerAddress("some.host"))) + .addClusterListener(new ClusterListener() { + @Override + public void clusterDescriptionChanged(final ClusterDescriptionChangedEvent event) { + ServerDescription serverDescription = event.getNewDescription().getServerDescriptions().get(0); + if (serverDescription.getException() != null) { + exceptionReceived.complete(exception); + } + } + })) + .inetAddressResolver(resolver) + .build(); + + try (MongoClient ignored = createMongoClient(settings)) { + assertEquals(exception, exceptionReceived.get(10, SECONDS)); + } + } + + @Test + public void testDnsClientConfiguration() throws InterruptedException, ExecutionException, TimeoutException { + DnsException exception = new DnsException("", new Exception()); + DnsClient dnsClient = (name, type) -> { + throw exception; + }; + + CompletableFuture exceptionReceived = new CompletableFuture<>(); + MongoClientSettings settings = MongoClientSettings.builder() + .applyConnectionString(new ConnectionString("mongodb+srv://free-java.mongodb-dev.net")) + .applyToClusterSettings(builder -> + builder.addClusterListener(new ClusterListener() { + @Override + public void clusterDescriptionChanged(final ClusterDescriptionChangedEvent event) { + MongoException srvResolutionException = event.getNewDescription().getSrvResolutionException(); + if (srvResolutionException != null) { + exceptionReceived.complete(srvResolutionException.getCause()); + } + } + })) + .dnsClient(dnsClient) + .build(); + + try (MongoClient ignored = createMongoClient(settings)) { + assertEquals(exception, exceptionReceived.get(1, SECONDS)); + } + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/DnsConfigurationTest.java b/driver-sync/src/test/functional/com/mongodb/client/DnsConfigurationTest.java new file mode 100644 index 00000000000..36593ba0239 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/DnsConfigurationTest.java @@ -0,0 +1,26 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.client; + +import com.mongodb.MongoClientSettings; + +public class DnsConfigurationTest extends AbstractDnsConfigurationTest { + @Override + protected MongoClient createMongoClient(final MongoClientSettings settings) { + return MongoClients.create(settings); + } +}