From 0364ef4bdd4c17a07801457334aee32b9f601828 Mon Sep 17 00:00:00 2001 From: Eric Milkie Date: Mon, 22 Apr 2013 14:48:02 -0400 Subject: SERVER-9339 ensure all rollback update/delete paths are recorded Conflicts: src/mongo/db/ops/update.cpp --- jstests/replsets/rollback5.js | 103 +++++++++++++++++++++++++++++++ src/mongo/db/dbhelpers.cpp | 4 +- src/mongo/db/ops/delete.cpp | 3 - src/mongo/db/ops/update.cpp | 3 - src/mongo/db/repl/rs_rollback.cpp | 10 +++ src/mongo/shell/shell_utils_launcher.cpp | 9 +++ 6 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 jstests/replsets/rollback5.js diff --git a/jstests/replsets/rollback5.js b/jstests/replsets/rollback5.js new file mode 100644 index 00000000000..5828dcf1b61 --- /dev/null +++ b/jstests/replsets/rollback5.js @@ -0,0 +1,103 @@ +// test that a rollback directory is created during a replica set rollback +// this also tests that updates are recorded in the rollback file +// (this test does no delete rollbacks) + +var replTest = new ReplSetTest({ name: 'rollback5', nodes: 3 }); +var nodes = replTest.nodeList(); + +var conns = replTest.startSet(); +var r = replTest.initiate({ "_id": "rollback5", + "members": [ + { "_id": 0, "host": nodes[0] }, + { "_id": 1, "host": nodes[1] }, + { "_id": 2, "host": nodes[2], arbiterOnly: true}] + }); + +// Make sure we have a master +var master = replTest.getMaster(); +var a_conn = conns[0]; +var b_conn = conns[1]; +a_conn.setSlaveOk(); +b_conn.setSlaveOk(); +var A = a_conn.getDB("test"); +var B = b_conn.getDB("test"); +var AID = replTest.getNodeId(a_conn); +var BID = replTest.getNodeId(b_conn); +var Apath = "/data/db/rollback5-0/"; +var Bpath = "/data/db/rollback5-1/"; +assert(master == conns[0], "conns[0] assumed to be master"); +assert(a_conn.host == master.host); + +// Make sure we have an arbiter +assert.soon(function () { + res = conns[2].getDB("admin").runCommand({ replSetGetStatus: 1 }); + return res.myState == 7; +}, "Arbiter failed to initialize."); + +A.foo.update({key:'value1'}, {$set: {req: 'req'}}, true); +A.foo.runCommand({getLastError : 1, w : 2, wtimeout : 60000}); +replTest.stop(AID); + +master = replTest.getMaster(); +assert(b_conn.host == master.host); +B.foo.update({key:'value1'}, {$set: {res: 'res'}}, true); +B.foo.runCommand({getLastError : 1, w : 1, wtimeout : 60000}); +replTest.stop(BID); +replTest.restart(AID); +master = replTest.getMaster(); +assert(a_conn.host == master.host); +A.foo.update({key:'value2'}, {$set: {req: 'req'}}, true); +A.foo.runCommand({getLastError : 1, w : 1, wtimeout : 60000}); +replTest.restart(BID); // should rollback +reconnect(B); + +print("BEFORE------------------"); +printjson(A.foo.find().toArray()); +printjson(B.foo.find().toArray()); + +replTest.awaitReplication(); +replTest.awaitSecondaryNodes(); + +print("AFTER------------------"); +printjson(A.foo.find().toArray()); +printjson(B.foo.find().toArray()); +assert.eq(2, A.foo.count()); +assert.eq('req', A.foo.findOne({key:'value1'}).req); +assert.eq(null, A.foo.findOne({key:'value1'}).res); +assert.eq(2, B.foo.count()); +assert.eq('req', B.foo.findOne({key:'value1'}).req); +assert.eq(null, B.foo.findOne({key:'value1'}).res); + +// check here for rollback files +var rollbackDir = Bpath + "rollback/"; +assert(pathExists(rollbackDir), "rollback directory was not created!"); + +print("rollback5.js SUCCESS"); +replTest.stopSet(15); + + +function wait(f) { + var n = 0; + while (!f()) { + if (n % 4 == 0) + print("rollback5.js waiting"); + if (++n == 4) { + print("" + f); + } + assert(n < 200, 'tried 200 times, giving up'); + sleep(1000); + } +} + +function reconnect(a) { + wait(function() { + try { + a.bar.stats(); + return true; + } catch(e) { + print(e); + return false; + } + }); +}; + diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp index 85b38a1ff78..5e6736624a3 100644 --- a/src/mongo/db/dbhelpers.cpp +++ b/src/mongo/db/dbhelpers.cpp @@ -380,7 +380,6 @@ namespace mongo { stringstream ss; ss << why << "." << terseCurrentTime(false) << "." << NUM++ << ".bson"; _file /= ss.str(); - } RemoveSaver::~RemoveSaver() { @@ -397,7 +396,8 @@ namespace mongo { _out = new ofstream(); _out->open( _file.string().c_str() , ios_base::out | ios_base::binary ); if ( ! _out->good() ) { - LOG( LL_WARNING ) << "couldn't create file: " << _file.string() << " for remove saving" << endl; + error() << "couldn't create file: " << _file.string() << + " for remove saving" << endl; delete _out; _out = 0; return; diff --git a/src/mongo/db/ops/delete.cpp b/src/mongo/db/ops/delete.cpp index 790dd543cd0..8d7b1cf9d3b 100644 --- a/src/mongo/db/ops/delete.cpp +++ b/src/mongo/db/ops/delete.cpp @@ -129,9 +129,6 @@ namespace mongo { } } - if ( rs ) - rs->goingToDelete( rloc.obj() /*cc->c->current()*/ ); - theDataFileMgr.deleteRecord(ns, rloc.rec(), rloc); nDeleted++; if ( foundAllResults ) { diff --git a/src/mongo/db/ops/update.cpp b/src/mongo/db/ops/update.cpp index 954d2e0b1f3..57c59c4446c 100644 --- a/src/mongo/db/ops/update.cpp +++ b/src/mongo/db/ops/update.cpp @@ -364,9 +364,6 @@ namespace mongo { d->paddingFits(); } else { - if ( rs ) - rs->goingToDelete( onDisk ); - BSONObj newObj = mss->createNewFromMods(); checkTooLarge(newObj); DiskLoc newLoc = theDataFileMgr.updateRecord(ns, diff --git a/src/mongo/db/repl/rs_rollback.cpp b/src/mongo/db/repl/rs_rollback.cpp index 043845873d5..c9c380e739d 100644 --- a/src/mongo/db/repl/rs_rollback.cpp +++ b/src/mongo/db/repl/rs_rollback.cpp @@ -465,6 +465,16 @@ namespace mongo { // todo: lots of overhead in context, this can be faster Client::Context c(d.ns); + + // Add the doc to our rollback file + BSONObj obj; + bool found = Helpers::findOne(d.ns, pattern, obj, false); + if ( found ) { + rs->goingToDelete( obj ); + } else { + error() << "rollback cannot find object by id" << endl; + } + if( i->second.isEmpty() ) { // wasn't on the primary; delete. /* TODO1.6 : can't delete from a capped collection. need to handle that here. */ diff --git a/src/mongo/shell/shell_utils_launcher.cpp b/src/mongo/shell/shell_utils_launcher.cpp index cfe679d4ea2..3484139f8cf 100644 --- a/src/mongo/shell/shell_utils_launcher.cpp +++ b/src/mongo/shell/shell_utils_launcher.cpp @@ -533,6 +533,14 @@ namespace mongo { return undefinedReturn; } + BSONObj PathExists( const BSONObj &a, void* data ) { + verify( a.nFields() == 1 ); + string path = a.firstElement().valuestrsafe(); + verify( !path.empty() ); + bool exists = boost::filesystem::exists(path); + return BSON( string( "" ) << exists ); + } + void copyDir( const boost::filesystem::path &from, const boost::filesystem::path &to ) { boost::filesystem::directory_iterator end; boost::filesystem::directory_iterator i( from ); @@ -748,6 +756,7 @@ namespace mongo { scope.injectNative( "clearRawMongoProgramOutput", ClearRawMongoProgramOutput ); scope.injectNative( "waitProgram" , WaitProgram ); scope.injectNative( "resetDbpath", ResetDbpath ); + scope.injectNative( "pathExists", PathExists ); scope.injectNative( "copyDbpath", CopyDbpath ); } } -- cgit v1.2.1