diff --git a/HISTORY b/HISTORY index ac4206e555d..38812e82eb1 100644 --- a/HISTORY +++ b/HISTORY @@ -8,6 +8,7 @@ - Force correct setting of read_secondary based on the read preference (Issue #741) - If using read preferences with secondaries queries will not fail if primary is down (Issue #744) - noOpen connection for Db.connect removed as not compatible with autodetection of Mongo type +- Mongos connection with auth not working (Issue #737) 1.1.11 2012-10-10 ----------------- diff --git a/lib/mongodb/connection/repl_set.js b/lib/mongodb/connection/repl_set.js index 2fef97a252f..e1e1eb825f9 100644 --- a/lib/mongodb/connection/repl_set.js +++ b/lib/mongodb/connection/repl_set.js @@ -89,6 +89,8 @@ var ReplSet = exports.ReplSet = function(servers, options) { this._numberOfServersLeftToInitialize = 0; // Do we record server stats or not this.recordQueryStats = false; + // Update health try server + this.updateHealthServerTry = 0; // Get the readPreference var readPreference = this.options['readPreference']; @@ -251,23 +253,6 @@ ReplSet.prototype.isPrimary = function(config) { */ ReplSet.prototype.isReadPrimary = ReplSet.prototype.isPrimary; -/** - * @ignore - **/ -ReplSet.prototype._checkReplicaSet = function() { - if(!this.haEnabled) return false; - var currentTime = new Date().getTime(); - - if (this.__isDoingReplSetHealth) return false; - - if((currentTime - this.lastReplicaSetTime) >= this.replicasetStatusCheckInterval) { - this.lastReplicaSetTime = currentTime; - return true; - } else { - return false; - } -} - /** * @ignore */ @@ -304,31 +289,62 @@ ReplSet.prototype.allServerInstances = function() { } /** - * Refresh the health status / configuration of this replicaset. - * Detect failover, addition of new nodes, etc. + * Enables high availability pings. * - * @param {Db} db + * @ignore */ -ReplSet.prototype.updateHealth = function(db, callback) { - if(!this._checkReplicaSet()) { - return callback(null, null); - } - +ReplSet.prototype._enableHA = function () { var self = this; - self.__isDoingReplSetHealth = true; + return check(); + + function ping () { + if("disconnected" == self._serverState) return; + + if(Object.keys(self._state.addresses).length == 0) return; + var selectedServer = self._state.addresses[Object.keys(self._state.addresses)[self.updateHealthServerTry++]]; + if(self.updateHealthServerTry >= Object.keys(self._state.addresses).length) self.updateHealthServerTry = 0; + if(selectedServer == null) return check(); + + // If we have an active db instance + if(self.dbInstances.length > 0) { + var db = self.dbInstances[0]; + + // Create a new master connection + var _server = new Server(selectedServer.host, selectedServer.port, { + auto_reconnect: false, + returnIsMasterResults: true, + slaveOk: true, + socketOptions: { connectTimeoutMS: 1000} + }); - var cmd = DbCommand.createIsMasterCommand(db); - db._executeQueryCommand(cmd, {failFast:true}, function(err, res) { - if(err) { - self.__isDoingReplSetHealth = false; - return callback(err, null); + // Connect using the new _server connection to not impact the driver + // behavior on any errors we could possibly run into + _server.connect(db, function(err, result, _server) { + if(err) { + if(_server.close) _server.close(); + return check(); + } + + // Create is master command + var cmd = DbCommand.createIsMasterCommand(db); + // Execute is master command + db._executeQueryCommand(cmd, {failFast:true, connection: _server.checkoutReader()}, function(err, res) { + // Close the connection used + _server.close(); + // If error let's set perform another check + if(err) return check(); + // Validate the replicaset + self._validateReplicaset(res, db.auths, function() { + check(); + }); + }); + }); } + } - self._validateReplicaset(res, db.auths, function() { - self.__isDoingReplSetHealth = false; - callback(null, null); - }); - }) + function check () { + self._haTimer = setTimeout(ping, self.replicasetStatusCheckInterval); + } } /** @@ -336,11 +352,10 @@ ReplSet.prototype.updateHealth = function(db, callback) { */ ReplSet.prototype._validateReplicaset = function(result, auths, cb) { var self = this; - var res = result.documents[0]; // manage master node changes - if (res.primary && self._state.master.name != res.primary) { + if(res.primary && self._state.master && self._state.master.name != res.primary) { // Delete master record so we can rediscover it delete self._state.addresses[self._state.master.name]; @@ -801,34 +816,6 @@ ReplSet.prototype._handleOnFullSetup = function (parent) { this._enableHA(); } -/** - * Enables high availability pings. - * - * @ignore - */ -ReplSet.prototype._enableHA = function () { - var self = this; - return check(); - - function check () { - self._haTimer = setTimeout(ping, self.replicasetStatusCheckInterval); - } - - function ping () { - // TODO do we ever become reconnected? - // if so need to solve restarting ping - if("disconnected" == self._serverState) return; - - // If we are connected let's perform a healthcheck - if("connected" == self._serverState - && Array.isArray(self.dbInstances) && self.dbInstances.length > 0) { - self.updateHealth(self.dbInstances[0], check); - } else { - check(); - } - } -} - /** * Disables high availability pings. * diff --git a/lib/mongodb/connection/server.js b/lib/mongodb/connection/server.js index 70a21fc52a0..6c03a5c64f3 100644 --- a/lib/mongodb/connection/server.js +++ b/lib/mongodb/connection/server.js @@ -338,8 +338,6 @@ Server.prototype.connect = function(dbInstance, options, callback) { // Only execute callback if we have a caller // chained is for findAndModify as it does not respect write concerns if(callbackInfo && callbackInfo.callback && callbackInfo.info && Array.isArray(callbackInfo.info.chained)) { - // console.log("============================ chained") - // console.dir(callbackInfo) // Check if callback has already been fired (missing chain command) var chained = callbackInfo.info.chained; var numberOfFoundCallbacks = 0; @@ -347,9 +345,6 @@ Server.prototype.connect = function(dbInstance, options, callback) { if(dbInstanceObject._hasHandler(chained[i])) numberOfFoundCallbacks++; } - // console.log("numberOfFoundCallbacks :: " + numberOfFoundCallbacks) - // console.log("chained :: " + chained.length) - // If we have already fired then clean up rest of chain and move on if(numberOfFoundCallbacks != chained.length) { for(var i = 0; i < chained.length; i++) { @@ -401,9 +396,6 @@ Server.prototype.connect = function(dbInstance, options, callback) { // chained commands var firstResult = mongoReply && mongoReply.documents; - // console.log("====================================================== parse") - // console.dir(firstResult) - // Check for an error, if we have one let's trigger the callback and clean up // The chained callbacks if(firstResult[0].err != null || firstResult[0].errmsg != null) { diff --git a/lib/mongodb/db.js b/lib/mongodb/db.js index fd66797c463..8fd64d486af 100644 --- a/lib/mongodb/db.js +++ b/lib/mongodb/db.js @@ -2038,7 +2038,6 @@ var _finishConnecting = function(serverConfig, object, options, callback) { // Safe settings var safe = {}; // Build the safe paramter if needed - // console.dir(object) if(object.db_options.journal) safe.j = object.db_options.journal; if(object.db_options.w) safe.w = object.db_options.w; if(object.db_options.fsync) safe.fsync = object.db_options.fsync; diff --git a/lib/mongodb/gridfs/gridstore.js b/lib/mongodb/gridfs/gridstore.js index e104147084d..29410d2af97 100644 --- a/lib/mongodb/gridfs/gridstore.js +++ b/lib/mongodb/gridfs/gridstore.js @@ -440,19 +440,19 @@ var writeBuffer = function(self, buffer, close, callback) { * @api private */ var buildMongoObject = function(self, callback) { - // Keeps the final chunk number - var chunkNumber = 0; - var previousChunkSize = 0; - // Get the correct chunk Number, if we have an empty chunk return the previous chunk number - if(null != self.currentChunk && self.currentChunk.chunkNumber > 0 && self.currentChunk.position == 0) { - chunkNumber = self.currentChunk.chunkNumber - 1; - } else { - chunkNumber = self.currentChunk.chunkNumber; - previousChunkSize = self.currentChunk.position; - } - - // Calcuate the length - var length = self.currentChunk != null ? (chunkNumber * self.chunkSize + previousChunkSize) : 0; + // // Keeps the final chunk number + // var chunkNumber = 0; + // var previousChunkSize = 0; + // // Get the correct chunk Number, if we have an empty chunk return the previous chunk number + // if(null != self.currentChunk && self.currentChunk.chunkNumber > 0 && self.currentChunk.position == 0) { + // chunkNumber = self.currentChunk.chunkNumber - 1; + // } else { + // chunkNumber = self.currentChunk.chunkNumber; + // previousChunkSize = self.currentChunk.position; + // } + + // // Calcuate the length + // var length = self.currentChunk != null ? (chunkNumber * self.chunkSize + previousChunkSize) : 0; var mongoObject = { '_id': self.fileId, 'filename': self.filename, diff --git a/test/auxilliary/replicaset_auth_connection_handling_test.js b/test/auxilliary/replicaset_auth_connection_handling_test.js new file mode 100644 index 00000000000..acaf592fd04 --- /dev/null +++ b/test/auxilliary/replicaset_auth_connection_handling_test.js @@ -0,0 +1,82 @@ +var mongodb = process.env['TEST_NATIVE'] != null ? require('../../lib/mongodb').native() : require('../../lib/mongodb').pure(); + +var testCase = require('nodeunit').testCase, + debug = require('util').debug, + inspect = require('util').inspect, + nodeunit = require('nodeunit'), + gleak = require('../../dev/tools/gleak'), + Db = mongodb.Db, + Cursor = mongodb.Cursor, + Collection = mongodb.Collection, + Server = mongodb.Server, + ReadPreference = mongodb.ReadPreference, + ReplSetServers = mongodb.ReplSetServers, + ReplicaSetManager = require('../../test/tools/replica_set_manager').ReplicaSetManager, + Step = require("step"); + +var MONGODB = 'integration_tests'; +var serverManager = null; +var RS = RS == null ? null : RS; + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.setUp = function(callback) { + RS = new ReplicaSetManager({retries:120, + // auth:true, + journal:true, + arbiter_count:0, + secondary_count:2, + passive_count:0}); + RS.startSet(true, function(err, result) { + if(err != null) throw err; + // Finish setup + callback(); + }); +} + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.tearDown = function(callback) { + callback(); +} + +exports['Should correctly handle replicaset master stepdown and stepup without loosing auth'] = function(test) { + var replSet = new ReplSetServers( [ + new Server( 'localhost', 30000), + new Server( 'localhost', 30001) + ], + {rs_name:"replica-set-foo", poolSize:1} + ); + + // Connect + new Db('replicaset_test_auth', replSet, {safe:false}).open(function(err, db) { + // Just set auths for the manager to handle it correctly + RS.setAuths("root", "root"); + // Add a user + db.admin().addUser("root", "root", function(err, result) { + test.equal(null, err); + + db.admin().authenticate("root", "root", function(err, result) { + test.equal(null, err); + test.ok(result); + + RS.killPrimary(9, function(err, result) { + db.collection('replicaset_test_auth').insert({a:1}, {safe:true}, function(err, result) { + test.equal(null, err); + + db.close(); + test.done(); + }); + }); + }); + }); + }); +} \ No newline at end of file diff --git a/test/tools/replica_set_manager.js b/test/tools/replica_set_manager.js index 898222ae01a..cf58a0d0e65 100644 --- a/test/tools/replica_set_manager.js +++ b/test/tools/replica_set_manager.js @@ -18,7 +18,7 @@ var ReplicaSetManager = exports.ReplicaSetManager = function(options) { this.host = options["host"] != null ? options["host"] : "localhost"; this.retries = options["retries"] != null ? options["retries"] : 60; this.config = {"_id": this.name, "version": 1, "members": []}; - this.durable = options["durable"] != null ? options["durable"] : false; + this.journal = options["journal"] != null ? options["journal"] : false; this.auth = options['auth'] != null ? options['auth'] : false; this.path = path.resolve("data"); this.killNodeWaitTime = options['kill_node_wait_time'] != null ? options['kill_node_wait_time'] : 1000; @@ -63,6 +63,10 @@ ReplicaSetManager.prototype.primary = function(callback) { }); } +ReplicaSetManager.prototype.setAuths = function(user, password) { + this.auths = {user: user, password: password}; +} + ReplicaSetManager.prototype.allHostPairsWithState = function(state, callback) { this.ensureUp(function(err, status) { if(err != null) return callback(err, null); @@ -381,6 +385,16 @@ ReplicaSetManager.prototype.getNodeWithState = function(state, callback) { }); } +var _authenticateIfNeeded = function(self, connection, callback) { + if(self.auths != null) { + connection.admin().authenticate(self.auths.user, self.auths.password, function(err, result) { + callback(); + }); + } else { + callback(); + } +} + ReplicaSetManager.prototype.ensureUp = function(callback) { var self = this; var numberOfInitiateRetries = this.retries; @@ -404,49 +418,18 @@ ReplicaSetManager.prototype.ensureUp = function(callback) { // We have a connection, execute command and update server object if(err == null && connection != null) { - // Check repl set get status - connection.admin().command({"replSetGetStatus": 1}, function(err, object) { - // Close connection - if(connection != null) connection.close(); - // Get documents - var documents = object.documents; - // Get status object - var status = documents[0]; - - // If no members set - if(status["members"] == null || err != null) { - // if we have a connection force close it + _authenticateIfNeeded(self, connection, function() { + // Check repl set get status + connection.admin().command({"replSetGetStatus": 1}, function(err, object) { + // Close connection if(connection != null) connection.close(); - // Ensure we perform enough retries - if(self.ensureUpRetries >= self.retries) { - // Set that we are done - done = true; - // Return error - return callback(new Error("Operation Failure"), null); - } else { - // Execute function again - setTimeout(ensureUpFunction, 1000); - } - } else { - // Establish all health member - var healthyMembers = status.members.filter(function(element) { - return element["health"] == 1 && [1, 2, 7].indexOf(element["state"]) != -1 - }); - - var stateCheck = status["members"].filter(function(element, indexOf, array) { - return element["state"] == 1; - }); - - if(healthyMembers.length == status.members.length && stateCheck.length > 0) { - // Set that we are done - done = true; - // if we have a connection force close it - if(connection != null) connection.close(); - // process.stdout.write("all members up! \n\n"); - if(!self.up) process.stdout.write("all members up!\n\n") - self.up = true; - return callback(null, status); - } else { + // Get documents + var documents = object.documents; + // Get status object + var status = documents[0]; + + // If no members set + if(status["members"] == null || err != null) { // if we have a connection force close it if(connection != null) connection.close(); // Ensure we perform enough retries @@ -459,8 +442,41 @@ ReplicaSetManager.prototype.ensureUp = function(callback) { // Execute function again setTimeout(ensureUpFunction, 1000); } + } else { + // Establish all health member + var healthyMembers = status.members.filter(function(element) { + return element["health"] == 1 && [1, 2, 7].indexOf(element["state"]) != -1 + }); + + var stateCheck = status["members"].filter(function(element, indexOf, array) { + return element["state"] == 1; + }); + + if(healthyMembers.length == status.members.length && stateCheck.length > 0) { + // Set that we are done + done = true; + // if we have a connection force close it + if(connection != null) connection.close(); + // process.stdout.write("all members up! \n\n"); + if(!self.up) process.stdout.write("all members up!\n\n") + self.up = true; + return callback(null, status); + } else { + // if we have a connection force close it + if(connection != null) connection.close(); + // Ensure we perform enough retries + if(self.ensureUpRetries >= self.retries) { + // Set that we are done + done = true; + // Return error + return callback(new Error("Operation Failure"), null); + } else { + // Execute function again + setTimeout(ensureUpFunction, 1000); + } + } } - } + }); }); } else if(err != null && connection != null) { if(connection != null) connection.close(); @@ -601,9 +617,9 @@ ReplicaSetManager.prototype.restart = start; ReplicaSetManager.prototype.startCmd = function(n) { // Create boot command - this.mongods[n]["start"] = "mongod --nojournal --oplogSize 1 --rest --noprealloc --smallfiles --replSet " + this.name + " --logpath '" + this.mongods[n]['log_path'] + "' " + + this.mongods[n]["start"] = "mongod --oplogSize 1 --rest --noprealloc --smallfiles --replSet " + this.name + " --logpath '" + this.mongods[n]['log_path'] + "' " + " --dbpath " + this.mongods[n]['db_path'] + " --port " + this.mongods[n]['port'] + " --fork"; - this.mongods[n]["start"] = this.durable ? this.mongods[n]["start"] + " --dur" : this.mongods[n]["start"]; + this.mongods[n]["start"] = this.journal ? this.mongods[n]["start"] + " --journal" : this.mongods[n]["start"] + " --nojournal"; if(this.auth) { this.mongods[n]["start"] = this.auth ? this.mongods[n]["start"] + " --auth --keyFile " + this.keyPath : this.mongods[n]["start"];