Skip to content

Commit 2c926df

Browse files
authored
Support load-balanced mode (#698)
Support load-balanced mode in the synchronous driver. Reactive streams driver support to follow. This is an implementation of the 1.0.0 version of the load balancer support specification published to https://github.com/mongodb/specifications/blob/master/source/load-balancers/load-balancers.rst. JAVA-4078, JAVA-4079, JAVA-4080, JAVA-4081, JAVA-4082, JAVA-4083
1 parent e70d49f commit 2c926df

File tree

159 files changed

+6508
-306
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+6508
-306
lines changed

.evergreen/.evg.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,22 @@ functions:
240240
script: |
241241
DRIVERS_TOOLS="${DRIVERS_TOOLS}" sh ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-local.sh
242242
243+
"run load-balancer":
244+
- command: shell.exec
245+
params:
246+
script: |
247+
DRIVERS_TOOLS=${DRIVERS_TOOLS} MONGODB_URI=${MONGODB_URI} bash ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh start
248+
- command: expansions.update
249+
params:
250+
file: lb-expansion.yml
251+
252+
"stop load-balancer":
253+
- command: shell.exec
254+
params:
255+
script: |
256+
cd ${DRIVERS_TOOLS}/.evergreen
257+
DRIVERS_TOOLS=${DRIVERS_TOOLS} bash ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh stop
258+
243259
"run tests":
244260
- command: shell.exec
245261
type: test
@@ -262,6 +278,18 @@ functions:
262278
REQUIRE_API_VERSION=${REQUIRE_API_VERSION} \
263279
.evergreen/run-tests.sh
264280
281+
"run load-balancer tests":
282+
- command: shell.exec
283+
type: test
284+
params:
285+
working_dir: "src"
286+
script: |
287+
${PREPARE_SHELL}
288+
AUTH="${AUTH}" SSL="${SSL}" JDK="${JDK}" \
289+
SINGLE_MONGOS_LB_URI="${SINGLE_MONGOS_LB_URI}" \
290+
MULTI_MONGOS_LB_URI="${MULTI_MONGOS_LB_URI}" \
291+
.evergreen/run-load-balancer-tests.sh
292+
265293
"run reactive streams tck tests":
266294
- command: shell.exec
267295
type: test
@@ -695,6 +723,7 @@ post:
695723
- func: "upload working dir logs"
696724
- func: "upload test results"
697725
- func: "cleanup"
726+
- func: "stop load-balancer"
698727

699728
tasks:
700729

@@ -711,6 +740,12 @@ tasks:
711740
- func: "bootstrap mongo-orchestration"
712741
- func: "run tests"
713742

743+
- name: load-balancer-test
744+
commands:
745+
- func: "bootstrap mongo-orchestration"
746+
- func: "run load-balancer"
747+
- func: "run load-balancer tests"
748+
714749
- name: "scala-tests"
715750
commands:
716751
- func: "bootstrap mongo-orchestration"
@@ -1425,6 +1460,12 @@ buildvariants:
14251460
tasks:
14261461
- name: "test"
14271462

1463+
- matrix_name: "tests-load-balancer-secure"
1464+
matrix_spec: { auth: "auth", ssl: "ssl", jdk: ["jdk11"], version: ["latest"], topology: "sharded-cluster", os: "ubuntu" }
1465+
display_name: "Load Balancer ${version} ${auth} ${ssl} ${jdk} ${os}"
1466+
tasks:
1467+
- name: "load-balancer-test"
1468+
14281469
- matrix_name: "tests-slow"
14291470
matrix_spec: { auth: "noauth", ssl: "*", jdk: "jdk8", version: ["4.2"], topology: "*", os: "linux" }
14301471
display_name: "Slow: ${version} ${topology} ${ssl} ${jdk} ${os} "

.evergreen/run-load-balancer-tests.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/bin/bash
2+
3+
set -o xtrace # Write all commands first to stderr
4+
set -o errexit # Exit the script with error if any of the commands fail
5+
6+
# Supported/used environment variables:
7+
# AUTH Set to enable authentication. Values are: "auth" / "noauth" (default)
8+
# SSL Set to enable SSL. Values are "ssl" / "nossl" (default)
9+
# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java
10+
# SINGLE_MONGOS_LB_URI Set the URI pointing to a load balancer configured with a single mongos server
11+
# MULTI_MONGOS_LB_URI Set the URI pointing to a load balancer configured with multiple mongos servers
12+
13+
AUTH=${AUTH:-noauth}
14+
SSL=${SSL:-nossl}
15+
MONGODB_URI=${MONGODB_URI:-}
16+
JDK=${JDK:-jdk8}
17+
18+
export JAVA_HOME="/opt/java/jdk11"
19+
20+
############################################
21+
# Main Program #
22+
############################################
23+
24+
if [ "$SSL" != "nossl" ]; then
25+
# We generate the keystore and truststore on every run with the certs in the drivers-tools repo
26+
if [ ! -f client.pkc ]; then
27+
openssl pkcs12 -CAfile ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem -export -in ${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem -out client.pkc -password pass:bithere
28+
fi
29+
30+
cp ${JAVA_HOME}/lib/security/cacerts mongo-truststore
31+
${JAVA_HOME}/bin/keytool -importcert -trustcacerts -file ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem -keystore mongo-truststore -storepass changeit -storetype JKS -noprompt
32+
33+
# We add extra gradle arguments for SSL
34+
GRADLE_EXTRA_VARS="-Pssl.enabled=true -Pssl.keyStoreType=pkcs12 -Pssl.keyStore=$(pwd)/client.pkc -Pssl.keyStorePassword=bithere -Pssl.trustStoreType=jks -Pssl.trustStore=$(pwd)/mongo-truststore -Pssl.trustStorePassword=changeit"
35+
SINGLE_MONGOS_LB_URI="${SINGLE_MONGOS_LB_URI}&ssl=true&sslInvalidHostNameAllowed=true"
36+
MULTI_MONGOS_LB_URI="${MULTI_MONGOS_LB_URI}&ssl=true&sslInvalidHostNameAllowed=true"
37+
fi
38+
39+
echo "Running $AUTH tests over $SSL and connecting to $SINGLE_MONGOS_LB_URI"
40+
41+
echo "Running tests with ${JDK}"
42+
./gradlew -version
43+
44+
./gradlew -PjdkHome=/opt/java/${JDK} \
45+
-Dorg.mongodb.test.uri=${SINGLE_MONGOS_LB_URI} \
46+
-Dorg.mongodb.test.transaction.uri=${MULTI_MONGOS_LB_URI} \
47+
${GRADLE_EXTRA_VARS} --stacktrace --info --continue driver-sync:test --tests LoadBalancerTest

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@
228228
* {@link MongoClientSettings#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but
229229
* will change to "unspecified" in the next major release.</li>
230230
* <li>{@code directConnection=true|false}. If true the driver will set the connection to be a direct connection to the host.</li>
231+
* <li>{@code loadBalanced=true|false}. If true the driver will assume that it's connecting to MongoDB through a load balancer.</li>
231232
* </ul>
232233
*
233234
* @mongodb.driver.manual reference/connection-string Connection String Format
@@ -237,7 +238,8 @@ public class ConnectionString {
237238

238239
private static final String MONGODB_PREFIX = "mongodb://";
239240
private static final String MONGODB_SRV_PREFIX = "mongodb+srv://";
240-
private static final Set<String> ALLOWED_OPTIONS_IN_TXT_RECORD = new HashSet<String>(asList("authsource", "replicaset"));
241+
private static final Set<String> ALLOWED_OPTIONS_IN_TXT_RECORD =
242+
new HashSet<String>(asList("authsource", "replicaset", "loadbalanced"));
241243
private static final String UTF_8 = "UTF-8";
242244

243245
private static final Logger LOGGER = Loggers.getLogger("uri");
@@ -250,6 +252,7 @@ public class ConnectionString {
250252
private final String connectionString;
251253

252254
private Boolean directConnection;
255+
private Boolean loadBalanced;
253256
private ReadPreference readPreference;
254257
private WriteConcern writeConcern;
255258
private Boolean retryWrites;
@@ -401,6 +404,18 @@ public ConnectionString(final String connectionString) {
401404
}
402405
}
403406

407+
if (loadBalanced != null && loadBalanced) {
408+
if (directConnection != null) {
409+
throw new IllegalArgumentException("directConnection can not be specified with loadBalanced=true");
410+
}
411+
if (requiredReplicaSetName != null) {
412+
throw new IllegalArgumentException("replicaSet can not be specified with loadBalanced=true");
413+
}
414+
if (hosts.size() > 1) {
415+
throw new IllegalArgumentException("Only one host can be specified with loadBalanced=true");
416+
}
417+
}
418+
404419
credential = createCredentials(combinedOptionsMaps, userName, password);
405420
warnOnUnsupportedOptions(combinedOptionsMaps);
406421
}
@@ -448,6 +463,7 @@ public ConnectionString(final String connectionString) {
448463
GENERAL_OPTIONS_KEYS.add("uuidrepresentation");
449464

450465
GENERAL_OPTIONS_KEYS.add("directconnection");
466+
GENERAL_OPTIONS_KEYS.add("loadbalanced");
451467

452468
COMPRESSOR_KEYS.add("compressors");
453469
COMPRESSOR_KEYS.add("zlibcompressionlevel");
@@ -551,6 +567,8 @@ private void translateOptions(final Map<String, List<String>> optionsMap) {
551567
uuidRepresentation = createUuidRepresentation(value);
552568
} else if (key.equals("directconnection")) {
553569
directConnection = parseBoolean(value, "directconnection");
570+
} else if (key.equals("loadbalanced")) {
571+
loadBalanced = parseBoolean(value, "loadbalanced");
554572
}
555573
}
556574

@@ -1119,6 +1137,17 @@ public Boolean isDirectConnection() {
11191137
return directConnection;
11201138
}
11211139

1140+
/**
1141+
* Indicates if the connection is through a load balancer.
1142+
*
1143+
* @return true if a load-balanced connection
1144+
* @since 4.3
1145+
*/
1146+
@Nullable
1147+
public Boolean isLoadBalanced() {
1148+
return loadBalanced;
1149+
}
1150+
11221151
/**
11231152
* Get the unparsed connection string.
11241153
*

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ public final List<ServerDescription> choose(final ClusterDescription clusterDesc
135135
case SHARDED:
136136
case STANDALONE:
137137
return chooseForNonReplicaSet(clusterDescription);
138+
case LOAD_BALANCED:
139+
return clusterDescription.getServerDescriptions();
138140
case UNKNOWN:
139141
return Collections.emptyList();
140142
default:

driver-core/src/main/com/mongodb/assertions/Assertions.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ public static boolean assertFalse(final boolean value) throws AssertionError {
177177
return false;
178178
}
179179

180+
/**
181+
* @throws AssertionError Always
182+
*/
183+
public static void fail() throws AssertionError {
184+
throw new AssertionError();
185+
}
186+
180187
private Assertions() {
181188
}
182189
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,12 @@ public enum ClusterConnectionMode {
3030
/**
3131
* Connect to multiple servers in a cluster (either a replica set or multiple mongos servers)
3232
*/
33-
MULTIPLE
33+
MULTIPLE,
34+
35+
/**
36+
* Connect to one or more mongos servers via a load balancer
37+
*
38+
* @since 4.3
39+
*/
40+
LOAD_BALANCED
3441
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,16 @@ public Builder addClusterListener(final ClusterListener clusterListener) {
265265
*/
266266
public Builder applyConnectionString(final ConnectionString connectionString) {
267267
Boolean directConnection = connectionString.isDirectConnection();
268-
269-
if (connectionString.isSrvProtocol()) {
268+
Boolean loadBalanced = connectionString.isLoadBalanced();
269+
270+
if (loadBalanced != null && loadBalanced) {
271+
mode(ClusterConnectionMode.LOAD_BALANCED);
272+
if (connectionString.isSrvProtocol()) {
273+
srvHost(connectionString.getHosts().get(0));
274+
} else {
275+
hosts(singletonList(createServerAddress(connectionString.getHosts().get(0))));
276+
}
277+
} else if (connectionString.isSrvProtocol()) {
270278
mode(ClusterConnectionMode.MULTIPLE);
271279
srvHost(connectionString.getHosts().get(0));
272280
} else if ((directConnection != null && directConnection)
@@ -553,13 +561,17 @@ private ClusterSettings(final Builder builder) {
553561
}
554562
}
555563

564+
if (builder.mode == ClusterConnectionMode.LOAD_BALANCED && builder.srvHost == null && builder.hosts.size() != 1) {
565+
throw new IllegalArgumentException("Multiple hosts cannot be specified when in load balancing mode");
566+
}
567+
556568
srvHost = builder.srvHost;
557569
hosts = builder.hosts;
558570
if (srvHost != null) {
559571
if (builder.mode == ClusterConnectionMode.SINGLE) {
560572
throw new IllegalArgumentException("An SRV host name was provided but the connection mode is not MULTIPLE");
561573
}
562-
mode = ClusterConnectionMode.MULTIPLE;
574+
mode = builder.mode != null ? builder.mode : ClusterConnectionMode.MULTIPLE;
563575
} else {
564576
if (builder.mode == ClusterConnectionMode.SINGLE && builder.hosts.size() > 1) {
565577
throw new IllegalArgumentException("Can not directly connect to more than one server");

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ public enum ClusterType {
3737
*/
3838
SHARDED,
3939

40+
/**
41+
* A load-balanced cluster, connected via a single load balancer
42+
*
43+
* @since 4.3
44+
*/
45+
LOAD_BALANCED,
4046
/**
4147
* The cluster type is not yet known.
4248
*/

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

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import com.mongodb.ServerAddress;
2020
import com.mongodb.annotations.Immutable;
21+
import com.mongodb.lang.Nullable;
2122
import org.bson.BsonArray;
23+
import org.bson.types.ObjectId;
2224

2325
import java.util.ArrayList;
2426
import java.util.Collections;
@@ -34,6 +36,7 @@
3436
*/
3537
@Immutable
3638
public class ConnectionDescription {
39+
@Nullable private final ObjectId serviceId;
3740
private final ConnectionId connectionId;
3841
private final int maxWireVersion;
3942
private final ServerType serverType;
@@ -90,6 +93,28 @@ public ConnectionDescription(final ConnectionId connectionId, final int maxWireV
9093
public ConnectionDescription(final ConnectionId connectionId, final int maxWireVersion,
9194
final ServerType serverType, final int maxBatchCount, final int maxDocumentSize,
9295
final int maxMessageSize, final List<String> compressors, final BsonArray saslSupportedMechanisms) {
96+
this(null, connectionId, maxWireVersion, serverType, maxBatchCount, maxDocumentSize, maxMessageSize, compressors,
97+
saslSupportedMechanisms);
98+
}
99+
100+
/**
101+
* Construct an instance.
102+
*
103+
* @param serviceId the service id, which may be null
104+
* @param connectionId the connection id
105+
* @param maxWireVersion the max wire version
106+
* @param serverType the server type
107+
* @param maxBatchCount the max batch count
108+
* @param maxDocumentSize the max document size in bytes
109+
* @param maxMessageSize the max message size in bytes
110+
* @param compressors the available compressors on the connection
111+
* @param saslSupportedMechanisms the supported SASL mechanisms
112+
* @since 4.3
113+
*/
114+
public ConnectionDescription(@Nullable final ObjectId serviceId, final ConnectionId connectionId, final int maxWireVersion,
115+
final ServerType serverType, final int maxBatchCount, final int maxDocumentSize,
116+
final int maxMessageSize, final List<String> compressors, final BsonArray saslSupportedMechanisms) {
117+
this.serviceId = serviceId;
93118
this.connectionId = connectionId;
94119
this.serverType = serverType;
95120
this.maxBatchCount = maxBatchCount;
@@ -99,7 +124,6 @@ public ConnectionDescription(final ConnectionId connectionId, final int maxWireV
99124
this.compressors = notNull("compressors", Collections.unmodifiableList(new ArrayList<String>(compressors)));
100125
this.saslSupportedMechanisms = saslSupportedMechanisms;
101126
}
102-
103127
/**
104128
* Creates a new connection description with the set connection id
105129
*
@@ -109,8 +133,21 @@ public ConnectionDescription(final ConnectionId connectionId, final int maxWireV
109133
*/
110134
public ConnectionDescription withConnectionId(final ConnectionId connectionId) {
111135
notNull("connectionId", connectionId);
112-
return new ConnectionDescription(connectionId, maxWireVersion, serverType, maxBatchCount, maxDocumentSize, maxMessageSize,
113-
compressors, saslSupportedMechanisms);
136+
return new ConnectionDescription(serviceId, connectionId, maxWireVersion, serverType, maxBatchCount, maxDocumentSize,
137+
maxMessageSize, compressors, saslSupportedMechanisms);
138+
}
139+
140+
/**
141+
* Creates a new connection description with the given service id
142+
*
143+
* @param serviceId the service id
144+
* @return the new connection description
145+
* @since 4.3
146+
*/
147+
public ConnectionDescription withServiceId(final ObjectId serviceId) {
148+
notNull("serviceId", serviceId);
149+
return new ConnectionDescription(serviceId, connectionId, maxWireVersion, serverType, maxBatchCount, maxDocumentSize,
150+
maxMessageSize, compressors, saslSupportedMechanisms);
114151
}
115152

116153
/**
@@ -131,6 +168,17 @@ public ConnectionId getConnectionId() {
131168
return connectionId;
132169
}
133170

171+
/**
172+
* Gets the id of the service this connection is to
173+
*
174+
* @return the service id, which may be null
175+
* @since 4.3
176+
*/
177+
@Nullable
178+
public ObjectId getServiceId() {
179+
return serviceId;
180+
}
181+
134182
/**
135183
* The latest version of the wire protocol that this MongoDB server is capable of using to communicate with clients.
136184
*

0 commit comments

Comments
 (0)