diff options
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/commands.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/commands/do_txn_cmd.cpp | 172 | ||||
-rw-r--r-- | src/mongo/db/commands/oplog_application_checks.h | 4 | ||||
-rw-r--r-- | src/mongo/db/initialize_operation_session_info.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/repl/SConscript | 20 | ||||
-rw-r--r-- | src/mongo/db/repl/do_txn.cpp | 313 | ||||
-rw-r--r-- | src/mongo/db/repl/do_txn.h | 57 | ||||
-rw-r--r-- | src/mongo/db/repl/do_txn_test.cpp | 335 | ||||
-rw-r--r-- | src/mongo/db/transaction_validation.cpp | 2 |
10 files changed, 2 insertions, 920 deletions
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index 8efc03ab32d..17bc2af8fca 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -114,7 +114,6 @@ const StringMap<int> txnCmdWhitelist = {{"abortTransaction", 1}, {"coordinateCommitTransaction", 1}, {"delete", 1}, {"distinct", 1}, - {"doTxn", 1}, {"find", 1}, {"findandmodify", 1}, {"findAndModify", 1}, @@ -129,7 +128,6 @@ const StringMap<int> txnCmdWhitelist = {{"abortTransaction", 1}, const StringMap<int> txnAdminCommands = {{"abortTransaction", 1}, {"commitTransaction", 1}, {"coordinateCommitTransaction", 1}, - {"doTxn", 1}, {"prepareTransaction", 1}}; } // namespace diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 61c39a599cd..d48a75d91b3 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -349,7 +349,6 @@ env.Library( "dbcheck.cpp", "dbcommands_d.cpp", "dbhash.cpp", - "do_txn_cmd.cpp", "driverHelpers.cpp", "haystack.cpp", "mr.cpp", diff --git a/src/mongo/db/commands/do_txn_cmd.cpp b/src/mongo/db/commands/do_txn_cmd.cpp deleted file mode 100644 index fbc542f952a..00000000000 --- a/src/mongo/db/commands/do_txn_cmd.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand - -#include "mongo/platform/basic.h" - -#include <vector> - -#include "mongo/bson/util/bson_check.h" -#include "mongo/bson/util/bson_extract.h" -#include "mongo/db/auth/authorization_session.h" -#include "mongo/db/catalog/collection_catalog.h" -#include "mongo/db/catalog/document_validation.h" -#include "mongo/db/client.h" -#include "mongo/db/commands.h" -#include "mongo/db/commands/oplog_application_checks.h" -#include "mongo/db/commands/test_commands_enabled.h" -#include "mongo/db/concurrency/write_conflict_exception.h" -#include "mongo/db/db_raii.h" -#include "mongo/db/dbdirectclient.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/repl/do_txn.h" -#include "mongo/db/repl/oplog.h" -#include "mongo/db/repl/oplog_entry_gen.h" -#include "mongo/db/repl/repl_client_info.h" -#include "mongo/db/service_context.h" -#include "mongo/util/log.h" -#include "mongo/util/scopeguard.h" -#include "mongo/util/uuid.h" - -namespace mongo { -namespace { - -/** - * Returns kNeedsUseUUID if the operation contains a UUID. Returns kOk if no conditions - * which must be specially handled are detected. Throws an exception if the input is malformed or - * if a command is in the list of ops. - */ -OplogApplicationValidity validateDoTxnCommand(const BSONObj& doTxnObj) { - auto parseOp = [](const BSONObj& opObj) { - try { - return repl::ReplOperation::parse(IDLParserErrorContext("doTxn"), opObj); - } catch (...) { - uasserted(ErrorCodes::FailedToParse, - str::stream() << "cannot apply a malformed operation in doTxn: " - << redact(opObj) << ": " << exceptionToStatus().toString()); - } - }; - - OplogApplicationValidity ret = OplogApplicationValidity::kOk; - - checkBSONType(BSONType::Array, doTxnObj.firstElement()); - // Check if the doTxn command is empty. There's no good reason for an empty transaction, - // so reject it. - uassert(ErrorCodes::InvalidOptions, - "An empty doTxn command is not allowed.", - !doTxnObj.firstElement().Array().empty()); - - // Iterate the ops. - for (BSONElement element : doTxnObj.firstElement().Array()) { - checkBSONType(BSONType::Object, element); - BSONObj opObj = element.Obj(); - auto op = parseOp(opObj); - - // If the op is a command, it's illegal. - uassert(ErrorCodes::InvalidOptions, - "Commands cannot be applied via doTxn.", - op.getOpType() != repl::OpTypeEnum::kCommand); - - // If the op uses any UUIDs at all then the user must possess extra privileges. - if (op.getUuid()) { - ret = OplogApplicationValidity::kNeedsUseUUID; - } - } - return ret; -} - -class DoTxnCmd : public BasicCommand { -public: - DoTxnCmd() : BasicCommand("doTxn") {} - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } - - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } - - bool supportsReadConcern(const std::string& dbName, - const BSONObj& cmdObj, - repl::ReadConcernLevel level) const { - // Support the read concerns before and after upconversion. - return level == repl::ReadConcernLevel::kLocalReadConcern || - level == repl::ReadConcernLevel::kSnapshotReadConcern; - } - - std::string help() const override { - return "internal (sharding)\n{ doTxn : [ ] , preCondition : [ { ns : ... , q : ... , " - "res : ... } ] }"; - } - - Status checkAuthForOperation(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj) const override { - OplogApplicationValidity validity = validateDoTxnCommand(cmdObj); - return OplogApplicationChecks::checkAuthForCommand(opCtx, dbname, cmdObj, validity); - } - - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - - validateDoTxnCommand(cmdObj); - - boost::optional<DisableDocumentValidation> maybeDisableValidation; - if (shouldBypassDocumentValidationForCommand(cmdObj)) - maybeDisableValidation.emplace(opCtx); - - auto status = OplogApplicationChecks::checkOperationArray(cmdObj.firstElement()); - uassertStatusOK(status); - - // TODO (SERVER-30217): When a write concern is provided to the doTxn command, we - // normally wait on the OpTime of whichever operation successfully completed last. This is - // erroneous, however, if the last operation in the array happens to be a write no-op and - // thus isn’t assigned an OpTime. Let the second to last operation in the doTxn be write - // A, the last operation in doTxn be write B. Let B do a no-op write and let the - // operation that caused B to be a no-op be C. If C has an OpTime after A but before B, - // then we won’t wait for C to be replicated and it could be rolled back, even though B - // was acknowledged. To fix this, we should wait for replication of the node’s last applied - // OpTime if the last write operation was a no-op write. - - auto doTxnStatus = CommandHelpers::appendCommandStatusNoThrow( - result, doTxn(opCtx, dbname, cmdObj, &result)); - - return doTxnStatus; - } -}; - -MONGO_REGISTER_TEST_COMMAND(DoTxnCmd); - -} // namespace -} // namespace mongo diff --git a/src/mongo/db/commands/oplog_application_checks.h b/src/mongo/db/commands/oplog_application_checks.h index 4831950b3d4..3537dc716ab 100644 --- a/src/mongo/db/commands/oplog_application_checks.h +++ b/src/mongo/db/commands/oplog_application_checks.h @@ -50,11 +50,9 @@ class OperationContext; // with a specified UUID, so both the forceUUID and useUUID actions must be authorized. // // kOk means no special conditions apply. -// -// Only kOk and kNeedsUseUUID are valid for 'doTxn'. All are valid for 'applyOps'. enum class OplogApplicationValidity { kOk, kNeedsUseUUID, kNeedsForceAndUseUUID, kNeedsSuperuser }; -// OplogApplicationChecks contains helper functions for checking the applyOps and doTxn commands. +// OplogApplicationChecks contains helper functions for checking the applyOps command. class OplogApplicationChecks { public: /** diff --git a/src/mongo/db/initialize_operation_session_info.cpp b/src/mongo/db/initialize_operation_session_info.cpp index 9be38b08946..0257878e145 100644 --- a/src/mongo/db/initialize_operation_session_info.cpp +++ b/src/mongo/db/initialize_operation_session_info.cpp @@ -143,22 +143,6 @@ OperationSessionInfoFromClient initializeOperationSessionInfo(OperationContext* osi.getStartTransaction().value()); } - // Populate the session info for doTxn command. - if (requestBody.firstElementFieldName() == "doTxn"_sd) { - uassert(ErrorCodes::InvalidOptions, - "doTxn can only be run with a transaction number.", - osi.getTxnNumber()); - uassert(ErrorCodes::OperationNotSupportedInTransaction, - "doTxn can not be run in a transaction", - !osi.getAutocommit()); - // 'autocommit' and 'startTransaction' are populated for 'doTxn' to get the oplog - // entry generation behavior used for multi-document transactions. The 'doTxn' - // command still logically behaves as a commit. - osi.setAutocommit(false); - osi.setStartTransaction(true); - } - - return osi; } diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index eda9c1c6315..871e321363d 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -32,7 +32,6 @@ env.Library( target='oplog', source=[ 'apply_ops.cpp', - 'do_txn.cpp', 'oplog.cpp', 'transaction_oplog_application.cpp', env.Idlc('apply_ops.idl')[0], @@ -116,25 +115,6 @@ env.Library( ], ) -env.CppUnitTest( - target='do_txn_test', - source=[ - 'do_txn_test.cpp', - ], - LIBDEPS=[ - 'oplog', - 'oplog_entry', - 'oplog_interface_local', - 'replmocks', - 'storage_interface_impl', - '$BUILD_DIR/mongo/db/auth/authmocks', - '$BUILD_DIR/mongo/db/s/op_observer_sharding_impl', - '$BUILD_DIR/mongo/db/service_context_d_test_fixture', - '$BUILD_DIR/mongo/db/transaction', - '$BUILD_DIR/mongo/rpc/command_status', - ], -) - env.Library( target='rollback_source_impl', source=[ diff --git a/src/mongo/db/repl/do_txn.cpp b/src/mongo/db/repl/do_txn.cpp deleted file mode 100644 index d9d6f7bf2ce..00000000000 --- a/src/mongo/db/repl/do_txn.cpp +++ /dev/null @@ -1,313 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand - -#include "mongo/platform/basic.h" - -#include "mongo/db/repl/do_txn.h" - -#include "mongo/bson/util/bson_extract.h" -#include "mongo/db/catalog/collection.h" -#include "mongo/db/catalog/collection_catalog.h" -#include "mongo/db/catalog/database.h" -#include "mongo/db/catalog/database_holder.h" -#include "mongo/db/catalog/document_validation.h" -#include "mongo/db/client.h" -#include "mongo/db/concurrency/lock_state.h" -#include "mongo/db/concurrency/write_conflict_exception.h" -#include "mongo/db/curop.h" -#include "mongo/db/db_raii.h" -#include "mongo/db/dbhelpers.h" -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/matcher/matcher.h" -#include "mongo/db/op_observer.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/query/collation/collation_spec.h" -#include "mongo/db/repl/replication_coordinator.h" -#include "mongo/db/service_context.h" -#include "mongo/db/transaction_participant.h" -#include "mongo/db/views/view_catalog.h" -#include "mongo/rpc/get_status_from_command_result.h" -#include "mongo/util/fail_point_service.h" -#include "mongo/util/log.h" - -namespace mongo { - -constexpr StringData DoTxn::kPreconditionFieldName; - -namespace { - -// If enabled, causes loop in _doTxn() to hang after applying current operation. -MONGO_FAIL_POINT_DEFINE(doTxnPauseBetweenOperations); - -/** - * Return true iff the doTxnCmd can be executed in a single WriteUnitOfWork. - */ -bool _areOpsCrudOnly(const BSONObj& doTxnCmd) { - for (const auto& elem : doTxnCmd.firstElement().Obj()) { - const char* opType = elem.Obj().getField("op").valuestrsafe(); - - // All atomic ops have an opType of length 1. - if (opType[0] == '\0' || opType[1] != '\0') - return false; - - // Only consider CRUD operations. - switch (*opType) { - case 'd': - case 'u': - break; - case 'i': - break; - // Fallthrough. - default: - return false; - } - } - - return true; -} - -Status _doTxn(OperationContext* opCtx, - const std::string& dbName, - const BSONObj& doTxnCmd, - BSONObjBuilder* result, - int* numApplied) { - BSONObj ops = doTxnCmd.firstElement().Obj(); - // apply - *numApplied = 0; - int errors = 0; - - BSONObjIterator i(ops); - BSONArrayBuilder ab; - invariant(opCtx->lockState()->inAWriteUnitOfWork()); - - // Apply each op in the given 'doTxn' command object. - while (i.more()) { - BSONElement e = i.next(); - const BSONObj& opObj = e.Obj(); - - boost::optional<NamespaceString> nss = NamespaceString(opObj["ns"].String()); - - // Need to check this here, or OldClientContext may fail an invariant. - if (!nss->isValid()) - return {ErrorCodes::InvalidNamespace, "invalid ns: " + nss->ns()}; - - Status status(ErrorCodes::InternalError, ""); - - AutoGetDb autoDb(opCtx, nss->db(), MODE_IX); - auto db = autoDb.getDb(); - if (!db) { - uasserted(ErrorCodes::NamespaceNotFound, - str::stream() << "cannot apply insert, delete, or update operation on a " - "non-existent namespace " - << nss->ns() << ": " << mongo::redact(opObj)); - } - - if (opObj.hasField("ui")) { - auto uuidStatus = UUID::parse(opObj["ui"]); - uassertStatusOK(uuidStatus.getStatus()); - // If "ui" is present, it overrides "nss" for the collection name. - nss = CollectionCatalog::get(opCtx).lookupNSSByUUID(uuidStatus.getValue()); - uassert(ErrorCodes::NamespaceNotFound, - str::stream() << "cannot find collection uuid " << uuidStatus.getValue(), - nss); - } - Lock::CollectionLock collLock(opCtx, *nss, MODE_IX); - auto collection = db->getCollection(opCtx, *nss); - - // When processing an update on a non-existent collection, applyOperation_inlock() - // returns UpdateOperationFailed on updates and allows the collection to be - // implicitly created on upserts. We detect both cases here and fail early with - // NamespaceNotFound. - // Additionally for inserts, we fail early on non-existent collections. - if (!collection && ViewCatalog::get(db)->lookup(opCtx, nss->ns())) { - uasserted(ErrorCodes::CommandNotSupportedOnView, - str::stream() << "doTxn not supported on a view: " << redact(opObj)); - } - if (!collection) { - uasserted(ErrorCodes::NamespaceNotFound, - str::stream() << "cannot apply operation on a non-existent namespace " - << nss->ns() << " with doTxn: " << redact(opObj)); - } - - // Setting alwaysUpsert to true makes sense only during oplog replay, and doTxn commands - // should not be executed during oplog replay. - const bool alwaysUpsert = false; - status = repl::applyOperation_inlock( - opCtx, db, opObj, alwaysUpsert, repl::OplogApplication::Mode::kApplyOpsCmd); - if (!status.isOK()) - return status; - - ab.append(status.isOK()); - if (!status.isOK()) { - log() << "doTxn error applying: " << status; - errors++; - } - - (*numApplied)++; - - if (MONGO_FAIL_POINT(doTxnPauseBetweenOperations)) { - MONGO_FAIL_POINT_PAUSE_WHILE_SET(doTxnPauseBetweenOperations); - } - } - - result->append("applied", *numApplied); - result->append("results", ab.arr()); - - if (errors != 0) { - return Status(ErrorCodes::UnknownError, "doTxn had one or more errors applying ops"); - } - - return Status::OK(); -} - -bool _hasPrecondition(const BSONObj& doTxnCmd) { - return doTxnCmd[DoTxn::kPreconditionFieldName].type() == Array; -} - -Status _checkPrecondition(OperationContext* opCtx, - const BSONObj& doTxnCmd, - BSONObjBuilder* result) { - // Precondition check must be done in a write unit of work to make sure it's - // sharing the same snapshot as the writes. - invariant(opCtx->lockState()->inAWriteUnitOfWork()); - - invariant(_hasPrecondition(doTxnCmd)); - - for (auto elem : doTxnCmd[DoTxn::kPreconditionFieldName].Obj()) { - auto preCondition = elem.Obj(); - if (preCondition["ns"].type() != BSONType::String) { - return {ErrorCodes::InvalidNamespace, - str::stream() << "ns in preCondition must be a string, but found type: " - << typeName(preCondition["ns"].type())}; - } - const NamespaceString nss(preCondition["ns"].valueStringData()); - if (!nss.isValid()) { - return {ErrorCodes::InvalidNamespace, "invalid ns: " + nss.ns()}; - } - - // Even if snapshot isolation is provided, database catalog still needs locking. - // Only X and IX mode locks are stashed by the WriteUnitOfWork and IS->IX upgrade - // is not supported, so we can not use IS mode here. - AutoGetCollection autoColl(opCtx, nss, MODE_IX); - - Database* database = autoColl.getDb(); - if (!database) { - return {ErrorCodes::NamespaceNotFound, "database in ns does not exist: " + nss.ns()}; - } - Collection* collection = autoColl.getCollection(); - if (!collection) { - return {ErrorCodes::NamespaceNotFound, "collection in ns does not exist: " + nss.ns()}; - } - - BSONObj realres; - auto qrStatus = QueryRequest::fromLegacyQuery(nss, preCondition["q"].Obj(), {}, 0, 0, 0); - if (!qrStatus.isOK()) { - return qrStatus.getStatus(); - } - auto recordId = Helpers::findOne( - opCtx, autoColl.getCollection(), std::move(qrStatus.getValue()), false); - if (!recordId.isNull()) { - realres = collection->docFor(opCtx, recordId).value(); - } - - - // Get collection default collation. - const CollatorInterface* collator = collection->getDefaultCollator(); - - boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, collator)); - Matcher matcher(preCondition["res"].Obj(), std::move(expCtx)); - if (!matcher.matches(realres)) { - result->append("got", realres); - result->append("whatFailed", preCondition); - return {ErrorCodes::BadValue, "preCondition failed"}; - } - } - - return Status::OK(); -} -} // namespace - -Status doTxn(OperationContext* opCtx, - const std::string& dbName, - const BSONObj& doTxnCmd, - BSONObjBuilder* result) { - auto txnParticipant = TransactionParticipant::get(opCtx); - uassert(ErrorCodes::InvalidOptions, "doTxn must be run within a transaction", txnParticipant); - invariant(txnParticipant.inMultiDocumentTransaction()); - invariant(opCtx->getWriteUnitOfWork()); - uassert( - ErrorCodes::InvalidOptions, "doTxn supports only CRUD opts.", _areOpsCrudOnly(doTxnCmd)); - auto hasPrecondition = _hasPrecondition(doTxnCmd); - - - // Acquire global lock in IX mode so that the replication state check will remain valid. - Lock::GlobalLock globalLock(opCtx, MODE_IX); - - auto replCoord = repl::ReplicationCoordinator::get(opCtx); - bool userInitiatedWritesAndNotPrimary = - opCtx->writesAreReplicated() && !replCoord->canAcceptWritesForDatabase(opCtx, dbName); - - if (userInitiatedWritesAndNotPrimary) - return Status(ErrorCodes::NotMaster, - str::stream() << "Not primary while applying ops to database " << dbName); - - int numApplied = 0; - - try { - BSONObjBuilder intermediateResult; - - // The transaction takes place in a global unit of work, so the precondition check - // and the writes will share the same snapshot. - if (hasPrecondition) { - uassertStatusOK(_checkPrecondition(opCtx, doTxnCmd, result)); - } - - numApplied = 0; - uassertStatusOK(_doTxn(opCtx, dbName, doTxnCmd, &intermediateResult, &numApplied)); - txnParticipant.commitUnpreparedTransaction(opCtx); - result->appendElements(intermediateResult.obj()); - } catch (const DBException& ex) { - txnParticipant.abortActiveUnpreparedOrStashPreparedTransaction(opCtx); - BSONArrayBuilder ab; - ++numApplied; - for (int j = 0; j < numApplied; j++) - ab.append(false); - result->append("applied", numApplied); - result->append("code", ex.code()); - result->append("codeName", ErrorCodes::errorString(ex.code())); - result->append("errmsg", ex.what()); - result->append("results", ab.arr()); - return Status(ErrorCodes::UnknownError, ex.what()); - } - - return Status::OK(); -} - -} // namespace mongo diff --git a/src/mongo/db/repl/do_txn.h b/src/mongo/db/repl/do_txn.h deleted file mode 100644 index 7bed2bf3700..00000000000 --- a/src/mongo/db/repl/do_txn.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ -#include "mongo/base/status.h" -#include "mongo/db/repl/oplog.h" - -namespace mongo { -class BSONObj; -class BSONObjBuilder; -class OperationContext; - -class DoTxn { -public: - static constexpr StringData kPreconditionFieldName = "preCondition"_sd; -}; - -/** - * Applies ops contained in 'doTxnCmd' and populates fields in 'result' to be returned to the - * caller. The information contained in 'result' can be returned to the user if called as part - * of the execution of an 'doTxn' command. - * - * The 'oplogApplicationMode' argument determines the semantics of the operations contained within - * the given command object. This function may be called as part of a direct user invocation of the - * 'doTxn' command, or as part of the application of an 'doTxn' oplog operation. In either - * case, the mode can be set to determine how the internal ops are executed. - */ -Status doTxn(OperationContext* opCtx, - const std::string& dbName, - const BSONObj& doTxnCmd, - BSONObjBuilder* result); - -} // namespace mongo diff --git a/src/mongo/db/repl/do_txn_test.cpp b/src/mongo/db/repl/do_txn_test.cpp deleted file mode 100644 index 561579a069c..00000000000 --- a/src/mongo/db/repl/do_txn_test.cpp +++ /dev/null @@ -1,335 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/db/catalog/collection_options.h" -#include "mongo/db/catalog/database_holder.h" -#include "mongo/db/client.h" -#include "mongo/db/op_observer_noop.h" -#include "mongo/db/op_observer_registry.h" -#include "mongo/db/repl/do_txn.h" -#include "mongo/db/repl/oplog_interface_local.h" -#include "mongo/db/repl/repl_client_info.h" -#include "mongo/db/repl/replication_coordinator_mock.h" -#include "mongo/db/repl/storage_interface_impl.h" -#include "mongo/db/s/op_observer_sharding_impl.h" -#include "mongo/db/service_context_d_test_fixture.h" -#include "mongo/db/session_catalog_mongod.h" -#include "mongo/db/transaction_participant.h" -#include "mongo/logger/logger.h" -#include "mongo/rpc/get_status_from_command_result.h" -#include "mongo/stdx/memory.h" - -namespace mongo { -namespace repl { -namespace { - -boost::optional<OplogEntry> onAllTransactionCommit(OperationContext* opCtx) { - OplogInterfaceLocal oplogInterface(opCtx); - auto oplogIter = oplogInterface.makeIterator(); - auto opEntry = unittest::assertGet(oplogIter->next()); - return unittest::assertGet(OplogEntry::parse(opEntry.first)); -} - -/** - * Mock OpObserver that tracks doTxn commit events. - */ -class OpObserverMock : public OpObserverNoop { -public: - /** - * Called by doTxn() when ops are ready to commit. - */ - void onUnpreparedTransactionCommit(OperationContext* opCtx, - const std::vector<repl::ReplOperation>& statements) override; - - - /** - * Called by doTxn() when ops are ready to commit. - */ - void onPreparedTransactionCommit( - OperationContext* opCtx, - OplogSlot commitOplogEntryOpTime, - Timestamp commitTimestamp, - const std::vector<repl::ReplOperation>& statements) noexcept override; - - // If present, holds the applyOps oplog entry written out by the ObObserverImpl - // onPreparedTransactionCommit or onUnpreparedTransactionCommit. - boost::optional<OplogEntry> applyOpsOplogEntry; -}; - -void OpObserverMock::onUnpreparedTransactionCommit( - OperationContext* opCtx, const std::vector<repl::ReplOperation>& statements) { - - applyOpsOplogEntry = onAllTransactionCommit(opCtx); -} - -void OpObserverMock::onPreparedTransactionCommit( - OperationContext* opCtx, - OplogSlot commitOplogEntryOpTime, - Timestamp commitTimestamp, - const std::vector<repl::ReplOperation>& statements) noexcept { - applyOpsOplogEntry = onAllTransactionCommit(opCtx); -} - -/** - * Test fixture for doTxn(). - */ -class DoTxnTest : public ServiceContextMongoDTest { -private: - void setUp() override; - void tearDown() override; - -protected: - OperationContext* opCtx() { - return _opCtx.get(); - } - - void checkTxnTable() { - auto result = _storage->findById( - opCtx(), - NamespaceString::kSessionTransactionsTableNamespace, - BSON(SessionTxnRecord::kSessionIdFieldName << opCtx()->getLogicalSessionId()->toBSON()) - .firstElement()); - if (!_opObserver->applyOpsOplogEntry) { - ASSERT_NOT_OK(result); - return; - } - auto txnRecord = SessionTxnRecord::parse(IDLParserErrorContext("parse txn record for test"), - unittest::assertGet(result)); - - ASSERT(opCtx()->getTxnNumber()); - ASSERT_EQ(*opCtx()->getTxnNumber(), txnRecord.getTxnNum()); - ASSERT_EQ(_opObserver->applyOpsOplogEntry->getOpTime(), txnRecord.getLastWriteOpTime()); - ASSERT(_opObserver->applyOpsOplogEntry->getWallClockTime()); - ASSERT_EQ(*_opObserver->applyOpsOplogEntry->getWallClockTime(), - txnRecord.getLastWriteDate()); - } - - OpObserverMock* _opObserver = nullptr; - std::unique_ptr<StorageInterface> _storage; - ServiceContext::UniqueOperationContext _opCtx; - boost::optional<MongoDOperationContextSession> _ocs; -}; - -void DoTxnTest::setUp() { - // Set up mongod. - ServiceContextMongoDTest::setUp(); - - const auto service = getServiceContext(); - _opCtx = cc().makeOperationContext(); - - // Set up ReplicationCoordinator and create oplog. - ReplicationCoordinator::set(service, stdx::make_unique<ReplicationCoordinatorMock>(service)); - setOplogCollectionName(service); - createOplog(_opCtx.get()); - - // Ensure that we are primary. - auto replCoord = ReplicationCoordinator::get(_opCtx.get()); - ASSERT_OK(replCoord->setFollowerMode(MemberState::RS_PRIMARY)); - - // onStepUp() relies on the storage interface to create the config.transactions table. - repl::StorageInterface::set(service, std::make_unique<StorageInterfaceImpl>()); - - // Set up session catalog - MongoDSessionCatalog::onStepUp(_opCtx.get()); - - // Need the OpObserverImpl in the registry in order for doTxn to work. - OpObserverRegistry* opObserverRegistry = - dynamic_cast<OpObserverRegistry*>(service->getOpObserver()); - opObserverRegistry->addObserver(stdx::make_unique<OpObserverShardingImpl>()); - - // Use OpObserverMock to track applyOps calls generated by doTxn(). - auto opObserver = stdx::make_unique<OpObserverMock>(); - _opObserver = opObserver.get(); - opObserverRegistry->addObserver(std::move(opObserver)); - - // This test uses StorageInterface to create collections and inspect documents inside - // collections. - _storage = stdx::make_unique<StorageInterfaceImpl>(); - - // Set up the transaction and session. - _opCtx->setLogicalSessionId(makeLogicalSessionIdForTest()); - _opCtx->setTxnNumber(0); // TxnNumber can always be 0 because we have a new session. - _ocs.emplace(_opCtx.get()); - - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, true); - txnParticipant.unstashTransactionResources(opCtx(), "doTxn"); -} - -void DoTxnTest::tearDown() { - _ocs = boost::none; - _opCtx = nullptr; - _storage = {}; - _opObserver = nullptr; - - // Reset default log level in case it was changed. - logger::globalLogDomain()->setMinimumLoggedSeverity(logger::LogComponent::kReplication, - logger::LogSeverity::Debug(0)); - - ServiceContextMongoDTest::tearDown(); -} - -/** - * Fixes up result document returned by doTxn and converts to Status. - */ -Status getStatusFromDoTxnResult(const BSONObj& result) { - if (result["ok"]) { - return getStatusFromCommandResult(result); - } - - BSONObjBuilder builder; - builder.appendElements(result); - auto code = result.getIntField("code"); - builder.appendIntOrLL("ok", code == 0); - auto newResult = builder.obj(); - return getStatusFromCommandResult(newResult); -} - -BSONObj makeInsertOperation(const NamespaceString& nss, - const OptionalCollectionUUID& uuid, - const BSONObj& documentToInsert) { - return uuid ? BSON("op" - << "i" - << "ns" << nss.ns() << "o" << documentToInsert << "ui" << *uuid) - : BSON("op" - << "i" - << "ns" << nss.ns() << "o" << documentToInsert); -} - -/** - * Creates an doTxn command object with a single insert operation. - */ -BSONObj makeDoTxnWithInsertOperation(const NamespaceString& nss, - const OptionalCollectionUUID& uuid, - const BSONObj& documentToInsert) { - auto insertOp = makeInsertOperation(nss, uuid, documentToInsert); - return BSON("doTxn" << BSON_ARRAY(insertOp)); -} - -/** - * Creates an applyOps command object with a single insert operation. - */ -BSONObj makeApplyOpsWithInsertOperation(const NamespaceString& nss, - const OptionalCollectionUUID& uuid, - const BSONObj& documentToInsert) { - auto insertOp = makeInsertOperation(nss, uuid, documentToInsert); - return BSON("applyOps" << BSON_ARRAY(insertOp)); -} - -TEST_F(DoTxnTest, AtomicDoTxnInsertIntoNonexistentCollectionReturnsNamespaceNotFoundInResult) { - NamespaceString nss("test.t"); - auto documentToInsert = BSON("_id" << 0); - auto cmdObj = makeDoTxnWithInsertOperation(nss, boost::none, documentToInsert); - BSONObjBuilder resultBuilder; - ASSERT_EQUALS(ErrorCodes::UnknownError, doTxn(opCtx(), "test", cmdObj, &resultBuilder)); - auto result = resultBuilder.obj(); - auto status = getStatusFromDoTxnResult(result); - ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, status); - checkTxnTable(); -} - -TEST_F(DoTxnTest, AtomicDoTxnInsertWithUuidIntoCollectionWithUuid) { - NamespaceString nss("test.t"); - - auto uuid = UUID::gen(); - - CollectionOptions collectionOptions; - collectionOptions.uuid = uuid; - ASSERT_OK(_storage->createCollection(opCtx(), nss, collectionOptions)); - - auto documentToInsert = BSON("_id" << 0); - auto cmdObj = makeDoTxnWithInsertOperation(nss, uuid, documentToInsert); - auto expectedCmdObj = makeApplyOpsWithInsertOperation(nss, uuid, documentToInsert); - BSONObjBuilder resultBuilder; - ASSERT_OK(doTxn(opCtx(), "test", cmdObj, &resultBuilder)); - ASSERT_EQ(expectedCmdObj.woCompare(_opObserver->applyOpsOplogEntry->getObject(), - BSONObj(), - BSONObj::ComparisonRules::kIgnoreFieldOrder | - BSONObj::ComparisonRules::kConsiderFieldName), - 0) - << "expected: " << expectedCmdObj - << " got: " << _opObserver->applyOpsOplogEntry->getObject(); - checkTxnTable(); -} - -TEST_F(DoTxnTest, AtomicDoTxnInsertWithUuidIntoCollectionWithOtherUuid) { - NamespaceString nss("test.t"); - - auto doTxnUuid = UUID::gen(); - - // Collection has a different UUID. - CollectionOptions collectionOptions; - collectionOptions.uuid = UUID::gen(); - ASSERT_NOT_EQUALS(doTxnUuid, *collectionOptions.uuid); - ASSERT_OK(_storage->createCollection(opCtx(), nss, collectionOptions)); - - // The doTxn returns a NamespaceNotFound error because of the failed UUID lookup - // even though a collection exists with the same namespace as the insert operation. - auto documentToInsert = BSON("_id" << 0); - auto cmdObj = makeDoTxnWithInsertOperation(nss, doTxnUuid, documentToInsert); - BSONObjBuilder resultBuilder; - ASSERT_EQUALS(ErrorCodes::UnknownError, doTxn(opCtx(), "test", cmdObj, &resultBuilder)); - auto result = resultBuilder.obj(); - auto status = getStatusFromDoTxnResult(result); - ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, status); - checkTxnTable(); -} - -TEST_F(DoTxnTest, AtomicDoTxnInsertWithoutUuidIntoCollectionWithUuid) { - NamespaceString nss("test.t"); - - auto uuid = UUID::gen(); - - CollectionOptions collectionOptions; - collectionOptions.uuid = uuid; - ASSERT_OK(_storage->createCollection(opCtx(), nss, collectionOptions)); - - auto documentToInsert = BSON("_id" << 0); - auto cmdObj = makeDoTxnWithInsertOperation(nss, boost::none, documentToInsert); - BSONObjBuilder resultBuilder; - ASSERT_OK(doTxn(opCtx(), "test", cmdObj, &resultBuilder)); - - // Insert operation provided by caller did not contain collection uuid but doTxn() should add - // the uuid to the oplog entry. - auto expectedCmdObj = makeApplyOpsWithInsertOperation(nss, uuid, documentToInsert); - ASSERT_EQ(expectedCmdObj.woCompare(_opObserver->applyOpsOplogEntry->getObject(), - BSONObj(), - BSONObj::ComparisonRules::kIgnoreFieldOrder | - BSONObj::ComparisonRules::kConsiderFieldName), - 0) - << "expected: " << expectedCmdObj - << " got: " << _opObserver->applyOpsOplogEntry->getObject(); - checkTxnTable(); -} - -} // namespace -} // namespace repl -} // namespace mongo diff --git a/src/mongo/db/transaction_validation.cpp b/src/mongo/db/transaction_validation.cpp index d69b935e5bb..a2af2228612 100644 --- a/src/mongo/db/transaction_validation.cpp +++ b/src/mongo/db/transaction_validation.cpp @@ -61,7 +61,7 @@ void validateWriteConcernForTransaction(const WriteConcernOptions& wcResult, Str "writeConcern is not allowed within a multi-statement transaction", wcResult.usedDefault || cmdName == "commitTransaction" || cmdName == "coordinateCommitTransaction" || cmdName == "abortTransaction" || - cmdName == "prepareTransaction" || cmdName == "doTxn"); + cmdName == "prepareTransaction"); } bool shouldCommandSkipSessionCheckout(StringData cmdName) { |