1 
  2 
  3 _parsePath = function() {
  4     var dbpath = "";
  5     for( var i = 0; i < arguments.length; ++i )
  6         if ( arguments[ i ] == "--dbpath" )
  7             dbpath = arguments[ i + 1 ];
  8 
  9     if ( dbpath == "" )
 10         throw "No dbpath specified";
 11 
 12     return dbpath;
 13 }
 14 
 15 _parsePort = function() {
 16     var port = "";
 17     for( var i = 0; i < arguments.length; ++i )
 18         if ( arguments[ i ] == "--port" )
 19             port = arguments[ i + 1 ];
 20 
 21     if ( port == "" )
 22         throw "No port specified";
 23     return port;
 24 }
 25 
 26 createMongoArgs = function( binaryName , args ){
 27     var fullArgs = [ binaryName ];
 28 
 29     if ( args.length == 1 && isObject( args[0] ) ){
 30         var o = args[0];
 31         for ( var k in o ){
 32             if ( k == "v" && isNumber( o[k] ) ){
 33                 var n = o[k];
 34                 if ( n > 0 ){
 35                     if ( n > 10 ) n = 10;
 36                     var temp = "-";
 37                     while ( n-- > 0 ) temp += "v";
 38                     fullArgs.push( temp );
 39                 }
 40             }
 41             else {
 42                 fullArgs.push( "--" + k );
 43                 if ( o[k] != "" )
 44                     fullArgs.push( "" + o[k] );
 45             }
 46         }
 47     }
 48     else {
 49         for ( var i=0; i<args.length; i++ )
 50             fullArgs.push( args[i] )
 51     }
 52 
 53     return fullArgs;
 54 }
 55 
 56 startMongodTest = function( port , dirname , restart ){
 57     var f = startMongod;
 58     if ( restart )
 59         f = startMongodNoReset;
 60     var conn = f.apply(  null , [
 61         { 
 62             port : port , 
 63             dbpath : "/data/db/" + dirname , 
 64             noprealloc : "" , 
 65             smallfiles : "" , 
 66             oplogSize : "2" , 
 67             nohttpinterface : ""
 68         } 
 69     ]
 70     );
 71     conn.name = "localhost:" + port;
 72     return conn;
 73 }
 74 
 75 // Start a mongod instance and return a 'Mongo' object connected to it.
 76 // This function's arguments are passed as command line arguments to mongod.
 77 // The specified 'dbpath' is cleared if it exists, created if not.
 78 startMongod = function(){
 79 
 80     var args = createMongoArgs( "mongod" , arguments );
 81 
 82     var dbpath = _parsePath.apply( null, args );
 83     resetDbpath( dbpath );
 84 
 85     return startMongoProgram.apply( null, args );
 86 }
 87 
 88 startMongodNoReset = function(){
 89     var args = createMongoArgs( "mongod" , arguments );
 90     return startMongoProgram.apply( null, args );
 91 }
 92 
 93 startMongos = function(){
 94     return startMongoProgram.apply( null, createMongoArgs( "mongos" , arguments ) );
 95 }
 96 
 97 // Start a mongo program instance (generally mongod or mongos) and return a
 98 // 'Mongo' object connected to it.  This function's first argument is the
 99 // program name, and subsequent arguments to this function are passed as
