Skip to content

Commit c5d60fc

Browse files
author
Thomas Reggi
authored
feat: directConnection adds unify behavior for replica set discovery
Adds `directConnection` option to unify behavior around configuration for replica set discovery. Migrated mongodb/specifications tests from commit "e56f5eceed7729f8b9b43a4a1f76c7e5840db49f". Skips SDAM tests for legacy topology / behavior that we do not intend to introduce to the legacy topology types. Users should switch to the unified topology. NODE-2452
1 parent 60e31b0 commit c5d60fc

File tree

81 files changed

+1608
-76
lines changed

Some content is hidden

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

81 files changed

+1608
-76
lines changed

lib/core/sdam/topology.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -813,10 +813,16 @@ function parseStringSeedlist(seedlist) {
813813
}
814814

815815
function topologyTypeFromSeedlist(seedlist, options) {
816+
if (options.directConnection) {
817+
return TopologyType.Single;
818+
}
819+
816820
const replicaSet = options.replicaSet || options.setName || options.rs_name;
817-
if (seedlist.length === 1 && !replicaSet) return TopologyType.Single;
818-
if (replicaSet) return TopologyType.ReplicaSetNoPrimary;
819-
return TopologyType.Unknown;
821+
if (replicaSet == null) {
822+
return TopologyType.Unknown;
823+
}
824+
825+
return TopologyType.ReplicaSetNoPrimary;
820826
}
821827

