Skip to content

Commit

Permalink
NODE-931 Validates all the options for MongoClient.connect and fixes …
Browse files Browse the repository at this point in the history
…missing connection settings
  • Loading branch information
christkv committed Feb 10, 2017
1 parent 38c037c commit 39102e1
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 14 deletions.
Expand Up @@ -63,12 +63,17 @@ The table below shows all settings and what topology they affect.
| **ignoreUndefined** | Server, ReplicaSet, Mongos | boolean | false | Specify if the BSON serializer should ignore undefined fields. |
| **raw** | Server, ReplicaSet, Mongos | boolean | false | Return document results as raw BSON buffers. |
| **promoteLongs** | Server, ReplicaSet, Mongos | boolean | true | Promotes Long values to number if they fit inside the 53 bits resolution. |
| **promoteBuffers** | Server, ReplicaSet, Mongos | boolean | false | Promotes Binary BSON values to native Node Buffers. |
| **promoteValues** | Server, ReplicaSet, Mongos | boolean | true | Promotes BSON values to native types where possible, set to false to only receive wrapper types. |
| **domainsEnabled** | Server, ReplicaSet, Mongos | boolean | false | Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit. |
| **bufferMaxEntries** | Server, ReplicaSet, Mongos | integer | -1 | Sets a cap on how many operations the driver will buffer up before giving up on getting a working connection, default is -1 which is unlimited. |
| **readPreference** | Server, ReplicaSet, Mongos | object | null | The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). |
| **pkFactory** | Server, ReplicaSet, Mongos | object | null | A primary key factory object for generation of custom _id keys. |
| **promiseLibrary** | Server, ReplicaSet, Mongos | object | null | A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible. |
| **readConcern** | Server, ReplicaSet, Mongos | object | null | Specify a read concern for the collection. (only MongoDB 3.2 or higher supported). |
| **maxStalenessSeconds** | Replicaset | number | null | Specify a maxStalenessSeconds value for secondary reads, minimum is 90 seconds |
| **loggerLevel** | Server, Replicaset, Mongos | string | null | Specify the log level used by the driver logger (error/warn/info/debug) |
| **logger** | Server, Replicaset, Mongos | object | null | Specify a customer logger mechanism, can be used to log using your app level logger |
# Ensure you connection string is valid for Replica Sets

The connection string passed to the driver must use the fully qualified host names for the servers as set in the replicaset config. Given the following configuration settings for your replicaset.
Expand Down
132 changes: 120 additions & 12 deletions lib/mongo_client.js
Expand Up @@ -26,6 +26,36 @@ var parse = require('./url_parser')
* db.close();
* });
*/
var validOptionNames = ['poolSize', 'ssl', 'sslValidate', 'sslCA', 'sslCert',
'sslKey', 'sslPass', 'autoReconnect', 'noDelay', 'keepAlive', 'connectTimeoutMS',
'socketTimeoutMS', 'reconnectTries', 'reconnectInterval', 'ha', 'haInterval',
'replicaSet', 'secondaryAcceptableLatencyMS', 'acceptableLatencyMS',
'connectWithNoPrimary', 'authSource', 'w', 'wtimeout', 'j', 'forceServerObjectId',
'serializeFunctions', 'ignoreUndefined', 'raw', 'promoteLongs', 'bufferMaxEntries',
'readPreference', 'pkFactory', 'promiseLibrary', 'readConcern', 'maxStalenessSeconds',
'loggerLevel', 'logger', 'promoteValues', 'promoteBuffers', 'promoteLongs',
'domainsEnabled', 'keepAliveInitialDelay', 'checkServerIdentity'];
var ignoreOptionNames = ['native_parser'];
var legacyOptionNames = ['server', 'replset', 'mongos', 'db'];

function validOptions(options) {
var _validOptions = validOptionNames.concat(legacyOptionNames);

for(var name in options) {
if(ignoreOptionNames.indexOf(name) != -1) {
continue;
}

if(_validOptions.indexOf(name) == -1) {
return new MongoError(f('option %s is not supported', name));
}

if(legacyOptionNames.indexOf(name) == -1) {
console.warn(f('the server/replset/mongos options are deprecated, '
+ 'all their options are supported at the top level of the options object [%s]', validOptionNames));
}
}
}

