// Test cloneCollection command var baseName = "jstests_clonecollection"; parallel = function() { return t.parallelStatus; } resetParallel = function() { parallel().drop(); } doParallel = function( work ) { resetParallel(); startMongoProgramNoConnect( "mongo", "--port", ports[ 1 ], "--eval", work + "; db.parallelStatus.save( {done:1} );", baseName ); } doneParallel = function() { return !!parallel().findOne(); } waitParallel = function() { assert.soon( function() { return doneParallel(); }, "parallel did not finish in time", 300000, 1000 ); } cloneNo = -1; startstartclone = function( spec ) { spec = spec || ""; cloneNo++; doParallel( "z = db.runCommand( {startCloneCollection:\"jstests_clonecollection.a\", from:\"localhost:" + ports[ 0 ] + "\"" + spec + " } ); print( \"clone_clone_clone_commandResult::" + cloneNo + "::\" + tojson( z , '' , true ) + \":::::\" );" ); } finishstartclone = function() { waitParallel(); // even after parallel shell finished, must wait for finishToken line to appear in log assert.soon( function() { raw = rawMongoProgramOutput().replace( /[\r\n]/gm , " " ) ret = raw.match( new RegExp( "clone_clone_clone_commandResult::" + cloneNo + "::(.*):::::" ) ); if ( ret == null ) { return false; } ret = ret[ 1 ]; return true; } ); eval( "ret = " + ret ); assert.commandWorked( ret ); return ret; } dofinishclonecmd = function( ret ) { finishToken = ret.finishToken; // Round-tripping through JS can corrupt the cursor ids we store as BSON // Date elements. Date( 0 ) will correspond to a cursorId value of 0, which // makes the db start scanning from the beginning of the collection. finishToken.cursorId = new Date( 0 ); return t.runCommand( {finishCloneCollection:finishToken} ); } finishclone = function( ret ) { assert.commandWorked( dofinishclonecmd( ret ) ); } ports = allocatePorts( 2 ); f = startMongod( "--port", ports[ 0 ], "--dbpath", "/data/db/" + baseName + "_from", "--nohttpinterface", "--bind_ip", "127.0.0.1" ).getDB( baseName ); t = startMongod( "--port", ports[ 1 ], "--dbpath", "/data/db/" + baseName + "_to", "--nohttpinterface", "--bind_ip", "127.0.0.1" ).getDB( baseName ); for( i = 0; i < 1000; ++i ) { f.a.save( { i: i } ); } assert.eq( 1000, f.a.find().count() , "A1" ); assert.commandWorked( t.cloneCollection( "localhost:" + ports[ 0 ], "a" ) ); assert.eq( 1000, t.a.find().count() , "A2" ); t.a.drop(); assert.commandWorked( t.cloneCollection( "localhost:" + ports[ 0 ], "a", { i: { $gte: 10, $lt: 20 } } ) ); assert.eq( 10, t.a.find().count() , "A3" ); t.a.drop(); assert.eq( 0, t.system.indexes.find().count() , "prep 2"); f.a.ensureIndex( { i: 1 } ); assert.eq( 2, f.system.indexes.find().count(), "expected index missing" ); assert.commandWorked( t.cloneCollection( "localhost:" + ports[ 0 ], "a" ) ); if ( t.system.indexes.find().count() != 2 ) { printjson( t.system.indexes.find().toArray() ); } assert.eq( 2, t.system.indexes.find().count(), "expected index missing" ); // Verify index works assert.eq( 50, t.a.find( { i: 50 } ).hint( { i: 1 } ).explain().indexBounds[0][0].i , "verify 1" ); assert.eq( 1, t.a.find( { i: 50 } ).hint( { i: 1 } ).toArray().length, "match length did not match expected" ); // Check that capped-ness is preserved on clone f.a.drop(); t.a.drop(); f.createCollection( "a", {capped:true,size:1000} ); assert( f.a.isCapped() ); assert.commandWorked( t.cloneCollection( "localhost:" + ports[ 0 ], "a" ) ); assert( t.a.isCapped(), "cloned collection not capped" ); // Now test insert + delete + update during clone f.a.drop(); t.a.drop(); for( i = 0; i < 100000; ++i ) { f.a.save( { i: i } ); } assert.eq( 100000, f.a.count() ); startstartclone( ", query:{i:{$gte:0}}" ); sleep( 200 ); f.a.save( { i: 200000 } ); f.a.save( { i: -1 } ); f.a.remove( { i: 0 } ); f.a.update( { i: 99998 }, { i: 99998, x: "y" } ); assert.eq( 100001, f.a.count() , "D0" ); ret = finishstartclone(); finishclone( ret ); assert.eq( 100000, t.a.find().count() , "D1" ); assert.eq( 1, t.a.find( { i: 200000 } ).count() , "D2" ); assert.eq( 0, t.a.find( { i: -1 } ).count() , "D3" ); assert.eq( 0, t.a.find( { i: 0 } ).count() , "D4" ); assert.eq( 1, t.a.find( { i: 99998, x: "y" } ).count() , "D5" ); // Now test oplog running out of space -- specify small size clone oplog for test. f.a.drop(); t.a.drop(); for( i = 0; i < 200000; ++i ) { f.a.save( { i: i } ); } assert.eq( 200000, f.a.count() , "E1" ); startstartclone( ", logSizeMb:1" ); ret = finishstartclone(); for( i = 200000; i < 250000; ++i ) { f.a.save( { i: i } ); } assert.eq( 250000, f.a.count() , "F0" ); assert.commandFailed( dofinishclonecmd( ret ) ); // Make sure the same works with standard size op log. f.a.drop(); t.a.drop(); for( i = 0; i < 200000; ++i ) { f.a.save( { i: i } ); } assert.eq( 200000, f.a.count() , "F1" ); startstartclone(); ret = finishstartclone(); for( i = 200000; i < 250000; ++i ) { f.a.save( { i: i } ); } assert.eq( 250000, f.a.count() , "F2" ); finishclone( ret ); assert.eq( 250000, t.a.find().count() , "F3" ); // Test startCloneCollection and finishCloneCollection commands. f.a.drop(); t.a.drop(); for( i = 0; i < 100000; ++i ) { f.a.save( { i: i } ); } assert.eq( 100000, f.a.count() , "G1" ); startstartclone(); sleep( 200 ); f.a.save( { i: -1 } ); ret = finishstartclone(); assert.eq( 100001, t.a.find().count() , "G2" ); f.a.save( { i: -2 } ); assert.eq( 100002, f.a.find().count() , "G3" ); finishclone( ret ); assert.eq( 100002, t.a.find().count() , "G4" );