mongo::Status mongo::convertToCapped(OperationContext* opCtx, const NamespaceString& collectionName, long long size) { StringData dbname = collectionName.db(); StringData shortSource = collectionName.coll(); AutoGetDb autoDb(opCtx, collectionName.db(), MODE_X); bool userInitiatedWritesAndNotPrimary = opCtx->writesAreReplicated() && !repl::ReplicationCoordinator::get(opCtx)->canAcceptWritesFor(opCtx, collectionName); if (userInitiatedWritesAndNotPrimary) { return Status(ErrorCodes::NotMaster, str::stream() << "Not primary while converting " << collectionName.ns() << " to a capped collection"); } Database* const db = autoDb.getDb(); if (!db) { return Status(ErrorCodes::NamespaceNotFound, str::stream() << "database " << dbname << " not found"); } BackgroundOperation::assertNoBgOpInProgForDb(dbname); // Generate a temporary collection name that will not collide with any existing collections. auto tmpNameResult = db->makeUniqueCollectionNamespace(opCtx, "tmp%%%%%.convertToCapped." + shortSource); if (!tmpNameResult.isOK()) { return tmpNameResult.getStatus().withContext( str::stream() << "Cannot generate temporary collection namespace to convert " << collectionName.ns() << " to a capped collection"); } const auto& longTmpName = tmpNameResult.getValue(); const auto shortTmpName = longTmpName.coll().toString(); { Status status = cloneCollectionAsCapped(opCtx, db, shortSource.toString(), shortTmpName, size, true); if (!status.isOK()) return status; } RenameCollectionOptions options; options.dropTarget = true; options.stayTemp = false; return renameCollection(opCtx, longTmpName, collectionName, options); }
void run() { NamespaceString source("unittests.rollback_rename_collection_src"); NamespaceString target("unittests.rollback_rename_collection_dest"); const ServiceContext::UniqueOperationContext opCtxPtr = cc().makeOperationContext(); OperationContext& opCtx = *opCtxPtr; dropDatabase(&opCtx, source); dropDatabase(&opCtx, target); Lock::GlobalWrite globalWriteLock(&opCtx); OldClientContext ctx(&opCtx, source.ns()); { WriteUnitOfWork uow(&opCtx); ASSERT(!collectionExists(&ctx, source.ns())); ASSERT(!collectionExists(&ctx, target.ns())); auto options = capped ? BSON("capped" << true << "size" << 1000) : BSONObj(); ASSERT_OK(userCreateNS(&opCtx, ctx.db(), source.ns(), options, CollectionOptions::parseForCommand, defaultIndexes)); uow.commit(); } ASSERT(collectionExists(&ctx, source.ns())); ASSERT(!collectionExists(&ctx, target.ns())); // END OF SETUP / START OF TEST { WriteUnitOfWork uow(&opCtx); ASSERT_OK(renameCollection(&opCtx, source, target)); ASSERT(!collectionExists(&ctx, source.ns())); ASSERT(collectionExists(&ctx, target.ns())); if (!rollback) { uow.commit(); } } if (rollback) { ASSERT(collectionExists(&ctx, source.ns())); ASSERT(!collectionExists(&ctx, target.ns())); } else { ASSERT(!collectionExists(&ctx, source.ns())); ASSERT(collectionExists(&ctx, target.ns())); } }
Status createCollectionForApplyOps(OperationContext* opCtx, const std::string& dbName, const BSONElement& ui, const BSONObj& cmdObj, const BSONObj& idIndex) { invariant(opCtx->lockState()->isDbLockedForMode(dbName, MODE_X)); auto db = dbHolder().get(opCtx, dbName); const NamespaceString newCollName(Command::parseNsCollectionRequired(dbName, cmdObj)); auto newCmd = cmdObj; // If a UUID is given, see if we need to rename a collection out of the way, and whether the // collection already exists under a different name. If so, rename it into place. As this is // done during replay of the oplog, the operations do not need to be atomic, just idempotent. // We need to do the renaming part in a separate transaction, as we cannot transactionally // create a database on MMAPv1, which could result in createCollection failing if the database // does not yet exist. if (ui.ok()) { // Return an optional, indicating whether we need to early return (if the collection already // exists, or in case of an error). using Result = boost::optional<Status>; auto result = writeConflictRetry(opCtx, "createCollectionForApplyOps", newCollName.ns(), [&] { WriteUnitOfWork wunit(opCtx); // Options need the field to be named "uuid", so parse/recreate. auto uuid = uassertStatusOK(UUID::parse(ui)); uassert(ErrorCodes::InvalidUUID, "Invalid UUID in applyOps create command: " + uuid.toString(), uuid.isRFC4122v4()); auto& catalog = UUIDCatalog::get(opCtx); auto currentName = catalog.lookupNSSByUUID(uuid); OpObserver* opObserver = getGlobalServiceContext()->getOpObserver(); if (currentName == newCollName) return Result(Status::OK()); // In the case of oplog replay, a future command may have created or renamed a // collection with that same name. In that case, renaming this future collection to // a random temporary name is correct: once all entries are replayed no temporary // names will remain. On MMAPv1 the rename can result in index names that are too // long. However this should only happen for initial sync and "resync collection" // for rollback, so we can let the error propagate resulting in an abort and restart // of the initial sync or result in rollback to fassert, requiring a resync of that // node. const bool stayTemp = true; if (auto futureColl = db ? db->getCollection(opCtx, newCollName) : nullptr) { auto tmpNameResult = db->makeUniqueCollectionNamespace(opCtx, "tmp%%%%%"); if (!tmpNameResult.isOK()) { return Result(Status(tmpNameResult.getStatus().code(), str::stream() << "Cannot generate temporary " "collection namespace for applyOps " "create command: collection: " << newCollName.ns() << ". error: " << tmpNameResult.getStatus().reason())); } const auto& tmpName = tmpNameResult.getValue(); Status status = db->renameCollection(opCtx, newCollName.ns(), tmpName.ns(), stayTemp); if (!status.isOK()) return Result(status); opObserver->onRenameCollection(opCtx, newCollName, tmpName, futureColl->uuid(), /*dropTarget*/ false, /*dropTargetUUID*/ {}, stayTemp); } // If the collection with the requested UUID already exists, but with a different // name, just rename it to 'newCollName'. if (catalog.lookupCollectionByUUID(uuid)) { Status status = db->renameCollection(opCtx, currentName.ns(), newCollName.ns(), stayTemp); if (!status.isOK()) return Result(status); opObserver->onRenameCollection(opCtx, currentName, newCollName, uuid, /*dropTarget*/ false, /*dropTargetUUID*/ {}, stayTemp); wunit.commit(); return Result(Status::OK()); } // A new collection with the specific UUID must be created, so add the UUID to the // creation options. Regular user collection creation commands cannot do this. auto uuidObj = uuid.toBSON(); newCmd = cmdObj.addField(uuidObj.firstElement()); wunit.commit(); return Result(boost::none); }); if (result) { return *result; } } return createCollection( opCtx, newCollName, newCmd, idIndex, CollectionOptions::parseForStorage); }