/*@ @param opstr: c userCreateNS i insert n no-op / keepalive d delete / remove u update */ void logOp(OperationContext* txn, const char* opstr, const char* ns, const BSONObj& obj, BSONObj* patt, bool* b, bool fromMigrate) { try { if ( getGlobalReplicationCoordinator()->isReplEnabled() ) { _logOp(txn, opstr, ns, 0, obj, patt, b, fromMigrate); } logOpForSharding(txn, opstr, ns, obj, patt, fromMigrate); logOpForDbHash(ns); getGlobalAuthorizationManager()->logOp(opstr, ns, obj, patt, b); if ( strstr( ns, ".system.js" ) ) { Scope::storedFuncMod(); // this is terrible } } catch (const DBException& ex) { severe() << "Fatal DBException in logOp(): " << ex.toString(); std::terminate(); } catch (const std::exception& ex) { severe() << "Fatal std::exception in logOp(): " << ex.what(); std::terminate(); } catch (...) { severe() << "Fatal error in logOp()"; std::terminate(); } }
void OpObserver::onDropDatabase(OperationContext* txn, const std::string& dbName) { BSONObj cmdObj = BSON("dropDatabase" << 1); repl::logOp(txn, "c", dbName.c_str(), cmdObj, nullptr, false); getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), cmdObj, nullptr); logOpForDbHash(txn, dbName.c_str()); }
void OpObserver::onApplyOps(OperationContext* txn, const std::string& dbName, const BSONObj& applyOpCmd) { repl::logOp(txn, "c", dbName.c_str(), applyOpCmd, nullptr, false); getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), applyOpCmd, nullptr); logOpForDbHash(txn, dbName.c_str()); }
void OpObserver::onDropIndex(OperationContext* txn, const std::string& dbName, const BSONObj& idxDescriptor) { repl::logOp(txn, "c", dbName.c_str(), idxDescriptor, nullptr, false); getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), idxDescriptor, nullptr); logOpForDbHash(txn, dbName.c_str()); }
void OpObserver::onCreateIndex(OperationContext* txn, const std::string& ns, BSONObj indexDoc, bool fromMigrate) { repl::logOp(txn, "i", ns.c_str(), indexDoc, nullptr, fromMigrate); getGlobalAuthorizationManager()->logOp(txn, "i", ns.c_str(), indexDoc, nullptr); logOpForSharding(txn, "i", ns.c_str(), indexDoc, nullptr, fromMigrate); logOpForDbHash(txn, ns.c_str()); }
void OpObserver::onUpdate(OperationContext* txn, oplogUpdateEntryArgs args) { repl::logOp(txn, "u", args.ns.c_str(), args.update, &args.criteria, args.fromMigrate); getGlobalAuthorizationManager()->logOp(txn, "u", args.ns.c_str(), args.update, &args.criteria); logOpForSharding(txn, "u", args.ns.c_str(), args.update, &args.criteria, args.fromMigrate); logOpForDbHash(txn, args.ns.c_str()); if (strstr(args.ns.c_str(), ".system.js")) { Scope::storedFuncMod(txn); } }
void OpObserver::onEmptyCapped(OperationContext* txn, const NamespaceString& collectionName) { std::string dbName = collectionName.db().toString() + ".$cmd"; BSONObj cmdObj = BSON("emptycapped" << collectionName.coll()); if (!collectionName.isSystemDotProfile()) { // do not replicate system.profile modifications repl::logOp(txn, "c", dbName.c_str(), cmdObj, nullptr, false); } getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), cmdObj, nullptr); logOpForDbHash(txn, dbName.c_str()); }
void OpObserver::onInsert(OperationContext* txn, const NamespaceString& ns, BSONObj doc, bool fromMigrate) { repl::logOp(txn, "i", ns.ns().c_str(), doc, nullptr, fromMigrate); getGlobalAuthorizationManager()->logOp(txn, "i", ns.ns().c_str(), doc, nullptr); logOpForSharding(txn, "i", ns.ns().c_str(), doc, nullptr, fromMigrate); logOpForDbHash(txn, ns.ns().c_str()); if (strstr(ns.ns().c_str(), ".system.js")) { Scope::storedFuncMod(txn); } }
void OpObserver::onDelete(OperationContext* txn, const std::string& ns, const BSONObj& idDoc, bool fromMigrate) { repl::logOp(txn, "d", ns.c_str(), idDoc, nullptr, fromMigrate); getGlobalAuthorizationManager()->logOp(txn, "d", ns.c_str(), idDoc, nullptr); logOpForSharding(txn, "d", ns.c_str(), idDoc, nullptr, fromMigrate); logOpForDbHash(txn, ns.c_str()); if (strstr(ns.c_str(), ".system.js")) { Scope::storedFuncMod(txn); } }
void OpObserver::onCollMod(OperationContext* txn, const std::string& dbName, const BSONObj& collModCmd) { BSONElement first = collModCmd.firstElement(); std::string coll = first.valuestr(); if (!NamespaceString(NamespaceString(dbName).db(), coll).isSystemDotProfile()) { // do not replicate system.profile modifications repl::logOp(txn, "c", dbName.c_str(), collModCmd, nullptr, false); } getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), collModCmd, nullptr); logOpForDbHash(txn, dbName.c_str()); }
void OpObserver::onRenameCollection(OperationContext* txn, const NamespaceString& fromCollection, const NamespaceString& toCollection, bool dropTarget, bool stayTemp) { std::string dbName = fromCollection.db().toString() + ".$cmd"; BSONObj cmdObj = BSON("renameCollection" << fromCollection.ns() << "to" << toCollection.ns() << "stayTemp" << stayTemp << "dropTarget" << dropTarget); repl::logOp(txn, "c", dbName.c_str(), cmdObj, nullptr, false); getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), cmdObj, nullptr); logOpForDbHash(txn, dbName.c_str()); }
void OpObserver::onCreateCollection(OperationContext* txn, const NamespaceString& collectionName, const CollectionOptions& options) { std::string dbName = collectionName.db().toString() + ".$cmd"; BSONObjBuilder b; b.append("create", collectionName.coll().toString()); b.appendElements(options.toBSON()); BSONObj cmdObj = b.obj(); if (!collectionName.isSystemDotProfile()) { // do not replicate system.profile modifications repl::logOp(txn, "c", dbName.c_str(), cmdObj, nullptr, false); } getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), cmdObj, nullptr); logOpForDbHash(txn, dbName.c_str()); }
/*@ @param opstr: c userCreateNS i insert n no-op / keepalive d delete / remove u update */ void logOp(OperationContext* txn, const char* opstr, const char* ns, const BSONObj& obj, BSONObj* patt, bool* b, bool fromMigrate) { try { // TODO SERVER-15192 remove this once all listeners are rollback-safe. class RollbackPreventer : public RecoveryUnit::Change { virtual void commit() {} virtual void rollback() { severe() << "Rollback of logOp not currently allowed (SERVER-15192)"; fassertFailed(18805); } }; txn->recoveryUnit()->registerChange(new RollbackPreventer()); if ( getGlobalReplicationCoordinator()->isReplEnabled() ) { _logOp(txn, opstr, ns, 0, obj, patt, b, fromMigrate); } logOpForSharding(txn, opstr, ns, obj, patt, fromMigrate); logOpForDbHash(ns); getGlobalAuthorizationManager()->logOp(opstr, ns, obj, patt, b); if ( strstr( ns, ".system.js" ) ) { Scope::storedFuncMod(); // this is terrible } } catch (const DBException& ex) { severe() << "Fatal DBException in logOp(): " << ex.toString(); std::terminate(); } catch (const std::exception& ex) { severe() << "Fatal std::exception in logOp(): " << ex.what(); std::terminate(); } catch (...) { severe() << "Fatal error in logOp()"; std::terminate(); } }
/*@ @param opstr: c userCreateNS i insert n no-op / keepalive d delete / remove u update */ void logOp(OperationContext* txn, const char* opstr, const char* ns, const BSONObj& obj, BSONObj* patt, bool* b, bool fromMigrate) { if ( replSettings.master ) { _logOp(txn, opstr, ns, 0, obj, patt, b, fromMigrate); } logOpForSharding(txn, opstr, ns, obj, patt, fromMigrate); logOpForDbHash(ns); getGlobalAuthorizationManager()->logOp(opstr, ns, obj, patt, b); if ( strstr( ns, ".system.js" ) ) { Scope::storedFuncMod(); // this is terrible } }
virtual bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if ( cmdObj.firstElement().type() != Array ) { errmsg = "ops has to be an array"; return false; } BSONObj ops = cmdObj.firstElement().Obj(); { // check input BSONObjIterator i( ops ); while ( i.more() ) { BSONElement e = i.next(); if (!_checkOperation(e, errmsg)) { return false; } } } // SERVER-4328 todo : is global ok or does this take a long time? i believe multiple // ns used so locking individually requires more analysis Lock::GlobalWrite globalWriteLock(txn->lockState()); DBDirectClient db(txn); // Preconditions check reads the database state, so needs to be done locked if ( cmdObj["preCondition"].type() == Array ) { BSONObjIterator i( cmdObj["preCondition"].Obj() ); while ( i.more() ) { BSONObj f = i.next().Obj(); DBDirectClient db( txn ); BSONObj realres = db.findOne( f["ns"].String() , f["q"].Obj() ); // Apply-ops would never have a $where matcher, so use the default callback, // which will throw an error if $where is found. Matcher m(f["res"].Obj()); if ( ! m.matches( realres ) ) { result.append( "got" , realres ); result.append( "whatFailed" , f ); errmsg = "pre-condition failed"; return false; } } } // apply int num = 0; int errors = 0; BSONObjIterator i( ops ); BSONArrayBuilder ab; const bool alwaysUpsert = cmdObj.hasField("alwaysUpsert") ? cmdObj["alwaysUpsert"].trueValue() : true; while ( i.more() ) { BSONElement e = i.next(); const BSONObj& temp = e.Obj(); // Ignore 'n' operations. const char *opType = temp["op"].valuestrsafe(); if (*opType == 'n') continue; string ns = temp["ns"].String(); // Run operations under a nested lock as a hack to prevent yielding. // // The list of operations is supposed to be applied atomically; yielding // would break atomicity by allowing an interruption or a shutdown to occur // after only some operations are applied. We are already locked globally // at this point, so taking a DBLock on the namespace creates a nested lock, // and yields are disallowed for operations that hold a nested lock. // // We do not have a wrapping WriteUnitOfWork so it is possible for a journal // commit to happen with a subset of ops applied. // TODO figure out what to do about this. Lock::DBLock lk(txn->lockState(), nsToDatabaseSubstring(ns), MODE_X); invariant(txn->lockState()->isRecursive()); Client::Context ctx(txn, ns); bool failed = repl::applyOperation_inlock(txn, ctx.db(), temp, false, alwaysUpsert); ab.append(!failed); if ( failed ) errors++; num++; logOpForDbHash(ns.c_str()); } result.append( "applied" , num ); result.append( "results" , ab.arr() ); if ( ! fromRepl ) { // We want this applied atomically on slaves // so we re-wrap without the pre-condition for speed string tempNS = str::stream() << dbname << ".$cmd"; // TODO: possibly use mutable BSON to remove preCondition field // once it is available BSONObjIterator iter(cmdObj); BSONObjBuilder cmdBuilder; while (iter.more()) { BSONElement elem(iter.next()); if (strcmp(elem.fieldName(), "preCondition") != 0) { cmdBuilder.append(elem); } } // We currently always logOp the command regardless of whether the individial ops // succeeded and rely on any failures to also happen on secondaries. This isn't // perfect, but it's what the command has always done and is part of its "correct" // behavior. WriteUnitOfWork wunit(txn); repl::logOp(txn, "c", tempNS.c_str(), cmdBuilder.done()); wunit.commit(); } if (errors != 0) { return false; } return true; }
virtual bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if ( cmdObj.firstElement().type() != Array ) { errmsg = "ops has to be an array"; return false; } BSONObj ops = cmdObj.firstElement().Obj(); { // check input BSONObjIterator i( ops ); while ( i.more() ) { BSONElement e = i.next(); if ( e.type() == Object ) continue; errmsg = "op not an object: "; errmsg += e.fieldName(); return false; } } // SERVER-4328 todo : is global ok or does this take a long time? i believe multiple // ns used so locking individually requires more analysis Lock::GlobalWrite globalWriteLock(txn->lockState()); // Preconditions check reads the database state, so needs to be done locked if ( cmdObj["preCondition"].type() == Array ) { BSONObjIterator i( cmdObj["preCondition"].Obj() ); while ( i.more() ) { BSONObj f = i.next().Obj(); DBDirectClient db( txn ); BSONObj realres = db.findOne( f["ns"].String() , f["q"].Obj() ); // Apply-ops would never have a $where matcher, so use the default callback, // which will throw an error if $where is found. Matcher m(f["res"].Obj()); if ( ! m.matches( realres ) ) { result.append( "got" , realres ); result.append( "whatFailed" , f ); errmsg = "pre-condition failed"; return false; } } } // apply int num = 0; int errors = 0; BSONObjIterator i( ops ); BSONArrayBuilder ab; const bool alwaysUpsert = cmdObj.hasField("alwaysUpsert") ? cmdObj["alwaysUpsert"].trueValue() : true; while ( i.more() ) { BSONElement e = i.next(); const BSONObj& temp = e.Obj(); string ns = temp["ns"].String(); // Run operations under a nested lock as a hack to prevent them from yielding. // // The list of operations is supposed to be applied atomically; yielding would break // atomicity by allowing an interruption or a shutdown to occur after only some // operations are applied. We are already locked globally at this point, so taking // a DBWrite on the namespace creates a nested lock, and yields are disallowed for // operations that hold a nested lock. Lock::DBWrite lk(txn->lockState(), ns); invariant(txn->lockState()->isRecursive()); Client::Context ctx(ns); bool failed = repl::applyOperation_inlock(txn, ctx.db(), temp, false, alwaysUpsert); ab.append(!failed); if ( failed ) errors++; num++; logOpForDbHash(ns.c_str()); } result.append( "applied" , num ); result.append( "results" , ab.arr() ); if ( ! fromRepl ) { // We want this applied atomically on slaves // so we re-wrap without the pre-condition for speed string tempNS = str::stream() << dbname << ".$cmd"; // TODO: possibly use mutable BSON to remove preCondition field // once it is available BSONObjIterator iter(cmdObj); BSONObjBuilder cmdBuilder; while (iter.more()) { BSONElement elem(iter.next()); if (strcmp(elem.fieldName(), "preCondition") != 0) { cmdBuilder.append(elem); } } repl::logOp(txn, "c", tempNS.c_str(), cmdBuilder.done()); } return errors == 0; }
virtual bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if ( cmdObj.firstElement().type() != Array ) { errmsg = "ops has to be an array"; return false; } BSONObj ops = cmdObj.firstElement().Obj(); { // check input BSONObjIterator i( ops ); while ( i.more() ) { BSONElement e = i.next(); if (!_checkOperation(e, errmsg)) { return false; } } } // SERVER-4328 todo : is global ok or does this take a long time? i believe multiple // ns used so locking individually requires more analysis ScopedTransaction scopedXact(txn, MODE_X); Lock::GlobalWrite globalWriteLock(txn->lockState()); if (!fromRepl && !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(dbname)) { return appendCommandStatus(result, Status(ErrorCodes::NotMaster, str::stream() << "Not primary while applying ops to database " << dbname)); } // Preconditions check reads the database state, so needs to be done locked if ( cmdObj["preCondition"].type() == Array ) { BSONObjIterator i( cmdObj["preCondition"].Obj() ); while ( i.more() ) { BSONObj f = i.next().Obj(); DBDirectClient db( txn ); BSONObj realres = db.findOne( f["ns"].String() , f["q"].Obj() ); // Apply-ops would never have a $where matcher, so use the default callback, // which will throw an error if $where is found. Matcher m(f["res"].Obj()); if ( ! m.matches( realres ) ) { result.append( "got" , realres ); result.append( "whatFailed" , f ); errmsg = "pre-condition failed"; return false; } } } // apply int num = 0; int errors = 0; BSONObjIterator i( ops ); BSONArrayBuilder ab; const bool alwaysUpsert = cmdObj.hasField("alwaysUpsert") ? cmdObj["alwaysUpsert"].trueValue() : true; while ( i.more() ) { BSONElement e = i.next(); const BSONObj& temp = e.Obj(); // Ignore 'n' operations. const char *opType = temp["op"].valuestrsafe(); if (*opType == 'n') continue; const string ns = temp["ns"].String(); // Run operations under a nested lock as a hack to prevent yielding. // // The list of operations is supposed to be applied atomically; yielding // would break atomicity by allowing an interruption or a shutdown to occur // after only some operations are applied. We are already locked globally // at this point, so taking a DBLock on the namespace creates a nested lock, // and yields are disallowed for operations that hold a nested lock. // // We do not have a wrapping WriteUnitOfWork so it is possible for a journal // commit to happen with a subset of ops applied. // TODO figure out what to do about this. Lock::GlobalWrite globalWriteLockDisallowTempRelease(txn->lockState()); // Ensures that yielding will not happen (see the comment above). DEV { Locker::LockSnapshot lockSnapshot; invariant(!txn->lockState()->saveLockStateAndUnlock(&lockSnapshot)); }; OldClientContext ctx(txn, ns); Status status(ErrorCodes::InternalError, ""); while (true) { try { // We assume that in the WriteConflict retry case, either the op rolls back // any changes it makes or is otherwise safe to rerun. status = repl::applyOperation_inlock(txn, ctx.db(), temp, false, alwaysUpsert); break; } catch (const WriteConflictException& wce) { LOG(2) << "WriteConflictException in applyOps command, retrying."; txn->recoveryUnit()->commitAndRestart(); continue; } } ab.append(status.isOK()); if (!status.isOK()) { errors++; } num++; WriteUnitOfWork wuow(txn); logOpForDbHash(txn, ns.c_str()); wuow.commit(); } result.append( "applied" , num ); result.append( "results" , ab.arr() ); if ( ! fromRepl ) { // We want this applied atomically on slaves // so we re-wrap without the pre-condition for speed string tempNS = str::stream() << dbname << ".$cmd"; // TODO: possibly use mutable BSON to remove preCondition field // once it is available BSONObjIterator iter(cmdObj); BSONObjBuilder cmdBuilder; while (iter.more()) { BSONElement elem(iter.next()); if (strcmp(elem.fieldName(), "preCondition") != 0) { cmdBuilder.append(elem); } } const BSONObj cmdRewritten = cmdBuilder.done(); // We currently always logOp the command regardless of whether the individial ops // succeeded and rely on any failures to also happen on secondaries. This isn't // perfect, but it's what the command has always done and is part of its "correct" // behavior. while (true) { try { WriteUnitOfWork wunit(txn); getGlobalEnvironment()->getOpObserver()->onApplyOps(txn, tempNS, cmdRewritten); wunit.commit(); break; } catch (const WriteConflictException& wce) { LOG(2) << "WriteConflictException while logging applyOps command, retrying."; txn->recoveryUnit()->commitAndRestart(); continue; } } } if (errors != 0) { return false; } return true; }