diff options
-rw-r--r-- | jstests/auth/lib/commands_lib.js | 16 | ||||
-rw-r--r-- | jstests/core/fsync.js | 11 | ||||
-rw-r--r-- | jstests/noPassthroughWithMongod/fsync2.js | 2 | ||||
-rw-r--r-- | jstests/repl/snapshot1.js | 4 | ||||
-rw-r--r-- | jstests/replsets/fsync_lock_read_secondaries.js | 2 | ||||
-rw-r--r-- | jstests/replsets/maxSyncSourceLagSecs.js | 2 | ||||
-rw-r--r-- | jstests/replsets/stepdown.js | 2 | ||||
-rw-r--r-- | jstests/replsets/stepdown3.js | 2 | ||||
-rw-r--r-- | src/mongo/db/commands/fsync.cpp | 163 | ||||
-rw-r--r-- | src/mongo/db/commands/fsync.h | 2 | ||||
-rw-r--r-- | src/mongo/db/instance.cpp | 66 | ||||
-rw-r--r-- | src/mongo/shell/db.js | 9 |
12 files changed, 201 insertions, 80 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js index c73439d2494..e4d5fbed16e 100644 --- a/jstests/auth/lib/commands_lib.js +++ b/jstests/auth/lib/commands_lib.js @@ -1144,6 +1144,22 @@ var authCommandsLib = { ] }, { + testname: "fsyncUnlock", + command: {fsyncUnlock: 1}, + testcases: [ + { + runOnDb: adminDbName, + roles: roles_hostManager, + privileges: [ + { resource: {cluster: true}, actions: ["fsync"] } + ], + expectFail: true + }, + { runOnDb: firstDbName, roles: {} }, + { runOnDb: secondDbName, roles: {} } + ] + }, + { testname: "geoNear", command: {geoNear: "x", near: [50, 50], num: 1}, setup: function (db) { diff --git a/jstests/core/fsync.js b/jstests/core/fsync.js index 53ebef0f97d..14c5d323591 100644 --- a/jstests/core/fsync.js +++ b/jstests/core/fsync.js @@ -36,3 +36,14 @@ assert.eq(2, fsyncLockDB.coll.count({})); // Ensure eval is not allowed to invoke fsyncLock assert(!db.eval('db.fsyncLock()').ok, "eval('db.fsyncLock()') should fail."); + +// Check that the fsyncUnlock pseudo-command (a lookup on cmd.$sys.unlock) +// still has the same effect as a legitimate 'fsyncUnlock' command +// TODO: remove this in in the release following MongoDB 3.2 when pseudo-commands +// are removed +var fsyncCommandRes = db.fsyncLock(); +assert(fsyncLockRes.ok, "fsyncLock command failed against admin DB"); +assert(db.currentOp().fsyncLock, "Value in db.currentOp incorrect for fsyncLocked server"); +var fsyncPseudoCommandRes = db.getSiblingDB("admin").$cmd.sys.unlock.findOne(); +assert(fsyncPseudoCommandRes.ok, "fsyncUnlock pseudo-command failed"); +assert(db.currentOp().fsyncLock == null, "fsyncUnlock is not null in db.currentOp"); diff --git a/jstests/noPassthroughWithMongod/fsync2.js b/jstests/noPassthroughWithMongod/fsync2.js index 7080837a99b..fcf4da08533 100644 --- a/jstests/noPassthroughWithMongod/fsync2.js +++ b/jstests/noPassthroughWithMongod/fsync2.js @@ -40,7 +40,7 @@ function doTest() { // Uncomment once SERVER-4243 is fixed //assert.eq(1, m.getDB(db.getName()).fsync2.count()); - assert( m.getDB("admin").$cmd.sys.unlock.findOne().ok ); + assert( m.getDB("admin").fsyncUnlock().ok ); assert.eq( 2, db.fsync2.count() ); diff --git a/jstests/repl/snapshot1.js b/jstests/repl/snapshot1.js index 3be37aa125b..076ec2403f9 100644 --- a/jstests/repl/snapshot1.js +++ b/jstests/repl/snapshot1.js @@ -14,7 +14,7 @@ for( i = 0; i < 1000; ++i ) m.getDB( "admin" ).runCommand( {fsync:1,lock:1} ); copyDbpath( rt1.getPath( true ), rt1.getPath( false ) ); -m.getDB( "admin" ).$cmd.sys.unlock.findOne(); +m.getDB( "admin" ).fsyncUnlock(); s1 = rt1.start( false, null, true ); assert.eq( 1000, s1.getDB( baseName )[ baseName ].count() ); @@ -23,7 +23,7 @@ assert.soon( function() { return 1001 == s1.getDB( baseName )[ baseName ].count( s1.getDB( "admin" ).runCommand( {fsync:1,lock:1} ); copyDbpath( rt1.getPath( false ), rt2.getPath( false ) ); -s1.getDB( "admin" ).$cmd.sys.unlock.findOne(); +s1.getDB( "admin" ).fsyncUnlock(); s2 = rt2.start( false, null, true ); assert.eq( 1001, s2.getDB( baseName )[ baseName ].count() ); diff --git a/jstests/replsets/fsync_lock_read_secondaries.js b/jstests/replsets/fsync_lock_read_secondaries.js index 7aab61fd4b2..d217e85241f 100644 --- a/jstests/replsets/fsync_lock_read_secondaries.js +++ b/jstests/replsets/fsync_lock_read_secondaries.js @@ -53,7 +53,7 @@ for (var i=0; i<docNum; i++) { // this should work just fine. var slave0count = slaves[0].getDB("foo").bar.count(); assert.eq(slave0count, 100, "Doc count in fsync lock wrong. Expected (=100), found " + slave0count); -assert(slaves[0].getDB("admin").$cmd.sys.unlock.findOne().ok); +assert(slaves[0].getDB("admin").fsyncUnlock().ok); // The secondary should have equal or more documents than what it had before. assert.soon(function() { diff --git a/jstests/replsets/maxSyncSourceLagSecs.js b/jstests/replsets/maxSyncSourceLagSecs.js index 8d4702510e1..1a7348c2e64 100644 --- a/jstests/replsets/maxSyncSourceLagSecs.js +++ b/jstests/replsets/maxSyncSourceLagSecs.js @@ -44,6 +44,6 @@ return (slaves[1].getDB("foo").bar.count() === 2); }, "slave should have caught up after syncing to primary."); - assert.commandWorked(slaves[0].getDB("admin").$cmd.sys.unlock.findOne()); + assert.commandWorked(slaves[0].getDB("admin").fsyncUnlock()); replTest.stopSet(); }()); diff --git a/jstests/replsets/stepdown.js b/jstests/replsets/stepdown.js index 474154ae6ef..275d7726d40 100644 --- a/jstests/replsets/stepdown.js +++ b/jstests/replsets/stepdown.js @@ -53,7 +53,7 @@ assert.eq(r2.ismaster, false); assert.eq(r2.secondary, true); print("\nunlock"); -printjson(locked.getDB("admin").$cmd.sys.unlock.findOne()); +printjson(locked.getDB("admin").fsyncUnlock()); print("\nreset stepped down time"); master.getDB("admin").runCommand({replSetFreeze:0}); diff --git a/jstests/replsets/stepdown3.js b/jstests/replsets/stepdown3.js index af7f84607b7..05ce573c883 100644 --- a/jstests/replsets/stepdown3.js +++ b/jstests/replsets/stepdown3.js @@ -46,5 +46,5 @@ print("result of gle:"); printjson(result); // unlock and shut down -printjson(locked.getDB("admin").$cmd.sys.unlock.findOne()); +printjson(locked.getDB("admin").fsyncUnlock()); replTest.stopSet(); diff --git a/src/mongo/db/commands/fsync.cpp b/src/mongo/db/commands/fsync.cpp index f9f829df3b7..b6fde72e309 100644 --- a/src/mongo/db/commands/fsync.cpp +++ b/src/mongo/db/commands/fsync.cpp @@ -26,16 +26,24 @@ * it in the license file. */ -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand #include "mongo/db/commands/fsync.h" +#include <iostream> +#include <memory> +#include <sstream> #include <string> #include <vector> +#include "mongo/base/init.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/audit.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/commands.h" @@ -43,12 +51,11 @@ #include "mongo/db/storage/mmap_v1/dur.h" #include "mongo/db/storage/storage_engine.h" #include "mongo/db/client.h" -#include "mongo/db/jsobj.h" #include "mongo/db/operation_context_impl.h" +#include "mongo/stdx/memory.h" #include "mongo/util/background.h" #include "mongo/util/log.h" - namespace mongo { using std::endl; @@ -152,7 +159,73 @@ namespace mongo { } return 1; } - } fsyncCmd; + }; + + namespace { + bool unlockFsync(); + } // namespace + + class FSyncUnlockCommand : public Command { + public: + + FSyncUnlockCommand() : Command("fsyncUnlock") {} + + bool isWriteCommandForConfigServer() const override { return false; } + + bool slaveOk() const override { return true; } + + bool adminOnly() const override { return true; } + + Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) override { + + bool isAuthorized = client->getAuthorizationSession()->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), + ActionType::unlock); + + if (isAuthorized) { + audit::logFsyncUnlockAuthzCheck(client, ErrorCodes::OK); + return Status::OK(); + } + else { + audit::logFsyncUnlockAuthzCheck(client, ErrorCodes::Unauthorized); + return Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + } + + bool run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& errmsg, + BSONObjBuilder& result, + bool fromRepl) override { + + log() << "command: unlock requested"; + + if (unlockFsync()) { + result.append("info", "unlock completed"); + return true; + } + else { + errmsg = "not locked"; + return false; + } + } + + }; + + namespace { + std::unique_ptr<FSyncCommand> fsyncCmd; + std::unique_ptr<FSyncUnlockCommand> fsyncUnlockCmd; + } // namespace + + MONGO_INITIALIZER(FSyncAndFsyncUnlock)(InitializerContext*) { + fsyncCmd = stdx::make_unique<FSyncCommand>(); + fsyncUnlockCmd = stdx::make_unique<FSyncUnlockCommand>(); + return Status::OK(); + } SimpleMutex filesLockedFsync("filesLockedFsync"); @@ -163,17 +236,17 @@ namespace mongo { ScopedTransaction transaction(&txn, MODE_X); Lock::GlobalWrite global(txn.lockState()); // No WriteUnitOfWork needed - SimpleMutex::scoped_lock lk(fsyncCmd.m); - - invariant(!fsyncCmd.locked); // impossible to get here if locked is true - try { + SimpleMutex::scoped_lock lk(fsyncCmd->m); + + invariant(!fsyncCmd->locked); // impossible to get here if locked is true + try { getDur().syncDataAndTruncateJournal(&txn); - } - catch( std::exception& e ) { + } + catch( std::exception& e ) { error() << "error doing syncDataAndTruncateJournal: " << e.what() << endl; - fsyncCmd.err = e.what(); - fsyncCmd._threadSync.notify_one(); - fsyncCmd.locked = false; + fsyncCmd->err = e.what(); + fsyncCmd->_threadSync.notify_one(); + fsyncCmd->locked = false; return; } @@ -183,47 +256,49 @@ namespace mongo { StorageEngine* storageEngine = getGlobalEnvironment()->getGlobalStorageEngine(); storageEngine->flushAllFiles(true); } - catch( std::exception& e ) { + catch( std::exception& e ) { error() << "error doing flushAll: " << e.what() << endl; - fsyncCmd.err = e.what(); - fsyncCmd._threadSync.notify_one(); - fsyncCmd.locked = false; + fsyncCmd->err = e.what(); + fsyncCmd->_threadSync.notify_one(); + fsyncCmd->locked = false; return; } - invariant(!fsyncCmd.locked); - fsyncCmd.locked = true; - - fsyncCmd._threadSync.notify_one(); + invariant(!fsyncCmd->locked); + fsyncCmd->locked = true; + + fsyncCmd->_threadSync.notify_one(); - while ( ! fsyncCmd.pendingUnlock ) { - fsyncCmd._unlockSync.wait(fsyncCmd.m); + while ( ! fsyncCmd->pendingUnlock ) { + fsyncCmd->_unlockSync.wait(fsyncCmd->m); } - fsyncCmd.pendingUnlock = false; - - fsyncCmd.locked = false; - fsyncCmd.err = "unlocked"; + fsyncCmd->pendingUnlock = false; - fsyncCmd._unlockSync.notify_one(); + fsyncCmd->locked = false; + fsyncCmd->err = "unlocked"; + + fsyncCmd->_unlockSync.notify_one(); } - bool lockedForWriting() { - return fsyncCmd.locked; + bool lockedForWriting() { + return fsyncCmd->locked; } - // @return true if unlocked - bool _unlockFsync() { - SimpleMutex::scoped_lock lk( fsyncCmd.m ); - if( !fsyncCmd.locked ) { - return false; - } - fsyncCmd.pendingUnlock = true; - fsyncCmd._unlockSync.notify_one(); - fsyncCmd._threadSync.notify_one(); - - while ( fsyncCmd.locked ) { - fsyncCmd._unlockSync.wait( fsyncCmd.m ); + namespace { + // @return true if unlocked + bool unlockFsync() { + SimpleMutex::scoped_lock lk( fsyncCmd->m ); + if( !fsyncCmd->locked ) { + return false; + } + fsyncCmd->pendingUnlock = true; + fsyncCmd->_unlockSync.notify_one(); + fsyncCmd->_threadSync.notify_one(); + + while ( fsyncCmd->locked ) { + fsyncCmd->_unlockSync.wait( fsyncCmd->m ); + } + return true; } - return true; - } + } // namespace } diff --git a/src/mongo/db/commands/fsync.h b/src/mongo/db/commands/fsync.h index f3fa2adb993..4072a1f6e50 100644 --- a/src/mongo/db/commands/fsync.h +++ b/src/mongo/db/commands/fsync.h @@ -34,4 +34,4 @@ namespace mongo { // Use this for blocking during an fsync-and-lock extern SimpleMutex filesLockedFsync; bool lockedForWriting(); -} +} // namespace mongo diff --git a/src/mongo/db/instance.cpp b/src/mongo/db/instance.cpp index 46e0edd4b13..242a81db415 100644 --- a/src/mongo/db/instance.cpp +++ b/src/mongo/db/instance.cpp @@ -180,32 +180,6 @@ namespace mongo { replyToQuery(0, m, dbresponse, obj); } - bool _unlockFsync(); - static void unlockFsync(OperationContext* txn, const char *ns, Message& m, DbResponse &dbresponse) { - BSONObj obj; - - const bool isAuthorized = txn->getClient()->getAuthorizationSession()->isAuthorizedForActionsOnResource( - ResourcePattern::forClusterResource(), ActionType::unlock); - audit::logFsyncUnlockAuthzCheck( - txn->getClient(), isAuthorized ? ErrorCodes::OK : ErrorCodes::Unauthorized); - if (!isAuthorized) { - obj = fromjson("{\"err\":\"unauthorized\"}"); - } - else if (strncmp(ns, "admin.", 6) != 0 ) { - obj = fromjson("{\"err\":\"unauthorized - this command must be run against the admin DB\"}"); - } - else { - log() << "command: unlock requested" << endl; - if( _unlockFsync() ) { - obj = fromjson("{ok:1,\"info\":\"unlock completed\"}"); - } - else { - obj = fromjson("{ok:0,\"errmsg\":\"not locked\"}"); - } - } - replyToQuery(0, m, dbresponse, obj); - } - namespace { void generateErrorResponse(const AssertionException* exception, @@ -324,6 +298,44 @@ namespace { dbResponse.responseTo = responseTo; } +namespace { + + // In SERVER-7775 we reimplemented the pseudo-commands fsyncUnlock, inProg, and killOp + // as ordinary commands. To support old clients for another release, this helper serves + // to execute the real command from the legacy pseudo-command codepath. + // TODO: remove after MongoDB 3.2 is released + void receivedPseudoCommand(OperationContext* txn, + const NamespaceString& nss, + Client& client, + DbResponse& dbResponse, + Message& message, + StringData realCommandName) { + Message interposed; + + NamespaceString interposedNss(nss.db(), "$cmd"); + + BSONObjBuilder cmdBob; + cmdBob.append(realCommandName, 1); + auto cmd = cmdBob.done(); + + // TODO: use OP_COMMAND here instead of constructing + // a legacy OP_QUERY style command + BufBuilder cmdMsgBuf; + cmdMsgBuf.appendNum(DataView(message.header().data()).readLE<int32_t>()); // flags + cmdMsgBuf.appendStr(interposedNss.db(), false); // not including null byte + cmdMsgBuf.appendStr(".$cmd"); + cmdMsgBuf.appendNum(0); // ntoskip + cmdMsgBuf.appendNum(1); // ntoreturn + cmdMsgBuf.appendBuf(cmd.objdata(), cmd.objsize()); + + interposed.setData(dbQuery, cmdMsgBuf.buf(), cmdMsgBuf.len()); + interposed.header().setId(message.header().getId()); + + receivedCommand(txn, interposedNss, client, dbResponse, interposed); + } + +} // namespace + static void receivedQuery(OperationContext* txn, const NamespaceString& nss, Client& c, @@ -408,7 +420,7 @@ namespace { return; } if (nsString.coll() == "$cmd.sys.unlock") { - unlockFsync(txn, ns, m, dbresponse); + receivedPseudoCommand(txn, nsString, c, dbresponse, m, "fsyncUnlock"); return; } } diff --git a/src/mongo/shell/db.js b/src/mongo/shell/db.js index 48e086ad9f6..9a6c5399067 100644 --- a/src/mongo/shell/db.js +++ b/src/mongo/shell/db.js @@ -975,7 +975,14 @@ DB.prototype.fsyncLock = function() { } DB.prototype.fsyncUnlock = function() { - return this.getSiblingDB("admin").$cmd.sys.unlock.findOne() + var res = this.adminCommand({fsyncUnlock: 1}); + if (!res.ok && + // handle both error messages for nonexistent command... + (res.errmsg.startsWith("no such cmd") || res.errmsg.startsWith("no such command"))) { + // fallback for old servers + res = this.getSiblingDB("admin").$cmd.sys.unlock.findOne(); + } + return res; } DB.autocomplete = function(obj){ |