/**
* Creates a new MongoClient instance
Expand All @@ -49,13 +79,48 @@ function MongoClient() {
*
* @method
* @param {string} url The connection URI string
* @param {object} [options=null] Optional settings.
* @param {boolean} [options.uri_decode_auth=false] Uri decode the user name and password for authentication
* @param {object} [options.db=null] A hash of options to set on the db object, see **Db constructor**
* @param {object} [options.server=null] A hash of options to set on the server objects, see **Server** constructor**
* @param {object} [options.replSet=null] A hash of options to set on the replSet object, see **ReplSet** constructor**
* @param {object} [options.mongos=null] A hash of options to set on the mongos object, see **Mongos** constructor**
* @param {object} [options] Optional settings.
* @param {number} [options.poolSize=5] poolSize The maximum size of the individual server pool.
* @param {boolean} [options.ssl=false] Enable SSL connection.
* @param {Buffer} [options.sslCA=undefined] SSL Certificate store binary buffer
* @param {Buffer} [options.sslCert=undefined] SSL Certificate binary buffer
* @param {Buffer} [options.sslKey=undefined] SSL Key file binary buffer
* @param {string} [options.sslPass=undefined] SSL Certificate pass phrase
* @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
* @param {boolean} [options.autoReconnect=true] Enable autoReconnect for single server instances
* @param {boolean} [options.noDelay=true] TCP Connection no delay
* @param {boolean} [options.keepAlive=0] The number of milliseconds to wait before initiating keepAlive on the TCP socket.
* @param {number} [options.connectTimeoutMS=30000] TCP Connection timeout setting
* @param {number} [options.socketTimeoutMS=30000] TCP Socket timeout setting
* @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
* @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
* @param {boolean} [options.ha=true] Control if high availability monitoring runs for Replicaset or Mongos proxies.
* @param {number} [options.haInterval=10000] The High availability period for replicaset inquiry
* @param {string} [options.replicaSet=undefined] The Replicaset set name
* @param {number} [options.secondaryAcceptableLatencyMS=15] Cutoff latency point in MS for Replicaset member selection
* @param {number} [options.acceptableLatencyMS=15] Cutoff latency point in MS for Mongos proxies selection.
* @param {boolean} [options.connectWithNoPrimary=false] Sets if the driver should connect even if no primary is available
* @param {string} [options.authSource=undefined] Define the database to authenticate against
* @param {(number|string)} [options.w=null] The write concern.
* @param {number} [options.wtimeout=null] The write concern timeout.
* @param {boolean} [options.j=false] Specify a journal write concern.
* @param {boolean} [options.forceServerObjectId=false] Force server to assign _id values instead of driver.
* @param {boolean} [options.serializeFunctions=false] Serialize functions on any object.
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
* @param {boolean} [options.raw=false] Return document results as raw BSON buffers.
* @param {boolean} [options.promoteLongs=true] Promotes Long values to number if they fit inside the 53 bits resolution.
* @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
* @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
* @param {number} [options.bufferMaxEntries=-1] Sets a cap on how many operations the driver will buffer up before giving up on getting a working connection, default is -1 which is unlimited.
* @param {(ReadPreference|string)} [options.readPreference=null] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
* @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
* @param {object} [options.pkFactory=null] A primary key factory object for generation of custom _id keys.
* @param {object} [options.promiseLibrary=null] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
* @param {object} [options.readConcern=null] Specify a read concern for the collection. (only MongoDB 3.2 or higher supported)
* @param {object} [options.readConcern.level='local'] Specify a read concern level for the collection operations, one of [local|majority]. (only MongoDB 3.2 or higher supported)
* @param {number} [options.maxStalenessSeconds=undefined] The max staleness to secondary reads (values under 10 seconds cannot be guaranteed);
* @param {string} [options.loggerLevel=undefined] The logging level (error/warn/info/debug)
* @param {object} [options.logger=undefined] Custom logger object
* @param {MongoClient~connectCallback} [callback] The command result callback
* @return {Promise} returns Promise if no callback passed
*/
Expand All @@ -74,13 +139,48 @@ var define = MongoClient.define = new Define('MongoClient', MongoClient, false);
* @method
* @static
* @param {string} url The connection URI string
* @param {object} [options=null] Optional settings.
* @param {boolean} [options.uri_decode_auth=false] Uri decode the user name and password for authentication
* @param {object} [options.db=null] A hash of options to set on the db object, see **Db constructor**
* @param {object} [options.server=null] A hash of options to set on the server objects, see **Server** constructor**
* @param {object} [options.replSet=null] A hash of options to set on the replSet object, see **ReplSet** constructor**
* @param {object} [options.mongos=null] A hash of options to set on the mongos object, see **Mongos** constructor**
* @param {object} [options] Optional settings.
* @param {number} [options.poolSize=5] poolSize The maximum size of the individual server pool.
* @param {boolean} [options.ssl=false] Enable SSL connection.
* @param {Buffer} [options.sslCA=undefined] SSL Certificate store binary buffer
* @param {Buffer} [options.sslCert=undefined] SSL Certificate binary buffer
* @param {Buffer} [options.sslKey=undefined] SSL Key file binary buffer
* @param {string} [options.sslPass=undefined] SSL Certificate pass phrase
* @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
* @param {boolean} [options.autoReconnect=true] Enable autoReconnect for single server instances
* @param {boolean} [options.noDelay=true] TCP Connection no delay
* @param {boolean} [options.keepAlive=0] The number of milliseconds to wait before initiating keepAlive on the TCP socket.
* @param {number} [options.connectTimeoutMS=30000] TCP Connection timeout setting
* @param {number} [options.socketTimeoutMS=30000] TCP Socket timeout setting
* @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
* @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
* @param {boolean} [options.ha=true] Control if high availability monitoring runs for Replicaset or Mongos proxies.
* @param {number} [options.haInterval=10000] The High availability period for replicaset inquiry
* @param {string} [options.replicaSet=undefined] The Replicaset set name
* @param {number} [options.secondaryAcceptableLatencyMS=15] Cutoff latency point in MS for Replicaset member selection
* @param {number} [options.acceptableLatencyMS=15] Cutoff latency point in MS for Mongos proxies selection.
* @param {boolean} [options.connectWithNoPrimary=false] Sets if the driver should connect even if no primary is available
* @param {string} [options.authSource=undefined] Define the database to authenticate against
* @param {(number|string)} [options.w=null] The write concern.
* @param {number} [options.wtimeout=null] The write concern timeout.
* @param {boolean} [options.j=false] Specify a journal write concern.
* @param {boolean} [options.forceServerObjectId=false] Force server to assign _id values instead of driver.
* @param {boolean} [options.serializeFunctions=false] Serialize functions on any object.
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
* @param {boolean} [options.raw=false] Return document results as raw BSON buffers.
* @param {boolean} [options.promoteLongs=true] Promotes Long values to number if they fit inside the 53 bits resolution.
* @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
* @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
* @param {number} [options.bufferMaxEntries=-1] Sets a cap on how many operations the driver will buffer up before giving up on getting a working connection, default is -1 which is unlimited.
* @param {(ReadPreference|string)} [options.readPreference=null] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
* @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
* @param {object} [options.pkFactory=null] A primary key factory object for generation of custom _id keys.
* @param {object} [options.promiseLibrary=null] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
* @param {object} [options.readConcern=null] Specify a read concern for the collection. (only MongoDB 3.2 or higher supported)
* @param {object} [options.readConcern.level='local'] Specify a read concern level for the collection operations, one of [local|majority]. (only MongoDB 3.2 or higher supported)
* @param {number} [options.maxStalenessSeconds=undefined] The max staleness to secondary reads (values under 10 seconds cannot be guaranteed);
* @param {string} [options.loggerLevel=undefined] The logging level (error/warn/info/debug)
* @param {object} [options.logger=undefined] Custom logger object
* @param {MongoClient~connectCallback} [callback] The command result callback
* @return {Promise} returns Promise if no callback passed
*/
Expand All @@ -90,6 +190,9 @@ MongoClient.connect = function(url, options, callback) {
options = args.length ? args.shift() : null;
options = options || {};

// Validate options object
var err = validOptions(options);

// Get the promiseLibrary
var promiseLibrary = options.promiseLibrary;

Expand All @@ -102,13 +205,18 @@ MongoClient.connect = function(url, options, callback) {
// Return a promise
if(typeof callback != 'function') {
return new promiseLibrary(function(resolve, reject) {
// Did we have a validation error
if(err) return reject(err);
// Attempt to connect
connect(url, options, function(err, db) {
if(err) return reject(err);
resolve(db);
});
});
}

// Did we have a validation error
if(err) return callback(err);
// Fallback to callback based connect
connect(url, options, callback);
}
Expand Down
3 changes: 2 additions & 1 deletion lib/replset.js
Expand Up @@ -45,7 +45,7 @@ var legalOptionNames = ['ha', 'haInterval', 'replicaSet', 'rs_name', 'secondaryA
, 'store', 'auto_reconnect', 'autoReconnect', 'emitError'
, 'keepAlive', 'noDelay', 'connectTimeoutMS', 'socketTimeoutMS', 'strategy', 'debug'
, 'loggerLevel', 'logger', 'reconnectTries', 'appname', 'domainsEnabled'
, 'servername', 'promoteLongs', 'promoteValues', 'promoteBuffers'];
, 'servername', 'promoteLongs', 'promoteValues', 'promoteBuffers', 'maxStalenessSeconds'];