100 // command line arguments to the program.
101 startMongoProgram = function(){
102     var port = _parsePort.apply( null, arguments );
103 
104     _startMongoProgram.apply( null, arguments );
105 
106     var m;
107     assert.soon
108     ( function() {
109         try {
110             m = new Mongo( "127.0.0.1:" + port );
111             return true;
112         } catch( e ) {
113         }
114         return false;
115     }, "unable to connect to mongo program on port " + port, 60000 );
116 
117     return m;
118 }
119 
120 // Start a mongo program instance.  This function's first argument is the
121 // program name, and subsequent arguments to this function are passed as
122 // command line arguments to the program.  Returns pid of the spawned program.
123 startMongoProgramNoConnect = function() {
124     return _startMongoProgram.apply( null, arguments );
125 }
126 
127 myPort = function() {
128     var m = db.getMongo();
129     if ( m.host.match( /:/ ) )
130         return m.host.match( /:(.*)/ )[ 1 ];
131     else
132         return 27017;
133 }
134 
135 ShardingTest = function( testName , numServers , verboseLevel , numMongos , otherParams ){
136     if ( ! otherParams )
137         otherParams = {}
138     this._connections = [];
139     
140     if ( otherParams.sync && numServers < 3 )
141         throw "if you want sync, you need at least 3 servers";
142 
143     for ( var i=0; i<numServers; i++){
144         var conn = startMongodTest( 30000 + i , testName + i );
145         this._connections.push( conn );
146     }
147 
148     if ( otherParams.sync ){
149         this._configDB = "localhost:30000,localhost:30001,localhost:30002";
150         this._configConnection = new Mongo( this._configDB );
151         this._configConnection.getDB( "config" ).settings.insert( { _id : "chunksize" , value : otherParams.chunksize || 50 } );        
152     }
153     else {
154         this._configDB = "localhost:30000";
155         this._connections[0].getDB( "config" ).settings.insert( { _id : "chunksize" , value : otherParams.chunksize || 50 } );
156     }
157 
158     this._mongos = [];
159     var startMongosPort = 31000;
160     for ( var i=0; i<(numMongos||1); i++ ){
161         var myPort =  startMongosPort - i;
162         var conn = startMongos( { port : startMongosPort - i , v : verboseLevel || 0 , configdb : this._configDB }  );
163         conn.name = "localhost:" + myPort;
164         this._mongos.push( conn );
165         if ( i == 0 )
166             this.s = conn;
167     }
168 
169     var admin = this.admin = this.s.getDB( "admin" );
170     this.config = this.s.getDB( "config" );
171 
172     this._connections.forEach(
173         function(z){
174             admin.runCommand( { addshard : z.name , allowLocal : true } );
175         }
176     );
177 }
178 
179 ShardingTest.prototype.getDB = function( name ){
180     return this.s.getDB( name );
181 }
182 
183 ShardingTest.prototype.getServerName = function( dbname ){
184     var x = this.config.databases.findOne( { _id : dbname } );
185     if ( x )
186         return x.primary;
187     this.config.databases.find().forEach( printjson );
188     throw "couldn't find dbname: " + dbname + " total: " + this.config.databases.count();
189 }
190 
191 ShardingTest.prototype.getServer = function( dbname ){
192     var name = this.getServerName( dbname );
193 
194     var x = this.config.shards.findOne( { _id : name } );
195     if ( x )
196         name = x.host;
197 
198     for ( var i=0; i<this._connections.length; i++ ){
199         var c = this._connections[i];
200         if ( name == c.name )
201             return c;
202     }
203 
204     throw "can't find server for: " + dbname + " name:" + name;
205 
206 }
207 
208 ShardingTest.prototype.normalize = function( x ){
209     var z = this.config.shards.findOne( { host : x } );
210     if ( z )
211         return z._id;
212     return x;
213 }
214 
215 ShardingTest.prototype.getOther = function( one ){
216     if ( this._connections.length != 2 )
217         throw "getOther only works with 2 servers";
218 
219     if ( this._connections[0] == one )
220         return this._connections[1];
221     return this._connections[0];
222 }
223 
224 ShardingTest.prototype.getFirstOther = function( one ){
225     for ( var i=0; i<this._connections.length; i++ ){
226         if ( this._connections[i] != one )
227         return this._connections[i];
228     }
229     throw "impossible";
230 }
231 
232 ShardingTest.prototype.stop = function(){
233     for ( var i=0; i<this._mongos.length; i++ ){
234         stopMongoProgram( 31000 - i );
235     }
236     for ( var i=0; i<this._connections.length; i++){
237         stopMongod( 30000 + i );
238     }
239 }
240 
241 ShardingTest.prototype.adminCommand = function(cmd){
242     var res = this.admin.runCommand( cmd );
243     if ( res && res.ok == 1 )
244         return true;
245 
246     throw "command " + tojson( cmd ) + " failed: " + tojson( res );
247 }
248 
249 ShardingTest.prototype.getChunksString = function( ns ){
250     var q = {}
251     if ( ns )
252         q.ns = ns;
253     return Array.tojson( this.config.chunks.find( q ).toArray() , "\n" );
254 }
255 
256 ShardingTest.prototype.printChunks = function( ns ){
257     print( this.getChunksString( ns ) );
258 }
259 
260 ShardingTest.prototype.printShardingStatus = function(){
261     printShardingStatus( this.config );
262 }
263 
264 ShardingTest.prototype.printCollectionInfo = function( ns , msg ){
265     var out = "";
266     if ( msg )
267         out += msg + "\n";
268     out += "sharding collection info: " + ns + "\n";
269     for ( var i=0; i<this._connections.length; i++ ){
270         var c = this._connections[i];
271         out += "  mongod " + c + " " + tojson( c.getCollection( ns ).getShardVersion() , " " , true ) + "\n";
272     }
273     for ( var i=0; i<this._mongos.length; i++ ){
274         var c = this._mongos[i];
275         out += "  mongos " + c + " " + tojson( c.getCollection( ns ).getShardVersion() , " " , true ) + "\n";
276     }
277     
278     print( out );
279 }
280 
281 printShardingStatus = function( configDB ){
282     if (configDB === undefined)
283         configDB = db.getSisterDB('config')
284     
285     var version = configDB.getCollection( "version" ).findOne();
286     if ( version == null ){
287         print( "not a shard db!" );
288         return;
289     }
290     
291     var raw = "";
292     var output = function(s){
293         raw += s + "\n";
294     }
295     output( "--- Sharding Status --- " );
296     output( "  sharding version: " + tojson( configDB.getCollection( "version" ).findOne() ) );
297     
298     output( "  shards:" );
299     configDB.shards.find().forEach( 
300         function(z){
301             output( "      " + tojson(z) );
302         }
303     );
304 
305     output( "  databases:" );
306     configDB.databases.find().sort( { name : 1 } ).forEach( 
307         function(db){
308             output( "\t" + tojson(db,"",true) );
309         
310             if (db.partitioned){
311                 for (ns in db.sharded){
312                     output("\t\t" + ns + " chunks:");
313                     configDB.chunks.find( { "ns" : ns } ).sort( { min : 1 } ).forEach( 
314                         function(chunk){
315                             output( "\t\t\t" + tojson( chunk.min ) + " -->> " + tojson( chunk.max ) + 
316                                    " on : " + chunk.shard + " " + tojson( chunk.lastmod ) );
317                         }
318                     );
319                 }
320             }
321         }
322     );
323     
324     print( raw );
325 }
326 
327 ShardingTest.prototype.sync = function(){
328     this.adminCommand( "connpoolsync" );
329 }
330 
331 ShardingTest.prototype.onNumShards = function( collName , dbName ){
332     this.sync(); // we should sync since we're going directly to mongod here
333     dbName = dbName || "test";
334     var num=0;
335     for ( var i=0; i<this._connections.length; i++ )
336         if ( this._connections[i].getDB( dbName ).getCollection( collName ).count() > 0 )
337             num++;
338     return num;
339 }
340 
341 ShardingTest.prototype.shardGo = function( collName , key , split , move , dbName ){
342     split = split || key;
343     move = move || split;
344     dbName = dbName || "test";
345 
346     var c = dbName + "." + collName;
347 
348     s.adminCommand( { shardcollection : c , key : key } );
349     s.adminCommand( { split : c , middle : split } );
350     s.adminCommand( { movechunk : c , find : move , to : this.getOther( s.getServer( dbName ) ).name } );
351     
352 }
353 
354 MongodRunner = function( port, dbpath, peer, arbiter, extraArgs ) {
355     this.port_ = port;
356     this.dbpath_ = dbpath;
357     this.peer_ = peer;
358     this.arbiter_ = arbiter;
359     this.extraArgs_ = extraArgs;
360 }
361 
362 MongodRunner.prototype.start = function( reuseData ) {
363     var args = [];
364     if ( reuseData ) {
365         args.push( "mongod" );
366     }
367     args.push( "--port" );
368     args.push( this.port_ );
369     args.push( "--dbpath" );
370     args.push( this.dbpath_ );
371     if ( this.peer_ && this.arbiter_ ) {
372         args.push( "--pairwith" );
373         args.push( this.peer_ );
374         args.push( "--arbiter" );
375         args.push( this.arbiter_ );
376         args.push( "--oplogSize" );
377         // small oplog by default so startup fast
378         args.push( "1" );
379     }
380     args.push( "--nohttpinterface" );
381     args.push( "--noprealloc" );
382     args.push( "--smallfiles" );
383     args.push( "--bind_ip" );
384     args.push( "127.0.0.1" );
385     if ( this.extraArgs_ ) {
386         args = args.concat( this.extraArgs_ );
387     }
388     removeFile( this.dbpath_ + "/mongod.lock" );
389     if ( reuseData ) {
390         return startMongoProgram.apply( null, args );
391     } else {
392         return startMongod.apply( null, args );
393     }
394 }
395 
396 MongodRunner.prototype.port = function() { return this.port_; }
397 
398 MongodRunner.prototype.toString = function() { return [ this.port_, this.dbpath_, this.peer_, this.arbiter_ ].toString(); }
399 
400 ReplPair = function( left, right, arbiter ) {
401     this.left_ = left;
402     this.leftC_ = null;
403     this.right_ = right;
404     this.rightC_ = null;
405     this.arbiter_ = arbiter;
406     this.arbiterC_ = null;
407     this.master_ = null;
408     this.slave_ = null;
409 }
410 
411 ReplPair.prototype.start = function( reuseData ) {
412     if ( this.arbiterC_ == null ) {
413         this.arbiterC_ = this.arbiter_.start();
414     }
415     if ( this.leftC_ == null ) {
416         this.leftC_ = this.left_.start( reuseData );
417     }
418     if ( this.rightC_ == null ) {
419         this.rightC_ = this.right_.start( reuseData );
420     }
421 }
422 
423 ReplPair.prototype.isMaster = function( mongo, debug ) {
424     var im = mongo.getDB( "admin" ).runCommand( { ismaster : 1 } );
425     assert( im && im.ok, "command ismaster failed" );
426     if ( debug ) {
427         printjson( im );
428     }
429     return im.ismaster;
430 }
431 
432 ReplPair.prototype.isInitialSyncComplete = function( mongo, debug ) {
433     var isc = mongo.getDB( "admin" ).runCommand( { isinitialsynccomplete : 1 } );
434     assert( isc && isc.ok, "command isinitialsynccomplete failed" );
435     if ( debug ) {
436         printjson( isc );
437     }
438     return isc.initialsynccomplete;
439 }
440 
441 ReplPair.prototype.checkSteadyState = function( state, expectedMasterHost, twoMasterOk, leftValues, rightValues, debug ) {
442     leftValues = leftValues || {};
443     rightValues = rightValues || {};
444 
445     var lm = null;
446     var lisc = null;
447     if ( this.leftC_ != null ) {
448         lm = this.isMaster( this.leftC_, debug );
449         leftValues[ lm ] = true;
450         lisc = this.isInitialSyncComplete( this.leftC_, debug );
451     }
452     var rm = null;
453     var risc = null;
454     if ( this.rightC_ != null ) {
455         rm = this.isMaster( this.rightC_, debug );
456         rightValues[ rm ] = true;
457         risc = this.isInitialSyncComplete( this.rightC_, debug );
458     }
459 
460     var stateSet = {}
461     state.forEach( function( i ) { stateSet[ i ] = true; } );
462     if ( !( 1 in stateSet ) || ( ( risc || risc == null ) && ( lisc || lisc == null ) ) ) {
463         if ( rm == 1 && lm != 1 ) {
464             assert( twoMasterOk || !( 1 in leftValues ) );
465             this.master_ = this.rightC_;
466             this.slave_ = this.leftC_;
467         } else if ( lm == 1 && rm != 1 ) {
468             assert( twoMasterOk || !( 1 in rightValues ) );
469             this.master_ = this.leftC_;
470             this.slave_ = this.rightC_;
471         }
472         if ( !twoMasterOk ) {
473             assert( lm != 1 || rm != 1, "two masters" );
474         }
475         // check for expected state
476         if ( state.sort().toString() == [ lm, rm ].sort().toString() ) {
477             if ( expectedMasterHost != null ) {
478                 if( expectedMasterHost == this.master_.host ) {
479                     return true;
480                 }
481             } else {
482                 return true;
483             }
484         }
485     }
486 
487     this.master_ = null;
488     this.slave_ = null;
489     return false;
490 }
491 
492 ReplPair.prototype.waitForSteadyState = function( state, expectedMasterHost, twoMasterOk, debug ) {
493     state = state || [ 1, 0 ];
494     twoMasterOk = twoMasterOk || false;
495     var rp = this;
496     var leftValues = {};
497     var rightValues = {};
498     assert.soon( function() { return rp.checkSteadyState( state, expectedMasterHost, twoMasterOk, leftValues, rightValues, debug ); },
499                 "rp (" + rp + ") failed to reach expected steady state (" + state + ")" );
500 }
501 
502 ReplPair.prototype.master = function() { return this.master_; }
503 ReplPair.prototype.slave = function() { return this.slave_; }
504 ReplPair.prototype.right = function() { return this.rightC_; }
505 ReplPair.prototype.left = function() { return this.leftC_; }
506 ReplPair.prototype.arbiter = function() { return this.arbiterC_; }
507 
508 ReplPair.prototype.killNode = function( mongo, signal ) {
509     signal = signal || 15;
510     if ( this.leftC_ != null && this.leftC_.host == mongo.host ) {
511         stopMongod( this.left_.port_ );
512         this.leftC_ = null;
513     }
514     if ( this.rightC_ != null && this.rightC_.host == mongo.host ) {
515         stopMongod( this.right_.port_ );
516         this.rightC_ = null;
517     }
518     if ( this.arbiterC_ != null && this.arbiterC_.host == mongo.host ) {
519         stopMongod( this.arbiter_.port_ );
520         this.arbiterC_ = null;
521     }
522 }
523 
524 ReplPair.prototype._annotatedNode = function( mongo ) {
525     var ret = "";
526     if ( mongo != null ) {
527         ret += " (connected)";
528         if ( this.master_ != null && mongo.host == this.master_.host ) {
529             ret += "(master)";
530         }
531         if ( this.slave_ != null && mongo.host == this.slave_.host ) {
532             ret += "(slave)";
533         }
534     }
535     return ret;
536 }
537 
538 ReplPair.prototype.toString = function() {
539     var ret = "";
540     ret += "left: " + this.left_;
541     ret += " " + this._annotatedNode( this.leftC_ );
542     ret += " right: " + this.right_;
543     ret += " " + this._annotatedNode( this.rightC_ );
544     return ret;
545 }
546 
547 
548 ToolTest = function( name ){
549     this.name = name;
550     this.port = allocatePorts(1)[0];
551     this.baseName = "jstests_tool_" + name;
552     this.root = "/data/db/" + this.baseName;
553     this.dbpath = this.root + "/";
554     this.ext = this.root + "_external/";
555     this.extFile = this.root + "_external/a";
556     resetDbpath( this.dbpath );
557 }
558 
559 ToolTest.prototype.startDB = function( coll ){
560     assert( ! this.m , "db already running" );
561  
562     this.m = startMongoProgram( "mongod" , "--port", this.port , "--dbpath" , this.dbpath , "--nohttpinterface", "--noprealloc" , "--smallfiles" , "--bind_ip", "127.0.0.1" );
563     this.db = this.m.getDB( this.baseName );
564     if ( coll )
565         return this.db.getCollection( coll );
566     return this.db;
567 }
568 
569 ToolTest.prototype.stop = function(){
570     if ( ! this.m )
571         return;
572     stopMongod( this.port );
573     this.m = null;
574     this.db = null;
575 }
576 
577 ToolTest.prototype.runTool = function(){
578     var a = [ "mongo" + arguments[0] ];
579 
580     var hasdbpath = false;
581     
582     for ( var i=1; i<arguments.length; i++ ){
583         a.push( arguments[i] );
584         if ( arguments[i] == "--dbpath" )
585             hasdbpath = true;
586     }
587 
588     if ( ! hasdbpath ){
589         a.push( "--host" );
590         a.push( "127.0.0.1:" + this.port );
591     }
592 
593     runMongoProgram.apply( null , a );
594 }
595 
596 
597 ReplTest = function( name, ports ){
598     this.name = name;
599     this.ports = ports || allocatePorts( 2 );
600 }
601 
602 ReplTest.prototype.getPort = function( master ){
603     if ( master )
604         return this.ports[ 0 ];
605     return this.ports[ 1 ]
606 }
607 
608 ReplTest.prototype.getPath = function( master ){
609     var p = "/data/db/" + this.name + "-";
610     if ( master )
611         p += "master";
612     else
613         p += "slave"
614     return p;
615 }
616 
617 
618 ReplTest.prototype.getOptions = function( master , extra , putBinaryFirst, norepl ){
619 
620     if ( ! extra )
621         extra = {};
622 
623     if ( ! extra.oplogSize )
624         extra.oplogSize = "1";
625         
626     var a = []
627     if ( putBinaryFirst )
628         a.push( "mongod" )
629     a.push( "--nohttpinterface", "--noprealloc", "--bind_ip" , "127.0.0.1" , "--smallfiles" );
630 
631     a.push( "--port" );
632     a.push( this.getPort( master ) );
633 
634     a.push( "--dbpath" );
635     a.push( this.getPath( master ) );
636     
637 
638     if ( !norepl ) {
639         if ( master ){
640             a.push( "--master" );
641         }
642         else {
643             a.push( "--slave" );
644             a.push( "--source" );
645             a.push( "127.0.0.1:" + this.ports[0] );
646         }
647     }
648     
649     for ( var k in extra ){
650         var v = extra[k];
651         a.push( "--" + k );
652         if ( v != null )
653             a.push( v );                    
654     }
655 
656     return a;
657 }
658 
659 ReplTest.prototype.start = function( master , options , restart, norepl ){
660     var lockFile = this.getPath( master ) + "/mongod.lock";
661     removeFile( lockFile );
662     var o = this.getOptions( master , options , restart, norepl );
663     if ( restart )
664         return startMongoProgram.apply( null , o );
665     else
666         return startMongod.apply( null , o );
667 }
668 
669 ReplTest.prototype.stop = function( master , signal ){
670     if ( arguments.length == 0 ){
671         this.stop( true );
672         this.stop( false );
673         return;
674     }
675     return stopMongod( this.getPort( master ) , signal || 15 );
676 }
677 
678 allocatePorts = function( n ) {
679     var ret = [];
680     for( var i = 31000; i < 31000 + n; ++i )
681         ret.push( i );
682     return ret;
683 }
684 
685 
686 SyncCCTest = function( testName ){
687     this._testName = testName;
688     this._connections = [];
689     
690     for ( var i=0; i<3; i++ ){
691         this._connections.push( startMongodTest( 30000 + i , testName + i ) );
692     }
693     
694     this.url = this._connections.map( function(z){ return z.name; } ).join( "," );
695     this.conn = new Mongo( this.url );
696 }
697 
698 SyncCCTest.prototype.stop = function(){
699     for ( var i=0; i<this._connections.length; i++){
700         stopMongod( 30000 + i );
701     }
702 }
703 
704 SyncCCTest.prototype.checkHashes = function( dbname , msg ){
705     var hashes = this._connections.map(
706         function(z){
707             return z.getDB( dbname ).runCommand( "dbhash" );
708         }
709     );
710 
711     for ( var i=1; i<hashes.length; i++ ){
712         assert.eq( hashes[0].md5 , hashes[i].md5 , "checkHash on " + dbname + " " + msg + "\n" + tojson( hashes ) )
713     }
714 }
715 
716 SyncCCTest.prototype.tempKill = function( num ){
717     num = num || 0;
718     stopMongod( 30000 + num );
719 }
720 
721 SyncCCTest.prototype.tempStart = function( num ){
722     num = num || 0;
723     this._connections[num] = startMongodTest( 30000 + num , this._testName + num , true );
724 }
725