diff options
Diffstat (limited to 'src/mongo/shell/servers.js')
-rwxr-xr-x | src/mongo/shell/servers.js | 2618 |
1 files changed, 2618 insertions, 0 deletions
diff --git a/src/mongo/shell/servers.js b/src/mongo/shell/servers.js new file mode 100755 index 00000000000..6b12918db2e --- /dev/null +++ b/src/mongo/shell/servers.js @@ -0,0 +1,2618 @@ +_parsePath = function() { + var dbpath = ""; + for( var i = 0; i < arguments.length; ++i ) + if ( arguments[ i ] == "--dbpath" ) + dbpath = arguments[ i + 1 ]; + + if ( dbpath == "" ) + throw "No dbpath specified"; + + return dbpath; +} + +_parsePort = function() { + var port = ""; + for( var i = 0; i < arguments.length; ++i ) + if ( arguments[ i ] == "--port" ) + port = arguments[ i + 1 ]; + + if ( port == "" ) + throw "No port specified"; + return port; +} + +connectionURLTheSame = function( a , b ){ + + if ( a == b ) + return true; + + if ( ! a || ! b ) + return false; + + if( a.host ) return connectionURLTheSame( a.host, b ) + if( b.host ) return connectionURLTheSame( a, b.host ) + + if( a.name ) return connectionURLTheSame( a.name, b ) + if( b.name ) return connectionURLTheSame( a, b.name ) + + if( a.indexOf( "/" ) < 0 && b.indexOf( "/" ) < 0 ){ + a = a.split( ":" ) + b = b.split( ":" ) + + if( a.length != b.length ) return false + + if( a.length == 2 && a[1] != b[1] ) return false + + if( a[0] == "localhost" || a[0] == "127.0.0.1" ) a[0] = getHostName() + if( b[0] == "localhost" || b[0] == "127.0.0.1" ) b[0] = getHostName() + + return a[0] == b[0] + } + else { + a0 = a.split( "/" )[0] + b0 = b.split( "/" )[0] + return a0 == b0 + } +} + +assert( connectionURLTheSame( "foo" , "foo" ) ) +assert( ! connectionURLTheSame( "foo" , "bar" ) ) + +assert( connectionURLTheSame( "foo/a,b" , "foo/b,a" ) ) +assert( ! connectionURLTheSame( "foo/a,b" , "bar/a,b" ) ) + +createMongoArgs = function( binaryName , args ){ + var fullArgs = [ binaryName ]; + + if ( args.length == 1 && isObject( args[0] ) ){ + var o = args[0]; + for ( var k in o ){ + if ( o.hasOwnProperty(k) ){ + if ( k == "v" && isNumber( o[k] ) ){ + var n = o[k]; + if ( n > 0 ){ + if ( n > 10 ) n = 10; + var temp = "-"; + while ( n-- > 0 ) temp += "v"; + fullArgs.push( temp ); + } + } + else { + fullArgs.push( "--" + k ); + if ( o[k] != "" ) + fullArgs.push( "" + o[k] ); + } + } + } + } + else { + for ( var i=0; i<args.length; i++ ) + fullArgs.push( args[i] ) + } + + return fullArgs; +} + + +MongoRunner = function(){} + +MongoRunner.dataDir = "/data/db" +MongoRunner.dataPath = "/data/db/" +MongoRunner.usedPortMap = {} +MongoRunner.logicalOptions = { runId : true, + pathOpts : true, + remember : true, + noRemember : true, + appendOptions : true, + restart : true, + noCleanData : true, + cleanData : true, + startClean : true, + forceLock : true, + useLogFiles : true, + useHostName : true, + useHostname : true, + noReplSet : true, + forgetPort : true, + arbiter : true, + noJournalPrealloc : true, + noJournal : true } + +MongoRunner.toRealPath = function( path, pathOpts ){ + + // Replace all $pathOptions with actual values + pathOpts = pathOpts || {} + path = path.replace( /\$dataPath/g, MongoRunner.dataPath ) + path = path.replace( /\$dataDir/g, MongoRunner.dataDir ) + for( key in pathOpts ){ + path = path.replace( RegExp( "\\$" + key, "g" ), pathOpts[ key ] ) + } + + // Relative path + if( ! path.startsWith( "/" ) ){ + if( path != "" && ! path.endsWith( "/" ) ) + path += "/" + + path = MongoRunner.dataPath + path + } + + return path + +} + +MongoRunner.toRealDir = function( path, pathOpts ){ + + path = MongoRunner.toRealPath( path, pathOpts ) + + if( path.endsWith( "/" ) ) + path = path.substring( 0, path.length - 1 ) + + return path +} + +MongoRunner.toRealFile = MongoRunner.toRealDir + +MongoRunner.nextOpenPort = function(){ + + var i = 0; + while( MongoRunner.usedPortMap[ "" + ( 27000 + i ) ] ) i++; + MongoRunner.usedPortMap[ "" + ( 27000 + i ) ] = true + + return 27000 + i + +} + +MongoRunner.arrOptions = function( binaryName , args ){ + + var fullArgs = [ binaryName ] + + if ( isObject( args ) || ( args.length == 1 && isObject( args[0] ) ) ){ + + var o = isObject( args ) ? args : args[0] + for ( var k in o ){ + + if( ! o.hasOwnProperty(k) || k in MongoRunner.logicalOptions ) continue + + if ( ( k == "v" || k == "verbose" ) && isNumber( o[k] ) ){ + var n = o[k] + if ( n > 0 ){ + if ( n > 10 ) n = 10 + var temp = "-" + while ( n-- > 0 ) temp += "v" + fullArgs.push( temp ) + } + } + else { + if( o[k] == undefined || o[k] == null ) continue + fullArgs.push( "--" + k ) + if ( o[k] != "" ) + fullArgs.push( "" + o[k] ) + } + } + } + else { + for ( var i=0; i<args.length; i++ ) + fullArgs.push( args[i] ) + } + + return fullArgs +} + +MongoRunner.arrToOpts = function( arr ){ + + var opts = {} + for( var i = 1; i < arr.length; i++ ){ + if( arr[i].startsWith( "-" ) ){ + var opt = arr[i].replace( /^-/, "" ).replace( /^-/, "" ) + + if( arr.length > i + 1 && ! arr[ i + 1 ].startsWith( "-" ) ){ + opts[ opt ] = arr[ i + 1 ] + i++ + } + else{ + opts[ opt ] = "" + } + + if( opt.replace( /v/g, "" ) == "" ){ + opts[ "verbose" ] = opt.length + } + } + } + + return opts +} + +MongoRunner.savedOptions = {} + +MongoRunner.mongoOptions = function( opts ){ + + // Initialize and create a copy of the opts + opts = Object.merge( opts || {}, {} ) + + if( ! opts.restart ) opts.restart = false + + // RunId can come from a number of places + if( isObject( opts.restart ) ){ + opts.runId = opts.restart + opts.restart = true + } + + if( isObject( opts.remember ) ){ + opts.runId = opts.remember + opts.remember = true + } + else if( opts.remember == undefined ){ + // Remember by default if we're restarting + opts.remember = opts.restart + } + + // If we passed in restart : <conn> or runId : <conn> + if( isObject( opts.runId ) && opts.runId.runId ) opts.runId = opts.runId.runId + + if( opts.restart && opts.remember ) opts = Object.merge( MongoRunner.savedOptions[ opts.runId ], opts ) + + // Create a new runId + opts.runId = opts.runId || ObjectId() + + // Save the port if required + if( ! opts.forgetPort ) opts.port = opts.port || MongoRunner.nextOpenPort() + + var shouldRemember = ( ! opts.restart && ! opts.noRemember ) || ( opts.restart && opts.appendOptions ) + + if ( shouldRemember ){ + MongoRunner.savedOptions[ opts.runId ] = Object.merge( opts, {} ) + } + + opts.port = opts.port || MongoRunner.nextOpenPort() + MongoRunner.usedPortMap[ "" + parseInt( opts.port ) ] = true + + opts.pathOpts = Object.merge( opts.pathOpts || {}, { port : "" + opts.port, runId : "" + opts.runId } ) + + return opts +} + +MongoRunner.mongodOptions = function( opts ){ + + opts = MongoRunner.mongoOptions( opts ) + + opts.dbpath = MongoRunner.toRealDir( opts.dbpath || "$dataDir/mongod-$port", + opts.pathOpts ) + + opts.pathOpts = Object.merge( opts.pathOpts, { dbpath : opts.dbpath } ) + + if( ! opts.logFile && opts.useLogFiles ){ + opts.logFile = opts.dbpath + "/mongod.log" + } + else if( opts.logFile ){ + opts.logFile = MongoRunner.toRealFile( opts.logFile, opts.pathOpts ) + } + + if( jsTestOptions().noJournalPrealloc || opts.noJournalPrealloc ) + opts.nopreallocj = "" + + if( jsTestOptions().noJournal || opts.noJournal ) + opts.nojournal = "" + + if( jsTestOptions().keyFile && !opts.keyFile) { + opts.keyFile = jsTestOptions().keyFile + } + + if( opts.noReplSet ) opts.replSet = null + if( opts.arbiter ) opts.oplogSize = 1 + + return opts +} + +MongoRunner.mongosOptions = function( opts ){ + + opts = MongoRunner.mongoOptions( opts ) + + opts.pathOpts = Object.merge( opts.pathOpts, + { configdb : opts.configdb.replace( /:|,/g, "-" ) } ) + + if( ! opts.logFile && opts.useLogFiles ){ + opts.logFile = MongoRunner.toRealFile( "$dataDir/mongos-$configdb-$port.log", + opts.pathOpts ) + } + else if( opts.logFile ){ + opts.logFile = MongoRunner.toRealFile( opts.logFile, opts.pathOpts ) + } + + if( jsTestOptions().keyFile && !opts.keyFile) { + opts.keyFile = jsTestOptions().keyFile + } + + return opts +} + +MongoRunner.runMongod = function( opts ){ + + var useHostName = false + var runId = null + if( isObject( opts ) ) { + + opts = MongoRunner.mongodOptions( opts ) + + useHostName = opts.useHostName || opts.useHostname + runId = opts.runId + + if( opts.forceLock ) removeFile( opts.dbpath + "/mongod.lock" ) + if( ( opts.cleanData || opts.startClean ) || ( ! opts.restart && ! opts.noCleanData ) ){ + print( "Resetting db path '" + opts.dbpath + "'" ) + resetDbpath( opts.dbpath ) + } + + opts = MongoRunner.arrOptions( "mongod", opts ) + } + + var mongod = startMongoProgram.apply( null, opts ) + mongod.commandLine = MongoRunner.arrToOpts( opts ) + mongod.name = (useHostName ? getHostName() : "localhost") + ":" + mongod.commandLine.port + mongod.host = mongod.name + mongod.port = parseInt( mongod.commandLine.port ) + mongod.runId = runId || ObjectId() + mongod.savedOptions = MongoRunner.savedOptions[ mongod.runId ] + + return mongod +} + +MongoRunner.runMongos = function( opts ){ + + var useHostName = false + var runId = null + if( isObject( opts ) ) { + + opts = MongoRunner.mongosOptions( opts ) + + useHostName = opts.useHostName || opts.useHostname + runId = opts.runId + + opts = MongoRunner.arrOptions( "mongos", opts ) + } + + var mongos = startMongoProgram.apply( null, opts ) + mongos.commandLine = MongoRunner.arrToOpts( opts ) + mongos.name = (useHostName ? getHostName() : "localhost") + ":" + mongos.commandLine.port + mongos.host = mongos.name + mongos.port = parseInt( mongos.commandLine.port ) + mongos.runId = runId || ObjectId() + mongos.savedOptions = MongoRunner.savedOptions[ mongos.runId ] + + return mongos +} + +MongoRunner.stopMongod = function( port, signal ){ + + if( ! port ) { + print( "Cannot stop mongo process " + port ) + return + } + + signal = signal || 15 + + if( port.port ) + port = parseInt( port.port ) + + if( port instanceof ObjectId ){ + var opts = MongoRunner.savedOptions( port ) + if( opts ) port = parseInt( opts.port ) + } + + var exitCode = stopMongod( parseInt( port ), parseInt( signal ) ) + + delete MongoRunner.usedPortMap[ "" + parseInt( port ) ] + + return exitCode +} + +MongoRunner.stopMongos = MongoRunner.stopMongod + +MongoRunner.isStopped = function( port ){ + + if( ! port ) { + print( "Cannot detect if process " + port + " is stopped." ) + return + } + + if( port.port ) + port = parseInt( port.port ) + + if( port instanceof ObjectId ){ + var opts = MongoRunner.savedOptions( port ) + if( opts ) port = parseInt( opts.port ) + } + + return MongoRunner.usedPortMap[ "" + parseInt( port ) ] ? false : true +} + +__nextPort = 27000; +startMongodTest = function (port, dirname, restart, extraOptions ) { + if (!port) + port = __nextPort++; + var f = startMongodEmpty; + if (restart) + f = startMongodNoReset; + if (!dirname) + dirname = "" + port; // e.g., data/db/27000 + + var useHostname = false; + if (extraOptions) { + useHostname = extraOptions.useHostname; + delete extraOptions.useHostname; + } + + + var options = + { + port: port, + dbpath: "/data/db/" + dirname, + noprealloc: "", + smallfiles: "", + oplogSize: "40", + nohttpinterface: "" + }; + + if( jsTestOptions().noJournal ) options["nojournal"] = "" + if( jsTestOptions().noJournalPrealloc ) options["nopreallocj"] = "" + if( jsTestOptions().auth ) options["auth"] = "" + if( jsTestOptions().keyFile && (!extraOptions || !extraOptions['keyFile']) ) options['keyFile'] = jsTestOptions().keyFile + + if ( extraOptions ) + Object.extend( options , extraOptions ); + + var conn = f.apply(null, [ options ] ); + + conn.name = (useHostname ? getHostName() : "localhost") + ":" + port; + + if (options['auth'] || options['keyFile']) { + if (!this.shardsvr && !options.replSet) { + jsTest.addAuth(conn); + } + jsTest.authenticate(conn); + } + return conn; +} + +// Start a mongod instance and return a 'Mongo' object connected to it. +// This function's arguments are passed as command line arguments to mongod. +// The specified 'dbpath' is cleared if it exists, created if not. +// var conn = startMongodEmpty("--port", 30000, "--dbpath", "asdf"); +startMongodEmpty = function () { + var args = createMongoArgs("mongod", arguments); + + var dbpath = _parsePath.apply(null, args); + resetDbpath(dbpath); + + return startMongoProgram.apply(null, args); +} +startMongod = function () { + print("startMongod WARNING DELETES DATA DIRECTORY THIS IS FOR TESTING ONLY"); + return startMongodEmpty.apply(null, arguments); +} +startMongodNoReset = function(){ + var args = createMongoArgs( "mongod" , arguments ); + return startMongoProgram.apply( null, args ); +} + +startMongos = function(args){ + return MongoRunner.runMongos(args); +} + +/* Start mongod or mongos and return a Mongo() object connected to there. + This function's first argument is "mongod" or "mongos" program name, \ + and subsequent arguments to this function are passed as + command line arguments to the program. +*/ +startMongoProgram = function(){ + var port = _parsePort.apply( null, arguments ); + + _startMongoProgram.apply( null, arguments ); + + var m; + assert.soon + ( function() { + try { + m = new Mongo( "127.0.0.1:" + port ); + return true; + } catch( e ) { + } + return false; + }, "unable to connect to mongo program on port " + port, 600 * 1000 ); + + return m; +} + +// Start a mongo program instance. This function's first argument is the +// program name, and subsequent arguments to this function are passed as +// command line arguments to the program. Returns pid of the spawned program. +startMongoProgramNoConnect = function() { + return _startMongoProgram.apply( null, arguments ); +} + +myPort = function() { + var m = db.getMongo(); + if ( m.host.match( /:/ ) ) + return m.host.match( /:(.*)/ )[ 1 ]; + else + return 27017; +} + +/** + * otherParams can be: + * * useHostname to use the hostname (instead of localhost) + */ +ShardingTest = function( testName , numShards , verboseLevel , numMongos , otherParams ){ + + // Check if testName is an object, if so, pull params from there + var keyFile = undefined + otherParams = Object.merge( otherParams || {}, {} ) + otherParams.extraOptions = otherParams.extraOptions || {} + + if( isObject( testName ) ){ + + var params = Object.merge( testName, {} ) + + testName = params.name || "test" + + otherParams = Object.merge( params.other || {}, {} ) + otherParams.extraOptions = otherParams.extraOptions || {} + + numShards = params.shards || 2 + verboseLevel = params.verbose || 0 + numMongos = params.mongos || 1 + + keyFile = params.keyFile || otherParams.keyFile || otherParams.extraOptions.keyFile + otherParams.nopreallocj = params.nopreallocj || otherParams.nopreallocj + otherParams.rs = params.rs || ( params.other ? params.other.rs : undefined ) + otherParams.chunksize = params.chunksize || ( params.other ? params.other.chunksize : undefined ) + + // Allow specifying options like : + // { mongos : [ { noprealloc : "" } ], config : [ { smallfiles : "" } ], shards : { rs : true, d : true } } + if( isObject( numShards ) ){ + var len = 0 + for( var i in numShards ){ + otherParams[ "" + i ] = numShards[i] + len++ + } + numShards = len + } + + if( isObject( numMongos ) ){ + var len = 0 + for( var i in numMongos ){ + otherParams[ "" + i ] = numMongos[i] + len++ + } + numMongos = len + } + else if( Array.isArray( numMongos ) ){ + for( var i = 0; i < numMongos.length; i++ ) + otherParams[ "s" + i ] = numMongos[i] + numMongos = numMongos.length + } + + if( isObject( params.config ) ){ + var len = 0 + for( var i in params.config ){ + otherParams[ "" + i ] = params.config[i] + len++ + } + + // If we're specifying explicit config options, we need separate config servers + otherParams.separateConfig = true + if( len == 3 ) otherParams.sync = true + else otherParams.sync = false + } + else if( Array.isArray( params.config ) ){ + for( var i = 0; i < params.config.length; i++ ) + otherParams[ "c" + i ] = params.config[i] + + // If we're specifying explicit config options, we need separate config servers + otherParams.separateConfig = true + if( params.config.length == 3 ) otherParams.sync = true + else otherParams.sync = false + } + else if( params.config ) { + + if( params.config == 3 ){ + otherParams.separateConfig = otherParams.separateConfig || true + otherParams.sync = true + } + + } + } + else { + // Handle legacy stuff + keyFile = otherParams.extraOptions.keyFile + } + + this._testName = testName + this._otherParams = otherParams + + var pathOpts = this.pathOpts = { testName : testName } + + var hasRS = false + for( var k in otherParams ){ + if( k.startsWith( "rs" ) ){ + hasRS = true + break + } + } + + if( hasRS ){ + otherParams.separateConfig = true + otherParams.useHostname = otherParams.useHostname == undefined ? true : otherParams.useHostname + } + + var localhost = otherParams.useHostname ? getHostName() : "localhost"; + + this._alldbpaths = [] + this._connections = [] + this._shardServers = this._connections + this._rs = [] + this._rsObjects = [] + + for ( var i = 0; i < numShards; i++ ) { + if( otherParams.rs || otherParams["rs" + i] ){ + + otherParams.separateConfig = true + + var setName = testName + "-rs" + i; + + rsDefaults = { useHostname : otherParams.useHostname, + noJournalPrealloc : otherParams.nopreallocj, + oplogSize : 40, + nodes : 3, + pathOpts : Object.merge( pathOpts, { shard : i } )} + + rsDefaults = Object.merge( rsDefaults, otherParams.rs ) + rsDefaults = Object.merge( rsDefaults, otherParams.rsOptions ) + rsDefaults = Object.merge( rsDefaults, otherParams["rs" + i] ) + + var numReplicas = rsDefaults.nodes || otherParams.numReplicas || 3 + delete rsDefaults.nodes + + print( "Replica set test!" ) + + var rs = new ReplSetTest( { name : setName , nodes : numReplicas , startPort : 31100 + ( i * 100 ), useHostName : otherParams.useHostname, keyFile : keyFile, shardSvr : true } ); + this._rs[i] = { setName : setName , test : rs , nodes : rs.startSet( rsDefaults ) , url : rs.getURL() }; + rs.initiate(); + this["rs" + i] = rs + + this._rsObjects[i] = rs + + this._alldbpaths.push( null ) + this._connections.push( null ) + } + else { + var options = { useHostname : otherParams.useHostname, + noJournalPrealloc : otherParams.nopreallocj, + port : 30000 + i, + pathOpts : Object.merge( pathOpts, { shard : i } ), + dbpath : "$testName$shard", + keyFile : keyFile + } + + options = Object.merge( options, otherParams.shardOptions ) + options = Object.merge( options, otherParams["d" + i] ) + + var conn = MongoRunner.runMongod( options ); + + this._alldbpaths.push( testName +i ) + this._connections.push( conn ); + this["shard" + i] = conn + this["d" + i] = conn + + this._rs[i] = null + this._rsObjects[i] = null + } + } + + // Do replication on replica sets if required + for ( var i = 0; i < numShards; i++ ){ + if( ! otherParams.rs && ! otherParams["rs" + i] ) continue + + var rs = this._rs[i].test; + + rs.getMaster().getDB( "admin" ).foo.save( { x : 1 } ) + rs.awaitReplication(); + + var rsConn = new Mongo( rs.getURL() ); + rsConn.name = rs.getURL(); + this._connections[i] = rsConn + this["shard" + i] = rsConn + rsConn.rs = rs + } + + this._configServers = [] + this._configNames = [] + + if ( otherParams.sync && ! otherParams.separateConfig && numShards < 3 ) + throw "if you want sync, you need at least 3 servers"; + + for ( var i = 0; i < ( otherParams.sync ? 3 : 1 ) ; i++ ) { + + var conn = null + + if( otherParams.separateConfig ){ + + var options = { useHostname : otherParams.useHostname, + noJournalPrealloc : otherParams.nopreallocj, + port : 40000 + i, + pathOpts : Object.merge( pathOpts, { config : i } ), + dbpath : "$testName-config$config", + keyFile : keyFile + } + + options = Object.merge( options, otherParams.configOptions ) + options = Object.merge( options, otherParams["c" + i] ) + + var conn = MongoRunner.runMongod( options ) + + // TODO: Needed? + this._alldbpaths.push( testName + "-config" + i ) + } + else{ + conn = this["shard" + i] + } + + this._configServers.push( conn ); + this._configNames.push( conn.name ) + this["config" + i] = conn + this["c" + i] = conn + } + + printjson( this._configDB = this._configNames.join( "," ) ) + this._configConnection = new Mongo( this._configDB ) + if ( ! otherParams.noChunkSize ) { + this._configConnection.getDB( "config" ).settings.insert( { _id : "chunksize" , value : otherParams.chunksize || otherParams.chunkSize || 50 } ) + } + + print( "ShardingTest " + this._testName + " :\n" + tojson( { config : this._configDB, shards : this._connections } ) ); + + this._mongos = [] + this._mongoses = this._mongos + for ( var i = 0; i < ( ( numMongos == 0 ? -1 : numMongos ) || 1 ); i++ ){ + + var options = { useHostname : otherParams.useHostname, + port : 31000 - i - 1, + pathOpts : Object.merge( pathOpts, { mongos : i } ), + configdb : this._configDB, + verbose : verboseLevel || 0, + keyFile : keyFile + } + + options = Object.merge( options, otherParams.mongosOptions ) + options = Object.merge( options, otherParams.extraOptions ) + options = Object.merge( options, otherParams["s" + i] ) + + var conn = MongoRunner.runMongos( options ) + + this._mongos.push( conn ); + if ( i == 0 ) this.s = conn + this["s" + i] = conn + } + + var admin = this.admin = this.s.getDB( "admin" ); + this.config = this.s.getDB( "config" ); + + if ( ! otherParams.manualAddShard ){ + this._shardNames = [] + var shardNames = this._shardNames + this._connections.forEach( + function(z){ + var n = z.name; + if ( ! n ){ + n = z.host; + if ( ! n ) + n = z; + } + print( "ShardingTest " + this._testName + " going to add shard : " + n ) + x = admin.runCommand( { addshard : n } ); + printjson( x ) + shardNames.push( x.shardAdded ) + z.shardName = x.shardAdded + } + ); + } + + if (jsTestOptions().keyFile && !keyFile) { + jsTest.addAuth(this._mongos[0]); + jsTest.authenticateNodes(this._connections); + jsTest.authenticateNodes(this._configServers); + jsTest.authenticateNodes(this._mongos); + } +} + +ShardingTest.prototype.getRSEntry = function( setName ){ + for ( var i=0; i<this._rs.length; i++ ) + if ( this._rs[i].setName == setName ) + return this._rs[i]; + throw "can't find rs: " + setName; +} + +ShardingTest.prototype.getConfigIndex = function( config ){ + + // Assume config is a # if not a conn object + if( ! isObject( config ) ) config = getHostName() + ":" + config + + for( var i = 0; i < this._configServers.length; i++ ){ + if( connectionURLTheSame( this._configServers[i], config ) ) return i + } + + return -1 +} + +ShardingTest.prototype.getDB = function( name ){ + return this.s.getDB( name ); +} + +ShardingTest.prototype.getServerName = function( dbname ){ + var x = this.config.databases.findOne( { _id : "" + dbname } ); + if ( x ) + return x.primary; + this.config.databases.find().forEach( printjson ); + throw "couldn't find dbname: " + dbname + " total: " + this.config.databases.count(); +} + + +ShardingTest.prototype.getNonPrimaries = function( dbname ){ + var x = this.config.databases.findOne( { _id : dbname } ); + if ( ! x ){ + this.config.databases.find().forEach( printjson ); + throw "couldn't find dbname: " + dbname + " total: " + this.config.databases.count(); + } + + return this.config.shards.find( { _id : { $ne : x.primary } } ).map( function(z){ return z._id; } ) +} + + +ShardingTest.prototype.getConnNames = function(){ + var names = []; + for ( var i=0; i<this._connections.length; i++ ){ + names.push( this._connections[i].name ); + } + return names; +} + +ShardingTest.prototype.getServer = function( dbname ){ + var name = this.getServerName( dbname ); + + var x = this.config.shards.findOne( { _id : name } ); + if ( x ) + name = x.host; + + var rsName = null; + if ( name.indexOf( "/" ) > 0 ) + rsName = name.substring( 0 , name.indexOf( "/" ) ); + + for ( var i=0; i<this._connections.length; i++ ){ + var c = this._connections[i]; + if ( connectionURLTheSame( name , c.name ) || + connectionURLTheSame( rsName , c.name ) ) + return c; + } + + throw "can't find server for: " + dbname + " name:" + name; + +} + +ShardingTest.prototype.normalize = function( x ){ + var z = this.config.shards.findOne( { host : x } ); + if ( z ) + return z._id; + return x; +} + +ShardingTest.prototype.getOther = function( one ){ + if ( this._connections.length < 2 ) + throw "getOther only works with 2 servers"; + + if ( one._mongo ) + one = one._mongo + + for( var i = 0; i < this._connections.length; i++ ){ + if( this._connections[i] != one ) return this._connections[i] + } + + return null +} + +ShardingTest.prototype.getAnother = function( one ){ + if(this._connections.length < 2) + throw "getAnother() only works with multiple servers"; + + if ( one._mongo ) + one = one._mongo + + for(var i = 0; i < this._connections.length; i++){ + if(this._connections[i] == one) + return this._connections[(i + 1) % this._connections.length]; + } +} + +ShardingTest.prototype.getFirstOther = function( one ){ + for ( var i=0; i<this._connections.length; i++ ){ + if ( this._connections[i] != one ) + return this._connections[i]; + } + throw "impossible"; +} + +ShardingTest.prototype.stop = function(){ + for ( var i=0; i<this._mongos.length; i++ ){ + stopMongoProgram( 31000 - i - 1 ); + } + for ( var i=0; i<this._connections.length; i++){ + stopMongod( 30000 + i ); + } + if ( this._rs ){ + for ( var i=0; i<this._rs.length; i++ ){ + if( this._rs[i] ) this._rs[i].test.stopSet( 15 ); + } + } + if( this._otherParams.separateConfig ){ + for ( var i=0; i<this._configServers.length; i++ ){ + MongoRunner.stopMongod( this._configServers[i] ) + } + } + if ( this._alldbpaths ){ + for( i=0; i<this._alldbpaths.length; i++ ){ + resetDbpath( "/data/db/" + this._alldbpaths[i] ); + } + } + + print('*** ShardingTest ' + this._testName + " completed successfully ***"); +} + +ShardingTest.prototype.adminCommand = function(cmd){ + var res = this.admin.runCommand( cmd ); + if ( res && res.ok == 1 ) + return true; + + throw "command " + tojson( cmd ) + " failed: " + tojson( res ); +} + +ShardingTest.prototype._rangeToString = function(r){ + return tojsononeline( r.min ) + " -> " + tojsononeline( r.max ); +} + +ShardingTest.prototype.printChangeLog = function(){ + var s = this; + this.config.changelog.find().forEach( + function(z){ + var msg = z.server + "\t" + z.time + "\t" + z.what; + for ( i=z.what.length; i<15; i++ ) + msg += " "; + msg += " " + z.ns + "\t"; + if ( z.what == "split" ){ + msg += s._rangeToString( z.details.before ) + " -->> (" + s._rangeToString( z.details.left ) + "),(" + s._rangeToString( z.details.right ) + ")"; + } + else if (z.what == "multi-split" ){ + msg += s._rangeToString( z.details.before ) + " -->> (" + z.details.number + "/" + z.details.of + " " + s._rangeToString( z.details.chunk ) + ")"; + } + else { + msg += tojsononeline( z.details ); + } + + print( "ShardingTest " + msg ) + } + ); + +} + +ShardingTest.prototype.getChunksString = function( ns ){ + var q = {} + if ( ns ) + q.ns = ns; + + var s = ""; + this.config.chunks.find( q ).sort( { ns : 1 , min : 1 } ).forEach( + function(z){ + s += " " + z._id + "\t" + z.lastmod.t + "|" + z.lastmod.i + "\t" + tojson(z.min) + " -> " + tojson(z.max) + " " + z.shard + " " + z.ns + "\n"; + } + ); + + return s; +} + +ShardingTest.prototype.printChunks = function( ns ){ + print( "ShardingTest " + this.getChunksString( ns ) ); +} + +ShardingTest.prototype.printShardingStatus = function(){ + printShardingStatus( this.config ); +} + +ShardingTest.prototype.printCollectionInfo = function( ns , msg ){ + var out = ""; + if ( msg ) + out += msg + "\n"; + out += "sharding collection info: " + ns + "\n"; + for ( var i=0; i<this._connections.length; i++ ){ + var c = this._connections[i]; + out += " mongod " + c + " " + tojson( c.getCollection( ns ).getShardVersion() , " " , true ) + "\n"; + } + for ( var i=0; i<this._mongos.length; i++ ){ + var c = this._mongos[i]; + out += " mongos " + c + " " + tojson( c.getCollection( ns ).getShardVersion() , " " , true ) + "\n"; + } + + out += this.getChunksString( ns ); + + print( "ShardingTest " + out ); +} + +printShardingStatus = function( configDB , verbose ){ + if (configDB === undefined) + configDB = db.getSisterDB('config') + + var version = configDB.getCollection( "version" ).findOne(); + if ( version == null ){ + print( "printShardingStatus: this db does not have sharding enabled. be sure you are connecting to a mongos from the shell and not to a mongod." ); + return; + } + + var raw = ""; + var output = function(s){ + raw += s + "\n"; + } + output( "--- Sharding Status --- " ); + output( " sharding version: " + tojson( configDB.getCollection( "version" ).findOne() ) ); + + output( " shards:" ); + configDB.shards.find().sort( { _id : 1 } ).forEach( + function(z){ + output( "\t" + tojsononeline( z ) ); + } + ); + + output( " databases:" ); + configDB.databases.find().sort( { name : 1 } ).forEach( + function(db){ + output( "\t" + tojsononeline(db,"",true) ); + + if (db.partitioned){ + configDB.collections.find( { _id : new RegExp( "^" + db._id + "\\." ) } ).sort( { _id : 1 } ).forEach( + function( coll ){ + if ( coll.dropped == false ){ + output("\t\t" + coll._id + " chunks:"); + + res = configDB.chunks.group( { cond : { ns : coll._id } , key : { shard : 1 } , reduce : function( doc , out ){ out.nChunks++; } , initial : { nChunks : 0 } } ); + var totalChunks = 0; + res.forEach( function(z){ + totalChunks += z.nChunks; + output( "\t\t\t\t" + z.shard + "\t" + z.nChunks ); + } ) + + if ( totalChunks < 20 || verbose ){ + configDB.chunks.find( { "ns" : coll._id } ).sort( { min : 1 } ).forEach( + function(chunk){ + output( "\t\t\t" + tojson( chunk.min ) + " -->> " + tojson( chunk.max ) + + " on : " + chunk.shard + " " + tojson( chunk.lastmod ) + " " + + ( chunk.jumbo ? "jumbo " : "" ) ); + } + ); + } + else { + output( "\t\t\ttoo many chunks to print, use verbose if you want to force print" ); + } + } + } + ) + } + } + ); + + print( raw ); +} + +printShardingSizes = function(){ + configDB = db.getSisterDB('config') + + var version = configDB.getCollection( "version" ).findOne(); + if ( version == null ){ + print( "printShardingSizes : not a shard db!" ); + return; + } + + var raw = ""; + var output = function(s){ + raw += s + "\n"; + } + output( "--- Sharding Status --- " ); + output( " sharding version: " + tojson( configDB.getCollection( "version" ).findOne() ) ); + + output( " shards:" ); + var shards = {}; + configDB.shards.find().forEach( + function(z){ + shards[z._id] = new Mongo(z.host); + output( " " + tojson(z) ); + } + ); + + var saveDB = db; + output( " databases:" ); + configDB.databases.find().sort( { name : 1 } ).forEach( + function(db){ + output( "\t" + tojson(db,"",true) ); + + if (db.partitioned){ + configDB.collections.find( { _id : new RegExp( "^" + db._id + "\." ) } ).sort( { _id : 1 } ).forEach( + function( coll ){ + output("\t\t" + coll._id + " chunks:"); + configDB.chunks.find( { "ns" : coll._id } ).sort( { min : 1 } ).forEach( + function(chunk){ + var mydb = shards[chunk.shard].getDB(db._id) + var out = mydb.runCommand({dataSize: coll._id, + keyPattern: coll.key, + min: chunk.min, + max: chunk.max }); + delete out.millis; + delete out.ok; + + output( "\t\t\t" + tojson( chunk.min ) + " -->> " + tojson( chunk.max ) + + " on : " + chunk.shard + " " + tojson( out ) ); + + } + ); + } + ) + } + } + ); + + print( raw ); +} + +ShardingTest.prototype.sync = function(){ + this.adminCommand( "connpoolsync" ); +} + +ShardingTest.prototype.onNumShards = function( collName , dbName ){ + this.sync(); // we should sync since we're going directly to mongod here + dbName = dbName || "test"; + var num=0; + for ( var i=0; i<this._connections.length; i++ ) + if ( this._connections[i].getDB( dbName ).getCollection( collName ).count() > 0 ) + num++; + return num; +} + + +ShardingTest.prototype.shardCounts = function( collName , dbName ){ + this.sync(); // we should sync since we're going directly to mongod here + dbName = dbName || "test"; + var counts = {} + for ( var i=0; i<this._connections.length; i++ ) + counts[i] = this._connections[i].getDB( dbName ).getCollection( collName ).count(); + return counts; +} + +ShardingTest.prototype.chunkCounts = function( collName , dbName ){ + dbName = dbName || "test"; + var x = {} + + s.config.shards.find().forEach( + function(z){ + x[z._id] = 0; + } + ); + + s.config.chunks.find( { ns : dbName + "." + collName } ).forEach( + function(z){ + if ( x[z.shard] ) + x[z.shard]++ + else + x[z.shard] = 1; + } + ); + return x; + +} + +ShardingTest.prototype.chunkDiff = function( collName , dbName ){ + var c = this.chunkCounts( collName , dbName ); + var min = 100000000; + var max = 0; + for ( var s in c ){ + if ( c[s] < min ) + min = c[s]; + if ( c[s] > max ) + max = c[s]; + } + print( "ShardingTest input: " + tojson( c ) + " min: " + min + " max: " + max ); + return max - min; +} + +ShardingTest.prototype.getShard = function( coll, query, includeEmpty ){ + var shards = this.getShards( coll, query, includeEmpty ) + assert.eq( shards.length, 1 ) + return shards[0] +} + +// Returns the shards on which documents matching a particular query reside +ShardingTest.prototype.getShards = function( coll, query, includeEmpty ){ + if( ! coll.getDB ) + coll = this.s.getCollection( coll ) + + var explain = coll.find( query ).explain() + var shards = [] + + if( explain.shards ){ + + for( var shardName in explain.shards ){ + for( var i = 0; i < explain.shards[shardName].length; i++ ){ + if( includeEmpty || ( explain.shards[shardName][i].n && explain.shards[shardName][i].n > 0 ) ) + shards.push( shardName ) + } + } + + } + + for( var i = 0; i < shards.length; i++ ){ + for( var j = 0; j < this._connections.length; j++ ){ + if ( connectionURLTheSame( this._connections[j] , shards[i] ) ){ + shards[i] = this._connections[j] + break; + } + } + } + + return shards +} + +ShardingTest.prototype.isSharded = function( collName ){ + + var collName = "" + collName + var dbName = undefined + + if( typeof collName.getCollectionNames == 'function' ){ + dbName = "" + collName + collName = undefined + } + + if( dbName ){ + var x = this.config.databases.findOne( { _id : dbname } ) + if( x ) return x.partitioned + else return false + } + + if( collName ){ + var x = this.config.collections.findOne( { _id : collName } ) + if( x ) return true + else return false + } + +} + +ShardingTest.prototype.shardGo = function( collName , key , split , move , dbName ){ + + split = ( split != false ? ( split || key ) : split ) + move = ( split != false && move != false ? ( move || split ) : false ) + + if( collName.getDB ) + dbName = "" + collName.getDB() + else dbName = dbName || "test"; + + var c = dbName + "." + collName; + if( collName.getDB ) + c = "" + collName + + var isEmpty = this.s.getCollection( c ).count() == 0 + + if( ! this.isSharded( dbName ) ) + this.s.adminCommand( { enableSharding : dbName } ) + + var result = this.s.adminCommand( { shardcollection : c , key : key } ) + if( ! result.ok ){ + printjson( result ) + assert( false ) + } + + if( split == false ) return + + result = this.s.adminCommand( { split : c , middle : split } ); + if( ! result.ok ){ + printjson( result ) + assert( false ) + } + + if( move == false ) return + + var result = null + for( var i = 0; i < 5; i++ ){ + result = this.s.adminCommand( { movechunk : c , find : move , to : this.getOther( this.getServer( dbName ) ).name } ); + if( result.ok ) break; + sleep( 5 * 1000 ); + } + printjson( result ) + assert( result.ok ) + +}; + +ShardingTest.prototype.shardColl = ShardingTest.prototype.shardGo + +ShardingTest.prototype.setBalancer = function( balancer ){ + if( balancer || balancer == undefined ){ + this.config.settings.update( { _id: "balancer" }, { $set : { stopped: false } } , true ) + } + else if( balancer == false ){ + this.config.settings.update( { _id: "balancer" }, { $set : { stopped: true } } , true ) + } +} + +ShardingTest.prototype.stopBalancer = function( timeout, interval ) { + this.setBalancer( false ) + + if( typeof db == "undefined" ) db = undefined + var oldDB = db + + db = this.config + sh.waitForBalancer( false, timeout, interval ) + db = oldDB +} + +ShardingTest.prototype.startBalancer = function( timeout, interval ) { + this.setBalancer( true ) + + if( typeof db == "undefined" ) db = undefined + var oldDB = db + + db = this.config + sh.waitForBalancer( true, timeout, interval ) + db = oldDB +} + +/** + * Run a mongod process. + * + * After initializing a MongodRunner, you must call start() on it. + * @param {int} port port to run db on, use allocatePorts(num) to requision + * @param {string} dbpath path to use + * @param {boolean} peer pass in false (DEPRECATED, was used for replica pair host) + * @param {boolean} arbiter pass in false (DEPRECATED, was used for replica pair host) + * @param {array} extraArgs other arguments for the command line + * @param {object} options other options include no_bind to not bind_ip to 127.0.0.1 + * (necessary for replica set testing) + */ +MongodRunner = function( port, dbpath, peer, arbiter, extraArgs, options ) { + this.port_ = port; + this.dbpath_ = dbpath; + this.peer_ = peer; + this.arbiter_ = arbiter; + this.extraArgs_ = extraArgs; + this.options_ = options ? options : {}; +}; + +/** + * Start this mongod process. + * + * @param {boolean} reuseData If the data directory should be left intact (default is to wipe it) + */ +MongodRunner.prototype.start = function( reuseData ) { + var args = []; + if ( reuseData ) { + args.push( "mongod" ); + } + args.push( "--port" ); + args.push( this.port_ ); + args.push( "--dbpath" ); + args.push( this.dbpath_ ); + args.push( "--nohttpinterface" ); + args.push( "--noprealloc" ); + args.push( "--smallfiles" ); + if (!this.options_.no_bind) { + args.push( "--bind_ip" ); + args.push( "127.0.0.1" ); + } + if ( this.extraArgs_ ) { + args = args.concat( this.extraArgs_ ); + } + removeFile( this.dbpath_ + "/mongod.lock" ); + if ( reuseData ) { + return startMongoProgram.apply( null, args ); + } else { + return startMongod.apply( null, args ); + } +} + +MongodRunner.prototype.port = function() { return this.port_; } + +MongodRunner.prototype.toString = function() { return [ this.port_, this.dbpath_, this.peer_, this.arbiter_ ].toString(); } + +ToolTest = function( name ){ + this.name = name; + this.port = allocatePorts(1)[0]; + this.baseName = "jstests_tool_" + name; + this.root = "/data/db/" + this.baseName; + this.dbpath = this.root + "/"; + this.ext = this.root + "_external/"; + this.extFile = this.root + "_external/a"; + resetDbpath( this.dbpath ); + resetDbpath( this.ext ); +} + +ToolTest.prototype.startDB = function( coll ){ + assert( ! this.m , "db already running" ); + + this.m = startMongoProgram( "mongod" , "--port", this.port , "--dbpath" , this.dbpath , "--nohttpinterface", "--noprealloc" , "--smallfiles" , "--bind_ip", "127.0.0.1" ); + this.db = this.m.getDB( this.baseName ); + if ( coll ) + return this.db.getCollection( coll ); + return this.db; +} + +ToolTest.prototype.stop = function(){ + if ( ! this.m ) + return; + stopMongod( this.port ); + this.m = null; + this.db = null; + + print('*** ' + this.name + " completed successfully ***"); +} + +ToolTest.prototype.runTool = function(){ + var a = [ "mongo" + arguments[0] ]; + + var hasdbpath = false; + + for ( var i=1; i<arguments.length; i++ ){ + a.push( arguments[i] ); + if ( arguments[i] == "--dbpath" ) + hasdbpath = true; + } + + if ( ! hasdbpath ){ + a.push( "--host" ); + a.push( "127.0.0.1:" + this.port ); + } + + return runMongoProgram.apply( null , a ); +} + + +ReplTest = function( name, ports ){ + this.name = name; + this.ports = ports || allocatePorts( 2 ); +} + +ReplTest.prototype.getPort = function( master ){ + if ( master ) + return this.ports[ 0 ]; + return this.ports[ 1 ] +} + +ReplTest.prototype.getPath = function( master ){ + var p = "/data/db/" + this.name + "-"; + if ( master ) + p += "master"; + else + p += "slave" + return p; +} + +ReplTest.prototype.getOptions = function( master , extra , putBinaryFirst, norepl ){ + + if ( ! extra ) + extra = {}; + + if ( ! extra.oplogSize ) + extra.oplogSize = "40"; + + var a = [] + if ( putBinaryFirst ) + a.push( "mongod" ) + a.push( "--nohttpinterface", "--noprealloc", "--bind_ip" , "127.0.0.1" , "--smallfiles" ); + + a.push( "--port" ); + a.push( this.getPort( master ) ); + + a.push( "--dbpath" ); + a.push( this.getPath( master ) ); + + if( jsTestOptions().noJournal ) a.push( "--nojournal" ) + if( jsTestOptions().noJournalPrealloc ) a.push( "--nopreallocj" ) + if( jsTestOptions().keyFile ) { + a.push( "--keyFile" ) + a.push( jsTestOptions().keyFile ) + } + + if ( !norepl ) { + if ( master ){ + a.push( "--master" ); + } + else { + a.push( "--slave" ); + a.push( "--source" ); + a.push( "127.0.0.1:" + this.ports[0] ); + } + } + + for ( var k in extra ){ + var v = extra[k]; + if( k in MongoRunner.logicalOptions ) continue + a.push( "--" + k ); + if ( v != null ) + a.push( v ); + } + + return a; +} + +ReplTest.prototype.start = function( master , options , restart, norepl ){ + var lockFile = this.getPath( master ) + "/mongod.lock"; + removeFile( lockFile ); + var o = this.getOptions( master , options , restart, norepl ); + + + if ( restart ) + return startMongoProgram.apply( null , o ); + else + return startMongod.apply( null , o ); +} + +ReplTest.prototype.stop = function( master , signal ){ + if ( arguments.length == 0 ){ + this.stop( true ); + this.stop( false ); + return; + } + + print('*** ' + this.name + " completed successfully ***"); + return stopMongod( this.getPort( master ) , signal || 15 ); +} + +allocatePorts = function( n , startPort ) { + var ret = []; + var start = startPort || 31000; + for( var i = start; i < start + n; ++i ) + ret.push( i ); + return ret; +} + + +SyncCCTest = function( testName , extraMongodOptions ){ + this._testName = testName; + this._connections = []; + + for ( var i=0; i<3; i++ ){ + this._connections.push( startMongodTest( 30000 + i , testName + i , false, extraMongodOptions ) ); + } + + this.url = this._connections.map( function(z){ return z.name; } ).join( "," ); + this.conn = new Mongo( this.url ); +} + +SyncCCTest.prototype.stop = function(){ + for ( var i=0; i<this._connections.length; i++){ + stopMongod( 30000 + i ); + } + + print('*** ' + this._testName + " completed successfully ***"); +} + +SyncCCTest.prototype.checkHashes = function( dbname , msg ){ + var hashes = this._connections.map( + function(z){ + return z.getDB( dbname ).runCommand( "dbhash" ); + } + ); + + for ( var i=1; i<hashes.length; i++ ){ + assert.eq( hashes[0].md5 , hashes[i].md5 , "checkHash on " + dbname + " " + msg + "\n" + tojson( hashes ) ) + } +} + +SyncCCTest.prototype.tempKill = function( num ){ + num = num || 0; + stopMongod( 30000 + num ); +} + +SyncCCTest.prototype.tempStart = function( num ){ + num = num || 0; + this._connections[num] = startMongodTest( 30000 + num , this._testName + num , true ); +} + + +function startParallelShell( jsCode, port ){ + var x; + + var args = ["mongo"]; + if (port) { + args.push("--port", port); + } + + if (TestData) { + jsCode = "TestData = " + tojson(TestData) + ";jsTest.authenticate(db.getMongo());" + jsCode; + } + + args.push("--eval", jsCode); + + if (typeof db == "object") { + args.push(db.getMongo().host); + } + + x = startMongoProgramNoConnect.apply(null, args); + return function(){ + waitProgram( x ); + }; +} + +var testingReplication = false; + +function skipIfTestingReplication(){ + if (testingReplication) { + print("skipIfTestingReplication skipping"); + quit(0); + } +} + +ReplSetTest = function( opts ){ + this.name = opts.name || "testReplSet"; + this.host = opts.host || getHostName(); + this.useHostName = opts.useHostName + this.numNodes = opts.nodes || 0; + this.oplogSize = opts.oplogSize || 40; + this.useSeedList = opts.useSeedList || false; + this.bridged = opts.bridged || false; + this.ports = []; + this.keyFile = opts.keyFile + this.shardSvr = opts.shardSvr || false; + + this.startPort = opts.startPort || 31000; + + this.nodeOptions = {} + if( isObject( this.numNodes ) ){ + var len = 0 + for( var i in this.numNodes ){ + var options = this.nodeOptions[ "n" + len ] = this.numNodes[i] + if( i.startsWith( "a" ) ) options.arbiter = true + len++ + } + this.numNodes = len + } + else if( Array.isArray( this.numNodes ) ){ + for( var i = 0; i < this.numNodes.length; i++ ) + this.nodeOptions[ "n" + i ] = this.numNodes[i] + this.numNodes = this.numNodes.length + } + + if(this.bridged) { + this.bridgePorts = []; + + var allPorts = allocatePorts( this.numNodes * 2 , this.startPort ); + for(var i=0; i < this.numNodes; i++) { + this.ports[i] = allPorts[i*2]; + this.bridgePorts[i] = allPorts[i*2 + 1]; + } + + this.initBridges(); + } + else { + this.ports = allocatePorts( this.numNodes , this.startPort ); + } + + this.nodes = [] + this.initLiveNodes() + + Object.extend( this, ReplSetTest.Health ) + Object.extend( this, ReplSetTest.State ) + +} + +ReplSetTest.prototype.initBridges = function() { + for(var i=0; i<this.ports.length; i++) { + startMongoProgram( "mongobridge", "--port", this.bridgePorts[i], "--dest", this.host + ":" + this.ports[i] ); + } +} + +// List of nodes as host:port strings. +ReplSetTest.prototype.nodeList = function() { + var list = []; + for(var i=0; i<this.ports.length; i++) { + list.push( this.host + ":" + this.ports[i]); + } + + return list; +} + +// Here we store a reference to all reachable nodes. +ReplSetTest.prototype.initLiveNodes = function() { + this.liveNodes = { master: null, slaves: [] } +} + +ReplSetTest.prototype.getNodeId = function(node) { + + if( node.toFixed ) return parseInt( node ) + + for( var i = 0; i < this.nodes.length; i++ ){ + if( this.nodes[i] == node ) return i + } + + if( node instanceof ObjectId ){ + for( var i = 0; i < this.nodes.length; i++ ){ + if( this.nodes[i].runId == node ) return i + } + } + + if( node.nodeId ) return parseInt( node.nodeId ) + + return undefined + +} + +ReplSetTest.prototype.getPort = function( n ){ + + n = this.getNodeId( n ) + + print( "ReplSetTest n: " + n + " ports: " + tojson( this.ports ) + "\t" + this.ports[n] + " " + typeof(n) ); + return this.ports[ n ]; +} + +ReplSetTest.prototype.getPath = function( n ){ + + if( n.host ) + n = this.getNodeId( n ) + + var p = "/data/db/" + this.name + "-"+n; + if ( ! this._alldbpaths ) + this._alldbpaths = [ p ]; + else + this._alldbpaths.push( p ); + return p; +} + +ReplSetTest.prototype.getReplSetConfig = function() { + var cfg = {}; + + cfg['_id'] = this.name; + cfg.members = []; + + for(i=0; i<this.ports.length; i++) { + member = {}; + member['_id'] = i; + + if(this.bridged) + var port = this.bridgePorts[i]; + else + var port = this.ports[i]; + + member['host'] = this.host + ":" + port; + if( this.nodeOptions[ "n" + i ] && this.nodeOptions[ "n" + i ].arbiter ) + member['arbiterOnly'] = true + + cfg.members.push(member); + } + + return cfg; +} + +ReplSetTest.prototype.getURL = function(){ + var hosts = []; + + for(i=0; i<this.ports.length; i++) { + + // Don't include this node in the replica set list + if(this.bridged && this.ports[i] == this.ports[n]) { + continue; + } + + var port; + // Connect on the right port + if(this.bridged) { + port = this.bridgePorts[i]; + } + else { + port = this.ports[i]; + } + + var str = this.host + ":" + port; + hosts.push(str); + } + + return this.name + "/" + hosts.join(","); +} + +ReplSetTest.prototype.getOptions = function( n , extra , putBinaryFirst ){ + + if ( ! extra ) + extra = {}; + + if ( ! extra.oplogSize ) + extra.oplogSize = this.oplogSize; + + var a = [] + + + if ( putBinaryFirst ) + a.push( "mongod" ); + + if ( extra.noReplSet ) { + delete extra.noReplSet; + } + else { + a.push( "--replSet" ); + + if( this.useSeedList ) { + a.push( this.getURL() ); + } + else { + a.push( this.name ); + } + } + + a.push( "--noprealloc", "--smallfiles" ); + + a.push( "--rest" ); + + a.push( "--port" ); + a.push( this.getPort( n ) ); + + a.push( "--dbpath" ); + a.push( this.getPath( ( n.host ? this.getNodeId( n ) : n ) ) ); + + if( this.keyFile ){ + a.push( "--keyFile" ) + a.push( keyFile ) + } + + if( jsTestOptions().noJournal ) a.push( "--nojournal" ) + if( jsTestOptions().noJournalPrealloc ) a.push( "--nopreallocj" ) + if( jsTestOptions().keyFile && !this.keyFile) { + a.push( "--keyFile" ) + a.push( jsTestOptions().keyFile ) + } + + for ( var k in extra ){ + var v = extra[k]; + if( k in MongoRunner.logicalOptions ) continue + a.push( "--" + k ); + if ( v != null ){ + if( v.replace ){ + v = v.replace(/\$node/g, "" + ( n.host ? this.getNodeId( n ) : n ) ) + v = v.replace(/\$set/g, this.name ) + v = v.replace(/\$path/g, this.getPath( n ) ) + } + a.push( v ); + } + } + + return a; +} + +ReplSetTest.prototype.startSet = function( options ) { + + var nodes = []; + print( "ReplSetTest Starting Set" ); + + for( n = 0 ; n < this.ports.length; n++ ) { + node = this.start(n, options) + nodes.push(node); + } + + this.nodes = nodes; + return this.nodes; +} + +ReplSetTest.prototype.callIsMaster = function() { + + var master = null; + this.initLiveNodes(); + + for(var i=0; i<this.nodes.length; i++) { + + try { + var n = this.nodes[i].getDB('admin').runCommand({ismaster:1}); + + if(n['ismaster'] == true) { + master = this.nodes[i] + this.liveNodes.master = master + } + else { + this.nodes[i].setSlaveOk(); + this.liveNodes.slaves.push(this.nodes[i]); + } + + } + catch(err) { + print("ReplSetTest Could not call ismaster on node " + i); + } + } + + return master || false; +} + +ReplSetTest.awaitRSClientHosts = function( conn, host, hostOk, rs ) { + + if( host.length ){ + for( var i = 0; i < host.length; i++ ) this.awaitOk( conn, host[i] ) + return + } + + if( hostOk == undefined ) hostOk = { ok : true } + if( host.host ) host = host.host + if( rs && rs.getMaster ) rs = rs.name + + print( "Awaiting " + host + " to be " + tojson( hostOk ) + " for " + conn + " (rs: " + rs + ")" ) + + var tests = 0 + assert.soon( function() { + var rsClientHosts = conn.getDB( "admin" ).runCommand( "connPoolStats" )[ "replicaSets" ] + if( tests++ % 10 == 0 ) + printjson( rsClientHosts ) + + for ( rsName in rsClientHosts ){ + if( rs && rs != rsName ) continue + for ( var i = 0; i < rsClientHosts[rsName].hosts.length; i++ ){ + var clientHost = rsClientHosts[rsName].hosts[ i ]; + if( clientHost.addr != host ) continue + + // Check that *all* host properties are set correctly + var propOk = true + for( var prop in hostOk ){ + if( clientHost[prop] != hostOk[prop] ){ + propOk = false + break + } + } + + if( propOk ) return true; + + } + } + return false; + }, "timed out waiting for replica set client to recognize hosts", + 3 * 20 * 1000 /* ReplicaSetMonitorWatcher updates every 20s */ ) + +} + +ReplSetTest.prototype.awaitSecondaryNodes = function( timeout ) { + var master = this.getMaster(); + var slaves = this.liveNodes.slaves; + var len = slaves.length; + + jsTest.attempt({context: this, timeout: 60000, desc: "Awaiting secondaries"}, function() { + var ready = true; + for(var i=0; i<len; i++) { + ready = ready && slaves[i].getDB("admin").runCommand({ismaster: 1})['secondary']; + } + + return ready; + }); +} + +ReplSetTest.prototype.getMaster = function( timeout ) { + var tries = 0; + var sleepTime = 500; + var t = timeout || 000; + var master = null; + + master = jsTest.attempt({context: this, timeout: 60000, desc: "Finding master"}, this.callIsMaster); + return master; +} + +ReplSetTest.prototype.getPrimary = ReplSetTest.prototype.getMaster + +ReplSetTest.prototype.getSecondaries = function( timeout ){ + var master = this.getMaster( timeout ) + var secs = [] + for( var i = 0; i < this.nodes.length; i++ ){ + if( this.nodes[i] != master ){ + secs.push( this.nodes[i] ) + } + } + return secs +} + +ReplSetTest.prototype.getSecondary = function( timeout ){ + return this.getSecondaries( timeout )[0]; +} + +ReplSetTest.prototype.status = function( timeout ){ + var master = this.callIsMaster() + if( ! master ) master = this.liveNodes.slaves[0] + return master.getDB("admin").runCommand({replSetGetStatus: 1}) +} + +// Add a node to the test set +ReplSetTest.prototype.add = function( config ) { + if(this.ports.length == 0) { + var nextPort = allocatePorts( 1, this.startPort )[0]; + } + else { + var nextPort = this.ports[this.ports.length-1] + 1; + } + print("ReplSetTest Next port: " + nextPort); + this.ports.push(nextPort); + printjson(this.ports); + + var nextId = this.nodes.length; + printjson(this.nodes); + print("ReplSetTest nextId:" + nextId); + var newNode = this.start( nextId ); + + return newNode; +} + +ReplSetTest.prototype.remove = function( nodeId ) { + nodeId = this.getNodeId( nodeId ) + this.nodes.splice( nodeId, 1 ); + this.ports.splice( nodeId, 1 ); +} + +ReplSetTest.prototype.initiate = function( cfg , initCmd , timeout ) { + var master = this.nodes[0].getDB("admin"); + var config = cfg || this.getReplSetConfig(); + var cmd = {}; + var cmdKey = initCmd || 'replSetInitiate'; + var timeout = timeout || 30000; + cmd[cmdKey] = config; + printjson(cmd); + + jsTest.attempt({context:this, timeout: timeout, desc: "Initiate replica set"}, function() { + var result = master.runCommand(cmd); + printjson(result); + return result['ok'] == 1; + }); + + // Setup authentication if running test with authentication + if (jsTestOptions().keyFile && !this.keyFile) { + if (!this.shardSvr) { + master = this.getMaster(); + jsTest.addAuth(master); + } + jsTest.authenticateNodes(this.nodes); + } +} + +ReplSetTest.prototype.reInitiate = function() { + var master = this.nodes[0]; + var c = master.getDB("local")['system.replset'].findOne(); + var config = this.getReplSetConfig(); + config.version = c.version + 1; + this.initiate( config , 'replSetReconfig' ); +} + +ReplSetTest.prototype.getLastOpTimeWritten = function() { + this.getMaster(); + jsTest.attempt({context : this, desc : "awaiting oplog query"}, + function() { + try { + this.latest = this.liveNodes.master.getDB("local")['oplog.rs'].find({}).sort({'$natural': -1}).limit(1).next()['ts']; + } + catch(e) { + print("ReplSetTest caught exception " + e); + return false; + } + return true; + }); +}; + +ReplSetTest.prototype.awaitReplication = function(timeout) { + timeout = timeout || 30000; + + this.getLastOpTimeWritten(); + + print("ReplSetTest " + this.latest); + + jsTest.attempt({context: this, timeout: timeout, desc: "awaiting replication"}, + function() { + try { + var synced = true; + for(var i=0; i<this.liveNodes.slaves.length; i++) { + var slave = this.liveNodes.slaves[i]; + + // Continue if we're connected to an arbiter + if(res = slave.getDB("admin").runCommand({replSetGetStatus: 1})) { + if(res.myState == 7) { + continue; + } + } + + slave.getDB("admin").getMongo().setSlaveOk(); + var log = slave.getDB("local")['oplog.rs']; + if(log.find({}).sort({'$natural': -1}).limit(1).hasNext()) { + var entry = log.find({}).sort({'$natural': -1}).limit(1).next(); + printjson( entry ); + var ts = entry['ts']; + print("ReplSetTest await TS for " + slave + " is " + ts.t+":"+ts.i + " and latest is " + this.latest.t+":"+this.latest.i); + + if (this.latest.t < ts.t || (this.latest.t == ts.t && this.latest.i < ts.i)) { + this.latest = this.liveNodes.master.getDB("local")['oplog.rs'].find({}).sort({'$natural': -1}).limit(1).next()['ts']; + } + + print("ReplSetTest await oplog size for " + slave + " is " + log.count()); + synced = (synced && friendlyEqual(this.latest,ts)) + } + else { + synced = false; + } + } + + if(synced) { + print("ReplSetTest await synced=" + synced); + } + return synced; + } + catch (e) { + print("ReplSetTest.awaitReplication: caught exception "+e); + + // we might have a new master now + this.getLastOpTimeWritten(); + + return false; + } + }); +} + +ReplSetTest.prototype.getHashes = function( db ){ + this.getMaster(); + var res = {}; + res.master = this.liveNodes.master.getDB( db ).runCommand( "dbhash" ) + res.slaves = this.liveNodes.slaves.map( function(z){ return z.getDB( db ).runCommand( "dbhash" ); } ) + return res; +} + +/** + * Starts up a server. Options are saved by default for subsequent starts. + * + * + * Options { remember : true } re-applies the saved options from a prior start. + * Options { noRemember : true } ignores the current properties. + * Options { appendOptions : true } appends the current options to those remembered. + * Options { startClean : true } clears the data directory before starting. + * + * @param @param {int|conn|[int|conn]} n array or single server number (0, 1, 2, ...) or conn + * @param {object} [options] + * @param {boolean} [restart] If false, the data directory will be cleared + * before the server starts. Defaults to false. + * + */ +ReplSetTest.prototype.start = function( n , options , restart , wait ){ + + if( n.length ){ + + var nodes = n + var started = [] + + for( var i = 0; i < nodes.length; i++ ){ + if( this.start( nodes[i], Object.merge({}, options), restart, wait ) ){ + started.push( nodes[i] ) + } + } + + return started + + } + + print( "ReplSetTest n is : " + n ) + + defaults = { useHostName : this.useHostName, + oplogSize : this.oplogSize, + keyFile : this.keyFile, + port : this.getPort( n ), + noprealloc : "", + smallfiles : "", + rest : "", + replSet : this.useSeedList ? this.getURL() : this.name, + dbpath : "$set-$node" } + + // TODO : should we do something special if we don't currently know about this node? + n = this.getNodeId( n ) + + options = Object.merge( defaults, options ) + options = Object.merge( options, this.nodeOptions[ "n" + n ] ) + + options.restart = restart + + var pathOpts = { node : n, set : this.name } + options.pathOpts = Object.merge( options.pathOpts || {}, pathOpts ) + + if( tojson(options) != tojson({}) ) + printjson(options) + + // make sure to call getPath, otherwise folders wont be cleaned + this.getPath(n); + + print("ReplSetTest " + (restart ? "(Re)" : "") + "Starting...."); + + var rval = this.nodes[n] = MongoRunner.runMongod( options ) + + if( ! rval ) return rval + + // Add replica set specific attributes + this.nodes[n].nodeId = n + + printjson( this.nodes ) + + wait = wait || false + if( ! wait.toFixed ){ + if( wait ) wait = 0 + else wait = -1 + } + + if( wait < 0 ) return rval + + // Wait for startup + this.waitForHealth( rval, this.UP, wait ) + + return rval + +} + + +/** + * Restarts a db without clearing the data directory by default. If the server is not + * stopped first, this function will not work. + * + * Option { startClean : true } forces clearing the data directory. + * + * @param {int|conn|[int|conn]} n array or single server number (0, 1, 2, ...) or conn + */ +ReplSetTest.prototype.restart = function( n , options, signal, wait ){ + // Can specify wait as third parameter, if using default signal + if( signal == true || signal == false ){ + wait = signal + signal = undefined + } + + this.stop( n, signal, wait && wait.toFixed ? wait : true ) + started = this.start( n , options , true, wait ); + + if (jsTestOptions().keyFile && !this.keyFile) { + if (started.length) { + // if n was an array of conns, start will return an array of connections + for (var i = 0; i < started.length; i++) { + jsTest.authenticate(started[i]); + } + } else { + jsTest.authenticate(started); + } + } + return started; +} + +ReplSetTest.prototype.stopMaster = function( signal , wait ) { + var master = this.getMaster(); + var master_id = this.getNodeId( master ); + return this.stop( master_id , signal , wait ); +} + +// Stops a particular node or nodes, specified by conn or id +ReplSetTest.prototype.stop = function( n , signal, wait /* wait for stop */ ){ + + // Flatten array of nodes to stop + if( n.length ){ + nodes = n + + var stopped = [] + for( var i = 0; i < nodes.length; i++ ){ + if( this.stop( nodes[i], signal, wait ) ) + stopped.push( nodes[i] ) + } + + return stopped + } + + // Can specify wait as second parameter, if using default signal + if( signal == true || signal == false ){ + wait = signal + signal = undefined + } + + wait = wait || false + if( ! wait.toFixed ){ + if( wait ) wait = 0 + else wait = -1 + } + + var port = this.getPort( n ); + print('ReplSetTest stop *** Shutting down mongod in port ' + port + ' ***'); + var ret = MongoRunner.stopMongod( port , signal ); + + if( ! ret || wait < 0 ) return ret + + // Wait for shutdown + this.waitForHealth( n, this.DOWN, wait ) + + return true +} + + +ReplSetTest.prototype.stopSet = function( signal , forRestart ) { + for(i=0; i < this.ports.length; i++) { + this.stop( i, signal ); + } + if ( ! forRestart && this._alldbpaths ){ + print("ReplSetTest stopSet deleting all dbpaths"); + for( i=0; i<this._alldbpaths.length; i++ ){ + resetDbpath( this._alldbpaths[i] ); + } + } + + print('ReplSetTest stopSet *** Shut down repl set - test worked ****' ) +}; + + +/** + * Waits until there is a master node + */ +ReplSetTest.prototype.waitForMaster = function( timeout ){ + + var master = undefined + + jsTest.attempt({context: this, timeout: timeout, desc: "waiting for master"}, function() { + return ( master = this.getMaster() ) + }); + + return master +} + + +/** + * Wait for a health indicator to go to a particular state or states. + * + * @param node is a single node or list of nodes, by id or conn + * @param state is a single state or list of states + * + */ +ReplSetTest.prototype.waitForHealth = function( node, state, timeout ){ + this.waitForIndicator( node, state, "health", timeout ) +} + +/** + * Wait for a state indicator to go to a particular state or states. + * + * @param node is a single node or list of nodes, by id or conn + * @param state is a single state or list of states + * + */ +ReplSetTest.prototype.waitForState = function( node, state, timeout ){ + this.waitForIndicator( node, state, "state", timeout ) +} + +/** + * Wait for a rs indicator to go to a particular state or states. + * + * @param node is a single node or list of nodes, by id or conn + * @param states is a single state or list of states + * @param ind is the indicator specified + * + */ +ReplSetTest.prototype.waitForIndicator = function( node, states, ind, timeout ){ + + if( node.length ){ + + var nodes = node + for( var i = 0; i < nodes.length; i++ ){ + if( states.length ) + this.waitForIndicator( nodes[i], states[i], ind, timeout ) + else + this.waitForIndicator( nodes[i], states, ind, timeout ) + } + + return; + } + + timeout = timeout || 30000; + + if( ! node.getDB ){ + node = this.nodes[node] + } + + if( ! states.length ) states = [ states ] + + print( "ReplSetTest waitForIndicator " + ind + " on " + node ) + printjson( states ) + print( "ReplSetTest waitForIndicator from node " + node ) + + var lastTime = null + var currTime = new Date().getTime() + var status = undefined + + jsTest.attempt({context: this, timeout: timeout, desc: "waiting for state indicator " + ind + " for " + timeout + "ms" }, function() { + + status = this.status() + + var printStatus = false + if( lastTime == null || ( currTime = new Date().getTime() ) - (1000 * 5) > lastTime ){ + if( lastTime == null ) print( "ReplSetTest waitForIndicator Initial status ( timeout : " + timeout + " ) :" ) + printjson( status ) + lastTime = new Date().getTime() + printStatus = true + } + + if (typeof status.members == 'undefined') { + return false; + } + + for( var i = 0; i < status.members.length; i++ ){ + if( printStatus ) print( "Status for : " + status.members[i].name + ", checking " + node.host + "/" + node.name ) + if( status.members[i].name == node.host || status.members[i].name == node.name ){ + for( var j = 0; j < states.length; j++ ){ + if( printStatus ) print( "Status " + " : " + status.members[i][ind] + " target state : " + states[j] ) + if( status.members[i][ind] == states[j] ) return true; + } + } + } + + return false + + }); + + print( "ReplSetTest waitForIndicator final status:" ) + printjson( status ) + +} + +ReplSetTest.Health = {} +ReplSetTest.Health.UP = 1 +ReplSetTest.Health.DOWN = 0 + +ReplSetTest.State = {} +ReplSetTest.State.PRIMARY = 1 +ReplSetTest.State.SECONDARY = 2 +ReplSetTest.State.RECOVERING = 3 + +/** + * Overflows a replica set secondary or secondaries, specified by id or conn. + */ +ReplSetTest.prototype.overflow = function( secondaries ){ + + // Create a new collection to overflow, allow secondaries to replicate + var master = this.getMaster() + var overflowColl = master.getCollection( "_overflow.coll" ) + overflowColl.insert({ replicated : "value" }) + this.awaitReplication() + + this.stop( secondaries, undefined, 5 * 60 * 1000 ) + + var count = master.getDB("local").oplog.rs.count(); + var prevCount = -1; + + // Keep inserting till we hit our capped coll limits + while (count != prevCount) { + + print("ReplSetTest overflow inserting 10000"); + + for (var i = 0; i < 10000; i++) { + overflowColl.insert({ overflow : "value" }); + } + prevCount = count; + this.awaitReplication(); + + count = master.getDB("local").oplog.rs.count(); + + print( "ReplSetTest overflow count : " + count + " prev : " + prevCount ); + + } + + // Restart all our secondaries and wait for recovery state + this.start( secondaries, { remember : true }, true, true ) + this.waitForState( secondaries, this.RECOVERING, 5 * 60 * 1000 ) + +} + + + + +/** + * Bridging allows you to test network partitioning. For example, you can set + * up a replica set, run bridge(), then kill the connection between any two + * nodes x and y with partition(x, y). + * + * Once you have called bridging, you cannot reconfigure the replica set. + */ +ReplSetTest.prototype.bridge = function() { + if (this.bridges) { + print("ReplSetTest bridge bridges have already been created!"); + return; + } + + var n = this.nodes.length; + + // create bridges + this.bridges = []; + for (var i=0; i<n; i++) { + var nodeBridges = []; + for (var j=0; j<n; j++) { + if (i == j) { + continue; + } + nodeBridges[j] = new ReplSetBridge(this, i, j); + } + this.bridges.push(nodeBridges); + } + print("ReplSetTest bridge bridges: " + this.bridges); + + // restart everyone independently + this.stopSet(null, true); + for (var i=0; i<n; i++) { + this.restart(i, {noReplSet : true}); + } + + // create new configs + for (var i=0; i<n; i++) { + config = this.nodes[i].getDB("local").system.replset.findOne(); + + if (!config) { + print("ReplSetTest bridge couldn't find config for "+this.nodes[i]); + printjson(this.nodes[i].getDB("local").system.namespaces.find().toArray()); + assert(false); + } + + var updateMod = {"$set" : {}}; + for (var j = 0; j<config.members.length; j++) { + if (config.members[j].host == this.host+":"+this.ports[i]) { + continue; + } + + updateMod['$set']["members."+j+".host"] = this.bridges[i][j].host; + } + print("ReplSetTest bridge for node " + i + ":"); + printjson(updateMod); + this.nodes[i].getDB("local").system.replset.update({},updateMod); + } + + this.stopSet(null, true); + + // start set + for (var i=0; i<n; i++) { + this.restart(i); + } + + return this.getMaster(); +}; + +/** + * This kills the bridge between two nodes. As parameters, specify the from and + * to node numbers. + * + * For example, with a three-member replica set, we'd have nodes 0, 1, and 2, + * with the following bridges: 0->1, 0->2, 1->0, 1->2, 2->0, 2->1. We can kill + * the connection between nodes 0 and 2 by calling replTest.partition(0,2) or + * replTest.partition(2,0) (either way is identical). Then the replica set would + * have the following bridges: 0->1, 1->0, 1->2, 2->1. + */ +ReplSetTest.prototype.partition = function(from, to) { + this.bridges[from][to].stop(); + this.bridges[to][from].stop(); +}; + +/** + * This reverses a partition created by partition() above. + */ +ReplSetTest.prototype.unPartition = function(from, to) { + this.bridges[from][to].start(); + this.bridges[to][from].start(); +}; + +ReplSetBridge = function(rst, from, to) { + var n = rst.nodes.length; + + var startPort = rst.startPort+n; + this.port = (startPort+(from*n+to)); + this.host = rst.host+":"+this.port; + + this.dest = rst.host+":"+rst.ports[to]; + this.start(); +}; + +ReplSetBridge.prototype.start = function() { + var args = ["mongobridge", "--port", this.port, "--dest", this.dest]; + print("ReplSetBridge starting: "+tojson(args)); + this.bridge = startMongoProgram.apply( null , args ); + print("ReplSetBridge started " + this.bridge); +}; + +ReplSetBridge.prototype.stop = function() { + print("ReplSetBridge stopping: " + this.port); + stopMongod(this.port); +}; + +ReplSetBridge.prototype.toString = function() { + return this.host+" -> "+this.dest; +}; |