// Get package.json variable
var driverVersion = require(__dirname + '/../package.json').version;
Expand Down Expand Up @@ -81,6 +81,7 @@ var release = os.release();
* @param {number} [options.socketOptions.connectTimeoutMS=10000] TCP Connection timeout setting
* @param {number} [options.socketOptions.socketTimeoutMS=0] TCP Socket timeout setting
* @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
* @param {number} [options.maxStalenessSeconds=undefined] The max staleness to secondary reads (values under 10 seconds cannot be guaranteed);
* @fires ReplSet#connect
* @fires ReplSet#ha
* @fires ReplSet#joined
Expand Down
2 changes: 2 additions & 0 deletions test/functional/authentication_tests.js
Expand Up @@ -658,6 +658,7 @@ var setUp = function(configuration, options, callback) {
dbpath: f('%s/../db/31001', __dirname),
}
}, {
// arbiter: true,
options: {
bind_ip: 'localhost', port: 31002,
dbpath: f('%s/../db/31002', __dirname),
Expand Down Expand Up @@ -718,6 +719,7 @@ exports['Should correctly handle replicaset master stepdown and stepup without l
// Add a user
db.admin().addUser("root", "root", {w:3, wtimeout: 25000}, function(err, result) {
test.equal(null, err);
// process.exit(0)

db.admin().authenticate("root", "root", function(err, result) {
test.equal(null, err);
Expand Down
41 changes: 41 additions & 0 deletions test/functional/mongo_client_options_tests.js
Expand Up @@ -20,6 +20,47 @@ exports['pass in server and db top level options'] = {
}
}

/**
* @ignore
*/
exports['pass in server and db top level options'] = {
metadata: { requires: { topology: 'single' } },

// The actual test we wish to run
test: function(configuration, test) {
var connect = configuration.require;

connect(configuration.url(),
{ autoReconnect: true, poolSize: 4 },
connectionTester(test, 'testConnectServerOptions', function(db) {
test.equal(1, db.serverConfig.poolSize);
test.equal(4, db.serverConfig.s.server.s.pool.size);
test.equal(true, db.serverConfig.autoReconnect);
db.close();
test.done();
}));
}
}

/**
* @ignore
*/
exports['should error on unexpected options'] = {
metadata: { requires: { topology: 'single' } },

// The actual test we wish to run
test: function(configuration, test) {
var connect = configuration.require;

connect(configuration.url(), {
autoReconnect: true, poolSize: 4, notlegal: {}
}, function(err, db) {
test.ok(err.message.indexOf('option notlegal is not supported') != -1);
test.done();
});
}
}

/**
* @ignore
*/
Expand Down
2 changes: 1 addition & 1 deletion test/functional/uri_tests.js
Expand Up @@ -187,7 +187,7 @@ exports['Should correctly connect using uri encoded username and password'] = {
db.addUser(user, pass, function(err) {
test.equal(null, err);
var uri = "mongodb://" + encodeURIComponent(user) + ":" + encodeURIComponent(pass) + "@localhost:27017/integration_tests";
MongoClient.connect(uri, {uri_decode_auth: true, native_parser:true}, function(err, authenticatedDb) {
MongoClient.connect(uri, {native_parser:true}, function(err, authenticatedDb) {
test.equal(null, err);

db.close();
Expand Down

0 comments on commit 39102e1

Please sign in to comment.