Skip to content

Commit

Permalink
feat: directConnection adds unify behavior for replica set discovery
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Thomas Reggi committed Apr 9, 2020
1 parent 60e31b0 commit c5d60fc
Show file tree
Hide file tree
Showing 81 changed files with 1,608 additions and 76 deletions.
12 changes: 9 additions & 3 deletions lib/core/sdam/topology.js
Original file line number Diff line number Diff line change
Expand Up @@ -813,10 +813,16 @@ function parseStringSeedlist(seedlist) {
}

function topologyTypeFromSeedlist(seedlist, options) {
if (options.directConnection) {
return TopologyType.Single;
}

const replicaSet = options.replicaSet || options.setName || options.rs_name;
if (seedlist.length === 1 && !replicaSet) return TopologyType.Single;
if (replicaSet) return TopologyType.ReplicaSetNoPrimary;
return TopologyType.Unknown;
if (replicaSet == null) {
return TopologyType.Unknown;
}

return TopologyType.ReplicaSetNoPrimary;
}

function randomSelection(array) {
Expand Down
20 changes: 17 additions & 3 deletions lib/core/sdam/topology_description.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class TopologyDescription {
}

if (topologyType === TopologyType.Unknown) {
if (serverType === ServerType.Standalone) {
if (serverType === ServerType.Standalone && this.servers.size !== 1) {
serverDescriptions.delete(address);
} else {
topologyType = topologyTypeForServerType(serverType);
Expand Down Expand Up @@ -274,8 +274,22 @@ class TopologyDescription {
}

function topologyTypeForServerType(serverType) {
if (serverType === ServerType.Mongos) return TopologyType.Sharded;
if (serverType === ServerType.RSPrimary) return TopologyType.ReplicaSetWithPrimary;
if (serverType === ServerType.Standalone) {
return TopologyType.Single;
}

if (serverType === ServerType.Mongos) {
return TopologyType.Sharded;
}

if (serverType === ServerType.RSPrimary) {
return TopologyType.ReplicaSetWithPrimary;
}

if (serverType === ServerType.RSGhost || serverType === ServerType.Unknown) {
return TopologyType.Unknown;
}

return TopologyType.ReplicaSetNoPrimary;
}

Expand Down
32 changes: 27 additions & 5 deletions lib/core/uri_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ function matchesParentDomain(srvAddress, parentDomain) {
function parseSrvConnectionString(uri, options, callback) {
const result = URL.parse(uri, true);

if (options.directConnection || options.directconnection) {
return callback(new MongoParseError('directConnection not supported with SRV URI'));
}

if (result.hostname.split('.').length < 3) {
return callback(new MongoParseError('URI does not have hostname, domain name and tld'));
}
Expand Down Expand Up @@ -215,7 +219,8 @@ const CASE_TRANSLATION = {
tlscertificatekeyfile: 'tlsCertificateKeyFile',
tlscertificatekeyfilepassword: 'tlsCertificateKeyFilePassword',
wtimeout: 'wTimeoutMS',
j: 'journal'
j: 'journal',
directconnection: 'directConnection'
};

/**
Expand Down Expand Up @@ -565,10 +570,6 @@ function parseConnectionString(uri, options, callback) {
return callback(new MongoParseError('Invalid protocol provided'));
}

if (protocol === PROTOCOL_MONGODB_SRV) {
return parseSrvConnectionString(uri, options, callback);
}

const dbAndQuery = cap[4].split('?');
const db = dbAndQuery.length > 0 ? dbAndQuery[0] : null;
const query = dbAndQuery.length > 1 ? dbAndQuery[1] : null;
Expand All @@ -581,6 +582,11 @@ function parseConnectionString(uri, options, callback) {
}

parsedOptions = Object.assign({}, parsedOptions, options);

if (protocol === PROTOCOL_MONGODB_SRV) {
return parseSrvConnectionString(uri, parsedOptions, callback);
}

const auth = { username: null, password: null, db: db && db !== '' ? qs.unescape(db) : null };
if (parsedOptions.auth) {
// maintain support for legacy options passed into `MongoClient`
Expand Down Expand Up @@ -674,6 +680,22 @@ function parseConnectionString(uri, options, callback) {
return callback(new MongoParseError('No hostname or hostnames provided in connection string'));
}

const directConnection = !!parsedOptions.directConnection;
if (directConnection && hosts.length !== 1) {
// If the option is set to true, the driver MUST validate that there is exactly one host given
// in the host list in the URI, and fail client creation otherwise.
return callback(new MongoParseError('directConnection option requires exactly one host'));
}

// NOTE: this behavior will go away in v4.0, we will always auto discover there
if (
parsedOptions.directConnection == null &&
hosts.length === 1 &&
parsedOptions.replicaSet == null
) {
parsedOptions.directConnection = true;
}

const result = {
hosts: hosts,
auth: auth.db || auth.username ? auth : null,
Expand Down
2 changes: 2 additions & 0 deletions lib/mongo_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ const validOptions = require('./operations/connect').validOptions;
* @param {boolean} [options.useUnifiedTopology] Enables the new unified topology layer
* @param {AutoEncrypter~AutoEncryptionOptions} [options.autoEncryption] Optionally enable client side auto encryption
* @param {DriverInfoOptions} [options.driverInfo] Allows a wrapping driver to amend the client metadata generated by the driver to include information about the wrapping driver
* @param {boolean} [options.directConnection=false] Enable directConnection
* @param {MongoClient~connectCallback} [callback] The command result callback
* @return {MongoClient} a MongoClient instance
*/
Expand Down Expand Up @@ -406,6 +407,7 @@ MongoClient.prototype.isConnected = function(options) {
* @param {number} [options.numberOfRetries=5] The number of retries for a tailable cursor
* @param {boolean} [options.auto_reconnect=true] Enable auto reconnecting for single server instances
* @param {number} [options.minSize] If present, the connection pool will be initialized with minSize connections, and will never dip below minSize connections
* @param {boolean} [options.directConnection=false] Enable directConnection
* @param {MongoClient~connectCallback} [callback] The command result callback
* @return {Promise<MongoClient>} returns Promise if no callback passed
*/
Expand Down
3 changes: 2 additions & 1 deletion lib/operations/connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ const validOptionNames = [
'tlsCertificateKeyFilePassword',
'minHeartbeatFrequencyMS',
'heartbeatFrequencyMS',
'waitQueueTimeoutMS'
'waitQueueTimeoutMS',
'directConnection'
];

const ignoreOptionNames = ['native_parser'];
Expand Down
16 changes: 13 additions & 3 deletions test/functional/core/replset_state.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,21 @@ describe('ReplicaSet state', function() {

fs.readdirSync(path)
.filter(x => x.indexOf('.json') !== -1)
.filter(x => !x.includes('repeated'))
.forEach(x => {
var testData = require(f('%s/%s', path, x));
const testData = require(f('%s/%s', path, x));
const description = testData.description;
it(description, function(done) {
if (
description.match(/Repeated ismaster response must be processed/) ||
description.match(/Primary mismatched me is not removed/) ||
description.match(/replicaSet URI option causes starting topology to be RSNP/) ||
description.match(/Discover secondary with directConnection URI option/) ||
description.match(/Discover ghost with directConnection URI option/)
) {
this.skip();
return;
}

it(testData.description, function(done) {
executeEntry(testData, done);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"uri": "mongodb+srv://test3.test.build.10gen.cc/?directConnection=false",
"seeds": [
"localhost.test.build.10gen.cc:27017"
],
"hosts": [
"localhost:27017",
"localhost:27018",
"localhost:27019"
],
"options": {
"ssl": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
uri: "mongodb+srv://test3.test.build.10gen.cc/?directConnection=false"
seeds:
- localhost.test.build.10gen.cc:27017
hosts:
- localhost:27017
- localhost:27018
- localhost:27019
options:
ssl: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"uri": "mongodb+srv://test3.test.build.10gen.cc/?directConnection=true",
"seeds": [],
"hosts": [],
"error": true,
"comment": "Should fail because directConnection=true is incompatible with SRV URIs."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
uri: "mongodb+srv://test3.test.build.10gen.cc/?directConnection=true"
seeds: []
hosts: []
error: true
comment: Should fail because directConnection=true is incompatible with SRV URIs.
2 changes: 2 additions & 0 deletions test/spec/server-discovery-and-monitoring/rs/compatible.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
description: "Replica set member with large maxWireVersion"

uri: "mongodb://a,b/?replicaSet=rs"

phases: [
{
responses: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
description: "Replica set member and an unknown server"

uri: "mongodb://a,b/?replicaSet=rs"

phases: [
{
responses: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"description": "Discover arbiters",
"uri": "mongodb://a/?replicaSet=rs",
"description": "Discover arbiters with directConnection URI option",
"uri": "mongodb://a/?directConnection=false",
"phases": [
{
"responses": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
description: "Discover arbiters"
description: "Discover arbiters with directConnection URI option"

uri: "mongodb://a/?replicaSet=rs"
uri: "mongodb://a/?directConnection=false"

phases: [

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"description": "Discover arbiters with replicaSet URI option",
"uri": "mongodb://a/?replicaSet=rs",
"phases": [
{
"responses": [
[
"a:27017",
{
"ok": 1,
"ismaster": true,
"hosts": [
"a:27017"
],
"arbiters": [
"b:27017"
],
"setName": "rs",
"minWireVersion": 0,
"maxWireVersion": 6
}
]
],
"outcome": {
"servers": {
"a:27017": {
"type": "RSPrimary",
"setName": "rs"
},
"b:27017": {
"type": "Unknown",
"setName": null
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
description: "Discover arbiters with replicaSet URI option"

uri: "mongodb://a/?replicaSet=rs"

phases: [

{
responses: [

["a:27017", {

ok: 1,
ismaster: true,
hosts: ["a:27017"],
arbiters: ["b:27017"],
setName: "rs",
minWireVersion: 0,
maxWireVersion: 6
}]
],

outcome: {

servers: {

"a:27017": {

type: "RSPrimary",
setName: "rs"
},

"b:27017": {

type: "Unknown",
setName:
}
},
topologyType: "ReplicaSetWithPrimary",
logicalSessionTimeoutMinutes: null,
setName: "rs"
}
}
]
31 changes: 31 additions & 0 deletions test/spec/server-discovery-and-monitoring/rs/discover_ghost.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"description": "Discover ghost with directConnection URI option",
"uri": "mongodb://b/?directConnection=false",
"phases": [
{
"responses": [
[
"b:27017",
{
"ok": 1,
"ismaster": false,
"isreplicaset": true,
"minWireVersion": 0,
"maxWireVersion": 6
}
]
],
"outcome": {
"servers": {
"b:27017": {
"type": "RSGhost",
"setName": null
}
},
"topologyType": "Unknown",
"logicalSessionTimeoutMinutes": null,
"setName": null
}
}
]
}
35 changes: 35 additions & 0 deletions test/spec/server-discovery-and-monitoring/rs/discover_ghost.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
description: "Discover ghost with directConnection URI option"

uri: "mongodb://b/?directConnection=false"

phases: [

{
responses: [

["b:27017", {

ok: 1,
ismaster: false,
isreplicaset: true,
minWireVersion: 0,
maxWireVersion: 6
}]
],

outcome: {

servers: {

"b:27017": {

type: "RSGhost",
setName:
}
},
topologyType: "Unknown",
logicalSessionTimeoutMinutes: null,
setName:
}
}
]

0 comments on commit c5d60fc

Please sign in to comment.