ServiceContextMongoDTest::ServiceContextMongoDTest(std::string engine, RepairAction repair) : _tempDir("service_context_d_test_fixture") { _stashedStorageParams.engine = std::exchange(storageGlobalParams.engine, std::move(engine)); _stashedStorageParams.engineSetByUser = std::exchange(storageGlobalParams.engineSetByUser, true); _stashedStorageParams.repair = std::exchange(storageGlobalParams.repair, (repair == RepairAction::kRepair)); auto const serviceContext = getServiceContext(); serviceContext->setServiceEntryPoint(std::make_unique<ServiceEntryPointMongod>(serviceContext)); auto logicalClock = std::make_unique<LogicalClock>(serviceContext); LogicalClock::set(serviceContext, std::move(logicalClock)); // Set up a fake no-op PeriodicRunner. No jobs will ever get run, which is // desired behavior for unit tests unrelated to background jobs. auto runner = std::make_unique<MockPeriodicRunnerImpl>(); serviceContext->setPeriodicRunner(std::move(runner)); storageGlobalParams.dbpath = _tempDir.path(); initializeStorageEngine(serviceContext, StorageEngineInitFlags::kNone); // Set up UUID Catalog observer. This is necessary because the Collection destructor contains an // invariant to ensure the UUID corresponding to that Collection object is no longer associated // with that Collection object in the UUIDCatalog. UUIDs may be registered in the UUIDCatalog // directly in certain code paths, but they can only be removed from the UUIDCatalog via a // UUIDCatalogObserver. It is therefore necessary to install the observer to ensure the // invariant in the Collection destructor is not triggered. auto observerRegistry = checked_cast<OpObserverRegistry*>(serviceContext->getOpObserver()); observerRegistry->addObserver(std::make_unique<UUIDCatalogObserver>()); }
void RollbackTest::setUp() { _storageInterface = new StorageInterfaceRollback(); auto serviceContext = getServiceContext(); auto consistencyMarkers = stdx::make_unique<ReplicationConsistencyMarkersMock>(); auto recovery = stdx::make_unique<ReplicationRecoveryImpl>(_storageInterface, consistencyMarkers.get()); _replicationProcess = stdx::make_unique<ReplicationProcess>( _storageInterface, std::move(consistencyMarkers), std::move(recovery)); _dropPendingCollectionReaper = new DropPendingCollectionReaper(_storageInterface); DropPendingCollectionReaper::set( serviceContext, std::unique_ptr<DropPendingCollectionReaper>(_dropPendingCollectionReaper)); StorageInterface::set(serviceContext, std::unique_ptr<StorageInterface>(_storageInterface)); _coordinator = new ReplicationCoordinatorRollbackMock(serviceContext); ReplicationCoordinator::set(serviceContext, std::unique_ptr<ReplicationCoordinator>(_coordinator)); setOplogCollectionName(serviceContext); _opCtx = makeOperationContext(); _replicationProcess->getConsistencyMarkers()->clearAppliedThrough(_opCtx.get(), {}); _replicationProcess->getConsistencyMarkers()->setMinValid(_opCtx.get(), OpTime{}); _replicationProcess->initializeRollbackID(_opCtx.get()).transitional_ignore(); // Increase rollback log component verbosity for unit tests. mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( logger::LogComponent::kReplicationRollback, logger::LogSeverity::Debug(2)); auto observerRegistry = checked_cast<OpObserverRegistry*>(serviceContext->getOpObserver()); observerRegistry->addObserver(std::make_unique<RollbackTestOpObserver>()); }
Status createCollectionForApplyOps(OperationContext* opCtx, const std::string& dbName, const BSONElement& ui, const BSONObj& cmdObj, const BSONObj& idIndex) { invariant(opCtx->lockState()->isDbLockedForMode(dbName, MODE_X)); const NamespaceString newCollName(CommandHelpers::parseNsCollectionRequired(dbName, cmdObj)); auto newCmd = cmdObj; auto databaseHolder = DatabaseHolder::get(opCtx); auto* const db = databaseHolder->getDb(opCtx, dbName); // 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); const auto currentName = catalog.lookupNSSByUUID(uuid); auto serviceContext = opCtx->getServiceContext(); auto opObserver = serviceContext->getOpObserver(); if (currentName == newCollName) return Result(Status::OK()); if (currentName.isDropPendingNamespace()) { log() << "CMD: create " << newCollName << " - existing collection with conflicting UUID " << uuid << " is in a drop-pending state: " << currentName; return Result(Status(ErrorCodes::NamespaceExists, str::stream() << "existing collection " << currentName.toString() << " with conflicting UUID " << uuid.toString() << " is in a drop-pending state.")); } // 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%%%%%.create"); if (!tmpNameResult.isOK()) { return Result(tmpNameResult.getStatus().withContext( str::stream() << "Cannot generate temporary " "collection namespace for applyOps " "create command: collection: " << newCollName)); } const auto& tmpName = tmpNameResult.getValue(); // It is ok to log this because this doesn't happen very frequently. log() << "CMD: create " << newCollName << " - renaming existing collection with conflicting UUID " << uuid << " to temporary collection " << tmpName; Status status = db->renameCollection(opCtx, newCollName, tmpName, stayTemp); if (!status.isOK()) return Result(status); opObserver->onRenameCollection(opCtx, newCollName, tmpName, futureColl->uuid(), /*dropTargetUUID*/ {}, /*numRecords*/ 0U, stayTemp); } // If the collection with the requested UUID already exists, but with a different // name, just rename it to 'newCollName'. if (catalog.lookupCollectionByUUID(uuid)) { uassert(40655, str::stream() << "Invalid name " << newCollName << " for UUID " << uuid, currentName.db() == newCollName.db()); Status status = db->renameCollection(opCtx, currentName, newCollName, stayTemp); if (!status.isOK()) return Result(status); opObserver->onRenameCollection(opCtx, currentName, newCollName, uuid, /*dropTargetUUID*/ {}, /*numRecords*/ 0U, 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); }