822828
function randomSelection(array) {

lib/core/sdam/topology_description.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ class TopologyDescription {
161161
}
162162

163163
if (topologyType === TopologyType.Unknown) {
164-
if (serverType === ServerType.Standalone) {
164+
if (serverType === ServerType.Standalone && this.servers.size !== 1) {
165165
serverDescriptions.delete(address);
166166
} else {
167167
topologyType = topologyTypeForServerType(serverType);
@@ -274,8 +274,22 @@ class TopologyDescription {
274274
}
275275

276276
function topologyTypeForServerType(serverType) {
277-
if (serverType === ServerType.Mongos) return TopologyType.Sharded;
278-
if (serverType === ServerType.RSPrimary) return TopologyType.ReplicaSetWithPrimary;
277+
if (serverType === ServerType.Standalone) {
278+
return TopologyType.Single;
279+
}
280+
281+
if (serverType === ServerType.Mongos) {
282+
return TopologyType.Sharded;
283+
}
284+
285+
if (serverType === ServerType.RSPrimary) {
286+
return TopologyType.ReplicaSetWithPrimary;
287+
}
288+
289+
if (serverType === ServerType.RSGhost || serverType === ServerType.Unknown) {
290+
return TopologyType.Unknown;
291+
}
292+
279293
return TopologyType.ReplicaSetNoPrimary;
280294
}
281295

lib/core/uri_parser.js

+27-5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ function matchesParentDomain(srvAddress, parentDomain) {
3737
function parseSrvConnectionString(uri, options, callback) {
3838
const result = URL.parse(uri, true);
3939

40+
if (options.directConnection || options.directconnection) {
41+
return callback(new MongoParseError('directConnection not supported with SRV URI'));
42+
}
43+
4044
if (result.hostname.split('.').length < 3) {
4145
return callback(new MongoParseError('URI does not have hostname, domain name and tld'));
4246
}
@@ -215,7 +219,8 @@ const CASE_TRANSLATION = {
215219
tlscertificatekeyfile: 'tlsCertificateKeyFile',
216220
tlscertificatekeyfilepassword: 'tlsCertificateKeyFilePassword',
217221
wtimeout: 'wTimeoutMS',
218-
j: 'journal'
222+
j: 'journal',
223+
directconnection: 'directConnection'
219224
};
220225

221226
/**
@@ -565,10 +570,6 @@ function parseConnectionString(uri, options, callback) {
565570
return callback(new MongoParseError('Invalid protocol provided'));
566571
}
567572

568-
if (protocol === PROTOCOL_MONGODB_SRV) {
569-
return parseSrvConnectionString(uri, options, callback);
570-
}
571-
572573
const dbAndQuery = cap[4].split('?');
573574
const db = dbAndQuery.length > 0 ? dbAndQuery[0] : null;
574575
const query = dbAndQuery.length > 1 ? dbAndQuery[1] : null;
@@ -581,6 +582,11 @@ function parseConnectionString(uri, options, callback) {
581582
}
582583

583584
parsedOptions = Object.assign({}, parsedOptions, options);
585+
586+
if (protocol === PROTOCOL_MONGODB_SRV) {
587+
return parseSrvConnectionString(uri, parsedOptions, callback);
588+
}
589+
584590
const auth = { username: null, password: null, db: db && db !== '' ? qs.unescape(db) : null };
585591
if (parsedOptions.auth) {
586592
// maintain support for legacy options passed into `MongoClient`
@@ -674,6 +680,22 @@ function parseConnectionString(uri, options, callback) {
674680
return callback(new MongoParseError('No hostname or hostnames provided in connection string'));
675681
}
676682

683+
const directConnection = !!parsedOptions.directConnection;
684+
if (directConnection && hosts.length !== 1) {
685+
// If the option is set to true, the driver MUST validate that there is exactly one host given
686+
// in the host list in the URI, and fail client creation otherwise.
687+
return callback(new MongoParseError('directConnection option requires exactly one host'));
688+
}
689+
690+
// NOTE: this behavior will go away in v4.0, we will always auto discover there
691+
if (
692+
parsedOptions.directConnection == null &&
693+
hosts.length === 1 &&
694+
parsedOptions.replicaSet == null
695+
) {
696+
parsedOptions.directConnection = true;
697+
}
698+
677699
const result = {
678700
hosts: hosts,
679701
auth: auth.db || auth.username ? auth : null,

lib/mongo_client.js

+2
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ const validOptions = require('./operations/connect').validOptions;
145145
* @param {boolean} [options.useUnifiedTopology] Enables the new unified topology layer
146146
* @param {AutoEncrypter~AutoEncryptionOptions} [options.autoEncryption] Optionally enable client side auto encryption
147147
* @param {DriverInfoOptions} [options.driverInfo] Allows a wrapping driver to amend the client metadata generated by the driver to include information about the wrapping driver
148+
* @param {boolean} [options.directConnection=false] Enable directConnection
148149
* @param {MongoClient~connectCallback} [callback] The command result callback
149150
* @return {MongoClient} a MongoClient instance
150151
*/
@@ -406,6 +407,7 @@ MongoClient.prototype.isConnected = function(options) {
406407
* @param {number} [options.numberOfRetries=5] The number of retries for a tailable cursor
407408
* @param {boolean} [options.auto_reconnect=true] Enable auto reconnecting for single server instances
408409
* @param {number} [options.minSize] If present, the connection pool will be initialized with minSize connections, and will never dip below minSize connections
410+
* @param {boolean} [options.directConnection=false] Enable directConnection
409411
* @param {MongoClient~connectCallback} [callback] The command result callback
410412
* @return {Promise<MongoClient>} returns Promise if no callback passed
411413
*/

lib/operations/connect.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ const validOptionNames = [
153153
'tlsCertificateKeyFilePassword',
154154
'minHeartbeatFrequencyMS',
155155
'heartbeatFrequencyMS',
156-
'waitQueueTimeoutMS'
156+
'waitQueueTimeoutMS',
157+
'directConnection'
157158
];
158159

159160
const ignoreOptionNames = ['native_parser'];

test/functional/core/replset_state.test.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,21 @@ describe('ReplicaSet state', function() {
1212

1313
fs.readdirSync(path)
1414
.filter(x => x.indexOf('.json') !== -1)
15-
.filter(x => !x.includes('repeated'))
1615
.forEach(x => {
17-
var testData = require(f('%s/%s', path, x));
16+
const testData = require(f('%s/%s', path, x));
17+
const description = testData.description;
18+
it(description, function(done) {
19+
if (
20+
description.match(/Repeated ismaster response must be processed/) ||
21+
description.match(/Primary mismatched me is not removed/) ||
22+
description.match(/replicaSet URI option causes starting topology to be RSNP/) ||
23+
description.match(/Discover secondary with directConnection URI option/) ||
24+
description.match(/Discover ghost with directConnection URI option/)
25+
) {
26+
this.skip();
27+
return;
28+
}
1829

19-
it(testData.description, function(done) {
2030
executeEntry(testData, done);
2131
});
2232
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"uri": "mongodb+srv://test3.test.build.10gen.cc/?directConnection=false",
3+
"seeds": [
4+
"localhost.test.build.10gen.cc:27017"
5+
],
6+
"hosts": [
7+
"localhost:27017",
8+
"localhost:27018",
9+
"localhost:27019"
10+
],
11+
"options": {
12+
"ssl": true
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
uri: "mongodb+srv://test3.test.build.10gen.cc/?directConnection=false"
2+
seeds:
3+
- localhost.test.build.10gen.cc:27017
4+
hosts:
5+
- localhost:27017
6+
- localhost:27018
7+
- localhost:27019
8+
options:
9+
ssl: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"uri": "mongodb+srv://test3.test.build.10gen.cc/?directConnection=true",
3+
"seeds": [],
4+
"hosts": [],
5+
"error": true,
6+
"comment": "Should fail because directConnection=true is incompatible with SRV URIs."
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
uri: "mongodb+srv://test3.test.build.10gen.cc/?directConnection=true"
2+
seeds: []
3+
hosts: []
4+
error: true
5+
comment: Should fail because directConnection=true is incompatible with SRV URIs.

test/spec/server-discovery-and-monitoring/rs/compatible.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
description: "Replica set member with large maxWireVersion"
2+
23
uri: "mongodb://a,b/?replicaSet=rs"
4+
35
phases: [
46
{
57
responses: [

test/spec/server-discovery-and-monitoring/rs/compatible_unknown.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
description: "Replica set member and an unknown server"
2+
23
uri: "mongodb://a,b/?replicaSet=rs"
4+
35
phases: [
46
{
57
responses: [

test/spec/server-discovery-and-monitoring/rs/discover_arbiters.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"description": "Discover arbiters",
3-
"uri": "mongodb://a/?replicaSet=rs",
2+
"description": "Discover arbiters with directConnection URI option",
3+
"uri": "mongodb://a/?directConnection=false",
44
"phases": [
55
{
66
"responses": [

test/spec/server-discovery-and-monitoring/rs/discover_arbiters.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
description: "Discover arbiters"
1+
description: "Discover arbiters with directConnection URI option"
22

3-
uri: "mongodb://a/?replicaSet=rs"
3+
uri: "mongodb://a/?directConnection=false"
44

55
phases: [
66

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"description": "Discover arbiters with replicaSet URI option",
3+
"uri": "mongodb://a/?replicaSet=rs",
4+
"phases": [
5+
{
6+
"responses": [
7+
[
8+
"a:27017",
9+
{
10+
"ok": 1,
11+
"ismaster": true,
12+
"hosts": [
13+
"a:27017"
14+
],
15+
"arbiters": [
16+
"b:27017"
17+
],
18+
"setName": "rs",
19+
"minWireVersion": 0,
20+
"maxWireVersion": 6
21+
}
22+
]
23+
],
24+
"outcome": {
25+
"servers": {
26+
"a:27017": {
27+
"type": "RSPrimary",
28+
"setName": "rs"
29+
},
30+
"b:27017": {
31+
"type": "Unknown",
32+
"setName": null
33+
}
34+
},
35+
"topologyType": "ReplicaSetWithPrimary",
36+
"logicalSessionTimeoutMinutes": null,
37+
"setName": "rs"
38+
}
39+
}
40+
]
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
description: "Discover arbiters with replicaSet URI option"
2+
3+
uri: "mongodb://a/?replicaSet=rs"
4+
5+
phases: [
6+
7+
{
8+
responses: [
9+
10+
["a:27017", {
11+
12+
ok: 1,
13+
ismaster: true,
14+
hosts: ["a:27017"],
15+
arbiters: ["b:27017"],
16+
setName: "rs",
17+
minWireVersion: 0,
18+
maxWireVersion: 6
19+
}]
20+
],
21+
22+
outcome: {
23+
24+
servers: {
25+
26+
"a:27017": {
27+
28+
type: "RSPrimary",
29+
setName: "rs"
30+
},
31+
32+
"b:27017": {
33+
34+
type: "Unknown",
35+
setName:
36+
}
37+
},
38+
topologyType: "ReplicaSetWithPrimary",
39+
logicalSessionTimeoutMinutes: null,
40+
setName: "rs"
41+
}
42+
}
43+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"description": "Discover ghost with directConnection URI option",
3+
"uri": "mongodb://b/?directConnection=false",
4+
"phases": [
5+
{
6+
"responses": [
7+
[
8+
"b:27017",
9+
{
10+
"ok": 1,
11+
"ismaster": false,
12+
"isreplicaset": true,
13+
"minWireVersion": 0,
14+
"maxWireVersion": 6
15+
}
16+
]
17+
],
18+
"outcome": {
19+
"servers": {
20+
"b:27017": {
21+
"type": "RSGhost",
22+
"setName": null
23+
}
24+
},
25+
"topologyType": "Unknown",
26+
"logicalSessionTimeoutMinutes": null,
27+
"setName": null
28+
}
29+
}
30+
]
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
description: "Discover ghost with directConnection URI option"
2+
3+
uri: "mongodb://b/?directConnection=false"
4+
5+
phases: [
6+
7+
{
8+
responses: [
9+
10+
["b:27017", {
11+
12+
ok: 1,
13+
ismaster: false,
14+
isreplicaset: true,
15+
minWireVersion: 0,
16+
maxWireVersion: 6
17+
}]
18+
],
19+
20+
outcome: {
21+
22+
servers: {
23+
24+
"b:27017": {
25+
26+
type: "RSGhost",
27+
setName:
28+
}
29+
},
30+
topologyType: "Unknown",
31+
logicalSessionTimeoutMinutes: null,
32+
setName:
33+
}
34+
}
35+
]

0 commit comments

Comments
 (0)