/** * Sets up a replica set. To make the set running, call {@link #startSet}, * followed by {@link #initiate} (and optionally, * {@link #awaitSecondaryNodes} to block till the set is fully operational). * Note that some of the replica start up parameters are not passed here, * but to the #startSet method. * * @param {Object} opts * * { * name {string}: name of this replica set. Default: 'testReplSet' * host {string}: name of the host machine. Hostname will be used * if not specified. * useHostName {boolean}: if true, use hostname of machine, * otherwise use localhost * nodes {number|Object|Array.}: number of replicas. Default: 0. * Can also be an Object (or Array). * Format for Object: * { * : replica member option Object. @see MongoRunner.runMongod * : and so on... * } * * Format for Array: * An array of replica member option Object. @see MongoRunner.runMongod * * Note: For both formats, a special boolean property 'arbiter' can be * specified to denote a member is an arbiter. * * nodeOptions {Object}: Options to apply to all nodes in the replica set. * Format for Object: * { cmdline-param-with-no-arg : "", * param-with-arg : arg } * This turns into "mongod --cmdline-param-with-no-arg --param-with-arg arg" * * oplogSize {number}: Default: 40 * useSeedList {boolean}: Use the connection string format of this set * as the replica set name (overrides the name property). Default: false * keyFile {string} * shardSvr {boolean}: Default: false * startPort {number}: port offset to be used for each replica. Default: 31000 * } * * Member variables: * numNodes {number} - number of nodes * nodes {Array.} - connection to replica set members */ ReplSetTest = function( opts ){ this.name = opts.name || "testReplSet"; this.useHostName = opts.useHostName == undefined ? true : opts.useHostName; this.host = this.useHostName ? (opts.host || getHostName()) : 'localhost'; this.oplogSize = opts.oplogSize || 40; this.useSeedList = opts.useSeedList || false; this.ports = []; this.keyFile = opts.keyFile this.shardSvr = opts.shardSvr || false; this.startPort = opts.startPort || 31000; this.nodeOptions = {} if( isObject( opts.nodes ) ){ var len = 0 for( var i in opts.nodes ){ var options = this.nodeOptions[ "n" + len ] = Object.merge(opts.nodeOptions, opts.nodes[i]); if( i.startsWith( "a" ) ) options.arbiter = true; len++ } this.numNodes = len } else if( Array.isArray( opts.nodes ) ){ for( var i = 0; i < opts.nodes.length; i++ ) this.nodeOptions[ "n" + i ] = Object.merge(opts.nodeOptions, opts.nodes[i]); this.numNodes = opts.nodes.length } else { for ( var i =0; i < opts.nodes; i++ ) this.nodeOptions[ "n" + i ] = opts.nodeOptions; this.numNodes = opts.nodes; } this.ports = allocatePorts( this.numNodes , this.startPort ); this.nodes = [] this.initLiveNodes() Object.extend( this, ReplSetTest.Health ) Object.extend( this, ReplSetTest.State ) } // List of nodes as host:port strings. ReplSetTest.prototype.nodeList = function() { var list = []; for(var i=0; i 1. If using // start() independently, independent version choices will be made // if( options && options.binVersion ){ options.binVersion = MongoRunner.versionIterator( options.binVersion ) } 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. * Option { auth : Object } object that contains the auth details for admin credentials. * Should contain the fields 'user' and 'pwd' * * @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, options ) started = this.start( n , options , true, wait ); if (jsTestOptions().keyFile || jsTestOptions().useX509) { 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, opts ) { var master = this.getMaster(); var master_id = this.getNodeId( master ); return this.stop( master_id , signal , wait, opts ); } /** * Stops a particular node or nodes, specified by conn or id * * @param {number} n the index of the replica set member to stop * @param {number} signal the signal number to use for killing * @param {boolean} wait * @param {Object} opts @see MongoRunner.stopMongod */ ReplSetTest.prototype.stop = function( n , signal, wait /* wait for stop */, opts ){ // 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, opts ) ) 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, opts ); if( ! ret || wait < 0 ) { print('ReplSetTest stop *** Mongod in port ' + port + ' shutdown with code (' + ret + '), wait (' + wait + ') ***'); return ret; } // Wait for shutdown this.waitForHealth( n, this.DOWN, wait ) return true } /** * Kill all members of this replica set. * * @param {number} signal The signal number to use for killing the members * @param {boolean} forRestart will not cleanup data directory or teardown * bridges if set to true. * @param {Object} opts @see MongoRunner.stopMongod */ ReplSetTest.prototype.stopSet = function( signal , forRestart, opts ) { for(var i=0; i < this.ports.length; i++) { this.stop( i, signal, false, opts ); } if ( forRestart ) { return; } if ( this._alldbpaths ){ print("ReplSetTest stopSet deleting all dbpaths"); for( i=0; i 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 }, "waiting for state indicator " + ind + " for " + timeout + "ms", timeout); 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 ReplSetTest.State.ARBITER = 7 /** * 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"); var bulk = overflowColl.initializeUnorderedBulkOp(); for (var i = 0; i < 10000; i++) { bulk.insert({ overflow : "value" }); } bulk.execute(); 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( opts ) { 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; i1, 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. * * The bidirectional parameter, which defaults to true, determines whether * replTest.partition(0,2) will stop the bridges for 0->2 and 2->0 (true), or * just 0->2 (false). */ ReplSetTest.prototype.partition = function(from, to, bidirectional) { bidirectional = typeof bidirectional !== 'undefined' ? bidirectional : true; this.bridges[from][to].stop(); if (bidirectional) { this.bridges[to][from].stop(); } }; /** * This reverses a partition created by partition() above. */ ReplSetTest.prototype.unPartition = function(from, to, bidirectional) { bidirectional = typeof bidirectional !== 'undefined' ? bidirectional : true; this.bridges[from][to].start(); if (bidirectional) { this.bridges[to][from].start(); } }; /** * Helpers for partitioning in only one direction so that the test files are more clear to readers. */ ReplSetTest.prototype.partitionOneWay = function(from, to) { this.partition(from, to, false); }; ReplSetTest.prototype.unPartitionOneWay = function(from, to) { this.unPartition(from, to, false); }; /** * Helpers for adding/removing delays from a partition. */ ReplSetTest.prototype.addPartitionDelay = function(from, to, delay, bidirectional) { bidirectional = typeof bidirectional !== 'undefined' ? bidirectional : true; this.bridges[from][to].setDelay(delay); if (bidirectional) { this.bridges[to][from].setDelay(delay); } }; ReplSetTest.prototype.removePartitionDelay = function(from, to, bidirectional) { this.addPartitionDelay(from, to, 0, bidirectional); }; ReplSetTest.prototype.addOneWayPartitionDelay = function(from, to, delay) { this.addPartitionDelay(from, to, delay, false); }; ReplSetTest.prototype.removeOneWayPartitionDelay = function(from, to) { this.addPartitionDelay(from, to, 0, false); };