Skip to content

Commit 7d591c4

Browse files
authored
Support srvServiceName option for SRV lookups (#843)
JAVA-4239
1 parent 4380f3f commit 7d591c4

22 files changed

+338
-110
lines changed

config/checkstyle/suppressions.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
<suppress checks="MethodLength" files="PojoRoundTripTest"/>
3333
<suppress checks="MethodLength" files="AbstractUnifiedTest"/>
34+
<suppress checks="MethodLength" files="ConnectionString"/>
3435

3536
<suppress checks="JavadocPackage" files="com[\\/]mongodb[\\/][^\\/]*\.java"/>
3637
<suppress checks="JavadocPackage" files="com[\\/]mongodb[\\/]client[\\/][^\\/]*\.java"/>

driver-core/src/main/com/mongodb/ConnectionString.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.mongodb;
1818

19+
import com.mongodb.connection.ClusterSettings;
1920
import com.mongodb.connection.ConnectionPoolSettings;
2021
import com.mongodb.diagnostics.logging.Logger;
2122
import com.mongodb.diagnostics.logging.Loggers;
@@ -77,7 +78,8 @@
7778
* connecting to a database server. For some authentication mechanisms, only the username is specified and the password is not,
7879
* in which case the ":" after the username is left off as well</li>
7980
* <li>{@code host} is the only required part of the URI. It identifies a single host name for which SRV records are looked up
80-
* from a Domain Name Server after prefixing the host name with {@code "_mongodb._tcp"}. The host/port for each SRV record becomes the
81+
* from a Domain Name Server after prefixing the host name with, by default, {@code "_mongodb._tcp"} ({@code "mongodb"} is the default SRV
82+
* service name, but can be replaced via the {@code srvServiceName} query parameter), The host/port for each SRV record becomes the
8183
* seed list used to connect, as if each one were provided as host/port pair in a URI using the normal mongodb protocol.</li>
8284
* <li>{@code /database} is the name of the database to login to and thus is only relevant if the
8385
* {@code username:password@} syntax is used. If not specified the "admin" database will be used by default.</li>
@@ -220,6 +222,11 @@
220222
* <li>{@code zlibCompressionLevel=integer}: Integer value from -1 to 9 representing the zlib compression level. Lower values will make
221223
* compression faster, while higher values will make compression better.</li>
222224
* </ul>
225+
* <p>SRV configuration:</p>
226+
* <ul>
227+
* <li>{@code srvServiceName=string}: The SRV service name. See {@link ClusterSettings#getSrvServiceName()} for details.</li>
228+
* <li>{@code srvMaxHosts=number}: The maximum number of hosts from the SRV record to connect to.</li>
229+
* </ul>
223230
* <p>General configuration:</p>
224231
* <ul>
225232
* <li>{@code retryWrites=true|false}. If true the driver will retry supported write operations if they fail due to a network error.
@@ -254,6 +261,7 @@ public class ConnectionString {
254261
private final String connectionString;
255262

256263
private Integer srvMaxHosts;
264+
private String srvServiceName;
257265
private Boolean directConnection;
258266
private Boolean loadBalanced;
259267
private ReadPreference readPreference;
@@ -404,6 +412,10 @@ public ConnectionString(final String connectionString) {
404412
throw new IllegalArgumentException("srvMaxHosts can only be specified with mongodb+srv protocol");
405413
}
406414

415+
if (!isSrvProtocol && srvServiceName != null) {
416+
throw new IllegalArgumentException("srvServiceName can only be specified with mongodb+srv protocol");
417+
}
418+
407419
if (directConnection != null && directConnection) {
408420
if (isSrvProtocol) {
409421
throw new IllegalArgumentException("Direct connections are not supported when using mongodb+srv protocol");
@@ -482,6 +494,7 @@ public ConnectionString(final String connectionString) {
482494
GENERAL_OPTIONS_KEYS.add("loadbalanced");
483495

484496
GENERAL_OPTIONS_KEYS.add("srvmaxhosts");
497+
GENERAL_OPTIONS_KEYS.add("srvservicename");
485498

486499
COMPRESSOR_KEYS.add("compressors");
487500
COMPRESSOR_KEYS.add("zlibcompressionlevel");
@@ -594,6 +607,8 @@ private void translateOptions(final Map<String, List<String>> optionsMap) {
594607
if (srvMaxHosts < 0) {
595608
throw new IllegalArgumentException("srvMaxHosts must be >= 0");
596609
}
610+
} else if (key.equals("srvservicename")) {
611+
srvServiceName = value;
597612
}
598613
}
599614

@@ -1134,6 +1149,19 @@ public Integer getSrvMaxHosts() {
11341149
return srvMaxHosts;
11351150
}
11361151

1152+
/**
1153+
* Gets the SRV service name.
1154+
*
1155+
* @return the SRV service name. Defaults to null in the connection string, but defaults to {@code "mongodb"} in
1156+
* {@link ClusterSettings}.
1157+
* @since 4.5
1158+
* @see ClusterSettings#getSrvServiceName()
1159+
*/
1160+
@Nullable
1161+
public String getSrvServiceName() {
1162+
return srvServiceName;
1163+
}
1164+
11371165
/**
11381166
* Gets the list of hosts
11391167
*
@@ -1474,7 +1502,9 @@ public boolean equals(final Object o) {
14741502
&& Objects.equals(heartbeatFrequency, that.heartbeatFrequency)
14751503
&& Objects.equals(applicationName, that.applicationName)
14761504
&& Objects.equals(compressorList, that.compressorList)
1477-
&& Objects.equals(uuidRepresentation, that.uuidRepresentation);
1505+
&& Objects.equals(uuidRepresentation, that.uuidRepresentation)
1506+
&& Objects.equals(srvServiceName, that.srvServiceName)
1507+
&& Objects.equals(srvMaxHosts, that.srvMaxHosts);
14781508
}
14791509

14801510
@Override
@@ -1483,6 +1513,6 @@ public int hashCode() {
14831513
writeConcern, retryWrites, retryReads, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime,
14841514
maxConnectionIdleTime, maxConnectionLifeTime, maxConnecting, connectTimeout, socketTimeout, sslEnabled,
14851515
sslInvalidHostnameAllowed, requiredReplicaSetName, serverSelectionTimeout, localThreshold, heartbeatFrequency,
1486-
applicationName, compressorList, uuidRepresentation);
1516+
applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts);
14871517
}
14881518
}

driver-core/src/main/com/mongodb/connection/ClusterSettings.java

Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.ArrayList;
2929
import java.util.LinkedHashSet;
3030
import java.util.List;
31+
import java.util.Objects;
3132
import java.util.Set;
3233
import java.util.concurrent.TimeUnit;
3334
import java.util.stream.Collectors;
@@ -49,6 +50,7 @@
4950
public final class ClusterSettings {
5051
private final String srvHost;
5152
private final Integer srvMaxHosts;
53+
private final String srvServiceName;
5254
private final List<ServerAddress> hosts;
5355
private final ClusterConnectionMode mode;
5456
private final ClusterType requiredClusterType;
@@ -86,6 +88,7 @@ public static final class Builder {
8688
private static final List<ServerAddress> DEFAULT_HOSTS = singletonList(new ServerAddress());
8789
private String srvHost;
8890
private Integer srvMaxHosts;
91+
private String srvServiceName = "mongodb";
8992
private List<ServerAddress> hosts = DEFAULT_HOSTS;
9093
private ClusterConnectionMode mode;
9194
private ClusterType requiredClusterType = ClusterType.UNKNOWN;
@@ -110,6 +113,8 @@ private Builder() {
110113
public Builder applySettings(final ClusterSettings clusterSettings) {
111114
notNull("clusterSettings", clusterSettings);
112115
srvHost = clusterSettings.srvHost;
116+
srvServiceName = clusterSettings.srvServiceName;
117+
srvMaxHosts = clusterSettings.srvMaxHosts;
113118
hosts = clusterSettings.hosts;
114119
mode = clusterSettings.mode;
115120
requiredReplicaSetName = clusterSettings.requiredReplicaSetName;
@@ -156,6 +161,28 @@ public Builder srvMaxHosts(final Integer srvMaxHosts) {
156161
return this;
157162
}
158163

164+
/**
165+
* Sets the SRV service name.
166+
*
167+
* <p>
168+
* The SRV resource record (<a href="https://www.rfc-editor.org/rfc/rfc2782">RFC 2782</a>)
169+
* service name, which is limited to 15 characters
170+
* (<a href="https://www.rfc-editor.org/rfc/rfc6335#section-5.1">RFC 6335 section 5.1</a>).
171+
* If specified, it is combined with the single host name specified by
172+
* {@link #getHosts()} as follows: {@code _srvServiceName._tcp.hostName}. The combined string is an SRV resource record
173+
* name (<a href="https://www.rfc-editor.org/rfc/rfc1035#section-2.3.1">RFC 1035 section 2.3.1</a>), which is limited to 255
174+
* characters (<a href="https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4">RFC 1035 section 2.3.4</a>).
175+
* </p>
176+
*
177+
* @param srvServiceName the SRV service name
178+
* @return this
179+
* @since 4.5
180+
*/
181+
public Builder srvServiceName(final String srvServiceName) {
182+
this.srvServiceName = notNull("srvServiceName", srvServiceName);
183+
return this;
184+
}
185+
159186
/**
160187
* Sets the hosts for the cluster. Any duplicate server addresses are removed from the list.
161188
*
@@ -302,6 +329,9 @@ public Builder applyConnectionString(final ConnectionString connectionString) {
302329
mode(ClusterConnectionMode.MULTIPLE);
303330
srvHost(connectionString.getHosts().get(0));
304331
srvMaxHosts(connectionString.getSrvMaxHosts());
332+
if (connectionString.getSrvServiceName() != null) {
333+
srvServiceName(connectionString.getSrvServiceName());
334+
}
305335
} else if ((directConnection != null && directConnection)
306336
|| (directConnection == null && connectionString.getHosts().size() == 1
307337
&& connectionString.getRequiredReplicaSetName() == null)) {
@@ -358,6 +388,26 @@ public Integer getSrvMaxHosts() {
358388
return srvMaxHosts;
359389
}
360390

391+
/**
392+
* Gets the SRV service name.
393+
*
394+
* <p>
395+
* The SRV resource record (<a href="https://www.rfc-editor.org/rfc/rfc2782">RFC 2782</a>)
396+
* service name, which is limited to 15 characters
397+
* (<a href="https://www.rfc-editor.org/rfc/rfc6335#section-5.1">RFC 6335 section 5.1</a>).
398+
* If specified, it is combined with the single host name specified by
399+
* {@link #getHosts()} as follows: {@code _srvServiceName._tcp.hostName}. The combined string is an SRV resource record
400+
* name (<a href="https://www.rfc-editor.org/rfc/rfc1035#section-2.3.1">RFC 1035 section 2.3.1</a>), which is limited to 255
401+
* characters (<a href="https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4">RFC 1035 section 2.3.4</a>).
402+
* </p>
403+
*
404+
* @return the SRV service name, which defaults to {@code "mongodb"}
405+
* @since 4.5
406+
*/
407+
public String getSrvServiceName() {
408+
return srvServiceName;
409+
}
410+
361411
/**
362412
* Gets the seed list of hosts for the cluster.
363413
*
@@ -477,60 +527,33 @@ public boolean equals(final Object o) {
477527
if (o == null || getClass() != o.getClass()) {
478528
return false;
479529
}
480-
481530
ClusterSettings that = (ClusterSettings) o;
482-
483-
if (serverSelectionTimeoutMS != that.serverSelectionTimeoutMS) {
484-
return false;
485-
}
486-
if (localThresholdMS != that.localThresholdMS) {
487-
return false;
488-
}
489-
if (srvHost != null ? !srvHost.equals(that.srvHost) : that.srvHost != null) {
490-
return false;
491-
}
492-
if (!hosts.equals(that.hosts)) {
493-
return false;
494-
}
495-
if (mode != that.mode) {
496-
return false;
497-
}
498-
if (requiredClusterType != that.requiredClusterType) {
499-
return false;
500-
}
501-
if (requiredReplicaSetName != null ? !requiredReplicaSetName.equals(that.requiredReplicaSetName)
502-
: that.requiredReplicaSetName != null) {
503-
return false;
504-
}
505-
if (serverSelector != null ? !serverSelector.equals(that.serverSelector) : that.serverSelector != null) {
506-
return false;
507-
}
508-
if (!clusterListeners.equals(that.clusterListeners)) {
509-
return false;
510-
}
511-
512-
return true;
531+
return localThresholdMS == that.localThresholdMS
532+
&& serverSelectionTimeoutMS == that.serverSelectionTimeoutMS
533+
&& Objects.equals(srvHost, that.srvHost)
534+
&& Objects.equals(srvMaxHosts, that.srvMaxHosts)
535+
&& srvServiceName.equals(that.srvServiceName)
536+
&& hosts.equals(that.hosts)
537+
&& mode == that.mode
538+
&& requiredClusterType == that.requiredClusterType
539+
&& Objects.equals(requiredReplicaSetName, that.requiredReplicaSetName)
540+
&& Objects.equals(serverSelector, that.serverSelector)
541+
&& clusterListeners.equals(that.clusterListeners);
513542
}
514543

515544
@Override
516545
public int hashCode() {
517-
int result = hosts.hashCode();
518-
result = 31 * result + (srvHost != null ? srvHost.hashCode() : 0);
519-
result = 31 * result + mode.hashCode();
520-
result = 31 * result + requiredClusterType.hashCode();
521-
result = 31 * result + (requiredReplicaSetName != null ? requiredReplicaSetName.hashCode() : 0);
522-
result = 31 * result + (serverSelector != null ? serverSelector.hashCode() : 0);
523-
result = 31 * result + (int) (serverSelectionTimeoutMS ^ (serverSelectionTimeoutMS >>> 32));
524-
result = 31 * result + (int) (localThresholdMS ^ (localThresholdMS >>> 32));
525-
result = 31 * result + clusterListeners.hashCode();
526-
return result;
546+
return Objects.hash(srvHost, srvMaxHosts, srvServiceName, hosts, mode, requiredClusterType, requiredReplicaSetName, serverSelector,
547+
localThresholdMS, serverSelectionTimeoutMS, clusterListeners);
527548
}
528549

529550
@Override
530551
public String toString() {
531552
return "{"
532553
+ (hosts.isEmpty() ? "" : "hosts=" + hosts)
533554
+ (srvHost == null ? "" : ", srvHost=" + srvHost)
555+
+ (srvServiceName == null ? "" : ", srvServiceName=" + srvServiceName)
556+
+ (srvMaxHosts == null ? "" : ", srvMaxHosts=" + srvMaxHosts)
534557
+ ", mode=" + mode
535558
+ ", requiredClusterType=" + requiredClusterType
536559
+ ", requiredReplicaSetName='" + requiredReplicaSetName + '\''
@@ -588,6 +611,7 @@ private ClusterSettings(final Builder builder) {
588611

589612
srvHost = builder.srvHost;
590613
srvMaxHosts = builder.srvMaxHosts;
614+
srvServiceName = builder.srvServiceName;
591615
hosts = builder.hosts;
592616
if (srvHost != null) {
593617
if (builder.mode == ClusterConnectionMode.SINGLE) {

driver-core/src/main/com/mongodb/internal/connection/DefaultDnsSrvRecordMonitor.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,19 @@ class DefaultDnsSrvRecordMonitor implements DnsSrvRecordMonitor {
3737
private static final Logger LOGGER = Loggers.getLogger("cluster");
3838

3939
private final String hostName;
40+
private final String srvServiceName;
4041
private final long rescanFrequencyMillis;
4142
private final long noRecordsRescanFrequencyMillis;
4243
private final DnsSrvRecordInitializer dnsSrvRecordInitializer;
4344
private final DnsResolver dnsResolver;
4445
private final Thread monitorThread;
4546
private volatile boolean isClosed;
4647

47-
DefaultDnsSrvRecordMonitor(final String hostName, final long rescanFrequencyMillis, final long noRecordsRescanFrequencyMillis,
48-
final DnsSrvRecordInitializer dnsSrvRecordInitializer, final ClusterId clusterId,
49-
final DnsResolver dnsResolver) {
48+
DefaultDnsSrvRecordMonitor(final String hostName, final String srvServiceName, final long rescanFrequencyMillis, final long noRecordsRescanFrequencyMillis,
49+
final DnsSrvRecordInitializer dnsSrvRecordInitializer, final ClusterId clusterId,
50+
final DnsResolver dnsResolver) {
5051
this.hostName = hostName;
52+
this.srvServiceName = srvServiceName;
5153
this.rescanFrequencyMillis = rescanFrequencyMillis;
5254
this.noRecordsRescanFrequencyMillis = noRecordsRescanFrequencyMillis;
5355
this.dnsSrvRecordInitializer = dnsSrvRecordInitializer;
@@ -75,7 +77,7 @@ private class DnsSrvRecordMonitorRunnable implements Runnable {
7577
public void run() {
7678
while (!isClosed && shouldContinueMonitoring()) {
7779
try {
78-
List<String> resolvedHostNames = dnsResolver.resolveHostFromSrvRecords(hostName);
80+
List<String> resolvedHostNames = dnsResolver.resolveHostFromSrvRecords(hostName, srvServiceName);
7981
Set<ServerAddress> hosts = createServerAddressSet(resolvedHostNames);
8082

8183
if (isClosed) {

driver-core/src/main/com/mongodb/internal/connection/DefaultDnsSrvRecordMonitorFactory.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ public DefaultDnsSrvRecordMonitorFactory(final ClusterId clusterId, final Server
3636
}
3737

3838
@Override
39-
public DnsSrvRecordMonitor create(final String hostName, final DnsSrvRecordInitializer dnsSrvRecordInitializer) {
40-
return new DefaultDnsSrvRecordMonitor(hostName, DEFAULT_RESCAN_FREQUENCY_MILLIS, noRecordsRescanFrequency, dnsSrvRecordInitializer,
41-
clusterId, new DefaultDnsResolver());
39+
public DnsSrvRecordMonitor create(final String hostName, final String srvServiceName, final DnsSrvRecordInitializer dnsSrvRecordInitializer) {
40+
return new DefaultDnsSrvRecordMonitor(hostName, srvServiceName, DEFAULT_RESCAN_FREQUENCY_MILLIS, noRecordsRescanFrequency,
41+
dnsSrvRecordInitializer, clusterId, new DefaultDnsResolver());
4242
}
4343
}

driver-core/src/main/com/mongodb/internal/connection/DnsMultiServerCluster.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ public DnsMultiServerCluster(final ClusterId clusterId, final ClusterSettings se
3939
final DnsSrvRecordMonitorFactory dnsSrvRecordMonitorFactory) {
4040
super(clusterId, settings, serverFactory);
4141
notNull("srvHost", settings.getSrvHost());
42-
dnsSrvRecordMonitor = dnsSrvRecordMonitorFactory.create(settings.getSrvHost(), new DnsSrvRecordInitializer() {
42+
dnsSrvRecordMonitor = dnsSrvRecordMonitorFactory.create(settings.getSrvHost(), settings.getSrvServiceName(),
43+
new DnsSrvRecordInitializer() {
4344
private volatile boolean initialized;
4445

4546
@Override

driver-core/src/main/com/mongodb/internal/connection/DnsSrvRecordMonitorFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
package com.mongodb.internal.connection;
1818

1919
public interface DnsSrvRecordMonitorFactory {
20-
DnsSrvRecordMonitor create(String hostName, DnsSrvRecordInitializer dnsSrvRecordInitializer);
20+
DnsSrvRecordMonitor create(String hostName, String srvServiceName, DnsSrvRecordInitializer dnsSrvRecordInitializer);
2121
}

driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ final class LoadBalancedCluster implements Cluster {
102102
initializationCompleted = true;
103103
} else {
104104
notNull("dnsSrvRecordMonitorFactory", dnsSrvRecordMonitorFactory);
105-
dnsSrvRecordMonitor = dnsSrvRecordMonitorFactory.create(settings.getSrvHost(), new DnsSrvRecordInitializer() {
105+
dnsSrvRecordMonitor = dnsSrvRecordMonitorFactory.create(settings.getSrvHost(), settings.getSrvServiceName(), new DnsSrvRecordInitializer() {
106106

107107
@Override
108108
public void initialize(final Collection<ServerAddress> hosts) {

driver-core/src/main/com/mongodb/internal/dns/DefaultDnsResolver.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,17 @@ The priority and weight are ignored, and we just concatenate the host (after rem
5252
domain equal to the domain of the srvHost.
5353
*/
5454
@Override
55-
public List<String> resolveHostFromSrvRecords(final String srvHost) {
55+
public List<String> resolveHostFromSrvRecords(final String srvHost, final String srvServiceName) {
5656
String srvHostDomain = srvHost.substring(srvHost.indexOf('.') + 1);
5757
List<String> srvHostDomainParts = asList(srvHostDomain.split("\\."));
5858
List<String> hosts = new ArrayList<String>();
5959
InitialDirContext dirContext = createDnsDirContext();
6060
try {
61-
Attributes attributes = dirContext.getAttributes("_mongodb._tcp." + srvHost, new String[]{"SRV"});
61+
String resourceRecordName = "_" + srvServiceName + "._tcp." + srvHost;
62+
Attributes attributes = dirContext.getAttributes(resourceRecordName, new String[]{"SRV"});
6263
Attribute attribute = attributes.get("SRV");
6364
if (attribute == null) {
64-
throw new MongoConfigurationException("No SRV records available for host " + srvHost);
65+
throw new MongoConfigurationException(format("No SRV records available for %s", resourceRecordName));
6566
}
6667
NamingEnumeration<?> srvRecordEnumeration = attribute.getAll();
6768
while (srvRecordEnumeration.hasMore()) {

driver-core/src/main/com/mongodb/internal/dns/DnsResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*/
2626
public interface DnsResolver {
2727

28-
List<String> resolveHostFromSrvRecords(String srvHost);
28+
List<String> resolveHostFromSrvRecords(String srvHost, String srvServiceName);
2929

3030
String resolveAdditionalQueryParametersFromTxtRecords(String host);
3131
}

0 commit comments

Comments
 (0)