diff options
Diffstat (limited to 'shell/servers.js')
| -rwxr-xr-x[-rw-r--r--] | shell/servers.js | 1151 |
1 files changed, 865 insertions, 286 deletions
diff --git a/shell/servers.js b/shell/servers.js index f713ecc..ad3b5eb 100644..100755 --- a/shell/servers.js +++ b/shell/servers.js @@ -1,5 +1,3 @@ - - _parsePath = function() { var dbpath = ""; for( var i = 0; i < arguments.length; ++i ) @@ -23,6 +21,25 @@ _parsePort = function() { return port; } +connectionURLTheSame = function( a , b ){ + if ( a == b ) + return true; + + if ( ! a || ! b ) + return false; + + a = a.split( "/" )[0] + b = b.split( "/" )[0] + + return a == b; +} + +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 ]; @@ -78,9 +95,12 @@ startMongodTest = function (port, dirname, restart, extraOptions ) { dbpath: "/data/db/" + dirname, noprealloc: "", smallfiles: "", - oplogSize: "2", + oplogSize: "40", nohttpinterface: "" }; + + if( jsTestOptions().noJournal ) options["nojournal"] = "" + if( jsTestOptions().noJournalPrealloc ) options["nopreallocj"] = "" if ( extraOptions ) Object.extend( options , extraOptions ); @@ -104,7 +124,7 @@ startMongodEmpty = function () { return startMongoProgram.apply(null, args); } startMongod = function () { - print("WARNING DELETES DATA DIRECTORY THIS IS FOR TESTING RENAME YOUR INVOCATION"); + print("startMongod WARNING DELETES DATA DIRECTORY THIS IS FOR TESTING ONLY"); return startMongodEmpty.apply(null, arguments); } startMongodNoReset = function(){ @@ -135,7 +155,7 @@ startMongoProgram = function(){ } catch( e ) { } return false; - }, "unable to connect to mongo program on port " + port, 300 * 1000 ); + }, "unable to connect to mongo program on port " + port, 600 * 1000 ); return m; } @@ -160,6 +180,19 @@ myPort = function() { * * 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 + if( testName && ! testName.charAt ){ + var params = testName + testName = params.name || "test" + numShards = params.shards || 2 + verboseLevel = params.verbose || 0 + numMongos = params.mongos || 1 + otherParams = params.other || {} + keyFile = params.keyFile || otherParams.keyFile + } + this._testName = testName; if ( ! otherParams ) @@ -172,16 +205,26 @@ ShardingTest = function( testName , numShards , verboseLevel , numMongos , other var localhost = otherParams.useHostname ? getHostName() : "localhost"; this._alldbpaths = [] - - + if ( otherParams.rs ){ localhost = getHostName(); // start replica sets this._rs = [] for ( var i=0; i<numShards; i++){ var setName = testName + "-rs" + i; - var rs = new ReplSetTest( { name : setName , nodes : 3 , startPort : 31100 + ( i * 100 ) } ); - this._rs[i] = { setName : setName , test : rs , nodes : rs.startSet( { oplogSize:40 } ) , url : rs.getURL() }; + + var rsDefaults = { oplogSize : 40, nodes : 3 } + var rsParams = otherParams["rs" + i] + + for( var param in rsParams ){ + rsDefaults[param] = rsParams[param] + } + + var numReplicas = rsDefaults.nodes || otherParams.numReplicas || 3 + delete rsDefaults.nodes + + var rs = new ReplSetTest( { name : setName , nodes : numReplicas , startPort : 31100 + ( i * 100 ), keyFile : keyFile } ); + this._rs[i] = { setName : setName , test : rs , nodes : rs.startSet( rsDefaults ) , url : rs.getURL() }; rs.initiate(); } @@ -190,27 +233,35 @@ ShardingTest = function( testName , numShards , verboseLevel , numMongos , other var rs = this._rs[i].test; rs.getMaster().getDB( "admin" ).foo.save( { x : 1 } ) rs.awaitReplication(); - this._connections.push( new Mongo( rs.getURL() ) ); + var xxx = new Mongo( rs.getURL() ); + xxx.name = rs.getURL(); + this._connections.push( xxx ); } this._configServers = [] - for ( var i=0; i<3; i++ ){ - var conn = startMongodTest( 30000 + i , testName + "-config" + i ); + for ( var i=0; i<3; i++ ){ + var options = otherParams.extraOptions + if( keyFile ) options["keyFile"] = keyFile + var conn = startMongodTest( 30000 + i , testName + "-config" + i, false, options ); this._alldbpaths.push( testName + "-config" + i ) this._configServers.push( conn ); } - + this._configDB = localhost + ":30000," + localhost + ":30001," + localhost + ":30002"; this._configConnection = new Mongo( this._configDB ); - this._configConnection.getDB( "config" ).settings.insert( { _id : "chunksize" , value : otherParams.chunksize || 50 } ); + if (!otherParams.noChunkSize) { + this._configConnection.getDB( "config" ).settings.insert( { _id : "chunksize" , value : otherParams.chunksize || 50 } ); + } } else { for ( var i=0; i<numShards; i++){ - var conn = startMongodTest( 30000 + i , testName + i, 0, {useHostname : otherParams.useHostname} ); + var options = { useHostname : otherParams.useHostname } + if( keyFile ) options["keyFile"] = keyFile + var conn = startMongodTest( 30000 + i , testName + i, 0, options ); this._alldbpaths.push( testName +i ) this._connections.push( conn ); } - + if ( otherParams.sync ){ this._configDB = localhost+":30000,"+localhost+":30001,"+localhost+":30002"; this._configConnection = new Mongo( this._configDB ); @@ -226,12 +277,19 @@ ShardingTest = function( testName , numShards , verboseLevel , numMongos , other var startMongosPort = 31000; for ( var i=0; i<(numMongos||1); i++ ){ var myPort = startMongosPort - i; - print("config: "+this._configDB); - var conn = startMongos( { port : startMongosPort - i , v : verboseLevel || 0 , configdb : this._configDB } ); + print("ShardingTest config: "+this._configDB); + var opts = { port : startMongosPort - i , v : verboseLevel || 0 , configdb : this._configDB }; + if( keyFile ) opts["keyFile"] = keyFile + for (var j in otherParams.extraOptions) { + opts[j] = otherParams.extraOptions[j]; + } + var conn = startMongos( opts ); conn.name = localhost + ":" + myPort; this._mongos.push( conn ); - if ( i == 0 ) + if ( i == 0 ) { this.s = conn; + } + this["s" + i] = conn } var admin = this.admin = this.s.getDB( "admin" ); @@ -246,7 +304,7 @@ ShardingTest = function( testName , numShards , verboseLevel , numMongos , other if ( ! n ) n = z; } - print( "going to add shard: " + n ) + print( "ShardingTest going to add shard: " + n ) x = admin.runCommand( { addshard : n } ); printjson( x ) } @@ -266,7 +324,7 @@ ShardingTest.prototype.getDB = function( name ){ } ShardingTest.prototype.getServerName = function( dbname ){ - var x = this.config.databases.findOne( { _id : dbname } ); + var x = this.config.databases.findOne( { _id : "" + dbname } ); if ( x ) return x.primary; this.config.databases.find().forEach( printjson ); @@ -300,12 +358,17 @@ ShardingTest.prototype.getServer = function( dbname ){ 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 ( name == c.name ) + if ( connectionURLTheSame( name , c.name ) || + connectionURLTheSame( rsName , c.name ) ) return c; } - + throw "can't find server for: " + dbname + " name:" + name; } @@ -318,15 +381,30 @@ ShardingTest.prototype.normalize = function( x ){ } ShardingTest.prototype.getOther = function( one ){ - if ( this._connections.length != 2 ) + 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 +} - if ( this._connections[0] == one ) - return this._connections[1]; - return this._connections[0]; +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 ){ @@ -355,7 +433,7 @@ ShardingTest.prototype.stop = function(){ } } - print('*** ' + this._testName + " completed successfully ***"); + print('*** ShardingTest ' + this._testName + " completed successfully ***"); } ShardingTest.prototype.adminCommand = function(cmd){ @@ -388,7 +466,7 @@ ShardingTest.prototype.printChangeLog = function(){ msg += tojsononeline( z.details ); } - print( msg ) + print( "ShardingTest " + msg ) } ); @@ -410,7 +488,7 @@ ShardingTest.prototype.getChunksString = function( ns ){ } ShardingTest.prototype.printChunks = function( ns ){ - print( this.getChunksString( ns ) ); + print( "ShardingTest " + this.getChunksString( ns ) ); } ShardingTest.prototype.printShardingStatus = function(){ @@ -433,7 +511,7 @@ ShardingTest.prototype.printCollectionInfo = function( ns , msg ){ out += this.getChunksString( ns ); - print( out ); + print( "ShardingTest " + out ); } printShardingStatus = function( configDB , verbose ){ @@ -442,7 +520,7 @@ printShardingStatus = function( configDB , verbose ){ var version = configDB.getCollection( "version" ).findOne(); if ( version == null ){ - print( "not a shard db!" ); + print( "printShardingStatus: not a shard db!" ); return; } @@ -454,16 +532,16 @@ printShardingStatus = function( configDB , verbose ){ output( " sharding version: " + tojson( configDB.getCollection( "version" ).findOne() ) ); output( " shards:" ); - configDB.shards.find().forEach( + configDB.shards.find().sort( { _id : 1 } ).forEach( function(z){ - output( " " + tojson(z) ); + output( "\t" + tojsononeline( z ) ); } ); output( " databases:" ); configDB.databases.find().sort( { name : 1 } ).forEach( function(db){ - output( "\t" + tojson(db,"",true) ); + output( "\t" + tojsononeline(db,"",true) ); if (db.partitioned){ configDB.collections.find( { _id : new RegExp( "^" + db._id + "\." ) } ).sort( { _id : 1 } ).forEach( @@ -487,7 +565,7 @@ printShardingStatus = function( configDB , verbose ){ ); } else { - output( "\t\t\ttoo many chunksn to print, use verbose if you want to force print" ); + output( "\t\t\ttoo many chunks to print, use verbose if you want to force print" ); } } } @@ -504,7 +582,7 @@ printShardingSizes = function(){ var version = configDB.getCollection( "version" ).findOne(); if ( version == null ){ - print( "not a shard db!" ); + print( "printShardingSizes : not a shard db!" ); return; } @@ -614,23 +692,124 @@ ShardingTest.prototype.chunkDiff = function( collName , dbName ){ if ( c[s] > max ) max = c[s]; } - print( "input: " + tojson( c ) + " min: " + min + " max: " + max ); + print( "ShardingTest input: " + tojson( c ) + " min: " + min + " max: " + max ); return max - min; } +ShardingTest.prototype.getShard = function( coll, query ){ + var shards = this.getShards( coll, query ) + 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 ){ + 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( 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].name , 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 || key; move = move || split; - dbName = dbName || "test"; + + if( collName.getDB ) + dbName = "" + collName.getDB() + else dbName = dbName || "test"; var c = dbName + "." + collName; + if( collName.getDB ) + c = "" + collName - s.adminCommand( { shardcollection : c , key : key } ); - s.adminCommand( { split : c , middle : split } ); - s.adminCommand( { movechunk : c , find : move , to : this.getOther( s.getServer( dbName ) ).name } ); + 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 ) + } + + result = this.s.adminCommand( { split : c , middle : split } ); + if( ! result.ok ){ + printjson( result ) + assert( false ) + } + + 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 ) + } +} + /** * Run a mongod process. * @@ -666,15 +845,6 @@ MongodRunner.prototype.start = function( reuseData ) { args.push( this.port_ ); args.push( "--dbpath" ); args.push( this.dbpath_ ); - if ( this.peer_ && this.arbiter_ ) { - args.push( "--pairwith" ); - args.push( this.peer_ ); - args.push( "--arbiter" ); - args.push( this.arbiter_ ); - args.push( "--oplogSize" ); - // small oplog by default so startup fast - args.push( "1" ); - } args.push( "--nohttpinterface" ); args.push( "--noprealloc" ); args.push( "--smallfiles" ); @@ -697,154 +867,6 @@ MongodRunner.prototype.port = function() { return this.port_; } MongodRunner.prototype.toString = function() { return [ this.port_, this.dbpath_, this.peer_, this.arbiter_ ].toString(); } - -ReplPair = function( left, right, arbiter ) { - this.left_ = left; - this.leftC_ = null; - this.right_ = right; - this.rightC_ = null; - this.arbiter_ = arbiter; - this.arbiterC_ = null; - this.master_ = null; - this.slave_ = null; -} - -ReplPair.prototype.start = function( reuseData ) { - if ( this.arbiterC_ == null ) { - this.arbiterC_ = this.arbiter_.start(); - } - if ( this.leftC_ == null ) { - this.leftC_ = this.left_.start( reuseData ); - } - if ( this.rightC_ == null ) { - this.rightC_ = this.right_.start( reuseData ); - } -} - -ReplPair.prototype.isMaster = function( mongo, debug ) { - var im = mongo.getDB( "admin" ).runCommand( { ismaster : 1 } ); - assert( im && im.ok, "command ismaster failed" ); - if ( debug ) { - printjson( im ); - } - return im.ismaster; -} - -ReplPair.prototype.isInitialSyncComplete = function( mongo, debug ) { - var isc = mongo.getDB( "admin" ).runCommand( { isinitialsynccomplete : 1 } ); - assert( isc && isc.ok, "command isinitialsynccomplete failed" ); - if ( debug ) { - printjson( isc ); - } - return isc.initialsynccomplete; -} - -ReplPair.prototype.checkSteadyState = function( state, expectedMasterHost, twoMasterOk, leftValues, rightValues, debug ) { - leftValues = leftValues || {}; - rightValues = rightValues || {}; - - var lm = null; - var lisc = null; - if ( this.leftC_ != null ) { - lm = this.isMaster( this.leftC_, debug ); - leftValues[ lm ] = true; - lisc = this.isInitialSyncComplete( this.leftC_, debug ); - } - var rm = null; - var risc = null; - if ( this.rightC_ != null ) { - rm = this.isMaster( this.rightC_, debug ); - rightValues[ rm ] = true; - risc = this.isInitialSyncComplete( this.rightC_, debug ); - } - - var stateSet = {} - state.forEach( function( i ) { stateSet[ i ] = true; } ); - if ( !( 1 in stateSet ) || ( ( risc || risc == null ) && ( lisc || lisc == null ) ) ) { - if ( rm == 1 && lm != 1 ) { - assert( twoMasterOk || !( 1 in leftValues ) ); - this.master_ = this.rightC_; - this.slave_ = this.leftC_; - } else if ( lm == 1 && rm != 1 ) { - assert( twoMasterOk || !( 1 in rightValues ) ); - this.master_ = this.leftC_; - this.slave_ = this.rightC_; - } - if ( !twoMasterOk ) { - assert( lm != 1 || rm != 1, "two masters" ); - } - // check for expected state - if ( state.sort().toString() == [ lm, rm ].sort().toString() ) { - if ( expectedMasterHost != null ) { - if( expectedMasterHost == this.master_.host ) { - return true; - } - } else { - return true; - } - } - } - - this.master_ = null; - this.slave_ = null; - return false; -} - -ReplPair.prototype.waitForSteadyState = function( state, expectedMasterHost, twoMasterOk, debug ) { - state = state || [ 1, 0 ]; - twoMasterOk = twoMasterOk || false; - var rp = this; - var leftValues = {}; - var rightValues = {}; - assert.soon( function() { return rp.checkSteadyState( state, expectedMasterHost, twoMasterOk, leftValues, rightValues, debug ); }, - "rp (" + rp + ") failed to reach expected steady state (" + state + ")" , 60000 ); -} - -ReplPair.prototype.master = function() { return this.master_; } -ReplPair.prototype.slave = function() { return this.slave_; } -ReplPair.prototype.right = function() { return this.rightC_; } -ReplPair.prototype.left = function() { return this.leftC_; } -ReplPair.prototype.arbiter = function() { return this.arbiterC_; } - -ReplPair.prototype.killNode = function( mongo, signal ) { - signal = signal || 15; - if ( this.leftC_ != null && this.leftC_.host == mongo.host ) { - stopMongod( this.left_.port_ ); - this.leftC_ = null; - } - if ( this.rightC_ != null && this.rightC_.host == mongo.host ) { - stopMongod( this.right_.port_ ); - this.rightC_ = null; - } - if ( this.arbiterC_ != null && this.arbiterC_.host == mongo.host ) { - stopMongod( this.arbiter_.port_ ); - this.arbiterC_ = null; - } -} - -ReplPair.prototype._annotatedNode = function( mongo ) { - var ret = ""; - if ( mongo != null ) { - ret += " (connected)"; - if ( this.master_ != null && mongo.host == this.master_.host ) { - ret += "(master)"; - } - if ( this.slave_ != null && mongo.host == this.slave_.host ) { - ret += "(slave)"; - } - } - return ret; -} - -ReplPair.prototype.toString = function() { - var ret = ""; - ret += "left: " + this.left_; - ret += " " + this._annotatedNode( this.leftC_ ); - ret += " right: " + this.right_; - ret += " " + this._annotatedNode( this.rightC_ ); - return ret; -} - ToolTest = function( name ){ this.name = name; this.port = allocatePorts(1)[0]; @@ -922,7 +944,7 @@ ReplTest.prototype.getOptions = function( master , extra , putBinaryFirst, norep extra = {}; if ( ! extra.oplogSize ) - extra.oplogSize = "1"; + extra.oplogSize = "40"; var a = [] if ( putBinaryFirst ) @@ -935,6 +957,8 @@ ReplTest.prototype.getOptions = function( master , extra , putBinaryFirst, norep a.push( "--dbpath" ); a.push( this.getPath( master ) ); + if( jsTestOptions().noJournal ) a.push( "--nojournal" ) + if( jsTestOptions().noJournalPrealloc ) a.push( "--nopreallocj" ) if ( !norepl ) { if ( master ){ @@ -1050,8 +1074,8 @@ var testingReplication = false; function skipIfTestingReplication(){ if (testingReplication) { - print( "skipping" ); - quit(0); + print("skipIfTestingReplication skipping"); + quit(0); } } @@ -1059,10 +1083,11 @@ ReplSetTest = function( opts ){ this.name = opts.name || "testReplSet"; this.host = opts.host || getHostName(); this.numNodes = opts.nodes || 0; - this.oplogSize = opts.oplogSize || 2; + this.oplogSize = opts.oplogSize || 40; this.useSeedList = opts.useSeedList || false; this.bridged = opts.bridged || false; this.ports = []; + this.keyFile = opts.keyFile this.startPort = opts.startPort || 31000; @@ -1084,6 +1109,10 @@ ReplSetTest = function( opts ){ this.nodes = []; this.nodeIds = {}; this.initLiveNodes(); + + Object.extend( this, ReplSetTest.Health ) + Object.extend( this, ReplSetTest.State ) + } ReplSetTest.prototype.initBridges = function() { @@ -1109,16 +1138,43 @@ ReplSetTest.prototype.initLiveNodes = function(){ } ReplSetTest.prototype.getNodeId = function(node) { - return this.nodeIds[node]; + + var result = this.nodeIds[node] + if( result ) return result + + if( node.toFixed ) return node + return node.nodeId + } ReplSetTest.prototype.getPort = function( n ){ + if( n.getDB ){ + // is a connection, look up + for( var i = 0; i < this.nodes.length; i++ ){ + if( this.nodes[i] == n ){ + n = i + break + } + } + } + + if ( typeof(n) == "object" && n.floatApprox ) + n = n.floatApprox + + // this is a hack for NumberInt + if ( n == 0 ) + n = 0; + + print( "ReplSetTest n: " + n + " ports: " + tojson( this.ports ) + "\t" + this.ports[n] + " " + typeof(n) ); return this.ports[ n ]; } ReplSetTest.prototype.getPath = function( n ){ - var p = "/data/db/" + this.name + "-"; - p += n.toString(); + + if( n.host ) + n = this.getNodeId( n ) + + var p = "/data/db/" + this.name + "-"+n; if ( ! this._alldbpaths ) this._alldbpaths = [ p ]; else @@ -1186,17 +1242,22 @@ ReplSetTest.prototype.getOptions = function( n , extra , putBinaryFirst ){ if ( putBinaryFirst ) - a.push( "mongod" ) - - a.push( "--replSet" ); + a.push( "mongod" ); - if( this.useSeedList ) { - a.push( this.getURL() ); + if ( extra.noReplSet ) { + delete extra.noReplSet; } else { - a.push( this.name ); + a.push( "--replSet" ); + + if( this.useSeedList ) { + a.push( this.getURL() ); + } + else { + a.push( this.name ); + } } - + a.push( "--noprealloc", "--smallfiles" ); a.push( "--rest" ); @@ -1205,13 +1266,27 @@ ReplSetTest.prototype.getOptions = function( n , extra , putBinaryFirst ){ a.push( this.getPort( n ) ); a.push( "--dbpath" ); - a.push( this.getPath( n ) ); - + 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" ) + for ( var k in extra ){ - var v = extra[k]; + var v = extra[k]; a.push( "--" + k ); - if ( v != null ) + 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; @@ -1219,7 +1294,7 @@ ReplSetTest.prototype.getOptions = function( n , extra , putBinaryFirst ){ ReplSetTest.prototype.startSet = function(options) { var nodes = []; - print( "Starting Set" ); + print( "ReplSetTest Starting Set" ); for(n=0; n<this.ports.length; n++) { node = this.start(n, options); @@ -1231,33 +1306,81 @@ ReplSetTest.prototype.startSet = function(options) { } 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; this.nodeIds[master] = i; + master.nodeId = i } else { this.nodes[i].setSlaveOk(); this.liveNodes.slaves.push(this.nodes[i]); this.nodeIds[this.nodes[i]] = i; + this.nodes[i].nodeId = i } } catch(err) { - print("Could not call ismaster on node " + i); + 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; @@ -1283,6 +1406,29 @@ ReplSetTest.prototype.getMaster = function( timeout ) { 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) { @@ -1291,13 +1437,13 @@ ReplSetTest.prototype.add = function( config ) { else { var nextPort = this.ports[this.ports.length-1] + 1; } - print("Next port: " + nextPort); + print("ReplSetTest Next port: " + nextPort); this.ports.push(nextPort); printjson(this.ports); var nextId = this.nodes.length; printjson(this.nodes); - print(nextId); + print("ReplSetTest nextId:" + nextId); var newNode = this.start(nextId); this.nodes.push(newNode); @@ -1323,7 +1469,7 @@ ReplSetTest.prototype.attempt = function( opts, func ) { tries += 1; sleep(sleepTime); if( tries * sleepTime > timeout) { - throw('[' + opts['desc'] + ']' + " timed out"); + throw('[' + opts['desc'] + ']' + " timed out after " + timeout + "ms ( " + tries + " tries )"); } } @@ -1354,50 +1500,76 @@ ReplSetTest.prototype.reInitiate = function() { this.initiate( config , 'replSetReconfig' ); } -ReplSetTest.prototype.awaitReplication = function() { - this.getMaster(); - - latest = this.liveNodes.master.getDB("local")['oplog.rs'].find({}).sort({'$natural': -1}).limit(1).next()['ts'] - print(latest); - - this.attempt({context: this, timeout: 30000, desc: "awaiting replication"}, - function() { - 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("TS for " + slave + " is " + ts.t+":"+ts.i + " and latest is " + latest.t+":"+latest.i); - - if (latest.t < ts.t || (latest.t == ts.t && latest.i < ts.i)) { - latest = this.liveNodes.master.getDB("local")['oplog.rs'].find({}).sort({'$natural': -1}).limit(1).next()['ts']; - } - - print("Oplog size for " + slave + " is " + log.count()); - synced = (synced && friendlyEqual(latest,ts)) - } - else { - synced = false; - } - } - - if(synced) { - print("Synced = " + synced); - } - return synced; - }); +ReplSetTest.prototype.getLastOpTimeWritten = function() { + this.getMaster(); + this.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); + + this.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 ){ @@ -1409,55 +1581,462 @@ ReplSetTest.prototype.getHashes = function( db ){ } /** - * Starts up a server. + * 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 {int} n server number (0, 1, 2, ...) + * @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 ){ +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.extend({}, options), restart, wait ) ){ + started.push( nodes[i] ) + } + } + + return started + + } + + print( "ReplSetTest n is : " + n ) + var lockFile = this.getPath( n ) + "/mongod.lock"; removeFile( lockFile ); - var o = this.getOptions( n , options , restart ); + + options = options || {} + var noRemember = options.noRemember + delete options.noRemember + var appendOptions = options.appendOptions + delete options.appendOptions + var startClean = options.startClean + delete options.startClean + + if( restart && options.remember ){ + delete options.remember + + var oldOptions = {} + if( this.savedStartOptions && this.savedStartOptions[n] ){ + oldOptions = this.savedStartOptions[n] + } + + var newOptions = options + var options = {} + Object.extend( options, oldOptions ) + Object.extend( options, newOptions ) + + } + + var shouldRemember = ( ! restart && ! noRemember ) || ( restart && appendOptions ) + + if ( shouldRemember ){ + this.savedStartOptions = this.savedStartOptions || {} + this.savedStartOptions[n] = options + } + + if( tojson(options) != tojson({}) ) + printjson(options) + + var o = this.getOptions( n , options , restart && ! startClean ); - print("Starting...."); - print( o ); + print("ReplSetTest " + (restart ? "(Re)" : "") + "Starting...."); + print("ReplSetTest " + o ); + + var rval = null if ( restart ) { - this.nodes[n] = startMongoProgram.apply( null , o ); - printjson(this.nodes); - return this.nodes[n]; + n = this.getNodeId( n ) + this.nodes[n] = ( startClean ? startMongod.apply( null , o ) : startMongoProgram.apply( null , o ) ); + this.nodes[n].host = this.nodes[n].host.replace( "127.0.0.1", this.host ) + if( shouldRemember ) this.savedStartOptions[this.nodes[n]] = options + printjson( this.nodes ) + rval = this.nodes[n]; } else { - return startMongod.apply( null , o ); + var conn = startMongod.apply( null , o ); + if( shouldRemember ) this.savedStartOptions[conn] = options + conn.host = conn.host.replace( "127.0.0.1", this.host ) + rval = conn; + } + + wait = wait || false + if( ! wait.toFixed ){ + if( wait ) wait = 0 + else wait = -1 } + + if( rval == null || wait < 0 ) return rval + + // Wait for startup + this.waitForHealth( rval, this.UP, wait ) + + return rval + } + /** - * Restarts a db without clearing the data directory. If the server is not - * stopped first, this function will not work. + * Restarts a db without clearing the data directory by default. If the server is not + * stopped first, this function will not work. * - * @param {int} n server number (0, 1, 2, ...) + * 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 ){ - return this.start( n , options , true ); +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 ) + return this.start( n , options , true, wait ); } -ReplSetTest.prototype.stop = function( n , signal ){ +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('*** Shutting down mongod in port ' + port + ' ***'); - return stopMongod( port , signal || 15 ); + print('ReplSetTest stop *** Shutting down mongod in port ' + port + ' ***'); + var ret = stopMongod( port , signal || 15 ); + + 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('*** Shut down repl set - test worked ****' ) + print('ReplSetTest stopSet *** Shut down repl set - test worked ****' ) +}; + + +/** + * Waits until there is a master node + */ +ReplSetTest.prototype.waitForMaster = function( timeout ){ + + var master = undefined + + this.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 ) + printjson( states ) + print( "ReplSetTest waitForIndicator from node " + node ) + + var lastTime = null + var currTime = new Date().getTime() + var status = undefined + + this.attempt({context: this, timeout: timeout, desc: "waiting for state indicator " + ind + " for " + timeout + "ms" }, function() { + + status = this.status() + + 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() + } + + for( var i = 0; i < status.members.length; i++ ){ + if( status.members[i].name == node.host ){ + for( var j = 0; j < states.length; 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; +}; |
