diff options
Diffstat (limited to 'src')
73 files changed, 334 insertions, 3498 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index ef1e749de69..53d0a9067e9 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -2144,6 +2144,7 @@ env.Library( '$BUILD_DIR/mongo/db/storage/ephemeral_for_test/storage_ephemeral_for_test', '$BUILD_DIR/mongo/db/storage/storage_control', '$BUILD_DIR/mongo/db/storage/storage_options', + '$BUILD_DIR/mongo/db/storage/wiredtiger/storage_wiredtiger', '$BUILD_DIR/mongo/util/clock_source_mock', 'index_builds_coordinator_mongod', 'service_context_d', diff --git a/src/mongo/db/catalog/catalog_test_fixture.h b/src/mongo/db/catalog/catalog_test_fixture.h index fc30f046d6f..b71933fa317 100644 --- a/src/mongo/db/catalog/catalog_test_fixture.h +++ b/src/mongo/db/catalog/catalog_test_fixture.h @@ -47,8 +47,8 @@ public: * Allows selection of storage engine to back the unit test, defaulting to ephemeralForTest * when not specified. */ - CatalogTestFixture() : CatalogTestFixture("ephemeralForTest") {} - explicit CatalogTestFixture(std::string engine) : ServiceContextMongoDTest(std::move(engine)) {} + explicit CatalogTestFixture(Options options = {}) + : ServiceContextMongoDTest(std::move(options)) {} OperationContext* operationContext(); repl::StorageInterface* storageInterface(); diff --git a/src/mongo/db/catalog/collection_test.cpp b/src/mongo/db/catalog/collection_test.cpp index 759a01fcd52..fd73c68bad8 100644 --- a/src/mongo/db/catalog/collection_test.cpp +++ b/src/mongo/db/catalog/collection_test.cpp @@ -56,6 +56,9 @@ using namespace mongo; class CollectionTest : public CatalogTestFixture { protected: + // TODO (SERVER-65194): Use wiredTiger. + CollectionTest() : CatalogTestFixture(Options{}.engine("ephemeralForTest")) {} + void makeCapped(NamespaceString nss, long long cappedSize = 8192); void makeTimeseries(NamespaceString nss); void makeCollectionForMultikey(NamespaceString nss, StringData indexName); @@ -639,6 +642,7 @@ TEST_F(CatalogTestFixture, CappedVisibilityEmptyInitialState) { RecordStore* rs = coll->getRecordStore(); auto doInsert = [&](OperationContext* opCtx) -> RecordId { + Lock::GlobalLock globalLock{opCtx, MODE_IX}; std::string data = "data"; return uassertStatusOK(rs->insertRecord(opCtx, data.c_str(), data.size(), Timestamp())); }; @@ -699,6 +703,7 @@ TEST_F(CatalogTestFixture, CappedVisibilityNonEmptyInitialState) { RecordStore* rs = coll->getRecordStore(); auto doInsert = [&](OperationContext* opCtx) -> RecordId { + Lock::GlobalLock globalLock{opCtx, MODE_IX}; std::string data = "data"; return uassertStatusOK(rs->insertRecord(opCtx, data.c_str(), data.size(), Timestamp())); }; @@ -768,7 +773,7 @@ TEST_F(CatalogTestFixture, CappedVisibilityNonEmptyInitialState) { ASSERT_ID_EQ(rs->getCursor(longLivedOpCtx.get())->seekExact(otherId), otherId); } -TEST_F(CatalogTestFixture, CappedCursorRollover) { +TEST_F(CollectionTest, CappedCursorRollover) { NamespaceString nss("test.t"); CollectionOptions options; options.capped = true; diff --git a/src/mongo/db/catalog/collection_validation_test.cpp b/src/mongo/db/catalog/collection_validation_test.cpp index 28db9ec0f06..4ffff4f653b 100644 --- a/src/mongo/db/catalog/collection_validation_test.cpp +++ b/src/mongo/db/catalog/collection_validation_test.cpp @@ -46,20 +46,7 @@ namespace { const NamespaceString kNss = NamespaceString("test.t"); -/** - * Test fixture for collection validation with the ephemeralForTest storage engine. - * Validation with {background:true} is not supported by the ephemeralForTest storage engine. - */ class CollectionValidationTest : public CatalogTestFixture { -public: - CollectionValidationTest() : CollectionValidationTest("ephemeralForTest") {} - -protected: - /** - * Allow inheriting classes to select a storage engine with which to run unit tests. - */ - explicit CollectionValidationTest(std::string engine) : CatalogTestFixture(std::move(engine)) {} - private: void setUp() override { CatalogTestFixture::setUp(); @@ -72,24 +59,6 @@ private: }; /** - * Test fixture for testing background collection validation on the wiredTiger engine, which is - * currently the only storage engine that supports background collection validation. - * - * Collection kNss will be created for each unit test, courtesy of inheritance from - * CollectionValidationTest. - */ -class BackgroundCollectionValidationTest : public CollectionValidationTest { -public: - /** - * Sets up the wiredTiger storage engine that supports data checkpointing. - * - * Background validation runs on a checkpoint, and therefore only on storage engines that - * support checkpoints. - */ - BackgroundCollectionValidationTest() : CollectionValidationTest("wiredTiger") {} -}; - -/** * Calls validate on collection kNss with both kValidateFull and kValidateNormal validation levels * and verifies the results. * @@ -242,7 +211,7 @@ TEST_F(CollectionValidationTest, ValidateEmpty) { /*numInvalidDocuments*/ 0, /*numErrors*/ 0); } -TEST_F(BackgroundCollectionValidationTest, BackgroundValidateEmpty) { +TEST_F(CollectionValidationTest, BackgroundValidateEmpty) { // Running on the WT storage engine. backgroundValidate(operationContext(), /*valid*/ true, @@ -261,7 +230,7 @@ TEST_F(CollectionValidationTest, Validate) { /*numInvalidDocuments*/ 0, /*numErrors*/ 0); } -TEST_F(BackgroundCollectionValidationTest, BackgroundValidate) { +TEST_F(CollectionValidationTest, BackgroundValidate) { auto opCtx = operationContext(); backgroundValidate(opCtx, /*valid*/ true, @@ -280,7 +249,7 @@ TEST_F(CollectionValidationTest, ValidateError) { /*numInvalidDocuments*/ 1, /*numErrors*/ 1); } -TEST_F(BackgroundCollectionValidationTest, BackgroundValidateError) { +TEST_F(CollectionValidationTest, BackgroundValidateError) { auto opCtx = operationContext(); backgroundValidate(opCtx, /*valid*/ false, @@ -314,7 +283,7 @@ void waitUntilValidateFailpointHasBeenReached() { ASSERT(CollectionValidation::getIsValidationPausedForTest()); } -TEST_F(BackgroundCollectionValidationTest, BackgroundValidateRunsConcurrentlyWithWrites) { +TEST_F(CollectionValidationTest, BackgroundValidateRunsConcurrentlyWithWrites) { auto opCtx = operationContext(); auto serviceContext = opCtx->getServiceContext(); @@ -378,7 +347,7 @@ KeyString::Value makeKeyStringWithoutRecordId(const KeyString::Value& keyStringW } // Verify calling validate() on a collection with old (pre-4.2) keys in a WT unique index. -TEST_F(BackgroundCollectionValidationTest, ValidateOldUniqueIndexKeyWarning) { +TEST_F(CollectionValidationTest, ValidateOldUniqueIndexKeyWarning) { auto opCtx = operationContext(); { diff --git a/src/mongo/db/catalog/database_test.cpp b/src/mongo/db/catalog/database_test.cpp index cd6f0a97401..dd72c38588c 100644 --- a/src/mongo/db/catalog/database_test.cpp +++ b/src/mongo/db/catalog/database_test.cpp @@ -68,6 +68,8 @@ private: void tearDown() override; protected: + explicit DatabaseTest(Options options = {}) : ServiceContextMongoDTest(std::move(options)) {} + ServiceContext::UniqueOperationContext _opCtx; NamespaceString _nss; }; @@ -213,30 +215,6 @@ TEST_F(DatabaseTest, DropCollectionDropsCollectionButDoesNotLogOperationIfWrites ASSERT_EQUALS(repl::OpTime(), dropOpTime); } -TEST_F(DatabaseTest, - DropCollectionRenamesCollectionToPendingDropNamespaceAndLogsOperationIfWritesAreReplicated) { - ASSERT_TRUE(_opCtx->writesAreReplicated()); - ASSERT_FALSE( - repl::ReplicationCoordinator::get(_opCtx.get())->isOplogDisabledFor(_opCtx.get(), _nss)); - - _testDropCollection(_opCtx.get(), _nss, true); - - // Drop optime is non-null because an op was written to the oplog. - auto dropOpTime = repl::ReplClientInfo::forClient(&cc()).getLastOp(); - ASSERT_GREATER_THAN(dropOpTime, repl::OpTime()); - - // Replicated collection is renamed with a special drop-pending names in the <db>.system.drop.* - // namespace. - auto dpns = _nss.makeDropPendingNamespace(dropOpTime); - ASSERT_TRUE(AutoGetCollectionForRead(_opCtx.get(), dpns).getCollection()); - - // Reaper should have the drop optime of the collection. - auto reaperEarliestDropOpTime = - repl::DropPendingCollectionReaper::get(_opCtx.get())->getEarliestDropOpTime(); - ASSERT_TRUE(reaperEarliestDropOpTime); - ASSERT_EQUALS(dropOpTime, *reaperEarliestDropOpTime); -} - TEST_F(DatabaseTest, DropCollectionRejectsProvidedDropOpTimeIfWritesAreReplicated) { ASSERT_TRUE(_opCtx->writesAreReplicated()); ASSERT_FALSE( @@ -259,32 +237,6 @@ TEST_F(DatabaseTest, DropCollectionRejectsProvidedDropOpTimeIfWritesAreReplicate ASSERT_EQUALS(ErrorCodes::BadValue, db->dropCollection(opCtx, nss, dropOpTime)); } -TEST_F( - DatabaseTest, - DropCollectionRenamesCollectionToPendingDropNamespaceUsingProvidedDropOpTimeButDoesNotLogOperation) { - repl::UnreplicatedWritesBlock uwb(_opCtx.get()); - ASSERT_FALSE(_opCtx->writesAreReplicated()); - ASSERT_TRUE( - repl::ReplicationCoordinator::get(_opCtx.get())->isOplogDisabledFor(_opCtx.get(), _nss)); - - repl::OpTime dropOpTime(Timestamp(Seconds(100), 0), 1LL); - _testDropCollection(_opCtx.get(), _nss, true, dropOpTime); - - // Last optime in repl client is null because we did not write to the oplog. - ASSERT_EQUALS(repl::OpTime(), repl::ReplClientInfo::forClient(&cc()).getLastOp()); - - // Replicated collection is renamed with a special drop-pending names in the <db>.system.drop.* - // namespace. - auto dpns = _nss.makeDropPendingNamespace(dropOpTime); - ASSERT_TRUE(mongo::AutoGetCollectionForRead(_opCtx.get(), dpns).getCollection()); - - // Reaper should have the drop optime of the collection. - auto reaperEarliestDropOpTime = - repl::DropPendingCollectionReaper::get(_opCtx.get())->getEarliestDropOpTime(); - ASSERT_TRUE(reaperEarliestDropOpTime); - ASSERT_EQUALS(dropOpTime, *reaperEarliestDropOpTime); -} - void _testDropCollectionThrowsExceptionIfThereAreIndexesInProgress(OperationContext* opCtx, const NamespaceString& nss) { writeConflictRetry(opCtx, "testDropCollectionWithIndexesInProgress", nss.ns(), [opCtx, nss] { diff --git a/src/mongo/db/catalog/multi_index_block_test.cpp b/src/mongo/db/catalog/multi_index_block_test.cpp index f867de098ed..2fb9caf7371 100644 --- a/src/mongo/db/catalog/multi_index_block_test.cpp +++ b/src/mongo/db/catalog/multi_index_block_test.cpp @@ -79,9 +79,6 @@ void MultiIndexBlockTest::setUp() { } void MultiIndexBlockTest::tearDown() { - auto service = getServiceContext(); - repl::ReplicationCoordinator::set(service, {}); - _indexer = {}; CatalogTestFixture::tearDown(); diff --git a/src/mongo/db/catalog/rename_collection_test.cpp b/src/mongo/db/catalog/rename_collection_test.cpp index 5273e1c7ddf..215069fd9b7 100644 --- a/src/mongo/db/catalog/rename_collection_test.cpp +++ b/src/mongo/db/catalog/rename_collection_test.cpp @@ -314,6 +314,9 @@ private: void tearDown() override; protected: + explicit RenameCollectionTest(Options options = {}) + : ServiceContextMongoDTest(std::move(options)) {} + ServiceContext::UniqueOperationContext _opCtx; repl::ReplicationCoordinatorMock* _replCoord = nullptr; OpObserverMock* _opObserver = nullptr; @@ -765,31 +768,6 @@ TEST_F(RenameCollectionTest, renameCollection(_opCtx.get(), _sourceNss, _targetNss, options)); } -TEST_F(RenameCollectionTest, RenameCollectionMakesTargetCollectionDropPendingIfDropTargetIsTrue) { - _createCollectionWithUUID(_opCtx.get(), _sourceNss); - auto targetUUID = _createCollectionWithUUID(_opCtx.get(), _targetNss); - RenameCollectionOptions options; - options.dropTarget = true; - ASSERT_OK(renameCollection(_opCtx.get(), _sourceNss, _targetNss, options)); - ASSERT_FALSE(_collectionExists(_opCtx.get(), _sourceNss)) - << "source collection " << _sourceNss << " still exists after successful rename"; - ASSERT_TRUE(_collectionExists(_opCtx.get(), _targetNss)) - << "target collection " << _targetNss << " missing after successful rename"; - - ASSERT_TRUE(_opObserver->onRenameCollectionCalled); - ASSERT(_opObserver->onRenameCollectionDropTarget); - ASSERT_EQUALS(targetUUID, *_opObserver->onRenameCollectionDropTarget); - - auto renameOpTime = _opObserver->renameOpTime; - ASSERT_GREATER_THAN(renameOpTime, repl::OpTime()); - - // Confirm that the target collection has been renamed to a drop-pending collection. - auto dpns = _targetNss.makeDropPendingNamespace(renameOpTime); - ASSERT_TRUE(_collectionExists(_opCtx.get(), dpns)) - << "target collection " << _targetNss - << " not renamed to drop-pending collection after successful rename"; -} - TEST_F(RenameCollectionTest, RenameCollectionOverridesDropTargetIfTargetCollectionIsMissingAndDropTargetIsTrue) { _createCollectionWithUUID(_opCtx.get(), _sourceNss); @@ -817,30 +795,6 @@ TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsRejectsRenameOpTimeIfWri renameCollectionForApplyOps(_opCtx.get(), dbName, boost::none, cmd, renameOpTime)); } -TEST_F(RenameCollectionTest, - RenameCollectionForApplyOpsMakesTargetCollectionDropPendingIfDropTargetIsTrue) { - repl::UnreplicatedWritesBlock uwb(_opCtx.get()); - ASSERT_FALSE(_opCtx->writesAreReplicated()); - - // OpObserver::preRenameCollection() must return a null OpTime when writes are not replicated. - _opObserver->renameOpTime = {}; - - _createCollection(_opCtx.get(), _sourceNss); - auto dropTargetUUID = _createCollectionWithUUID(_opCtx.get(), _targetNss); - auto dbName = _sourceNss.db().toString(); - auto cmd = BSON("renameCollection" << _sourceNss.ns() << "to" << _targetNss.ns() << "dropTarget" - << dropTargetUUID); - - repl::OpTime renameOpTime = {Timestamp(Seconds(200), 1U), 1LL}; - ASSERT_OK(renameCollectionForApplyOps(_opCtx.get(), dbName, boost::none, cmd, renameOpTime)); - - // Confirm that the target collection has been renamed to a drop-pending collection. - auto dpns = _targetNss.makeDropPendingNamespace(renameOpTime); - ASSERT_TRUE(_collectionExists(_opCtx.get(), dpns)) - << "target collection " << _targetNss - << " not renamed to drop-pending collection after successful rename for applyOps"; -} - DEATH_TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsTriggersFatalAssertionIfLogOpReturnsValidOpTime, "unexpected renameCollection oplog entry written to the oplog") { @@ -894,69 +848,6 @@ TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsDropTargetByUUIDEvenIfSo ASSERT_FALSE(_collectionExists(_opCtx.get(), dropTargetNss)); } -TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsDropTargetEvenIfSourceIsDropPending) { - repl::OpTime dropOpTime(Timestamp(Seconds(100), 0), 1LL); - auto dropPendingNss = _sourceNss.makeDropPendingNamespace(dropOpTime); - - auto dropTargetUUID = _createCollectionWithUUID(_opCtx.get(), _targetNss); - auto uuid = _createCollectionWithUUID(_opCtx.get(), dropPendingNss); - auto cmd = - BSON("renameCollection" << dropPendingNss.ns() << "to" << _targetNss.ns() << "dropTarget" - << "true"); - - repl::UnreplicatedWritesBlock uwb(_opCtx.get()); - repl::OpTime renameOpTime = {Timestamp(Seconds(200), 1U), 1LL}; - ASSERT_OK(renameCollectionForApplyOps( - _opCtx.get(), dropPendingNss.db().toString(), uuid, cmd, renameOpTime)); - - // Source collections stays in drop-pending state. - ASSERT_TRUE(_collectionExists(_opCtx.get(), dropPendingNss)); - ASSERT_FALSE(_collectionExists(_opCtx.get(), _targetNss)); - ASSERT_EQUALS(_targetNss.makeDropPendingNamespace(renameOpTime), - _getCollectionNssFromUUID(_opCtx.get(), dropTargetUUID)); -} - -TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsDropTargetByUUIDEvenIfSourceIsDropPending) { - repl::OpTime dropOpTime(Timestamp(Seconds(100), 0), 1LL); - auto dropPendingNss = _sourceNss.makeDropPendingNamespace(dropOpTime); - auto dropTargetNss = NamespaceString("test.bar2"); - - _createCollectionWithUUID(_opCtx.get(), _targetNss); - - auto dropTargetUUID = _createCollectionWithUUID(_opCtx.get(), dropTargetNss); - auto uuid = _createCollectionWithUUID(_opCtx.get(), dropPendingNss); - auto cmd = BSON("renameCollection" << dropPendingNss.ns() << "to" << _targetNss.ns() - << "dropTarget" << dropTargetUUID); - - repl::UnreplicatedWritesBlock uwb(_opCtx.get()); - repl::OpTime renameOpTime = {Timestamp(Seconds(200), 1U), 1LL}; - ASSERT_OK(renameCollectionForApplyOps( - _opCtx.get(), dropPendingNss.db().toString(), uuid, cmd, renameOpTime)); - - // Source collections stays in drop-pending state. - ASSERT_TRUE(_collectionExists(_opCtx.get(), dropPendingNss)); - ASSERT_FALSE(_collectionExists(_opCtx.get(), dropTargetNss)); - ASSERT_EQUALS(dropTargetNss.makeDropPendingNamespace(renameOpTime), - _getCollectionNssFromUUID(_opCtx.get(), dropTargetUUID)); - ASSERT_TRUE(_collectionExists(_opCtx.get(), _targetNss)); -} - -TEST_F(RenameCollectionTest, RenameCollectionForApplyOpsDropTargetByUUIDEvenIfSourceEqualsTarget) { - auto dropTargetUUID = _createCollectionWithUUID(_opCtx.get(), _targetNss); - auto uuid = _createCollectionWithUUID(_opCtx.get(), _sourceNss); - auto cmd = BSON("renameCollection" << _sourceNss.ns() << "to" << _sourceNss.ns() << "dropTarget" - << dropTargetUUID); - repl::UnreplicatedWritesBlock uwb(_opCtx.get()); - repl::OpTime renameOpTime = {Timestamp(Seconds(200), 1U), 1LL}; - auto dpns = _targetNss.makeDropPendingNamespace(renameOpTime); - ASSERT_OK(renameCollectionForApplyOps( - _opCtx.get(), _sourceNss.db().toString(), uuid, cmd, renameOpTime)); - - ASSERT_TRUE(_collectionExists(_opCtx.get(), _sourceNss)); - ASSERT_TRUE(_collectionExists(_opCtx.get(), dpns)); - ASSERT_FALSE(_collectionExists(_opCtx.get(), _targetNss)); -} - void _testRenameCollectionStayTemp(OperationContext* opCtx, const NamespaceString& sourceNss, const NamespaceString& targetNss, diff --git a/src/mongo/db/catalog/throttle_cursor_test.cpp b/src/mongo/db/catalog/throttle_cursor_test.cpp index 2f272e3eeef..2710e627c3f 100644 --- a/src/mongo/db/catalog/throttle_cursor_test.cpp +++ b/src/mongo/db/catalog/throttle_cursor_test.cpp @@ -55,6 +55,10 @@ private: void setUp() override; void tearDown() override; +protected: + // TODO (SERVER-65221): Use wiredTiger. + ThrottleCursorTest() : CatalogTestFixture(Options{}.engine("ephemeralForTest")) {} + public: void setMaxMbPerSec(int maxMbPerSec); diff --git a/src/mongo/db/catalog/validate_state_test.cpp b/src/mongo/db/catalog/validate_state_test.cpp index 45868b0ad87..e8e4bfbbbd9 100644 --- a/src/mongo/db/catalog/validate_state_test.cpp +++ b/src/mongo/db/catalog/validate_state_test.cpp @@ -52,8 +52,6 @@ const NamespaceString kNss = NamespaceString("fooDB.fooColl"); class ValidateStateTest : public CatalogTestFixture { public: - ValidateStateTest() : CatalogTestFixture("wiredTiger") {} - /** * Create collection 'nss'. It will possess a default _id index. */ diff --git a/src/mongo/db/commands/mr_test.cpp b/src/mongo/db/commands/mr_test.cpp index a4c8b4da699..745d883c8e1 100644 --- a/src/mongo/db/commands/mr_test.cpp +++ b/src/mongo/db/commands/mr_test.cpp @@ -520,6 +520,7 @@ TEST_F(MapReduceCommandTest, PrimaryStepDownPreventsTemporaryCollectionDrops) { // Temporary collections should still be present because the server will not accept writes after // stepping down. + _opCtx->recoveryUnit()->setTimestampReadSource(RecoveryUnit::ReadSource::kLastApplied); for (const auto& tempNss : _opObserver->tempNamespaces) { ASSERT_OK(_storage.getCollectionCount(_opCtx.get(), tempNss).getStatus()) << "missing mapReduce temporary collection: " << tempNss; diff --git a/src/mongo/db/db_raii_multi_collection_test.cpp b/src/mongo/db/db_raii_multi_collection_test.cpp index fa47f44c3df..1ad777c076a 100644 --- a/src/mongo/db/db_raii_multi_collection_test.cpp +++ b/src/mongo/db/db_raii_multi_collection_test.cpp @@ -45,8 +45,6 @@ namespace { class AutoGetCollectionMultiTest : public CatalogTestFixture { public: - AutoGetCollectionMultiTest() : CatalogTestFixture("wiredTiger") {} - typedef std::pair<ServiceContext::UniqueClient, ServiceContext::UniqueOperationContext> ClientAndCtx; diff --git a/src/mongo/db/db_raii_test.cpp b/src/mongo/db/db_raii_test.cpp index 076d8cbbe13..3c578a29370 100644 --- a/src/mongo/db/db_raii_test.cpp +++ b/src/mongo/db/db_raii_test.cpp @@ -51,7 +51,6 @@ namespace { class DBRAIITestFixture : public CatalogTestFixture { public: - DBRAIITestFixture() : CatalogTestFixture("wiredTiger") {} typedef std::pair<ServiceContext::UniqueClient, ServiceContext::UniqueOperationContext> ClientAndCtx; diff --git a/src/mongo/db/exec/queued_data_stage_test.cpp b/src/mongo/db/exec/queued_data_stage_test.cpp index fadb53807b4..37f895ebb6b 100644 --- a/src/mongo/db/exec/queued_data_stage_test.cpp +++ b/src/mongo/db/exec/queued_data_stage_test.cpp @@ -53,8 +53,7 @@ const static NamespaceString kNss("db.dummy"); class QueuedDataStageTest : public ServiceContextMongoDTest { public: - QueuedDataStageTest() { - getServiceContext()->setFastClockSource(std::make_unique<ClockSourceMock>()); + QueuedDataStageTest() : ServiceContextMongoDTest(Options{}.useMockClock(true)) { _opCtx = makeOperationContext(); } diff --git a/src/mongo/db/exec/sbe/sbe_plan_stage_test.cpp b/src/mongo/db/exec/sbe/sbe_plan_stage_test.cpp index 04dbe8bc454..77b1997bdee 100644 --- a/src/mongo/db/exec/sbe/sbe_plan_stage_test.cpp +++ b/src/mongo/db/exec/sbe/sbe_plan_stage_test.cpp @@ -66,6 +66,7 @@ PlanStageTestFixture::generateVirtualScanMulti(int32_t numSlots, const BSONArray } void PlanStageTestFixture::prepareTree(CompileCtx* ctx, PlanStage* root) { + Lock::GlobalLock globalLock{opCtx(), MODE_IS}; root->attachToOperationContext(opCtx()); root->prepare(*ctx); root->open(false); diff --git a/src/mongo/db/exec/sort_test.cpp b/src/mongo/db/exec/sort_test.cpp index 6c9bc2d9379..7266665304a 100644 --- a/src/mongo/db/exec/sort_test.cpp +++ b/src/mongo/db/exec/sort_test.cpp @@ -55,8 +55,7 @@ class SortStageDefaultTest : public ServiceContextMongoDTest { public: static constexpr uint64_t kMaxMemoryUsageBytes = 1024u * 1024u; - SortStageDefaultTest() { - getServiceContext()->setFastClockSource(std::make_unique<ClockSourceMock>()); + SortStageDefaultTest() : ServiceContextMongoDTest(Options{}.useMockClock(true)) { _opCtx = makeOperationContext(); CollatorFactoryInterface::set(getServiceContext(), std::make_unique<CollatorFactoryMock>()); } diff --git a/src/mongo/db/key_generator_update_test.cpp b/src/mongo/db/key_generator_update_test.cpp index cb1191e5621..70464ca01ff 100644 --- a/src/mongo/db/key_generator_update_test.cpp +++ b/src/mongo/db/key_generator_update_test.cpp @@ -49,11 +49,11 @@ namespace { class KeyGeneratorUpdateTest : public ConfigServerTestFixture { protected: + KeyGeneratorUpdateTest() : ConfigServerTestFixture(Options{}.useMockClock(true)) {} + void setUp() override { ConfigServerTestFixture::setUp(); - auto clockSource = std::make_unique<ClockSourceMock>(); - operationContext()->getServiceContext()->setFastClockSource(std::move(clockSource)); _catalogClient = std::make_unique<KeysCollectionClientSharded>( Grid::get(operationContext())->catalogClient()); } diff --git a/src/mongo/db/keys_collection_manager_sharding_test.cpp b/src/mongo/db/keys_collection_manager_sharding_test.cpp index ba22b2c0481..2e4b175296e 100644 --- a/src/mongo/db/keys_collection_manager_sharding_test.cpp +++ b/src/mongo/db/keys_collection_manager_sharding_test.cpp @@ -51,15 +51,11 @@ public: } protected: + KeysManagerShardedTest() : ConfigServerTestFixture(Options{}.useMockClock(true)) {} + void setUp() override { ConfigServerTestFixture::setUp(); - auto clockSource = std::make_unique<ClockSourceMock>(); - // Timestamps of "0 seconds" are not allowed, so we must advance our clock mock to the first - // real second. - clockSource->advance(Seconds(1)); - - operationContext()->getServiceContext()->setFastClockSource(std::move(clockSource)); auto catalogClient = std::make_unique<KeysCollectionClientSharded>( Grid::get(operationContext())->catalogClient()); _keyManager = @@ -77,12 +73,26 @@ private: }; TEST_F(KeysManagerShardedTest, GetKeyForValidationTimesOutIfRefresherIsNotRunning) { - operationContext()->setDeadlineAfterNowBy(Microseconds(250 * 1000), - ErrorCodes::ExceededTimeLimit); + Milliseconds maxTime{25}; + operationContext()->setDeadlineAfterNowBy(maxTime, ErrorCodes::ExceededTimeLimit); + + AtomicWord<bool> done{false}; + stdx::thread t{[&] { + ASSERT_THROWS(keyManager()->getKeysForValidation( + operationContext(), 1, LogicalTime(Timestamp(100, 0))), + DBException); + done.store(true); + }}; + + int numTimesAdvanced = 0; + while (!done.load()) { + ClockSourceMock clock; + clock.advance(maxTime); + ++numTimesAdvanced; + } + t.join(); - ASSERT_THROWS( - keyManager()->getKeysForValidation(operationContext(), 1, LogicalTime(Timestamp(100, 0))), - DBException); + ASSERT_GT(numTimesAdvanced, 0); } TEST_F(KeysManagerShardedTest, GetKeyForValidationErrorsIfKeyDoesntExist) { @@ -371,6 +381,8 @@ TEST_F(KeysManagerShardedTest, HasSeenKeysIsFalseUntilKeysAreFound) { class KeysManagerDirectTest : public ConfigServerTestFixture { protected: + KeysManagerDirectTest() : ConfigServerTestFixture(Options{}.useMockClock(true)) {} + const UUID kMigrationId1 = UUID::gen(); const UUID kMigrationId2 = UUID::gen(); @@ -381,12 +393,6 @@ protected: void setUp() override { ConfigServerTestFixture::setUp(); - auto clockSource = std::make_unique<ClockSourceMock>(); - // Timestamps of "0 seconds" are not allowed, so we must advance our clock mock to the first - // real second. - clockSource->advance(Seconds(1)); - - operationContext()->getServiceContext()->setFastClockSource(std::move(clockSource)); _keyManager = std::make_unique<KeysCollectionManager>( "dummy", std::make_unique<KeysCollectionClientDirect>(), Seconds(1)); } diff --git a/src/mongo/db/logical_time_validator_test.cpp b/src/mongo/db/logical_time_validator_test.cpp index cad15fb328a..96a942ccda5 100644 --- a/src/mongo/db/logical_time_validator_test.cpp +++ b/src/mongo/db/logical_time_validator_test.cpp @@ -54,11 +54,11 @@ public: } protected: + LogicalTimeValidatorTest() : ConfigServerTestFixture(Options{}.useMockClock(true)) {} + void setUp() override { ConfigServerTestFixture::setUp(); - auto clockSource = std::make_unique<ClockSourceMock>(); - operationContext()->getServiceContext()->setFastClockSource(std::move(clockSource)); auto catalogClient = std::make_unique<KeysCollectionClientSharded>( Grid::get(operationContext())->catalogClient()); diff --git a/src/mongo/db/op_observer_impl_test.cpp b/src/mongo/db/op_observer_impl_test.cpp index a4bff71f00b..db67426378f 100644 --- a/src/mongo/db/op_observer_impl_test.cpp +++ b/src/mongo/db/op_observer_impl_test.cpp @@ -192,6 +192,8 @@ public: } protected: + explicit OpObserverTest(Options options = {}) : ServiceContextMongoDTest(std::move(options)) {} + // Assert that the oplog has the expected number of entries, and return them std::vector<BSONObj> getNOplogEntries(OperationContext* opCtx, int numExpected) { std::vector<BSONObj> allOplogEntries; @@ -847,6 +849,9 @@ public: } protected: + // TODO (SERVER-65219): Use wiredTiger. + OpObserverTxnParticipantTest() : OpObserverTest(Options{}.engine("ephemeralForTest")) {} + Session* session() { return OperationContextSession::get(opCtx()); } diff --git a/src/mongo/db/query/classic_stage_builder_test.cpp b/src/mongo/db/query/classic_stage_builder_test.cpp index 407cd67e426..cb4efe466dc 100644 --- a/src/mongo/db/query/classic_stage_builder_test.cpp +++ b/src/mongo/db/query/classic_stage_builder_test.cpp @@ -44,8 +44,9 @@ const static NamespaceString kNss("db.dummy"); class ClassicStageBuilderTest : public ServiceContextMongoDTest { public: + ClassicStageBuilderTest() : ServiceContextMongoDTest(Options{}.useMockClock(true)) {} + void setUp() { - getServiceContext()->setFastClockSource(std::make_unique<ClockSourceMock>()); _opCtx = makeOperationContext(); _workingSet = std::make_unique<WorkingSet>(); } diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index 2f419fcdf60..07b2e725e95 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -1643,7 +1643,6 @@ if wiredtiger: 'roll_back_local_operations_test.cpp', 'rollback_checker_test.cpp', 'rollback_impl_test.cpp', - 'rs_rollback_test.cpp', 'scatter_gather_test.cpp', 'speculative_majority_read_info_test.cpp', 'split_horizon_test.cpp', diff --git a/src/mongo/db/repl/idempotency_test_fixture.h b/src/mongo/db/repl/idempotency_test_fixture.h index 3c79dc60a0e..93766f8d51e 100644 --- a/src/mongo/db/repl/idempotency_test_fixture.h +++ b/src/mongo/db/repl/idempotency_test_fixture.h @@ -89,7 +89,7 @@ StringBuilder& operator<<(StringBuilder& sb, const CollectionState& state); class IdempotencyTest : public OplogApplierImplTest { public: - IdempotencyTest() : OplogApplierImplTest("wiredTiger") { + IdempotencyTest() { globalFailPointRegistry() .find("doUntimestampedWritesForIdempotencyTests") ->setMode(FailPoint::alwaysOn); diff --git a/src/mongo/db/repl/mock_repl_coord_server_fixture.cpp b/src/mongo/db/repl/mock_repl_coord_server_fixture.cpp index bc8704c8622..9a76ffc13b4 100644 --- a/src/mongo/db/repl/mock_repl_coord_server_fixture.cpp +++ b/src/mongo/db/repl/mock_repl_coord_server_fixture.cpp @@ -47,6 +47,7 @@ #include "mongo/db/repl/storage_interface_mock.h" #include "mongo/db/service_context.h" #include "mongo/db/service_context_d_test_fixture.h" +#include "mongo/db/storage/snapshot_manager.h" namespace mongo { @@ -90,6 +91,14 @@ void MockReplCoordServerFixture::setUp() { repl::DropPendingCollectionReaper::set( service, std::make_unique<repl::DropPendingCollectionReaper>(repl::StorageInterface::get(service))); + + // Set a committed snapshot so that we can perform majority reads. + WriteUnitOfWork wuow{_opCtx.get()}; + if (auto snapshotManager = + _opCtx->getServiceContext()->getStorageEngine()->getSnapshotManager()) { + snapshotManager->setCommittedSnapshot(repl::getNextOpTime(_opCtx.get()).getTimestamp()); + } + wuow.commit(); } void MockReplCoordServerFixture::insertOplogEntry(const repl::OplogEntry& entry) { diff --git a/src/mongo/db/repl/mock_repl_coord_server_fixture.h b/src/mongo/db/repl/mock_repl_coord_server_fixture.h index 7f52f4a3f21..e0859800f63 100644 --- a/src/mongo/db/repl/mock_repl_coord_server_fixture.h +++ b/src/mongo/db/repl/mock_repl_coord_server_fixture.h @@ -57,6 +57,10 @@ public: OperationContext* opCtx(); +protected: + explicit MockReplCoordServerFixture(Options options = {}) + : ServiceContextMongoDTest(std::move(options)) {} + private: ServiceContext::UniqueOperationContext _opCtx; repl::StorageInterfaceMock* _storageInterface; diff --git a/src/mongo/db/repl/oplog_applier_impl_test_fixture.h b/src/mongo/db/repl/oplog_applier_impl_test_fixture.h index 7013192fba9..e1b188232ae 100644 --- a/src/mongo/db/repl/oplog_applier_impl_test_fixture.h +++ b/src/mongo/db/repl/oplog_applier_impl_test_fixture.h @@ -195,12 +195,11 @@ public: }; class OplogApplierImplTest : public ServiceContextMongoDTest { -public: - OplogApplierImplTest(){}; - OplogApplierImplTest(std::string storageEngine) - : ServiceContextMongoDTest(std::move(storageEngine)){}; - protected: + // TODO (SERVER-65297): Use wiredTiger. + explicit OplogApplierImplTest() + : ServiceContextMongoDTest(Options{}.engine("ephemeralForTest")) {} + void _testApplyOplogEntryOrGroupedInsertsCrudOperation(ErrorCodes::Error expectedError, const OplogEntry& op, bool expectedApplyOpCalled); diff --git a/src/mongo/db/repl/primary_only_service_test_fixture.h b/src/mongo/db/repl/primary_only_service_test_fixture.h index 906cbd15297..c72a2fe85e1 100644 --- a/src/mongo/db/repl/primary_only_service_test_fixture.h +++ b/src/mongo/db/repl/primary_only_service_test_fixture.h @@ -54,6 +54,9 @@ public: void tearDown() override; protected: + explicit PrimaryOnlyServiceMongoDTest(Options options = {}) + : ServiceContextMongoDTest(std::move(options)) {} + void startup(OperationContext* opCtx); void shutdown(); diff --git a/src/mongo/db/repl/replication_consistency_markers_impl_test.cpp b/src/mongo/db/repl/replication_consistency_markers_impl_test.cpp index 465289de440..3d58ef476e4 100644 --- a/src/mongo/db/repl/replication_consistency_markers_impl_test.cpp +++ b/src/mongo/db/repl/replication_consistency_markers_impl_test.cpp @@ -203,7 +203,7 @@ TEST_F(ReplicationConsistencyMarkersTest, ReplicationConsistencyMarkers) { OpTime startOpTime({Seconds(123), 0}, 1LL); OpTime endOpTime({Seconds(456), 0}, 1LL); consistencyMarkers.setAppliedThrough(opCtx, startOpTime); - consistencyMarkers.setMinValid(opCtx, endOpTime); + consistencyMarkers.setMinValid(opCtx, endOpTime, true /* alwaysAllowUntimestampedWrite */); consistencyMarkers.setOplogTruncateAfterPoint(opCtx, endOpTime.getTimestamp()); ASSERT_EQ(consistencyMarkers.getAppliedThrough(opCtx), startOpTime); diff --git a/src/mongo/db/repl/replication_coordinator_impl_heartbeat_v1_test.cpp b/src/mongo/db/repl/replication_coordinator_impl_heartbeat_v1_test.cpp index 6cb6e55609c..d403a7d259f 100644 --- a/src/mongo/db/repl/replication_coordinator_impl_heartbeat_v1_test.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl_heartbeat_v1_test.cpp @@ -80,6 +80,8 @@ TEST(ReplSetHeartbeatArgs, AcceptsUnknownField) { class ReplCoordHBV1Test : public ReplCoordTest { protected: + explicit ReplCoordHBV1Test() : ReplCoordTest(Options{}.useMockClock(true)) {} + void assertMemberState(MemberState expected, std::string msg = ""); ReplSetHeartbeatResponse receiveHeartbeatFrom( const ReplSetConfig& rsConfig, diff --git a/src/mongo/db/repl/replication_coordinator_mock.cpp b/src/mongo/db/repl/replication_coordinator_mock.cpp index d396c2c429d..22118c874aa 100644 --- a/src/mongo/db/repl/replication_coordinator_mock.cpp +++ b/src/mongo/db/repl/replication_coordinator_mock.cpp @@ -39,6 +39,7 @@ #include "mongo/db/repl/read_concern_args.h" #include "mongo/db/repl/sync_source_resolver.h" #include "mongo/db/repl/tenant_migration_decoration.h" +#include "mongo/db/storage/snapshot_manager.h" #include "mongo/db/write_concern_options.h" #include "mongo/util/assert_util.h" @@ -234,12 +235,23 @@ void ReplicationCoordinatorMock::setMyHeartbeatMessage(const std::string& msg) { // TODO } +void ReplicationCoordinatorMock::_setMyLastAppliedOpTimeAndWallTime( + const OpTimeAndWallTime& opTimeAndWallTime) { + _myLastAppliedOpTime = opTimeAndWallTime.opTime; + _myLastAppliedWallTime = opTimeAndWallTime.wallTime; + + if (auto storageEngine = _service->getStorageEngine()) { + if (auto snapshotManager = storageEngine->getSnapshotManager()) { + snapshotManager->setCommittedSnapshot(opTimeAndWallTime.opTime.getTimestamp()); + } + } +} + void ReplicationCoordinatorMock::setMyLastAppliedOpTimeAndWallTime( const OpTimeAndWallTime& opTimeAndWallTime) { stdx::lock_guard<Mutex> lk(_mutex); - _myLastAppliedOpTime = opTimeAndWallTime.opTime; - _myLastAppliedWallTime = opTimeAndWallTime.wallTime; + _setMyLastAppliedOpTimeAndWallTime(opTimeAndWallTime); } void ReplicationCoordinatorMock::setMyLastDurableOpTimeAndWallTime( @@ -255,8 +267,7 @@ void ReplicationCoordinatorMock::setMyLastAppliedOpTimeAndWallTimeForward( stdx::lock_guard<Mutex> lk(_mutex); if (opTimeAndWallTime.opTime > _myLastAppliedOpTime) { - _myLastAppliedOpTime = opTimeAndWallTime.opTime; - _myLastAppliedWallTime = opTimeAndWallTime.wallTime; + _setMyLastAppliedOpTimeAndWallTime(opTimeAndWallTime); } } diff --git a/src/mongo/db/repl/replication_coordinator_mock.h b/src/mongo/db/repl/replication_coordinator_mock.h index a77d5ad4982..8e48a9bd8d7 100644 --- a/src/mongo/db/repl/replication_coordinator_mock.h +++ b/src/mongo/db/repl/replication_coordinator_mock.h @@ -420,6 +420,8 @@ public: virtual WriteConcernTagChanges* getWriteConcernTagChanges() override; private: + void _setMyLastAppliedOpTimeAndWallTime(const OpTimeAndWallTime& opTimeAndWallTime); + ServiceContext* const _service; ReplSettings _settings; StorageInterface* _storage = nullptr; diff --git a/src/mongo/db/repl/replication_coordinator_test_fixture.cpp b/src/mongo/db/repl/replication_coordinator_test_fixture.cpp index ced8e4499dc..d1bcdcc92ab 100644 --- a/src/mongo/db/repl/replication_coordinator_test_fixture.cpp +++ b/src/mongo/db/repl/replication_coordinator_test_fixture.cpp @@ -81,7 +81,7 @@ BSONObj ReplCoordTest::addProtocolVersion(const BSONObj& configDoc, int protocol return builder.obj(); } -ReplCoordTest::ReplCoordTest() { +ReplCoordTest::ReplCoordTest(Options options) : ServiceContextMongoDTest(std::move(options)) { _settings.setReplSetString("mySet/node1:12345,node2:54321"); } @@ -167,8 +167,6 @@ void ReplCoordTest::init() { replicationProcess, _storageInterface, seed); - service->setFastClockSource(std::make_unique<ClockSourceMock>()); - service->setPreciseClockSource(std::make_unique<ClockSourceMock>()); } void ReplCoordTest::init(const ReplSettings& settings) { diff --git a/src/mongo/db/repl/replication_coordinator_test_fixture.h b/src/mongo/db/repl/replication_coordinator_test_fixture.h index 79845ffcfa1..ad1e52a1af3 100644 --- a/src/mongo/db/repl/replication_coordinator_test_fixture.h +++ b/src/mongo/db/repl/replication_coordinator_test_fixture.h @@ -92,7 +92,7 @@ public: } protected: - ReplCoordTest(); + explicit ReplCoordTest(Options options = Options{}.useMockClock(true)); virtual ~ReplCoordTest(); /** diff --git a/src/mongo/db/repl/replication_recovery_test.cpp b/src/mongo/db/repl/replication_recovery_test.cpp index 7b32092e747..2107eaff181 100644 --- a/src/mongo/db/repl/replication_recovery_test.cpp +++ b/src/mongo/db/repl/replication_recovery_test.cpp @@ -136,6 +136,9 @@ public: class ReplicationRecoveryTest : public ServiceContextMongoDTest { protected: + // TODO (SERVER-65304): Use wiredTiger. + ReplicationRecoveryTest() : ServiceContextMongoDTest(Options{}.engine("ephemeralForTest")) {} + OperationContext* getOperationContext() { return _opCtx.get(); } diff --git a/src/mongo/db/repl/rollback_impl_test.cpp b/src/mongo/db/repl/rollback_impl_test.cpp index afab6b658a7..af9e2d85dad 100644 --- a/src/mongo/db/repl/rollback_impl_test.cpp +++ b/src/mongo/db/repl/rollback_impl_test.cpp @@ -152,6 +152,9 @@ private: friend class RollbackImplTest::Listener; protected: + // TODO (SERVER-65305): Use wiredTiger. + RollbackImplTest() : RollbackTest(Options{}.engine("ephemeralForTest")) {} + /** * Creates a new mock collection with name 'nss' via the StorageInterface and associates 'uuid' * with the new collection in the CollectionCatalog. There must not already exist a collection diff --git a/src/mongo/db/repl/rollback_test_fixture.cpp b/src/mongo/db/repl/rollback_test_fixture.cpp index b4a064a7bce..eaf3e1152f4 100644 --- a/src/mongo/db/repl/rollback_test_fixture.cpp +++ b/src/mongo/db/repl/rollback_test_fixture.cpp @@ -296,75 +296,5 @@ StatusWith<BSONObj> RollbackSourceMock::getCollectionInfoByUUID(const std::strin const UUID& uuid) const { return BSON("options" << BSONObj() << "info" << BSON("uuid" << uuid)); } - -RollbackResyncsCollectionOptionsTest::RollbackSourceWithCollectionOptions:: - RollbackSourceWithCollectionOptions(std::unique_ptr<OplogInterface> oplog, - BSONObj collOptionsObj) - : RollbackSourceMock(std::move(oplog)), collOptionsObj(collOptionsObj) {} - -StatusWith<BSONObj> -RollbackResyncsCollectionOptionsTest::RollbackSourceWithCollectionOptions::getCollectionInfoByUUID( - const std::string& db, const UUID& uuid) const { - return BSON("options" << collOptionsObj << "info" << BSON("uuid" << uuid)); -} - -void RollbackResyncsCollectionOptionsTest::resyncCollectionOptionsTest( - CollectionOptions localCollOptions, BSONObj remoteCollOptionsObj) { - resyncCollectionOptionsTest(localCollOptions, - remoteCollOptionsObj, - BSON("collMod" - << "coll" - << "validationLevel" - << "strict"), - "coll"); -} -void RollbackResyncsCollectionOptionsTest::resyncCollectionOptionsTest( - CollectionOptions localCollOptions, - BSONObj remoteCollOptionsObj, - BSONObj collModCmd, - std::string collName) { - createOplog(_opCtx.get()); - - auto dbName = "test"; - auto nss = NamespaceString(dbName, collName); - - auto coll = _createCollection(_opCtx.get(), nss.toString(), localCollOptions); - - auto commonOpUuid = unittest::assertGet(UUID::parse("f005ba11-cafe-bead-f00d-123456789abc")); - auto commonOpBson = BSON("ts" << Timestamp(1, 1) << "t" << 1LL << "op" - << "n" - << "o" << BSONObj() << "ns" - << "rollback_test.test" - << "wall" << Date_t() << "ui" << commonOpUuid); - - auto commonOperation = std::make_pair(commonOpBson, RecordId(1)); - - auto collectionModificationOperation = - makeCommandOp(Timestamp(Seconds(2), 0), coll->uuid(), nss.toString(), collModCmd, 2); - - RollbackSourceWithCollectionOptions rollbackSource( - std::unique_ptr<OplogInterface>(new OplogInterfaceMock({commonOperation})), - remoteCollOptionsObj); - - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({collectionModificationOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - // Make sure the collection options are correct. - AutoGetCollectionForReadCommand autoColl(_opCtx.get(), NamespaceString(nss.toString())); - auto collAfterRollbackOptions = autoColl->getCollectionOptions(); - - BSONObjBuilder expectedOptionsBob; - if (localCollOptions.uuid) { - localCollOptions.uuid.get().appendToBuilder(&expectedOptionsBob, "uuid"); - } - expectedOptionsBob.appendElements(remoteCollOptionsObj); - - ASSERT_BSONOBJ_EQ(expectedOptionsBob.obj(), collAfterRollbackOptions.toBSON()); -} } // namespace repl } // namespace mongo diff --git a/src/mongo/db/repl/rollback_test_fixture.h b/src/mongo/db/repl/rollback_test_fixture.h index bae1e9293b4..9bb4103b656 100644 --- a/src/mongo/db/repl/rollback_test_fixture.h +++ b/src/mongo/db/repl/rollback_test_fixture.h @@ -57,7 +57,7 @@ namespace repl { */ class RollbackTest : public ServiceContextMongoDTest { public: - RollbackTest() = default; + explicit RollbackTest(Options options = {}) : ServiceContextMongoDTest(std::move(options)) {} /** * Initializes the service context and task executor. @@ -294,42 +294,5 @@ private: HostAndPort _source; }; -/** - * Test fixture to ensure that rollback re-syncs collection options from a sync source and updates - * the local collection options correctly. A test operates on a single test collection, and is - * parameterized on two arguments: - * - * 'localCollOptions': the collection options that the local test collection is initially created - * with. - * - * 'remoteCollOptionsObj': the collection options object that the sync source will respond with to - * the rollback node when it fetches collection metadata. - * - * If no command is provided, a collMod operation with a 'validationLevel' argument is used to - * trigger a collection metadata resync, since the rollback of collMod operations does not take into - * account the actual command object. It simply re-syncs all the collection options. - */ -class RollbackResyncsCollectionOptionsTest : public RollbackTest { - - class RollbackSourceWithCollectionOptions : public RollbackSourceMock { - public: - RollbackSourceWithCollectionOptions(std::unique_ptr<OplogInterface> oplog, - BSONObj collOptionsObj); - - StatusWith<BSONObj> getCollectionInfoByUUID(const std::string& db, - const UUID& uuid) const override; - - BSONObj collOptionsObj; - }; - -public: - void resyncCollectionOptionsTest(CollectionOptions localCollOptions, - BSONObj remoteCollOptionsObj, - BSONObj collModCmd, - std::string collName); - void resyncCollectionOptionsTest(CollectionOptions localCollOptions, - BSONObj remoteCollOptionsObj); -}; - } // namespace repl } // namespace mongo diff --git a/src/mongo/db/repl/rs_rollback_test.cpp b/src/mongo/db/repl/rs_rollback_test.cpp deleted file mode 100644 index cdeff56a1d5..00000000000 --- a/src/mongo/db/repl/rs_rollback_test.cpp +++ /dev/null @@ -1,2971 +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_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - -#include "mongo/platform/basic.h" - -#include <initializer_list> -#include <memory> -#include <utility> - -#include "mongo/db/catalog/collection_catalog.h" -#include "mongo/db/catalog/database_holder.h" -#include "mongo/db/catalog/drop_indexes.h" -#include "mongo/db/catalog/index_catalog.h" -#include "mongo/db/client.h" -#include "mongo/db/concurrency/d_concurrency.h" -#include "mongo/db/db_raii.h" -#include "mongo/db/dbhelpers.h" -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/index_builds_coordinator.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/namespace_string.h" -#include "mongo/db/op_observer_noop.h" -#include "mongo/db/op_observer_registry.h" -#include "mongo/db/read_write_concern_defaults.h" -#include "mongo/db/repl/drop_pending_collection_reaper.h" -#include "mongo/db/repl/oplog.h" -#include "mongo/db/repl/oplog_interface.h" -#include "mongo/db/repl/oplog_interface_mock.h" -#include "mongo/db/repl/rollback_source.h" -#include "mongo/db/repl/rollback_test_fixture.h" -#include "mongo/db/repl/rs_rollback.h" -#include "mongo/db/s/shard_identity_rollback_notifier.h" -#include "mongo/unittest/death_test.h" -#include "mongo/unittest/unittest.h" -#include "mongo/util/net/hostandport.h" - -namespace mongo { -namespace { - -using namespace mongo::repl; -using namespace mongo::repl::rollback_internal; - -const auto kIndexVersion = IndexDescriptor::IndexVersion::kV2; - -class RSRollbackTest : public RollbackTest {}; - - -OplogInterfaceMock::Operation makeNoopOplogEntryAndRecordId(Seconds seconds) { - OpTime ts(Timestamp(seconds, 0), 0); - return std::make_pair(BSON("ts" << ts.getTimestamp()), RecordId(1)); -} - -OplogInterfaceMock::Operation makeDropIndexOplogEntry(const CollectionPtr& collection, - BSONObj key, - std::string indexName, - int time) { - auto indexSpec = - BSON("key" << key << "name" << indexName << "v" << static_cast<int>(kIndexVersion)); - - return std::make_pair( - BSON("ts" << Timestamp(Seconds(time), 0) << "op" - << "c" - << "ui" << collection->uuid() << "ns" - << "test.$cmd" - << "o" << BSON("dropIndexes" << collection->ns().coll() << "index" << indexName) - << "o2" << indexSpec << "wall" << Date_t()), - RecordId(time)); -} - -OplogInterfaceMock::Operation makeStartIndexBuildOplogEntry(const CollectionPtr& collection, - UUID buildUUID, - BSONObj spec, - int time) { - auto entry = BSON("startIndexBuild" << collection->ns().coll() << "indexBuildUUID" << buildUUID - << "indexes" << BSON_ARRAY(spec)); - - return std::make_pair(BSON("ts" << Timestamp(Seconds(time), 0) << "op" - << "c" - << "ns" - << "test.$cmd" - << "ui" << collection->uuid() << "o" << entry << "wall" - << Date_t()), - RecordId(time)); -} - -OplogInterfaceMock::Operation makeCommitIndexBuildOplogEntry(const CollectionPtr& collection, - UUID buildUUID, - BSONObj spec, - int time) { - auto entry = BSON("commitIndexBuild" << collection->ns().coll() << "indexBuildUUID" << buildUUID - << "indexes" << BSON_ARRAY(spec)); - - return std::make_pair(BSON("ts" << Timestamp(Seconds(time), 0) << "op" - << "c" - << "ns" - << "test.$cmd" - << "ui" << collection->uuid() << "o" << entry << "wall" - << Date_t()), - RecordId(time)); -} - -OplogInterfaceMock::Operation makeAbortIndexBuildOplogEntry(const CollectionPtr& collection, - UUID buildUUID, - BSONObj spec, - int time) { - Status cause = {ErrorCodes::IndexBuildAborted, "test"}; - - BSONObjBuilder causeBuilder; - causeBuilder.appendBool("ok", 0); - cause.serializeErrorToBSON(&causeBuilder); - auto entry = - BSON("abortIndexBuild" << collection->ns().coll() << "indexBuildUUID" << buildUUID - << "indexes" << BSON_ARRAY(spec) << "cause" << causeBuilder.done()); - - return std::make_pair(BSON("ts" << Timestamp(Seconds(time), 0) << "op" - << "c" - << "ns" - << "test.$cmd" - << "ui" << collection->uuid() << "o" << entry << "wall" - << Date_t()), - RecordId(time)); -} - -OplogInterfaceMock::Operation makeCreateIndexOplogEntry(const CollectionPtr& collection, - BSONObj key, - std::string indexName, - int time) { - auto indexSpec = - BSON("createIndexes" << collection->ns().coll() << "v" << static_cast<int>(kIndexVersion) - << "key" << key << "name" << indexName); - - return std::make_pair(BSON("ts" << Timestamp(Seconds(time), 0) << "op" - << "c" - << "ns" - << "test.$cmd" - << "ui" << collection->uuid() << "o" << indexSpec << "wall" - << Date_t()), - RecordId(time)); -} - -OplogInterfaceMock::Operation makeRenameCollectionOplogEntry( - const NamespaceString& renameFrom, - const NamespaceString& renameTo, - const UUID collectionUUID, - const boost::optional<UUID>& dropTarget, - const bool stayTemp, - OpTime opTime) { - BSONObjBuilder cmd; - cmd.append("renameCollection", renameFrom.ns()); - cmd.append("to", renameTo.ns()); - cmd.append("stayTemp", stayTemp); - - BSONObj obj = cmd.obj(); - - if (dropTarget) { - obj = obj.addField(BSON("dropTarget" << *dropTarget).firstElement()); - } - return std::make_pair(BSON("ts" << opTime.getTimestamp() << "t" << opTime.getTerm() << "op" - << "c" - << "ui" << collectionUUID << "ns" << renameFrom.ns() << "o" - << obj << "wall" << Date_t()), - RecordId(opTime.getTimestamp().getSecs())); -} - -BSONObj makeOp(long long seconds) { - auto uuid = unittest::assertGet(UUID::parse("f005ba11-cafe-bead-f00d-123456789abc")); - return BSON("ts" << Timestamp(seconds, seconds) << "t" << seconds << "op" - << "n" - << "o" << BSONObj() << "ns" - << "rs_rollback.test" - << "ui" << uuid << "wall" << Date_t()); -} - -int recordId = 0; -OplogInterfaceMock::Operation makeOpAndRecordId(long long seconds) { - return std::make_pair(makeOp(seconds), RecordId(++recordId)); -} - -// Create an index on an empty collection. Returns the number of indexes that exist on the -// collection after the given index is created. -int _createIndexOnEmptyCollection(OperationContext* opCtx, - Collection* coll, - NamespaceString nss, - BSONObj indexSpec) { - Lock::DBLock dbLock(opCtx, nss.db(), MODE_X); - auto indexCatalog = coll->getIndexCatalog(); - WriteUnitOfWork wunit(opCtx); - ASSERT_OK(indexCatalog->createIndexOnEmptyCollection(opCtx, coll, indexSpec).getStatus()); - wunit.commit(); - return indexCatalog->numIndexesReady(opCtx); -} - -TEST_F(RSRollbackTest, InconsistentMinValid) { - _replicationProcess->getConsistencyMarkers()->setAppliedThrough( - _opCtx.get(), OpTime(Timestamp(Seconds(1), 0), 0)); - _replicationProcess->getConsistencyMarkers()->setMinValid(_opCtx.get(), - OpTime(Timestamp(Seconds(2), 0), 0)); - auto status = syncRollback(_opCtx.get(), - OplogInterfaceMock(), - RollbackSourceMock(std::make_unique<OplogInterfaceMock>()), - {}, - {}, - _coordinator, - _replicationProcess.get()); - ASSERT_EQUALS(ErrorCodes::UnrecoverableRollbackError, status.code()); - ASSERT_STRING_CONTAINS(status.reason(), "unable to determine common point"); -} - -TEST_F(RSRollbackTest, OplogStartMissing) { - OpTime ts(Timestamp(Seconds(1), 0), 0); - auto operation = std::make_pair(BSON("ts" << ts.getTimestamp()), RecordId()); - OplogInterfaceMock::Operations remoteOperations({operation}); - auto remoteOplog = std::make_unique<OplogInterfaceMock>(remoteOperations); - ASSERT_EQUALS(ErrorCodes::OplogStartMissing, - syncRollback(_opCtx.get(), - OplogInterfaceMock(), - RollbackSourceMock(std::move(remoteOplog)), - {}, - {}, - _coordinator, - _replicationProcess.get()) - .code()); -} - -TEST_F(RSRollbackTest, NoRemoteOpLog) { - OpTime ts(Timestamp(Seconds(1), 0), 0); - auto operation = std::make_pair(BSON("ts" << ts.getTimestamp()), RecordId()); - auto status = syncRollback(_opCtx.get(), - OplogInterfaceMock({operation}), - RollbackSourceMock(std::make_unique<OplogInterfaceMock>()), - {}, - {}, - _coordinator, - _replicationProcess.get()); - ASSERT_EQUALS(ErrorCodes::UnrecoverableRollbackError, status.code()); - ASSERT_STRING_CONTAINS(status.reason(), "unable to determine common point"); -} - -TEST_F(RSRollbackTest, RemoteGetRollbackIdThrows) { - OpTime ts(Timestamp(Seconds(1), 0), 0); - auto operation = std::make_pair(BSON("ts" << ts.getTimestamp()), RecordId()); - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(std::unique_ptr<OplogInterface> oplog) - : RollbackSourceMock(std::move(oplog)) {} - int getRollbackId() const override { - uassert(ErrorCodes::UnknownError, "getRollbackId() failed", false); - } - }; - ASSERT_THROWS_CODE(syncRollback(_opCtx.get(), - OplogInterfaceMock({operation}), - RollbackSourceLocal(std::make_unique<OplogInterfaceMock>()), - {}, - {}, - _coordinator, - _replicationProcess.get()), - AssertionException, - ErrorCodes::UnknownError); -} - -TEST_F(RSRollbackTest, RemoteGetRollbackIdDiffersFromRequiredRBID) { - OpTime ts(Timestamp(Seconds(1), 0), 0); - auto operation = std::make_pair(BSON("ts" << ts.getTimestamp()), RecordId()); - - class RollbackSourceLocal : public RollbackSourceMock { - public: - using RollbackSourceMock::RollbackSourceMock; - int getRollbackId() const override { - return 2; - } - }; - - ASSERT_THROWS_CODE(syncRollback(_opCtx.get(), - OplogInterfaceMock({operation}), - RollbackSourceLocal(std::make_unique<OplogInterfaceMock>()), - {}, - 1, - _coordinator, - _replicationProcess.get()), - AssertionException, - ErrorCodes::duplicateCodeForTest(40506)); -} - -TEST_F(RSRollbackTest, BothOplogsAtCommonPoint) { - createOplog(_opCtx.get()); - auto operation = makeOpAndRecordId(1); - ASSERT_OK( - syncRollback(_opCtx.get(), - OplogInterfaceMock({operation}), - RollbackSourceMock(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - operation, - }))), - {}, - {}, - _coordinator, - _replicationProcess.get())); -} - -/** - * Test function to roll back a delete operation. - * Returns number of records in collection after rolling back delete operation. - * If collection does not exist after rolling back, returns -1. - */ -int _testRollbackDelete(OperationContext* opCtx, - ReplicationCoordinator* coordinator, - ReplicationProcess* replicationProcess, - UUID uuid, - const BSONObj& documentAtSource, - const bool collectionAtSourceExists = true) { - auto commonOperation = makeOpAndRecordId(1); - auto deleteOperation = - std::make_pair(BSON("ts" << Timestamp(Seconds(2), 0) << "op" - << "d" - << "ui" << uuid << "ns" - << "test.t" - << "o" << BSON("_id" << 0) << "wall" << Date_t()), - RecordId(2)); - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(const BSONObj& documentAtSource, - std::unique_ptr<OplogInterface> oplog, - const bool collectionAtSourceExists) - : RollbackSourceMock(std::move(oplog)), - called(false), - _documentAtSource(documentAtSource), - _collectionAtSourceExists(collectionAtSourceExists) {} - std::pair<BSONObj, NamespaceString> findOneByUUID(const std::string& db, - UUID uuid, - const BSONObj& filter) const override { - called = true; - if (!_collectionAtSourceExists) { - uassertStatusOKWithContext( - Status(ErrorCodes::NamespaceNotFound, "MockNamespaceNotFoundMsg"), - "find command using UUID failed."); - } - return {_documentAtSource, NamespaceString()}; - } - mutable bool called; - - private: - BSONObj _documentAtSource; - bool _collectionAtSourceExists; - }; - RollbackSourceLocal rollbackSource(documentAtSource, - std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - })), - collectionAtSourceExists); - ASSERT_OK(syncRollback(opCtx, - OplogInterfaceMock({deleteOperation, commonOperation}), - rollbackSource, - {}, - {}, - coordinator, - replicationProcess)); - ASSERT_TRUE(rollbackSource.called); - - Lock::DBLock dbLock(opCtx, "test", MODE_S); - Lock::CollectionLock collLock(opCtx, NamespaceString("test.t"), MODE_S); - auto databaseHolder = DatabaseHolder::get(opCtx); - auto db = databaseHolder->getDb(opCtx, TenantDatabaseName(boost::none, "test")); - ASSERT_TRUE(db); - auto collection = CollectionCatalog::get(opCtx)->lookupCollectionByNamespace( - opCtx, NamespaceString("test.t")); - if (!collection) { - return -1; - } - return collection->getRecordStore()->numRecords(opCtx); -} - -TEST_F(RSRollbackTest, RollbackDeleteNoDocumentAtSourceCollectionDoesNotExist) { - createOplog(_opCtx.get()); - ASSERT_EQUALS( - -1, - _testRollbackDelete( - _opCtx.get(), _coordinator, _replicationProcess.get(), UUID::gen(), BSONObj())); -} - -TEST_F(RSRollbackTest, RollbackDeleteDocCmdCollectionAtSourceDropped) { - const bool collectionAtSourceExists = false; - const NamespaceString nss("test.t"); - createOplog(_opCtx.get()); - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_X); - auto databaseHolder = DatabaseHolder::get(_opCtx.get()); - auto db = databaseHolder->openDb(_opCtx.get(), TenantDatabaseName(boost::none, nss.db())); - ASSERT_TRUE(db); - } - ASSERT_EQUALS(-1, - _testRollbackDelete(_opCtx.get(), - _coordinator, - _replicationProcess.get(), - UUID::gen(), - BSONObj(), - collectionAtSourceExists)); -} - -TEST_F(RSRollbackTest, RollbackDeleteNoDocumentAtSourceCollectionExistsNonCapped) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto coll = _createCollection(_opCtx.get(), "test.t", options); - _testRollbackDelete( - _opCtx.get(), _coordinator, _replicationProcess.get(), coll->uuid(), BSONObj()); - ASSERT_EQUALS( - 0, - _testRollbackDelete( - _opCtx.get(), _coordinator, _replicationProcess.get(), coll->uuid(), BSONObj())); -} - -TEST_F(RSRollbackTest, RollbackDeleteNoDocumentAtSourceCollectionExistsCapped) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - options.capped = true; - auto coll = _createCollection(_opCtx.get(), "test.t", options); - ASSERT_EQUALS( - 0, - _testRollbackDelete( - _opCtx.get(), _coordinator, _replicationProcess.get(), coll->uuid(), BSONObj())); -} - -TEST_F(RSRollbackTest, RollbackDeleteRestoreDocument) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto coll = _createCollection(_opCtx.get(), "test.t", options); - BSONObj doc = BSON("_id" << 0 << "a" << 1); - _testRollbackDelete(_opCtx.get(), _coordinator, _replicationProcess.get(), coll->uuid(), doc); - ASSERT_EQUALS(1, - _testRollbackDelete( - _opCtx.get(), _coordinator, _replicationProcess.get(), coll->uuid(), doc)); -} - -TEST_F(RSRollbackTest, RollbackInsertDocumentWithNoId) { - createOplog(_opCtx.get()); - auto commonOperation = makeOpAndRecordId(1); - auto insertDocumentOperation = - std::make_pair(BSON("ts" << Timestamp(Seconds(2), 0) << "op" - << "i" - << "ui" << UUID::gen() << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("a" << 1)), - RecordId(2)); - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(std::unique_ptr<OplogInterface> oplog) - : RollbackSourceMock(std::move(oplog)), called(false) {} - BSONObj findOne(const NamespaceString& nss, const BSONObj& filter) const { - called = true; - return BSONObj(); - } - mutable bool called; - - private: - BSONObj _documentAtSource; - }; - RollbackSourceLocal rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - startCapturingLogMessages(); - auto status = syncRollback(_opCtx.get(), - OplogInterfaceMock({insertDocumentOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get()); - stopCapturingLogMessages(); - ASSERT_EQUALS(ErrorCodes::UnrecoverableRollbackError, status.code()); - ASSERT_STRING_CONTAINS(status.reason(), "unable to determine common point"); - ASSERT_EQUALS(1, countTextFormatLogLinesContaining("Cannot roll back op with no _id")); - ASSERT_FALSE(rollbackSource.called); -} - -TEST_F(RSRollbackTest, RollbackCreateIndexCommand) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto collection = _createCollection(_opCtx.get(), nss.toString(), options); - auto indexSpec = - BSON("v" << static_cast<int>(kIndexVersion) << "key" << BSON("a" << 1) << "name" - << "a_1"); - - int numIndexes = _createIndexOnEmptyCollection(_opCtx.get(), collection, nss, indexSpec); - ASSERT_EQUALS(2, numIndexes); - - auto commonOperation = makeOpAndRecordId(1); - auto createIndexOperation = makeCreateIndexOplogEntry(collection, BSON("a" << 1), "a_1", 2); - - // Collection pointer will be stale after rollback - collection = nullptr; - - // Repeat index creation operation and confirm that rollback attempts to drop index just once. - // This can happen when an index is re-created with different options. - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - startCapturingLogMessages(); - ASSERT_OK(syncRollback( - _opCtx.get(), - OplogInterfaceMock({createIndexOperation, createIndexOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - stopCapturingLogMessages(); - ASSERT_EQUALS(1, countTextFormatLogLinesContaining("Dropped index in rollback")); - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_S); - auto indexCatalog = CollectionCatalog::get(_opCtx.get()) - ->lookupCollectionByNamespace(_opCtx.get(), nss) - ->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(1, indexCatalog->numIndexesReady(_opCtx.get())); - } -} - -TEST_F(RSRollbackTest, RollbackCreateIndexCommandIndexNotInCatalog) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test.t"); - auto collection = _createCollection(_opCtx.get(), nss, options); - auto indexSpec = BSON("key" << BSON("a" << 1) << "name" - << "a_1"); - // Skip index creation to trigger warning during rollback. - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_S); - auto indexCatalog = collection->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(1, indexCatalog->numIndexesReady(_opCtx.get())); - } - - auto commonOperation = makeOpAndRecordId(1); - auto createIndexOperation = makeCreateIndexOplogEntry(collection, BSON("a" << 1), "a_1", 2); - - // Collection pointer will be stale after rollback - collection = nullptr; - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - startCapturingLogMessages(); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({createIndexOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - stopCapturingLogMessages(); - ASSERT_EQUALS(1, countTextFormatLogLinesContaining("Rollback failed to drop index")); - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_S); - auto indexCatalog = CollectionCatalog::get(_opCtx.get()) - ->lookupCollectionByNamespace(_opCtx.get(), nss) - ->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(1, indexCatalog->numIndexesReady(_opCtx.get())); - } -} - -TEST_F(RSRollbackTest, RollbackDropIndexCommandWithOneIndex) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test.t"); - auto collection = _createCollection(_opCtx.get(), nss, options); - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_S); - auto indexCatalog = collection->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(1, indexCatalog->numIndexesReady(_opCtx.get())); - } - - auto commonOperation = makeOpAndRecordId(1); - auto dropIndexOperation = makeDropIndexOplogEntry(collection, BSON("a" << 1), "a_1", 2); - - // Collection pointer will be stale after rollback - collection = nullptr; - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({dropIndexOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_S); - auto indexCatalog = CollectionCatalog::get(_opCtx.get()) - ->lookupCollectionByNamespace(_opCtx.get(), nss) - ->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(2, indexCatalog->numIndexesReady(_opCtx.get())); - } -} - -TEST_F(RSRollbackTest, RollbackDropIndexCommandWithMultipleIndexes) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test.t"); - auto collection = _createCollection(_opCtx.get(), nss, options); - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_S); - auto indexCatalog = collection->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(1, indexCatalog->numIndexesReady(_opCtx.get())); - } - - auto commonOperation = makeOpAndRecordId(1); - - auto dropIndexOperation1 = makeDropIndexOplogEntry(collection, BSON("a" << 1), "a_1", 2); - auto dropIndexOperation2 = makeDropIndexOplogEntry(collection, BSON("b" << 1), "b_1", 3); - - // Collection pointer will be stale after rollback - collection = nullptr; - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - ASSERT_OK(syncRollback( - _opCtx.get(), - OplogInterfaceMock({dropIndexOperation2, dropIndexOperation1, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_S); - auto indexCatalog = CollectionCatalog::get(_opCtx.get()) - ->lookupCollectionByNamespace(_opCtx.get(), nss) - ->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(3, indexCatalog->numIndexesReady(_opCtx.get())); - } -} - -TEST_F(RSRollbackTest, RollingBackCreateAndDropOfSameIndexIgnoresBothCommands) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test.t"); - auto collection = _createCollection(_opCtx.get(), nss, options); - - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_X); - auto indexCatalog = collection->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(1, indexCatalog->numIndexesReady(_opCtx.get())); - } - - auto commonOperation = makeOpAndRecordId(1); - - auto createIndexOperation = makeCreateIndexOplogEntry(collection, BSON("a" << 1), "a_1", 2); - - auto dropIndexOperation = makeDropIndexOplogEntry(collection, BSON("a" << 1), "a_1", 3); - - // Collection pointer will be stale after rollback - collection = nullptr; - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - ASSERT_OK(syncRollback( - _opCtx.get(), - OplogInterfaceMock({dropIndexOperation, createIndexOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_S); - auto indexCatalog = CollectionCatalog::get(_opCtx.get()) - ->lookupCollectionByNamespace(_opCtx.get(), nss) - ->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(1, indexCatalog->numIndexesReady(_opCtx.get())); - auto indexDescriptor = indexCatalog->findIndexByName(_opCtx.get(), "a_1", false); - ASSERT(!indexDescriptor); - } -} - -TEST_F(RSRollbackTest, RollingBackCreateIndexAndRenameWithLongName) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto collection = _createCollection(_opCtx.get(), nss.toString(), options); - - auto longName = std::string(115, 'a'); - auto indexSpec = BSON("v" << static_cast<int>(kIndexVersion) << "key" << BSON("b" << 1) - << "name" << longName); - - int numIndexes = _createIndexOnEmptyCollection(_opCtx.get(), collection, nss, indexSpec); - ASSERT_EQUALS(2, numIndexes); - - auto commonOperation = makeOpAndRecordId(1); - - auto createIndexOperation = makeCreateIndexOplogEntry(collection, BSON("b" << 1), longName, 2); - - // A collection rename will fail if it would cause an index name to become more than 128 bytes. - // The old collection name plus the index name is not too long, but the new collection name - // plus the index name is too long. - auto newName = NamespaceString("test", "collcollcollcollcoll"); - auto renameCollectionOperation = makeRenameCollectionOplogEntry( - newName, nss, collection->uuid(), boost::none, false, OpTime(Timestamp(Seconds(2), 0), 1)); - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - ASSERT_OK(syncRollback( - _opCtx.get(), - OplogInterfaceMock({createIndexOperation, renameCollectionOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - { - AutoGetCollectionForReadCommand coll(_opCtx.get(), newName); - auto indexCatalog = coll.getCollection()->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(1, indexCatalog->numIndexesReady(_opCtx.get())); - - std::vector<const IndexDescriptor*> indexes; - indexCatalog->findIndexesByKeyPattern(_opCtx.get(), BSON("b" << 1), false, &indexes); - ASSERT(indexes.size() == 0); - } -} - -TEST_F(RSRollbackTest, RollingBackDropAndCreateOfSameIndexNameWithDifferentSpecs) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto collection = _createCollection(_opCtx.get(), nss.toString(), options); - - auto indexSpec = - BSON("v" << static_cast<int>(kIndexVersion) << "key" << BSON("b" << 1) << "name" - << "a_1"); - - int numIndexes = _createIndexOnEmptyCollection(_opCtx.get(), collection, nss, indexSpec); - ASSERT_EQUALS(2, numIndexes); - - auto commonOperation = makeOpAndRecordId(1); - - auto dropIndexOperation = makeDropIndexOplogEntry(collection, BSON("a" << 1), "a_1", 2); - - auto createIndexOperation = makeCreateIndexOplogEntry(collection, BSON("b" << 1), "a_1", 3); - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - startCapturingLogMessages(); - ASSERT_OK(syncRollback( - _opCtx.get(), - OplogInterfaceMock({createIndexOperation, dropIndexOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - stopCapturingLogMessages(); - { - Lock::DBLock dbLock(_opCtx.get(), nss.db(), MODE_S); - auto indexCatalog = CollectionCatalog::get(_opCtx.get()) - ->lookupCollectionByNamespace(_opCtx.get(), nss) - ->getIndexCatalog(); - ASSERT(indexCatalog); - ASSERT_EQUALS(2, indexCatalog->numIndexesReady(_opCtx.get())); - ASSERT_EQUALS(1, countTextFormatLogLinesContaining("Dropped index in rollback")); - ASSERT_EQUALS(1, countTextFormatLogLinesContaining("Created index in rollback")); - std::vector<const IndexDescriptor*> indexes; - indexCatalog->findIndexesByKeyPattern(_opCtx.get(), BSON("a" << 1), false, &indexes); - ASSERT(indexes.size() == 1); - ASSERT(indexes[0]->indexName() == "a_1"); - - std::vector<const IndexDescriptor*> indexes2; - indexCatalog->findIndexesByKeyPattern(_opCtx.get(), BSON("b" << 1), false, &indexes2); - ASSERT(indexes2.size() == 0); - } -} - -TEST_F(RSRollbackTest, RollbackCreateIndexCommandMissingIndexName) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto collection = _createCollection(_opCtx.get(), "test.t", options); - auto commonOperation = makeOpAndRecordId(1); - BSONObj command = BSON("createIndexes" - << "t" - << "ns" - << "test.t" - << "wall" << Date_t() << "v" << static_cast<int>(kIndexVersion) << "key" - << BSON("a" << 1)); - - auto createIndexOperation = std::make_pair(BSON("ts" << Timestamp(Seconds(2), 0) << "op" - << "c" - << "ns" - << "test.$cmd" - << "wall" << Date_t() << "ui" - << collection->uuid() << "o" << command), - RecordId(2)); - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - startCapturingLogMessages(); - auto status = syncRollback(_opCtx.get(), - OplogInterfaceMock({createIndexOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get()); - stopCapturingLogMessages(); - ASSERT_EQUALS(ErrorCodes::UnrecoverableRollbackError, status.code()); - ASSERT_STRING_CONTAINS(status.reason(), "unable to determine common point"); - ASSERT_EQUALS(1, - countTextFormatLogLinesContaining( - "Missing index name in createIndexes operation on rollback")); -} - -// Generators of standard index keys and names given an index 'id'. -std::string idxKey(std::string id) { - return "key_" + id; -}; -std::string idxName(std::string id) { - return "index_" + id; -}; - -// Create an index spec object given the namespace and the index 'id'. -BSONObj idxSpec(NamespaceString nss, std::string id) { - return BSON("v" << static_cast<int>(kIndexVersion) << "key" << BSON(idxKey(id) << 1) << "name" - << idxName(id)); -} - -// Returns the number of indexes that exist on the given collection. -int numIndexesOnColl(OperationContext* opCtx, NamespaceString nss, const CollectionPtr& coll) { - Lock::DBLock dbLock(opCtx, nss.db(), MODE_X); - auto indexCatalog = coll->getIndexCatalog(); - ASSERT(indexCatalog); - return indexCatalog->numIndexesReady(opCtx); -} - -int numIndexesInProgress(OperationContext* opCtx, NamespaceString nss, const CollectionPtr& coll) { - Lock::DBLock dbLock(opCtx, nss.db(), MODE_X); - auto indexCatalog = coll->getIndexCatalog(); - ASSERT(indexCatalog); - return indexCatalog->numIndexesInProgress(opCtx); -} - -TEST_F(RSRollbackTest, RollbackDropIndexOnCollectionWithTwoExistingIndexes) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto coll = _createCollection(_opCtx.get(), nss.toString(), options); - - // Create the necessary indexes. Index 0 is created and dropped in the sequence of ops that will - // be rolled back, so we only create index 1. - int numIndexes = _createIndexOnEmptyCollection(_opCtx.get(), coll, nss, idxSpec(nss, "1")); - ASSERT_EQUALS(2, numIndexes); - - auto commonOp = makeOpAndRecordId(1); - - // The ops that will be rolled back. - auto createIndex0Op = makeCreateIndexOplogEntry(coll, BSON(idxKey("0") << 1), idxName("0"), 2); - auto createIndex1Op = makeCreateIndexOplogEntry(coll, BSON(idxKey("1") << 1), idxName("1"), 3); - auto dropIndex0Op = makeDropIndexOplogEntry(coll, BSON(idxKey("0") << 1), idxName("0"), 4); - - auto remoteOplog = {commonOp}; - auto localOplog = {dropIndex0Op, createIndex1Op, createIndex0Op, commonOp}; - - // Set up the mock rollback source and then run rollback. - RollbackSourceMock rollbackSource(std::make_unique<OplogInterfaceMock>(remoteOplog)); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock(localOplog), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - // Make sure the collection indexes are in the proper state post-rollback. - ASSERT_EQUALS( - 1, - numIndexesOnColl( - _opCtx.get(), - nss, - CollectionCatalog::get(_opCtx.get())->lookupCollectionByNamespace(_opCtx.get(), nss))); -} - -TEST_F(RSRollbackTest, RollbackTwoIndexDropsPrecededByTwoIndexCreationsOnSameCollection) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto coll = _createCollection(_opCtx.get(), nss.toString(), options); - - auto commonOp = makeOpAndRecordId(1); - - // The ops that will be rolled back. - auto createIndex0Op = makeCreateIndexOplogEntry(coll, BSON(idxKey("0") << 1), idxName("0"), 2); - auto createIndex1Op = makeCreateIndexOplogEntry(coll, BSON(idxKey("1") << 1), idxName("1"), 3); - auto dropIndex0Op = makeDropIndexOplogEntry(coll, BSON(idxKey("0") << 1), idxName("0"), 4); - auto dropIndex1Op = makeDropIndexOplogEntry(coll, BSON(idxKey("1") << 1), idxName("1"), 5); - - auto remoteOplog = {commonOp}; - auto localOplog = {dropIndex1Op, dropIndex0Op, createIndex1Op, createIndex0Op, commonOp}; - - // Set up the mock rollback source and then run rollback. - RollbackSourceMock rollbackSource(std::make_unique<OplogInterfaceMock>(remoteOplog)); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock(localOplog), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - // Make sure the collection indexes are in the proper state post-rollback. - ASSERT_EQUALS( - 1, - numIndexesOnColl( - _opCtx.get(), - nss, - CollectionCatalog::get(_opCtx.get())->lookupCollectionByNamespace(_opCtx.get(), nss))); -} - -TEST_F(RSRollbackTest, RollbackMultipleCreateIndexesOnSameCollection) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto coll = _createCollection(_opCtx.get(), nss.toString(), options); - - auto commonOp = makeOpAndRecordId(1); - - // Create all of the necessary indexes. - _createIndexOnEmptyCollection(_opCtx.get(), coll, nss, idxSpec(nss, "0")); - _createIndexOnEmptyCollection(_opCtx.get(), coll, nss, idxSpec(nss, "1")); - _createIndexOnEmptyCollection(_opCtx.get(), coll, nss, idxSpec(nss, "2")); - ASSERT_EQUALS(4, numIndexesOnColl(_opCtx.get(), nss, coll)); - - // The ops that will be rolled back. - auto createIndex0Op = makeCreateIndexOplogEntry(coll, BSON(idxKey("0") << 1), idxName("0"), 2); - auto createIndex1Op = makeCreateIndexOplogEntry(coll, BSON(idxKey("1") << 1), idxName("1"), 3); - auto createIndex2Op = makeCreateIndexOplogEntry(coll, BSON(idxKey("2") << 1), idxName("2"), 4); - - auto remoteOplog = {commonOp}; - auto localOplog = {createIndex2Op, createIndex1Op, createIndex0Op, commonOp}; - - // Set up the mock rollback source and then run rollback. - RollbackSourceMock rollbackSource(std::make_unique<OplogInterfaceMock>(remoteOplog)); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock(localOplog), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - // Make sure the collection indexes are in the proper state post-rollback. - ASSERT_EQUALS( - 1, - numIndexesOnColl( - _opCtx.get(), - nss, - CollectionCatalog::get(_opCtx.get())->lookupCollectionByNamespace(_opCtx.get(), nss))); -} - -TEST_F(RSRollbackTest, RollbackCreateDropRecreateIndexOnCollection) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto coll = _createCollection(_opCtx.get(), nss.toString(), options); - - // Create the necessary indexes. Index 0 is created, dropped, and created again in the - // sequence of ops, so we create that index. - auto indexSpec = BSON("v" << static_cast<int>(kIndexVersion) << "key" << BSON(idxKey("0") << 1) - << "name" << idxName("0")); - - int numIndexes = _createIndexOnEmptyCollection(_opCtx.get(), coll, nss, indexSpec); - ASSERT_EQUALS(2, numIndexes); - - auto commonOp = makeOpAndRecordId(1); - - // The ops that will be rolled back. - auto createIndex0Op = makeCreateIndexOplogEntry(coll, BSON(idxKey("0") << 1), idxName("0"), 2); - auto dropIndex0Op = makeDropIndexOplogEntry(coll, BSON(idxKey("0") << 1), idxName("0"), 3); - auto createIndex0AgainOp = - makeCreateIndexOplogEntry(coll, BSON(idxKey("0") << 1), idxName("0"), 4); - - auto remoteOplog = {commonOp}; - auto localOplog = {createIndex0AgainOp, dropIndex0Op, createIndex0Op, commonOp}; - - // Set up the mock rollback source and then run rollback. - RollbackSourceMock rollbackSource(std::make_unique<OplogInterfaceMock>(remoteOplog)); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock(localOplog), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - // Make sure the collection indexes are in the proper state post-rollback. - ASSERT_EQUALS( - 1, - numIndexesOnColl( - _opCtx.get(), - nss, - CollectionCatalog::get(_opCtx.get())->lookupCollectionByNamespace(_opCtx.get(), nss))); -} - -TEST_F(RSRollbackTest, RollbackCommitIndexBuild) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto coll = _createCollection(_opCtx.get(), nss.toString(), options); - - // Create the necessary index. - auto indexSpec = BSON("v" << static_cast<int>(kIndexVersion) << "key" << BSON(idxKey("0") << 1) - << "name" << idxName("0") << "collation" - << BSON("locale" - << "fr")); - - int numIndexes = _createIndexOnEmptyCollection(_opCtx.get(), coll, nss, indexSpec); - ASSERT_EQUALS(2, numIndexes); - - auto buildUUID = UUID::gen(); - // Store the commit quorum value for the index build in config.system.indexBuilds collection. - _insertDocument(_opCtx.get(), - NamespaceString::kIndexBuildEntryNamespace, - BSON("_id" << buildUUID << "collectionUUID" << options.uuid.get() - << "indexNames" << BSON_ARRAY(idxName("0")) << "commitQuorum" << 0)); - - auto commonOp = makeOpAndRecordId(1); - - auto commitIndexBuild = makeCommitIndexBuildOplogEntry(coll, buildUUID, indexSpec, 2); - - // Roll back a commit oplog entry, which will drop and restart the index build. - auto remoteOplog = {commonOp}; - auto localOplog = {commitIndexBuild, commonOp}; - - // Collection pointer will be stale after rollback - coll = nullptr; - - // Set up the mock rollback source and then run rollback. - RollbackSourceMock rollbackSource(std::make_unique<OplogInterfaceMock>(remoteOplog)); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock(localOplog), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - auto collAfterRollback = - CollectionCatalog::get(_opCtx.get())->lookupCollectionByNamespace(_opCtx.get(), nss); - - // Make sure the collection indexes are in the proper state post-rollback. - ASSERT_EQUALS(1, numIndexesOnColl(_opCtx.get(), nss, collAfterRollback)); - ASSERT_EQUALS(1, numIndexesInProgress(_opCtx.get(), nss, collAfterRollback)); - - // Kill the index build we just restarted so the fixture can shut down. - ASSERT_OK(_coordinator->setFollowerMode(MemberState::RS_ROLLBACK)); - ASSERT(IndexBuildsCoordinator::get(_opCtx.get()) - ->abortIndexBuildByBuildUUID( - _opCtx.get(), buildUUID, IndexBuildAction::kRollbackAbort, "")); -} - -TEST_F(RSRollbackTest, RollbackAbortIndexBuild) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto coll = _createCollection(_opCtx.get(), nss.toString(), options); - - // Create the necessary index. - auto indexSpec = BSON("v" << static_cast<int>(kIndexVersion) << "key" << BSON(idxKey("0") << 1) - << "name" << idxName("0") << "collation" - << BSON("locale" - << "fr")); - - int numIndexes = _createIndexOnEmptyCollection(_opCtx.get(), coll, nss, indexSpec); - ASSERT_EQUALS(2, numIndexes); - - auto buildUUID = UUID::gen(); - // Store the commit quorum value for the index build in config.system.indexBuilds collection. - _insertDocument(_opCtx.get(), - NamespaceString::kIndexBuildEntryNamespace, - BSON("_id" << buildUUID << "collectionUUID" << options.uuid.get() - << "indexNames" << BSON_ARRAY(idxName("0")) << "commitQuorum" << 0)); - - auto commonOp = makeOpAndRecordId(1); - - auto abortIndexBuild = makeAbortIndexBuildOplogEntry(coll, buildUUID, indexSpec, 2); - - // Roll back an abort oplog entry, which will drop and restart the index build. - auto remoteOplog = {commonOp}; - auto localOplog = {abortIndexBuild, commonOp}; - - // Collection pointer will be stale after rollback - coll = nullptr; - - // Set up the mock rollback source and then run rollback. - RollbackSourceMock rollbackSource(std::make_unique<OplogInterfaceMock>(remoteOplog)); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock(localOplog), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - auto collAfterRollback = - CollectionCatalog::get(_opCtx.get())->lookupCollectionByNamespace(_opCtx.get(), nss); - - // Make sure the collection indexes are in the proper state post-rollback. - ASSERT_EQUALS(1, numIndexesOnColl(_opCtx.get(), nss, collAfterRollback)); - ASSERT_EQUALS(1, numIndexesInProgress(_opCtx.get(), nss, collAfterRollback)); - - // Kill the index build we just restarted so the fixture can shut down. - ASSERT_OK(_coordinator->setFollowerMode(MemberState::RS_ROLLBACK)); - ASSERT(IndexBuildsCoordinator::get(_opCtx.get()) - ->abortIndexBuildByBuildUUID( - _opCtx.get(), buildUUID, IndexBuildAction::kRollbackAbort, "")); -} - -TEST_F(RSRollbackTest, AbortedIndexBuildsAreRestarted) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto coll = _createCollection(_opCtx.get(), nss.toString(), options); - - // Create the necessary index. - auto indexSpec = BSON("v" << static_cast<int>(kIndexVersion) << "key" << BSON(idxKey("0") << 1) - << "name" << idxName("0") << "collation" - << BSON("locale" - << "fr")); - - int numIndexes = _createIndexOnEmptyCollection(_opCtx.get(), coll, nss, indexSpec); - ASSERT_EQUALS(2, numIndexes); - - auto buildUUID = UUID::gen(); - // Store the commit quorum value for the index build in config.system.indexBuilds collection. - _insertDocument(_opCtx.get(), - NamespaceString::kIndexBuildEntryNamespace, - BSON("_id" << buildUUID << "collectionUUID" << options.uuid.get() - << "indexNames" << BSON_ARRAY(idxName("0")) << "commitQuorum" << 0)); - - auto commonOp = makeOpAndRecordId(1); - - // Don't roll-back anything. - auto remoteOplog = {commonOp}; - auto localOplog = {commonOp}; - - // Even though the index has already completed, simulate that we aborted the index build before - // rollback. We expect the index to be dropped and rebuilt. - IndexBuildDetails build(coll->uuid()); - build.indexSpecs.push_back(indexSpec); - - IndexBuilds abortedBuilds{{buildUUID, build}}; - - // Collection pointer will be stale after rollback - coll = nullptr; - - // Set up the mock rollback source and then run rollback. - RollbackSourceMock rollbackSource(std::make_unique<OplogInterfaceMock>(remoteOplog)); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock(localOplog), - rollbackSource, - abortedBuilds, - {}, - _coordinator, - _replicationProcess.get())); - - auto collAfterRollback = - CollectionCatalog::get(_opCtx.get())->lookupCollectionByNamespace(_opCtx.get(), nss); - - // Make sure the collection indexes are in the proper state post-rollback. - ASSERT_EQUALS(1, numIndexesOnColl(_opCtx.get(), nss, collAfterRollback)); - ASSERT_EQUALS(1, numIndexesInProgress(_opCtx.get(), nss, collAfterRollback)); - - // Kill the index build we just restarted so the fixture can shut down. - ASSERT_OK(_coordinator->setFollowerMode(MemberState::RS_ROLLBACK)); - ASSERT(IndexBuildsCoordinator::get(_opCtx.get()) - ->abortIndexBuildByBuildUUID( - _opCtx.get(), buildUUID, IndexBuildAction::kRollbackAbort, "")); -} - -TEST_F(RSRollbackTest, AbortedIndexBuildsAreNotRestartedWhenStartIsRolledBack) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - NamespaceString nss("test", "coll"); - auto coll = _createCollection(_opCtx.get(), nss.toString(), options); - - // Create the necessary index. - auto indexSpec = BSON("v" << static_cast<int>(kIndexVersion) << "key" << BSON(idxKey("0") << 1) - << "name" << idxName("0") << "collation" - << BSON("locale" - << "fr")); - - int numIndexes = _createIndexOnEmptyCollection(_opCtx.get(), coll, nss, indexSpec); - ASSERT_EQUALS(2, numIndexes); - - auto commonOp = makeOpAndRecordId(1); - - // Roll-back a startIndexBuild oplog entry. This will cancel out with the aborted index build, - // and the index will be dropped after rollback. - auto buildUUID = UUID::gen(); - auto startIndexBuildOp = makeStartIndexBuildOplogEntry(coll, buildUUID, indexSpec, 2); - - auto remoteOplog = {commonOp}; - auto localOplog = {startIndexBuildOp, commonOp}; - - // Create an index build to abort. - IndexBuildDetails build(coll->uuid()); - build.indexSpecs.push_back(indexSpec); - IndexBuilds abortedBuilds{{buildUUID, build}}; - - // Collection pointer will be stale after rollback - coll = nullptr; - - // Set up the mock rollback source and then run rollback. - RollbackSourceMock rollbackSource(std::make_unique<OplogInterfaceMock>(remoteOplog)); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock(localOplog), - rollbackSource, - abortedBuilds, - {}, - _coordinator, - _replicationProcess.get())); - - auto collAfterRollback = - CollectionCatalog::get(_opCtx.get())->lookupCollectionByNamespace(_opCtx.get(), nss); - - // The aborted index build should have been dropped. - ASSERT_EQUALS(1, numIndexesOnColl(_opCtx.get(), nss, collAfterRollback)); - ASSERT_EQUALS(0, numIndexesInProgress(_opCtx.get(), nss, collAfterRollback)); -} - -TEST_F(RSRollbackTest, RollbackUnknownCommand) { - createOplog(_opCtx.get()); - auto commonOperation = makeOpAndRecordId(1); - auto unknownCommandOperation = std::make_pair(BSON("ts" << Timestamp(Seconds(2), 0) << "op" - << "c" - << "ui" << UUID::gen() << "ns" - << "test.t" - << "wall" << Date_t() << "o" - << BSON("emptycapped" - << "t")), - RecordId(2)); - - auto status = - syncRollback(_opCtx.get(), - OplogInterfaceMock({unknownCommandOperation, commonOperation}), - RollbackSourceMock(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))), - {}, - {}, - _coordinator, - _replicationProcess.get()); - ASSERT_EQUALS(ErrorCodes::UnrecoverableRollbackError, status.code()); - ASSERT_STRING_CONTAINS(status.reason(), "unable to determine common point"); -} - -TEST_F(RSRollbackTest, RollbackRenameCollectionInSameDatabaseCommand) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto collection = _createCollection(_opCtx.get(), "test.y", options); - UUID collectionUUID = collection->uuid(); - - OpTime renameTime = OpTime(Timestamp(2, 0), 5); - - auto commonOperation = makeOpAndRecordId(1); - auto renameCollectionOperation = makeRenameCollectionOplogEntry(NamespaceString("test.x"), - NamespaceString("test.y"), - collectionUUID, - boost::none, - false, - renameTime); - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - { - AutoGetCollectionForReadCommand renamedColl(_opCtx.get(), NamespaceString("test.y")); - ASSERT_TRUE(renamedColl.getCollection()); - - AutoGetCollectionForReadCommand oldCollName(_opCtx.get(), NamespaceString("test.x")); - ASSERT_FALSE(oldCollName.getCollection()); - } - - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({renameCollectionOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - { - AutoGetCollectionForReadCommand renamedColl(_opCtx.get(), NamespaceString("test.y")); - ASSERT_FALSE(renamedColl.getCollection()); - - AutoGetCollectionForReadCommand oldCollName(_opCtx.get(), NamespaceString("test.x")); - ASSERT_TRUE(oldCollName.getCollection()); - - // Remote collection options should have been empty. - auto collAfterRollbackOptions = oldCollName->getCollectionOptions(); - ASSERT_BSONOBJ_EQ(BSON("uuid" << *options.uuid), collAfterRollbackOptions.toBSON()); - } -} - -TEST_F(RSRollbackTest, - RollingBackRenameCollectionFromTempToPermanentCollectionSetsCollectionOptionToTemp) { - createOplog(_opCtx.get()); - - auto renameFromNss = NamespaceString("test.renameFrom"); - auto renameToNss = NamespaceString("test.renameTo"); - - CollectionOptions options; - options.uuid = UUID::gen(); - ASSERT_FALSE(options.temp); - - // Create the collection and save its UUID. - auto collection = _createCollection(_opCtx.get(), renameToNss, options); - auto collectionUUID = collection->uuid(); - - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(std::unique_ptr<OplogInterface> oplog) - : RollbackSourceMock(std::move(oplog)) {} - StatusWith<BSONObj> getCollectionInfoByUUID(const std::string& db, const UUID& uuid) const { - getCollectionInfoCalled = true; - return BSON("info" << BSON("uuid" << uuid) << "options" << BSON("temp" << true)); - } - mutable bool getCollectionInfoCalled = false; - }; - - auto commonOperation = makeOpAndRecordId(1); - - bool stayTemp = false; - auto renameCollectionOperation = makeRenameCollectionOplogEntry(NamespaceString(renameFromNss), - NamespaceString(renameToNss), - collectionUUID, - boost::none, - stayTemp, - OpTime(Timestamp(2, 0), 5)); - - RollbackSourceLocal rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({renameCollectionOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - ASSERT_TRUE(rollbackSource.getCollectionInfoCalled); - - AutoGetCollectionForReadCommand autoColl(_opCtx.get(), NamespaceString(renameFromNss)); - auto collAfterRollbackOptions = autoColl->getCollectionOptions(); - ASSERT_TRUE(collAfterRollbackOptions.temp); - ASSERT_BSONOBJ_EQ(BSON("uuid" << *options.uuid << "temp" << true), - collAfterRollbackOptions.toBSON()); -} - -TEST_F(RSRollbackTest, RollbackRenameCollectionInDatabaseWithDropTargetTrueCommand) { - createOplog(_opCtx.get()); - - OpTime dropTime = OpTime(Timestamp(2, 0), 5); - auto dpns = NamespaceString("test.y").makeDropPendingNamespace(dropTime); - CollectionOptions droppedCollOptions; - droppedCollOptions.uuid = UUID::gen(); - auto droppedColl = _createCollection(_opCtx.get(), dpns, droppedCollOptions); - _dropPendingCollectionReaper->addDropPendingNamespace(_opCtx.get(), dropTime, dpns); - auto droppedCollectionUUID = droppedColl->uuid(); - - CollectionOptions renamedCollOptions; - renamedCollOptions.uuid = UUID::gen(); - auto renamedCollection = _createCollection(_opCtx.get(), "test.y", renamedCollOptions); - auto renamedCollectionUUID = renamedCollection->uuid(); - - auto commonOperation = makeOpAndRecordId(1); - auto renameCollectionOperation = makeRenameCollectionOplogEntry(NamespaceString("test.x"), - NamespaceString("test.y"), - renamedCollectionUUID, - droppedCollectionUUID, - false, - dropTime); - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - { - AutoGetCollectionForReadCommand autoCollDropPending(_opCtx.get(), dpns); - ASSERT_TRUE(autoCollDropPending.getCollection()); - - AutoGetCollectionForReadCommand renamedColl(_opCtx.get(), NamespaceString("test.y")); - ASSERT_TRUE(renamedColl.getCollection()); - - AutoGetCollectionForReadCommand oldCollName(_opCtx.get(), NamespaceString("test.x")); - ASSERT_FALSE(oldCollName.getCollection()); - } - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({renameCollectionOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - { - AutoGetCollectionForReadCommand autoCollDropPending(_opCtx.get(), dpns); - ASSERT_FALSE(autoCollDropPending.getCollection()); - - AutoGetCollectionForReadCommand renamedColl(_opCtx.get(), NamespaceString("test.x")); - ASSERT_TRUE(renamedColl.getCollection()); - ASSERT_EQUALS(renamedColl.getCollection()->uuid(), renamedCollectionUUID); - - AutoGetCollectionForReadCommand droppedColl(_opCtx.get(), NamespaceString("test.y")); - ASSERT_TRUE(droppedColl.getCollection()); - ASSERT_EQUALS(droppedColl.getCollection()->uuid(), droppedCollectionUUID); - } -} - - -void _testRollbackRenamingCollectionsToEachOther(OperationContext* opCtx, - ReplicationCoordinator* replicationCoordinator, - ReplicationProcess* replicationProcess, - const CollectionOptions& coll1Options, - const CollectionOptions& coll2Options) { - createOplog(opCtx); - - auto collection1 = RollbackTest::_createCollection(opCtx, "test.y", coll1Options); - auto collection1UUID = collection1->uuid(); - - auto collection2 = RollbackTest::_createCollection(opCtx, "test.x", coll2Options); - auto collection2UUID = collection2->uuid(); - - ASSERT_NOT_EQUALS(collection1UUID, collection2UUID); - - auto commonOperation = makeOpAndRecordId(1); - auto renameCollectionOperationXtoZ = makeRenameCollectionOplogEntry(NamespaceString("test.x"), - NamespaceString("test.z"), - collection1UUID, - boost::none, - false, - OpTime(Timestamp(2, 0), 5)); - - auto renameCollectionOperationYtoX = makeRenameCollectionOplogEntry(NamespaceString("test.y"), - NamespaceString("test.x"), - collection2UUID, - boost::none, - false, - OpTime(Timestamp(3, 0), 5)); - - auto renameCollectionOperationZtoY = makeRenameCollectionOplogEntry(NamespaceString("test.z"), - NamespaceString("test.y"), - collection1UUID, - boost::none, - false, - OpTime(Timestamp(4, 0), 5)); - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - ASSERT_OK(syncRollback(opCtx, - OplogInterfaceMock({renameCollectionOperationZtoY, - renameCollectionOperationYtoX, - renameCollectionOperationXtoZ, - commonOperation}), - rollbackSource, - {}, - {}, - replicationCoordinator, - replicationProcess)); - - { - - AutoGetCollectionForReadCommand coll1(opCtx, NamespaceString("test.x")); - ASSERT_TRUE(coll1.getCollection()); - ASSERT_EQUALS(coll1.getCollection()->uuid(), collection1UUID); - - AutoGetCollectionForReadCommand coll2(opCtx, NamespaceString("test.y")); - ASSERT_TRUE(coll2.getCollection()); - ASSERT_EQUALS(coll2.getCollection()->uuid(), collection2UUID); - } -} - -TEST_F(RSRollbackTest, RollbackRenamingCollectionsToEachOtherWithoutValidationOptions) { - CollectionOptions coll1Options; - coll1Options.uuid = UUID::gen(); - - CollectionOptions coll2Options; - coll2Options.uuid = UUID::gen(); - - _testRollbackRenamingCollectionsToEachOther( - _opCtx.get(), _coordinator, _replicationProcess.get(), coll1Options, coll2Options); -} - -TEST_F(RSRollbackTest, RollbackRenamingCollectionsToEachOtherWithValidationOptions) { - CollectionOptions coll1Options; - coll1Options.uuid = UUID::gen(); - coll1Options.validator = BSON("x" << BSON("$exists" << 1)); - coll1Options.validationLevel = ValidationLevelEnum::moderate; - coll1Options.validationAction = ValidationActionEnum::warn; - - CollectionOptions coll2Options; - coll2Options.uuid = UUID::gen(); - coll2Options.validator = BSON("y" << BSON("$exists" << 1)); - coll2Options.validationLevel = ValidationLevelEnum::strict; - coll2Options.validationAction = ValidationActionEnum::error; - - // renameOutOfTheWay() uses a temporary namespace to rename either of the two collections - // affected by rollback. The temporary namespace should be able to support collections with - // validation enabled. - _testRollbackRenamingCollectionsToEachOther( - _opCtx.get(), _coordinator, _replicationProcess.get(), coll1Options, coll2Options); -} - -TEST_F(RSRollbackTest, RollbackDropCollectionThenRenameCollectionToDroppedCollectionNS) { - createOplog(_opCtx.get()); - - CollectionOptions renamedCollOptions; - renamedCollOptions.uuid = UUID::gen(); - auto renamedCollection = _createCollection(_opCtx.get(), "test.x", renamedCollOptions); - auto renamedCollectionUUID = renamedCollection->uuid(); - - OpTime dropTime = OpTime(Timestamp(2, 0), 5); - auto dpns = NamespaceString("test.x").makeDropPendingNamespace(dropTime); - CollectionOptions droppedCollOptions; - droppedCollOptions.uuid = UUID::gen(); - auto droppedCollection = _createCollection(_opCtx.get(), dpns, droppedCollOptions); - auto droppedCollectionUUID = droppedCollection->uuid(); - _dropPendingCollectionReaper->addDropPendingNamespace(_opCtx.get(), dropTime, dpns); - - auto commonOperation = makeOpAndRecordId(1); - - auto dropCollectionOperation = - std::make_pair(BSON("ts" << dropTime.getTimestamp() << "t" << dropTime.getTerm() << "op" - << "c" - << "ui" << droppedCollectionUUID << "ns" - << "test.x" - << "wall" << Date_t() << "o" - << BSON("drop" - << "x")), - RecordId(2)); - - auto renameCollectionOperation = makeRenameCollectionOplogEntry(NamespaceString("test.y"), - NamespaceString("test.x"), - renamedCollectionUUID, - boost::none, - false, - OpTime(Timestamp(3, 0), 5)); - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - { - AutoGetCollectionForReadCommand autoCollDropPending(_opCtx.get(), dpns); - ASSERT_TRUE(autoCollDropPending.getCollection()); - AutoGetCollectionForReadCommand autoCollX(_opCtx.get(), NamespaceString("test.x")); - ASSERT_TRUE(autoCollX.getCollection()); - AutoGetCollectionForReadCommand autoCollY(_opCtx.get(), NamespaceString("test.y")); - ASSERT_FALSE(autoCollY.getCollection()); - } - ASSERT_OK(syncRollback( - _opCtx.get(), - OplogInterfaceMock({renameCollectionOperation, dropCollectionOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - { - AutoGetCollectionForReadCommand autoCollDropPending(_opCtx.get(), dpns); - ASSERT_FALSE(autoCollDropPending.getCollection()); - - AutoGetCollectionForReadCommand autoCollX(_opCtx.get(), NamespaceString("test.x")); - ASSERT_TRUE(autoCollX.getCollection()); - ASSERT_EQUALS(autoCollX.getCollection()->uuid(), droppedCollectionUUID); - - AutoGetCollectionForReadCommand autoCollY(_opCtx.get(), NamespaceString("test.y")); - ASSERT_TRUE(autoCollY.getCollection()); - ASSERT_EQUALS(autoCollY.getCollection()->uuid(), renamedCollectionUUID); - } -} - -TEST_F(RSRollbackTest, RollbackRenameCollectionThenCreateNewCollectionWithOldName) { - createOplog(_opCtx.get()); - - CollectionOptions renamedCollOptions; - renamedCollOptions.uuid = UUID::gen(); - auto renamedCollection = _createCollection(_opCtx.get(), "test.y", renamedCollOptions); - auto renamedCollectionUUID = renamedCollection->uuid(); - - CollectionOptions createdCollOptions; - createdCollOptions.uuid = UUID::gen(); - auto createdCollection = _createCollection(_opCtx.get(), "test.x", createdCollOptions); - auto createdCollectionUUID = createdCollection->uuid(); - - auto commonOperation = makeOpAndRecordId(1); - - auto renameCollectionOperation = makeRenameCollectionOplogEntry(NamespaceString("test.x"), - NamespaceString("test.y"), - renamedCollectionUUID, - boost::none, - false, - OpTime(Timestamp(2, 0), 5)); - - auto createCollectionOperation = - std::make_pair(BSON("ts" << Timestamp(Seconds(3), 0) << "op" - << "c" - << "ui" << createdCollectionUUID << "ns" - << "test.x" - << "wall" << Date_t() << "o" - << BSON("create" - << "x")), - RecordId(3)); - - - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - { - AutoGetCollectionForReadCommand renamedColl(_opCtx.get(), NamespaceString("test.y")); - ASSERT_TRUE(renamedColl.getCollection()); - AutoGetCollectionForReadCommand createdColl(_opCtx.get(), NamespaceString("test.x")); - ASSERT_TRUE(createdColl.getCollection()); - } - ASSERT_OK(syncRollback( - _opCtx.get(), - OplogInterfaceMock({createCollectionOperation, renameCollectionOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - - { - - AutoGetCollectionForReadCommand renamedColl(_opCtx.get(), NamespaceString("test.x")); - ASSERT_TRUE(renamedColl.getCollection()); - ASSERT_EQUALS(renamedColl.getCollection()->uuid(), renamedCollectionUUID); - - AutoGetCollectionForReadCommand createdColl(_opCtx.get(), NamespaceString("test.y")); - ASSERT_FALSE(createdColl.getCollection()); - } -} - -TEST_F(RSRollbackTest, RollbackCollModCommandFailsIfRBIDChangesWhileSyncingCollectionMetadata) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto coll = _createCollection(_opCtx.get(), "test.t", options); - - auto commonOperation = makeOpAndRecordId(1); - auto collModOperation = std::make_pair(BSON("ts" << Timestamp(Seconds(2), 0) << "op" - << "c" - << "ui" << coll->uuid() << "ns" - << "test.t" - << "wall" << Date_t() << "o" - << BSON("collMod" - << "t" - << "validationLevel" - << "off")), - RecordId(2)); - class RollbackSourceLocal : public RollbackSourceMock { - public: - using RollbackSourceMock::RollbackSourceMock; - int getRollbackId() const override { - return getCollectionInfoCalled ? 1 : 0; - } - StatusWith<BSONObj> getCollectionInfoByUUID(const std::string& db, - const UUID& uuid) const override { - getCollectionInfoCalled = true; - return BSONObj(); - } - mutable bool getCollectionInfoCalled = false; - }; - RollbackSourceLocal rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - - ASSERT_THROWS_CODE(syncRollback(_opCtx.get(), - OplogInterfaceMock({collModOperation, commonOperation}), - rollbackSource, - {}, - 0, - _coordinator, - _replicationProcess.get()), - DBException, - 40508); - ASSERT(rollbackSource.getCollectionInfoCalled); -} - -TEST_F(RSRollbackTest, RollbackDropDatabaseCommand) { - createOplog(_opCtx.get()); - auto commonOperation = makeOpAndRecordId(1); - // 'dropDatabase' operations are special and do not include a UUID field. - auto dropDatabaseOperation = - std::make_pair(BSON("ts" << Timestamp(Seconds(2), 0) << "op" - << "c" - << "ns" - << "test.$cmd" - << "wall" << Date_t() << "o" << BSON("dropDatabase" << 1)), - RecordId(2)); - RollbackSourceMock rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({dropDatabaseOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); -} - -BSONObj makeApplyOpsOplogEntry(Timestamp ts, std::initializer_list<BSONObj> ops) { - // applyOps oplog entries are special and do not include a UUID field. - BSONObjBuilder entry; - entry << "ts" << ts << "op" - << "c" - << "ns" - << "admin" - << "wall" << Date_t(); - { - BSONObjBuilder cmd(entry.subobjStart("o")); - BSONArrayBuilder subops(entry.subarrayStart("applyOps")); - for (const auto& op : ops) { - subops << op; - } - } - return entry.obj(); -} - -OpTime getOpTimeFromOplogEntry(const BSONObj& entry) { - const BSONElement tsElement = entry["ts"]; - const BSONElement termElement = entry["t"]; - ASSERT_EQUALS(bsonTimestamp, tsElement.type()) << entry; - ASSERT_TRUE(termElement.eoo() || termElement.isNumber()) << entry; - long long term = 1LL; - if (!termElement.eoo()) { - term = termElement.numberLong(); - } - return OpTime(tsElement.timestamp(), term); -} - -TEST_F(RSRollbackTest, RollbackApplyOpsCommand) { - createOplog(_opCtx.get()); - CollectionPtr coll; - CollectionOptions options; - options.uuid = UUID::gen(); - { - AutoGetDb autoDb(_opCtx.get(), "test", MODE_X); - mongo::WriteUnitOfWork wuow(_opCtx.get()); - coll = CollectionCatalog::get(_opCtx.get()) - ->lookupCollectionByNamespace(_opCtx.get(), NamespaceString("test.t")); - if (!coll) { - auto db = autoDb.ensureDbExists(_opCtx.get()); - coll = db->createCollection(_opCtx.get(), NamespaceString("test.t"), options); - } - ASSERT(coll); - OpDebug* const nullOpDebug = nullptr; - ASSERT_OK(coll->insertDocument( - _opCtx.get(), InsertStatement(BSON("_id" << 1 << "v" << 2)), nullOpDebug, false)); - ASSERT_OK(coll->insertDocument( - _opCtx.get(), InsertStatement(BSON("_id" << 2 << "v" << 4)), nullOpDebug, false)); - ASSERT_OK(coll->insertDocument( - _opCtx.get(), InsertStatement(BSON("_id" << 4)), nullOpDebug, false)); - wuow.commit(); - } - UUID uuid = coll->uuid(); - const auto commonOperation = makeOpAndRecordId(1); - const auto applyOpsOperation = - std::make_pair(makeApplyOpsOplogEntry( - Timestamp(Seconds(2), 0), - {BSON("op" - << "u" - << "ui" << uuid << "ts" << Timestamp(1, 1) << "t" << 1LL << "ns" - << "test.t" - << "o2" << BSON("_id" << 1) << "wall" << Date_t() << "o" - << BSON("_id" << 1 << "v" << 2)), - BSON("op" - << "u" - << "ui" << uuid << "ts" << Timestamp(2, 1) << "t" << 1LL << "ns" - << "test.t" - << "o2" << BSON("_id" << 2) << "wall" << Date_t() << "o" - << BSON("_id" << 2 << "v" << 4)), - BSON("op" - << "d" - << "ui" << uuid << "ts" << Timestamp(3, 1) << "t" << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 3)), - BSON("op" - << "i" - << "ui" << uuid << "ts" << Timestamp(4, 1) << "t" << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 4)), - // applyOps internal oplog entries are not required - // to have a timestamp. - BSON("op" - << "i" - << "ui" << uuid << "ts" << Timestamp(4, 1) << "t" << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 4)), - BSON("op" - << "i" - << "ui" << uuid << "t" << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 4)), - BSON("op" - << "i" - << "ui" << uuid << "t" << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 4))}), - RecordId(2)); - - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(std::unique_ptr<OplogInterface> oplog) - : RollbackSourceMock(std::move(oplog)) {} - - std::pair<BSONObj, NamespaceString> findOneByUUID(const std::string& db, - UUID uuid, - const BSONObj& filter) const override { - int numFields = 0; - for (const auto& element : filter) { - ++numFields; - ASSERT_EQUALS("_id", element.fieldNameStringData()) << filter; - } - ASSERT_EQUALS(1, numFields) << filter; - searchedIds.insert(filter.firstElement().numberInt()); - switch (filter.firstElement().numberInt()) { - case 1: - return {BSON("_id" << 1 << "v" << 1), NamespaceString()}; - case 2: - return {BSON("_id" << 2 << "v" << 3), NamespaceString()}; - case 3: - return {BSON("_id" << 3 << "v" << 5), NamespaceString()}; - case 4: - return {}; - } - FAIL("Unexpected findOne request") << filter; - return {}; // Unreachable; why doesn't compiler know? - } - - mutable std::multiset<int> searchedIds; - } rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({commonOperation}))); - - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({applyOpsOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - ASSERT_EQUALS(4U, rollbackSource.searchedIds.size()); - ASSERT_EQUALS(1U, rollbackSource.searchedIds.count(1)); - ASSERT_EQUALS(1U, rollbackSource.searchedIds.count(2)); - ASSERT_EQUALS(1U, rollbackSource.searchedIds.count(3)); - ASSERT_EQUALS(1U, rollbackSource.searchedIds.count(4)); - - AutoGetCollectionForReadCommand acr(_opCtx.get(), NamespaceString("test.t")); - BSONObj result; - ASSERT(Helpers::findOne(_opCtx.get(), acr.getCollection(), BSON("_id" << 1), result)); - ASSERT_EQUALS(1, result["v"].numberInt()) << result; - ASSERT(Helpers::findOne(_opCtx.get(), acr.getCollection(), BSON("_id" << 2), result)); - ASSERT_EQUALS(3, result["v"].numberInt()) << result; - ASSERT(Helpers::findOne(_opCtx.get(), acr.getCollection(), BSON("_id" << 3), result)); - ASSERT_EQUALS(5, result["v"].numberInt()) << result; - ASSERT_FALSE(Helpers::findOne(_opCtx.get(), acr.getCollection(), BSON("_id" << 4), result)) - << result; -} - -TEST_F(RSRollbackTest, RollbackCreateCollectionCommand) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto coll = _createCollection(_opCtx.get(), "test.t", options); - - auto commonOperation = makeOpAndRecordId(1); - auto createCollectionOperation = std::make_pair(BSON("ts" << Timestamp(Seconds(2), 0) << "op" - << "c" - << "ui" << coll->uuid() << "ns" - << "test.t" - << "wall" << Date_t() << "o" - << BSON("create" - << "t")), - RecordId(2)); - RollbackSourceMock rollbackSource( - std::unique_ptr<OplogInterface>(new OplogInterfaceMock({commonOperation}))); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({createCollectionOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - { - Lock::DBLock dbLock(_opCtx.get(), "test", MODE_S); - auto databaseHolder = DatabaseHolder::get(_opCtx.get()); - auto db = databaseHolder->getDb(_opCtx.get(), TenantDatabaseName(boost::none, "test")); - ASSERT_TRUE(db); - ASSERT_FALSE(CollectionCatalog::get(_opCtx.get()) - ->lookupCollectionByNamespace(_opCtx.get(), NamespaceString("test.t"))); - } -} - -TEST_F(RSRollbackTest, RollbackCollectionModificationCommand) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto coll = _createCollection(_opCtx.get(), "test.t", options); - - auto commonOperation = makeOpAndRecordId(1); - - BSONObj collModCmd = BSON("collMod" - << "t" - << "validationLevel" - << "strict"); - auto collectionModificationOperation = - makeCommandOp(Timestamp(Seconds(2), 0), coll->uuid(), "test.t", collModCmd, 2); - - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(std::unique_ptr<OplogInterface> oplog) - : RollbackSourceMock(std::move(oplog)), called(false) {} - - // Remote collection options are empty. - StatusWith<BSONObj> getCollectionInfoByUUID(const std::string& db, const UUID& uuid) const { - called = true; - return BSON("options" << BSONObj() << "info" << BSON("uuid" << uuid)); - } - mutable bool called; - }; - RollbackSourceLocal rollbackSource( - std::unique_ptr<OplogInterface>(new OplogInterfaceMock({commonOperation}))); - - startCapturingLogMessages(); - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({collectionModificationOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - stopCapturingLogMessages(); - - ASSERT_TRUE(rollbackSource.called); - for (const auto& message : getCapturedTextFormatLogMessages()) { - ASSERT_TRUE(message.find("ignoring op with no _id during rollback. ns: test.t") == - std::string::npos); - } - - // Make sure the collection options are correct. - AutoGetCollectionForReadCommand autoColl(_opCtx.get(), NamespaceString("test.t")); - auto collAfterRollbackOptions = autoColl->getCollectionOptions(); - ASSERT_BSONOBJ_EQ(BSON("uuid" << *options.uuid), collAfterRollbackOptions.toBSON()); -} - -TEST_F(RollbackResyncsCollectionOptionsTest, - FullRemoteCollectionValidationOptionsAndEmptyLocalValidationOptions) { - // Empty local collection options. - CollectionOptions localCollOptions; - localCollOptions.uuid = UUID::gen(); - - // Full remote collection validation options. - BSONObj remoteCollOptionsObj = - BSON("validator" << BSON("x" << BSON("$exists" << 1)) << "validationLevel" - << "moderate" - << "validationAction" - << "warn"); - - resyncCollectionOptionsTest(localCollOptions, remoteCollOptionsObj); -} - -TEST_F(RollbackResyncsCollectionOptionsTest, - PartialRemoteCollectionValidationOptionsAndEmptyLocalValidationOptions) { - CollectionOptions localCollOptions; - localCollOptions.uuid = UUID::gen(); - - BSONObj remoteCollOptionsObj = BSON("validationLevel" - << "moderate" - << "validationAction" - << "warn"); - - resyncCollectionOptionsTest(localCollOptions, remoteCollOptionsObj); -} - -TEST_F(RollbackResyncsCollectionOptionsTest, - PartialRemoteCollectionValidationOptionsAndFullLocalValidationOptions) { - CollectionOptions localCollOptions; - localCollOptions.uuid = UUID::gen(); - localCollOptions.validator = BSON("x" << BSON("$exists" << 1)); - localCollOptions.validationLevel = ValidationLevelEnum::moderate; - localCollOptions.validationAction = ValidationActionEnum::warn; - - BSONObj remoteCollOptionsObj = BSON("validationLevel" - << "strict" - << "validationAction" - << "error"); - - - resyncCollectionOptionsTest(localCollOptions, remoteCollOptionsObj); -} - -TEST_F(RollbackResyncsCollectionOptionsTest, - EmptyRemoteCollectionValidationOptionsAndEmptyLocalValidationOptions) { - CollectionOptions localCollOptions; - localCollOptions.uuid = UUID::gen(); - - BSONObj remoteCollOptionsObj = BSONObj(); - - resyncCollectionOptionsTest(localCollOptions, remoteCollOptionsObj); -} - -TEST_F(RollbackResyncsCollectionOptionsTest, - EmptyRemoteCollectionValidationOptionsAndFullLocalValidationOptions) { - CollectionOptions localCollOptions; - localCollOptions.uuid = UUID::gen(); - localCollOptions.validator = BSON("x" << BSON("$exists" << 1)); - localCollOptions.validationLevel = ValidationLevelEnum::moderate; - localCollOptions.validationAction = ValidationActionEnum::warn; - - BSONObj remoteCollOptionsObj = BSONObj(); - - resyncCollectionOptionsTest(localCollOptions, remoteCollOptionsObj); -} - -TEST_F(RollbackResyncsCollectionOptionsTest, LocalTempCollectionRemotePermanentCollection) { - CollectionOptions localCollOptions; - localCollOptions.uuid = UUID::gen(); - localCollOptions.temp = true; - - BSONObj remoteCollOptionsObj = BSONObj(); - - resyncCollectionOptionsTest(localCollOptions, remoteCollOptionsObj); -} - -TEST_F(RollbackResyncsCollectionOptionsTest, LocalPermanentCollectionRemoteTempCollection) { - CollectionOptions localCollOptions; - localCollOptions.uuid = UUID::gen(); - - BSONObj remoteCollOptionsObj = BSON("temp" << true); - - resyncCollectionOptionsTest(localCollOptions, remoteCollOptionsObj); -} - -TEST_F(RollbackResyncsCollectionOptionsTest, BothCollectionsTemp) { - CollectionOptions localCollOptions; - localCollOptions.uuid = UUID::gen(); - localCollOptions.temp = true; - - BSONObj remoteCollOptionsObj = BSON("temp" << true); - - resyncCollectionOptionsTest(localCollOptions, remoteCollOptionsObj); -} - -TEST_F(RollbackResyncsCollectionOptionsTest, ChangingTempStatusAlsoChangesOtherCollectionOptions) { - CollectionOptions localCollOptions; - localCollOptions.uuid = UUID::gen(); - localCollOptions.temp = true; - - BSONObj remoteCollOptionsObj = BSON("validationLevel" - << "strict" - << "validationAction" - << "error"); - - resyncCollectionOptionsTest(localCollOptions, remoteCollOptionsObj); -} - -TEST_F(RSRollbackTest, RollbackCollectionModificationCommandInvalidCollectionOptions) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto coll = _createCollection(_opCtx.get(), "test.t", options); - - auto commonOperation = makeOpAndRecordId(1); - - BSONObj collModCmd = BSON("collMod" - << "t" - << "validationLevel" - << "strict"); - auto collectionModificationOperation = - makeCommandOp(Timestamp(Seconds(2), 0), coll->uuid(), "test.t", collModCmd, 2); - - - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(std::unique_ptr<OplogInterface> oplog) - : RollbackSourceMock(std::move(oplog)) {} - StatusWith<BSONObj> getCollectionInfoByUUID(const std::string& db, const UUID& uuid) const { - return BSON("options" << 12345); - } - }; - RollbackSourceLocal rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({ - commonOperation, - }))); - auto status = - syncRollback(_opCtx.get(), - OplogInterfaceMock({collectionModificationOperation, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get()); - ASSERT_EQUALS(ErrorCodes::UnrecoverableRollbackError, status.code()); - ASSERT_STRING_CONTAINS(status.reason(), "Failed to parse options"); -} - -TEST(RSRollbackTest, LocalEntryWithoutNsIsFatal) { - const auto validOplogEntry = BSON("op" - << "i" - << "ui" << UUID::gen() << "ts" << Timestamp(1, 1) << "t" - << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 1 << "a" << 1)); - FixUpInfo fui; - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, validOplogEntry, false)); - const auto invalidOplogEntry = BSON("op" - << "i" - << "ui" << UUID::gen() << "ts" << Timestamp(1, 1) << "t" - << 1LL << "ns" - << "" - << "wall" << Date_t() << "o" - << BSON("_id" << 1 << "a" << 1)); - ASSERT_THROWS(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, invalidOplogEntry, false), - RSFatalException); -} - -TEST(RSRollbackTest, LocalEntryWithoutOIsFatal) { - const auto validOplogEntry = BSON("op" - << "i" - << "ui" << UUID::gen() << "ts" << Timestamp(1, 1) << "t" - << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 1 << "a" << 1)); - FixUpInfo fui; - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, validOplogEntry, false)); - const auto invalidOplogEntry = BSON("op" - << "i" - << "ui" << UUID::gen() << "ts" << Timestamp(1, 1) << "t" - << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSONObj()); - ASSERT_THROWS(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, invalidOplogEntry, false), - RSFatalException); -} - -DEATH_TEST_F(RSRollbackTest, LocalUpdateEntryWithoutO2IsFatal, "Fatal assertion") { - const auto invalidOplogEntry = BSON("op" - << "u" - << "ui" << UUID::gen() << "ts" << Timestamp(1, 1) << "t" - << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" - << BSON("_id" << 1 << "a" << 1)); - FixUpInfo fui; - updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, invalidOplogEntry, false) - .ignore(); -} - -TEST(RSRollbackTest, LocalUpdateEntryWithEmptyO2IsFatal) { - const auto validOplogEntry = BSON("op" - << "u" - << "ui" << UUID::gen() << "ts" << Timestamp(1, 1) << "t" - << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 1 << "a" << 1) - << "o2" << BSON("_id" << 1)); - FixUpInfo fui; - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, validOplogEntry, false)); - const auto invalidOplogEntry = BSON("op" - << "u" - << "ui" << UUID::gen() << "ts" << Timestamp(1, 1) << "t" - << 1LL << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 1 << "a" << 1) - << "o2" << BSONObj()); - ASSERT_THROWS(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, invalidOplogEntry, false), - RSFatalException); -} - -DEATH_TEST_F(RSRollbackTest, LocalEntryWithTxnNumberWithoutSessionIdIsFatal, "invariant") { - auto validOplogEntry = BSON("ts" << Timestamp(Seconds(1), 0) << "t" << 1LL << "op" - << "i" - << "ui" << UUID::gen() << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 1 << "a" << 1)); - FixUpInfo fui; - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, validOplogEntry, false)); - - const auto txnNumber = BSON("txnNumber" << 1LL); - const auto noSessionIdOrStmtId = validOplogEntry.addField(txnNumber.firstElement()); - - const auto stmtId = BSON("stmtId" << 1); - const auto noSessionId = noSessionIdOrStmtId.addField(stmtId.firstElement()); - ASSERT_THROWS(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, noSessionId, false), - RSFatalException); -} - -TEST_F(RSRollbackTest, LocalEntryWithTxnNumberWithoutTxnTableUUIDIsFatal) { - // If txnNumber is present, but the transaction collection has no UUID, rollback fails. - UUID uuid = UUID::gen(); - auto lsid = makeLogicalSessionIdForTest(); - auto entryWithTxnNumber = - BSON("ts" << Timestamp(Seconds(1), 0) << "t" << 1LL << "op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 1 << "a" << 1) << "txnNumber" << 1LL - << "stmtId" << 1 << "lsid" << lsid.toBSON()); - - FixUpInfo fui; - ASSERT_THROWS(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, entryWithTxnNumber, false), - RSFatalException); -} - -TEST_F(RSRollbackTest, LocalEntryWithTxnNumberAddsTransactionTableDocToBeRefetched) { - FixUpInfo fui; - - // With no txnNumber present, no extra documents need to be refetched. - auto entryWithoutTxnNumber = - BSON("ts" << Timestamp(Seconds(1), 0) << "t" << 1LL << "op" - << "i" - << "ui" << UUID::gen() << "ns" - << "test.t2" - << "wall" << Date_t() << "o" << BSON("_id" << 2 << "a" << 2)); - - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, entryWithoutTxnNumber, false)); - ASSERT_EQ(fui.docsToRefetch.size(), 1U); - - // If txnNumber is present, and the transaction table exists and has a UUID, the session - // transactions table document corresponding to the oplog entry's sessionId also needs to be - // refetched. - UUID uuid = UUID::gen(); - auto lsid = makeLogicalSessionIdForTest(); - auto entryWithTxnNumber = - BSON("ts" << Timestamp(Seconds(1), 0) << "t" << 1LL << "op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 1 << "a" << 1) << "txnNumber" << 1LL - << "stmtId" << 1 << "lsid" << lsid.toBSON()); - UUID transactionTableUUID = UUID::gen(); - fui.transactionTableUUID = transactionTableUUID; - - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, entryWithTxnNumber, false)); - ASSERT_EQ(fui.docsToRefetch.size(), 3U); - - auto expectedObj = BSON("_id" << lsid.toBSON()); - DocID expectedTxnDoc(expectedObj, expectedObj.firstElement(), transactionTableUUID); - ASSERT_TRUE(fui.docsToRefetch.find(expectedTxnDoc) != fui.docsToRefetch.end()); -} - -TEST_F(RSRollbackTest, LocalEntryWithPartialTxnAddsTransactionTableDocToBeRefetched) { - FixUpInfo fui; - - // If txnNumber is present, and the transaction table exists and has a UUID, the session - // transactions table document corresponding to the oplog entry's sessionId also needs to be - // refetched. This is true even if "partialTxn" is set indicating this is part of a transaction - // that may not have been committed. - UUID uuid = UUID::gen(); - auto lsid = makeLogicalSessionIdForTest(); - auto entryWithTxnNumber = - BSON("ts" << Timestamp(Seconds(1), 0) << "t" << 1LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" - << BSON("applyOps" << BSON_ARRAY(BSON("op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "o" << BSON("_id" << 1 << "a" << 1))) - << "partialTxn" << true) - << "txnNumber" << 1LL << "stmtId" << 1 << "lsid" << lsid.toBSON()); - UUID transactionTableUUID = UUID::gen(); - fui.transactionTableUUID = transactionTableUUID; - - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, entryWithTxnNumber, false)); - ASSERT_EQ(fui.docsToRefetch.size(), 1U); - - auto expectedObj = BSON("_id" << lsid.toBSON()); - DocID expectedTxnDoc(expectedObj, expectedObj.firstElement(), transactionTableUUID); - ASSERT_TRUE(fui.docsToRefetch.find(expectedTxnDoc) != fui.docsToRefetch.end()); -} - -TEST_F(RSRollbackTest, LocalAbortTxnRefetchesTransactionTableEntry) { - // A rolled back abort, even if we rolled back no transaction operations, should refetch the - // transaction table entry. - FixUpInfo fui; - - auto lsid = makeLogicalSessionIdForTest(); - auto abortTxnEntry = - BSON("ts" << Timestamp(Seconds(1), 1) << "t" << 1LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" << BSON("abortTransaction" << 1) << "txnNumber" - << 1LL << "stmtId" << 1 << "lsid" << lsid.toBSON() << "prevOpTime" - << BSON("ts" << Timestamp(Seconds(1), 0) << "t" << 1LL)); - - UUID transactionTableUUID = UUID::gen(); - fui.transactionTableUUID = transactionTableUUID; - - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, abortTxnEntry, false)); - ASSERT_EQ(fui.docsToRefetch.size(), 1U); - - auto expectedObj = BSON("_id" << lsid.toBSON()); - DocID expectedTxnDoc(expectedObj, expectedObj.firstElement(), transactionTableUUID); - ASSERT_TRUE(fui.docsToRefetch.find(expectedTxnDoc) != fui.docsToRefetch.end()); -} - -TEST_F(RSRollbackTest, LocalEntryWithAbortedPartialTxnRefetchesOnlyTransactionTableEntry) { - FixUpInfo fui; - - // If txnNumber is present, and the transaction table exists and has a UUID, the session - // transactions table document corresponding to the oplog entry's sessionId also needs to be - // refetched. This is true even if "partialTxn" is set indicating this is part of a transaction - // that may not have been committed, and even if it is known that the transaction aborted. - UUID uuid = UUID::gen(); - auto lsid = makeLogicalSessionIdForTest(); - auto abortTxnEntry = - BSON("ts" << Timestamp(Seconds(1), 2) << "t" << 1LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" << BSON("abortTransaction" << 1) << "txnNumber" - << 1LL << "stmtId" << 1 << "lsid" << lsid.toBSON() << "prevOpTime" - << BSON("ts" << Timestamp(Seconds(1), 1) << "t" << 1LL)); - - auto entryWithTxnNumber = - BSON("ts" << Timestamp(Seconds(1), 1) << "t" << 1LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" - << BSON("applyOps" << BSON_ARRAY(BSON("op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "o" << BSON("_id" << 1 << "a" << 1))) - << "partialTxn" << true) - << "txnNumber" << 1LL << "stmtId" << 1 << "lsid" << lsid.toBSON()); - UUID transactionTableUUID = UUID::gen(); - fui.transactionTableUUID = transactionTableUUID; - - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, abortTxnEntry, false)); - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, entryWithTxnNumber, false)); - ASSERT_EQ(fui.docsToRefetch.size(), 1U); - - auto expectedObj = BSON("_id" << lsid.toBSON()); - DocID expectedTxnDoc(expectedObj, expectedObj.firstElement(), transactionTableUUID); - ASSERT_TRUE(fui.docsToRefetch.find(expectedTxnDoc) != fui.docsToRefetch.end()); -} - -TEST_F(RSRollbackTest, LocalEntryWithCommittedTxnRefetchesDocsAndTransactionTableEntry) { - FixUpInfo fui; - UUID uuid = UUID::gen(); - auto lsid = makeLogicalSessionIdForTest(); - auto commitTxnEntry = - BSON("ts" << Timestamp(Seconds(1), 2) << "t" << 1LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" - << BSON("applyOps" << BSON_ARRAY(BSON("op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "o" << BSON("_id" << 2 << "a" << 2))) - << "count" << 2) - << "txnNumber" << 1LL << "stmtId" << 2 << "lsid" << lsid.toBSON() << "prevOpTime" - << BSON("ts" << Timestamp(Seconds(1), 1) << "t" << 1LL)); - auto commitTxnOperation = std::make_pair(commitTxnEntry, RecordId(2)); - - auto partialTxnEntry = - BSON("ts" << Timestamp(Seconds(1), 1) << "t" << 1LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" - << BSON("applyOps" << BSON_ARRAY(BSON("op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "o" << BSON("_id" << 1 << "a" << 1))) - << "partialTxn" << true) - << "txnNumber" << 1LL << "stmtId" << 1 << "lsid" << lsid.toBSON() << "prevOpTime" - << BSON("ts" << Timestamp(0, 0) << "t" << -1LL)); - - auto partialTxnOperation = std::make_pair(partialTxnEntry, RecordId(1)); - UUID transactionTableUUID = UUID::gen(); - fui.transactionTableUUID = transactionTableUUID; - - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, - OplogInterfaceMock({commitTxnOperation, partialTxnOperation}), - fui, - commitTxnEntry, - false)); - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, - OplogInterfaceMock({commitTxnOperation, partialTxnOperation}), - fui, - partialTxnEntry, - false)); - ASSERT_EQ(fui.docsToRefetch.size(), 3U); - - auto expectedObj = BSON("_id" << lsid.toBSON()); - DocID expectedTxnDoc(expectedObj, expectedObj.firstElement(), transactionTableUUID); - ASSERT_TRUE(fui.docsToRefetch.find(expectedTxnDoc) != fui.docsToRefetch.end()); - - auto expectedCrudObj = BSON("_id" << 2); - auto expectedCrudDoc = DocID(expectedObj, expectedObj.firstElement(), transactionTableUUID); - ASSERT_TRUE(fui.docsToRefetch.find(expectedCrudDoc) != fui.docsToRefetch.end()); - - expectedCrudObj = BSON("_id" << 1); - expectedCrudDoc = DocID(expectedObj, expectedObj.firstElement(), transactionTableUUID); - ASSERT_TRUE(fui.docsToRefetch.find(expectedCrudDoc) != fui.docsToRefetch.end()); -} - -TEST_F(RSRollbackTest, RollbackFetchesTransactionOperationBeforeCommonPoint) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto coll = _createCollection(_opCtx.get(), "test.t", options); - options.uuid = UUID::gen(); - auto txnTable = _createCollection(_opCtx.get(), "config.transactions", options); - - auto commonOperation = makeOpAndRecordId(10); - UUID uuid = coll->uuid(); - auto lsid = makeLogicalSessionIdForTest(); - auto commitTxnEntry = - BSON("ts" << Timestamp(Seconds(10), 12) << "t" << 10LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" - << BSON("applyOps" << BSON_ARRAY(BSON("op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "o" << BSON("_id" << 0 << "a" << 0))) - << "count" << 3) - << "txnNumber" << 1LL << "stmtId" << 3 << "lsid" << lsid.toBSON() << "prevOpTime" - << BSON("ts" << Timestamp(Seconds(10), 11) << "t" << 10LL)); - auto commitTxnOperation = std::make_pair(commitTxnEntry, RecordId(12)); - - auto entryAfterCommonPoint = - BSON("ts" << Timestamp(Seconds(10), 11) << "t" << 10LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" - << BSON("applyOps" << BSON_ARRAY(BSON("op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "o" << BSON("_id" << 1 << "a" << 1))) - << "partialTxn" << true) - << "txnNumber" << 1LL << "stmtId" << 2 << "lsid" << lsid.toBSON() << "prevOpTime" - << BSON("ts" << Timestamp(Seconds(10), 9) << "t" << 10LL)); - auto operationAfterCommonPoint = std::make_pair(entryAfterCommonPoint, RecordId(11)); - - auto entryBeforeCommonPoint = - BSON("ts" << Timestamp(Seconds(10), 9) << "t" << 10LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" - << BSON("applyOps" << BSON_ARRAY(BSON("op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "o" << BSON("_id" << 2 << "a" << 2))) - << "partialTxn" << true) - << "txnNumber" << 1LL << "stmtId" << 1 << "lsid" << lsid.toBSON() << "prevOpTime" - << BSON("ts" << Timestamp(0, 0) << "t" << -1LL)); - auto operationBeforeCommonPoint = std::make_pair(entryBeforeCommonPoint, RecordId(9)); - - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(std::unique_ptr<OplogInterface> oplog, const UUID& txnTableUuid) - : RollbackSourceMock(std::move(oplog)), _txnTableUuid(txnTableUuid) {} - - std::pair<BSONObj, NamespaceString> findOneByUUID(const std::string& db, - UUID uuid, - const BSONObj& filter) const override { - int numFields = 0; - if (uuid == _txnTableUuid) { - // This unit test does not test transaction table fetches. - return {BSONObj(), NamespaceString::kSessionTransactionsTableNamespace}; - } - for (const auto& element : filter) { - ++numFields; - ASSERT_EQUALS("_id", element.fieldNameStringData()) << filter; - } - ASSERT_EQUALS(1, numFields) << filter; - searchedIds.insert(filter.firstElement().numberInt()); - switch (filter.firstElement().numberInt()) { - case 0: - return {BSON("_id" << 0 << "v" << 0), NamespaceString()}; - case 1: - return {BSON("_id" << 1 << "v" << 1), NamespaceString()}; - case 2: - return {BSON("_id" << 2 << "v" << 3), NamespaceString()}; - } - FAIL("Unexpected findOne request") << filter; - return {}; // Unreachable; why doesn't compiler know? - } - - mutable std::multiset<int> searchedIds; - - private: - UUID _txnTableUuid; - - } rollbackSource(std::unique_ptr<OplogInterface>( - new OplogInterfaceMock({commonOperation, operationBeforeCommonPoint})), - txnTable->uuid()); - - ASSERT_OK(syncRollback(_opCtx.get(), - OplogInterfaceMock({commitTxnOperation, - operationAfterCommonPoint, - commonOperation, - operationBeforeCommonPoint}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get())); - ASSERT_EQUALS(3U, rollbackSource.searchedIds.size()); - ASSERT_EQUALS(1U, rollbackSource.searchedIds.count(0)); - ASSERT_EQUALS(1U, rollbackSource.searchedIds.count(1)); - ASSERT_EQUALS(1U, rollbackSource.searchedIds.count(2)); -} - -TEST_F(RSRollbackTest, RollbackIncompleteTransactionReturnsUnrecoverableRollbackError) { - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto coll = _createCollection(_opCtx.get(), "test.t", options); - options.uuid = UUID::gen(); - auto txnTable = _createCollection(_opCtx.get(), "config.transactions", options); - - auto commonOperation = makeOpAndRecordId(10); - UUID uuid = coll->uuid(); - auto lsid = makeLogicalSessionIdForTest(); - auto commitTxnEntry = - BSON("ts" << Timestamp(Seconds(10), 12) << "t" << 10LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" - << BSON("applyOps" << BSON_ARRAY(BSON("op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "o" << BSON("_id" << 0 << "a" << 0))) - << "count" << 3) - << "stmtId" << 3 << "lsid" << lsid.toBSON() << "prevOpTime" - << BSON("ts" << Timestamp(Seconds(10), 11) << "t" << 10LL)); - auto commitTxnOperation = std::make_pair(commitTxnEntry, RecordId(12)); - - auto entryAfterCommonPoint = - BSON("ts" << Timestamp(Seconds(10), 11) << "t" << 10LL << "op" - << "c" - << "ns" - << "admin.$cmd" - << "wall" << Date_t() << "o" - << BSON("applyOps" << BSON_ARRAY(BSON("op" - << "i" - << "ui" << uuid << "ns" - << "test.t" - << "o" << BSON("_id" << 1 << "a" << 1))) - << "partialTxn" << true) - << "txnNumber" << 1LL << "stmtId" << 2 << "lsid" << lsid.toBSON() << "prevOpTime" - << BSON("ts" << Timestamp(Seconds(10), 9) << "t" << 10LL)); - auto operationAfterCommonPoint = std::make_pair(entryAfterCommonPoint, RecordId(11)); - - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(std::unique_ptr<OplogInterface> oplog, const UUID& txnTableUuid) - : RollbackSourceMock(std::move(oplog)), _txnTableUuid(txnTableUuid) {} - - std::pair<BSONObj, NamespaceString> findOneByUUID(const std::string& db, - UUID uuid, - const BSONObj& filter) const override { - if (uuid == _txnTableUuid) { - // This unit test does not test transaction table fetches. - return {BSONObj(), NamespaceString::kSessionTransactionsTableNamespace}; - } else { - return {BSONObj(), NamespaceString()}; - } - } - - private: - UUID _txnTableUuid; - } rollbackSource(std::unique_ptr<OplogInterface>(new OplogInterfaceMock({commonOperation})), - txnTable->uuid()); - - - auto status = syncRollback( - _opCtx.get(), - OplogInterfaceMock({commitTxnOperation, operationAfterCommonPoint, commonOperation}), - rollbackSource, - {}, - {}, - _coordinator, - _replicationProcess.get()); - ASSERT_EQUALS(ErrorCodes::UnrecoverableRollbackError, status.code()); -} - -TEST_F(RSRollbackTest, RollbackFailsIfTransactionDocumentRefetchReturnsDifferentNamespace) { - createOplog(_opCtx.get()); - - // Create a valid FixUpInfo struct for rolling back a single CRUD operation that has a - // transaction number and session id. - FixUpInfo fui; - - auto entryWithTxnNumber = - BSON("ts" << Timestamp(Seconds(2), 1) << "t" << 1LL << "op" - << "i" - << "ui" << UUID::gen() << "ns" - << "test.t" - << "wall" << Date_t() << "o" << BSON("_id" << 1 << "a" << 1) << "txnNumber" << 1LL - << "stmtId" << 1 << "lsid" << makeLogicalSessionIdForTest().toBSON()); - - UUID transactionTableUUID = UUID::gen(); - fui.transactionTableUUID = transactionTableUUID; - - auto commonOperation = makeOpAndRecordId(1); - fui.commonPoint = OpTime(Timestamp(Seconds(1), 1), 1LL); - fui.commonPointOurDiskloc = RecordId(1); - - fui.rbid = 1; - - // The FixUpInfo will have an extra doc to refetch: the corresponding transaction table entry. - ASSERT_OK(updateFixUpInfoFromLocalOplogEntry( - nullptr /* opCtx */, OplogInterfaceMock(), fui, entryWithTxnNumber, false)); - ASSERT_EQ(fui.docsToRefetch.size(), 2U); - - { - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(std::unique_ptr<OplogInterface> oplog) - : RollbackSourceMock(std::move(oplog)) {} - std::pair<BSONObj, NamespaceString> findOneByUUID( - const std::string& db, UUID uuid, const BSONObj& filter) const override { - return {BSONObj(), NamespaceString::kSessionTransactionsTableNamespace}; - } - int getRollbackId() const override { - return 1; - } - }; - - // Should not throw, since findOneByUUID will return the expected namespace. - syncFixUp(_opCtx.get(), - fui, - RollbackSourceLocal( - std::unique_ptr<OplogInterface>(new OplogInterfaceMock({commonOperation}))), - _coordinator, - _replicationProcess.get()); - } - - { - class RollbackSourceLocal : public RollbackSourceMock { - public: - RollbackSourceLocal(std::unique_ptr<OplogInterface> oplog) - : RollbackSourceMock(std::move(oplog)) {} - std::pair<BSONObj, NamespaceString> findOneByUUID( - const std::string& db, UUID uuid, const BSONObj& filter) const override { - return {BSONObj(), NamespaceString("foo.bar")}; - } - int getRollbackId() const override { - return 1; - } - }; - - // The returned namespace will not be the expected one, implying a rename/drop of the - // transactions collection across this node and the sync source, so rollback should fail. - ASSERT_THROWS(syncFixUp(_opCtx.get(), - fui, - RollbackSourceLocal(std::unique_ptr<OplogInterface>( - new OplogInterfaceMock({commonOperation}))), - _coordinator, - _replicationProcess.get()), - RSFatalException); - } -} - -TEST_F(RSRollbackTest, RollbackReturnsImmediatelyOnFailureToTransitionToRollback) { - // On failing to transition to ROLLBACK, rollback() should return immediately and not call - // syncRollback(). We provide an empty oplog so that if syncRollback() is called erroneously, - // we would go fatal. - OplogInterfaceMock localOplogWithSingleOplogEntry({makeNoopOplogEntryAndRecordId(Seconds(1))}); - RollbackSourceMock rollbackSourceWithInvalidOplog(std::make_unique<OplogInterfaceMock>()); - - // Inject ReplicationCoordinator::setFollowerMode() error. We set the current member state - // because it will be logged by rollback() on failing to transition to ROLLBACK. - ASSERT_OK(_coordinator->setFollowerMode(MemberState::RS_SECONDARY)); - _coordinator->failSettingFollowerMode(MemberState::RS_ROLLBACK, ErrorCodes::NotSecondary); - - startCapturingLogMessages(); - rollback(_opCtx.get(), - localOplogWithSingleOplogEntry, - rollbackSourceWithInvalidOplog, - {}, - _coordinator, - _replicationProcess.get()); - stopCapturingLogMessages(); - - ASSERT_EQUALS(1, - countTextFormatLogLinesContaining("Cannot perform replica set state transition")); - ASSERT_EQUALS(MemberState(MemberState::RS_SECONDARY), _coordinator->getMemberState()); -} - -DEATH_TEST_REGEX_F(RSRollbackTest, - RollbackUnrecoverableRollbackErrorTriggersFatalAssertion, - "Unable to complete rollback. A full resync may be needed") { - // rollback() should abort on getting UnrecoverableRollbackError from syncRollback(). An empty - // local oplog will make syncRollback() return the intended error. - OplogInterfaceMock localOplogWithSingleOplogEntry({makeNoopOplogEntryAndRecordId(Seconds(1))}); - RollbackSourceMock rollbackSourceWithInvalidOplog(std::make_unique<OplogInterfaceMock>()); - - rollback(_opCtx.get(), - localOplogWithSingleOplogEntry, - rollbackSourceWithInvalidOplog, - {}, - _coordinator, - _replicationProcess.get()); -} - -TEST_F(RSRollbackTest, RollbackLogsRetryMessageAndReturnsOnNonUnrecoverableRollbackError) { - // If local oplog is empty, syncRollback() returns OplogStartMissing (instead of - // UnrecoverableRollbackError when the remote oplog is missing). rollback() should log a message - // about retrying rollback later before returning. - OplogInterfaceMock localOplogWithNoEntries; - OplogInterfaceMock::Operations remoteOperations({makeNoopOplogEntryAndRecordId(Seconds(1))}); - auto remoteOplog = std::make_unique<OplogInterfaceMock>(remoteOperations); - RollbackSourceMock rollbackSourceWithValidOplog(std::move(remoteOplog)); - auto noopSleepSecsFn = [](int) {}; - - startCapturingLogMessages(); - rollback(_opCtx.get(), - localOplogWithNoEntries, - rollbackSourceWithValidOplog, - {}, - _coordinator, - _replicationProcess.get(), - noopSleepSecsFn); - stopCapturingLogMessages(); - - ASSERT_EQUALS(1, - countTextFormatLogLinesContaining( - "Rollback cannot complete at this time (retrying later)")); - ASSERT_EQUALS(MemberState(MemberState::RS_RECOVERING), _coordinator->getMemberState()); -} - -DEATH_TEST_F(RSRollbackTest, - RollbackTriggersFatalAssertionOnDetectingShardIdentityDocumentRollback, - "shardIdentity document rollback detected. Shutting down to clear in-memory sharding " - "state. Restarting this process should safely return it to a healthy state") { - auto commonOperation = makeNoopOplogEntryAndRecordId(Seconds(1)); - OplogInterfaceMock localOplog({commonOperation}); - RollbackSourceMock rollbackSource( - std::unique_ptr<OplogInterface>(new OplogInterfaceMock({commonOperation}))); - - ASSERT_FALSE(ShardIdentityRollbackNotifier::get(_opCtx.get())->didRollbackHappen()); - ShardIdentityRollbackNotifier::get(_opCtx.get())->recordThatRollbackHappened(); - ASSERT_TRUE(ShardIdentityRollbackNotifier::get(_opCtx.get())->didRollbackHappen()); - - createOplog(_opCtx.get()); - rollback(_opCtx.get(), localOplog, rollbackSource, {}, _coordinator, _replicationProcess.get()); -} - -DEATH_TEST_REGEX_F( - RSRollbackTest, - RollbackTriggersFatalAssertionOnFailingToTransitionToRecoveringAfterSyncRollbackReturns, - "Failed to perform replica set state transition") { - auto commonOperation = makeNoopOplogEntryAndRecordId(Seconds(1)); - OplogInterfaceMock localOplog({commonOperation}); - RollbackSourceMock rollbackSource( - std::unique_ptr<OplogInterface>(new OplogInterfaceMock({commonOperation}))); - - _coordinator->failSettingFollowerMode(MemberState::RS_RECOVERING, ErrorCodes::IllegalOperation); - - createOplog(_opCtx.get()); - rollback(_opCtx.get(), localOplog, rollbackSource, {}, _coordinator, _replicationProcess.get()); -} - -// The testcases used here are trying to detect off-by-one errors in -// FixUpInfo::removeAllDocsToRefectchFor. -TEST(FixUpInfoTest, RemoveAllDocsToRefetchForWorks) { - const auto normalHolder = BSON("" << OID::gen()); - const auto normalKey = normalHolder.firstElement(); - - UUID uuid1 = UUID::gen(); - UUID uuid2 = UUID::gen(); - UUID uuid3 = UUID::gen(); - - // Can't use ASSERT_EQ with this since it isn't ostream-able. Failures will at least give you - // the size. If that isn't enough, use GDB. - using DocSet = std::set<DocID>; - - FixUpInfo fui; - fui.docsToRefetch = { - DocID::minFor(uuid1), - DocID{{}, normalKey, uuid1}, - DocID::maxFor(uuid1), - - DocID::minFor(uuid2), - DocID{{}, normalKey, uuid2}, - DocID::maxFor(uuid2), - - DocID::minFor(uuid3), - DocID{{}, normalKey, uuid3}, - DocID::maxFor(uuid3), - }; - - // Remove from the middle. - fui.removeAllDocsToRefetchFor(uuid2); - ASSERT((fui.docsToRefetch == - DocSet{ - DocID::minFor(uuid1), - DocID{{}, normalKey, uuid1}, - DocID::maxFor(uuid1), - - DocID::minFor(uuid3), - DocID{{}, normalKey, uuid3}, - DocID::maxFor(uuid3), - })) - << "remaining docs: " << fui.docsToRefetch.size(); - - // Remove from the end. - fui.removeAllDocsToRefetchFor(uuid3); - ASSERT((fui.docsToRefetch == - DocSet{ - DocID::minFor(uuid1), // This comment helps clang-format. - DocID{{}, normalKey, uuid1}, - DocID::maxFor(uuid1), - })) - << "remaining docs: " << fui.docsToRefetch.size(); - - // Everything else. - fui.removeAllDocsToRefetchFor(uuid1); - ASSERT((fui.docsToRefetch == DocSet{})) << "remaining docs: " << fui.docsToRefetch.size(); -} - -TEST_F(RSRollbackTest, RollbackInvalidatesDefaultRWConcernCache) { - auto& rwcDefaults = ReadWriteConcernDefaults::get(getServiceContext()); - - // Put initial defaults in the cache. - { - RWConcernDefault origDefaults; - origDefaults.setUpdateOpTime(Timestamp(10, 20)); - origDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(1234)); - _lookupMock.setLookupCallReturnValue(std::move(origDefaults)); - } - auto origCachedDefaults = rwcDefaults.getDefault(_opCtx.get()); - ASSERT_EQ(Timestamp(10, 20), *origCachedDefaults.getUpdateOpTime()); - ASSERT_EQ(Date_t::fromMillisSinceEpoch(1234), *origCachedDefaults.getUpdateWallClockTime()); - - // Change the mock's defaults, but don't invalidate the cache yet. The cache should still return - // the original defaults. - { - RWConcernDefault newDefaults; - newDefaults.setUpdateOpTime(Timestamp(50, 20)); - newDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(5678)); - _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); - - auto cachedDefaults = rwcDefaults.getDefault(_opCtx.get()); - ASSERT_EQ(Timestamp(10, 20), *cachedDefaults.getUpdateOpTime()); - ASSERT_EQ(Date_t::fromMillisSinceEpoch(1234), *cachedDefaults.getUpdateWallClockTime()); - } - - // Rollback via refetch should invalidate the cache and getting the defaults should now return - // the latest value. - createOplog(_opCtx.get()); - CollectionOptions options; - options.uuid = UUID::gen(); - auto coll = _createCollection(_opCtx.get(), "test.t", options); - BSONObj doc = BSON("_id" << 0 << "a" << 1); - _testRollbackDelete(_opCtx.get(), _coordinator, _replicationProcess.get(), coll->uuid(), doc); - - auto newCachedDefaults = rwcDefaults.getDefault(_opCtx.get()); - ASSERT_EQ(Timestamp(50, 20), *newCachedDefaults.getUpdateOpTime()); - ASSERT_EQ(Date_t::fromMillisSinceEpoch(5678), *newCachedDefaults.getUpdateWallClockTime()); -} - -} // namespace -} // namespace mongo diff --git a/src/mongo/db/repl/storage_interface_impl_test.cpp b/src/mongo/db/repl/storage_interface_impl_test.cpp index 060e18a0361..6912206db8c 100644 --- a/src/mongo/db/repl/storage_interface_impl_test.cpp +++ b/src/mongo/db/repl/storage_interface_impl_test.cpp @@ -52,6 +52,7 @@ #include "mongo/db/repl/replication_coordinator_mock.h" #include "mongo/db/repl/storage_interface_impl.h" #include "mongo/db/service_context_d_test_fixture.h" +#include "mongo/db/storage/snapshot_manager.h" #include "mongo/stdx/thread.h" #include "mongo/transport/transport_layer_mock.h" #include "mongo/unittest/unittest.h" @@ -385,12 +386,6 @@ TEST_F(StorageInterfaceImplTest, GetRollbackIDReturnsBadStatusIfRollbackIDIsNotI ASSERT_EQUALS(ErrorCodes::TypeMismatch, storage.getRollbackID(opCtx).getStatus()); } -TEST_F(StorageInterfaceImplTest, SnapshotSupported) { - auto opCtx = getOperationContext(); - Status status = opCtx->recoveryUnit()->majorityCommittedSnapshotAvailable(); - ASSERT(status.isOK()); -} - TEST_F(StorageInterfaceImplTest, InsertDocumentsReturnsOKWhenNoOperationsAreGiven) { auto opCtx = getOperationContext(); auto nss = makeNamespace(_agent); diff --git a/src/mongo/db/repl/storage_timestamp_test.cpp b/src/mongo/db/repl/storage_timestamp_test.cpp index f03c8306fa3..fead4dab5ee 100644 --- a/src/mongo/db/repl/storage_timestamp_test.cpp +++ b/src/mongo/db/repl/storage_timestamp_test.cpp @@ -251,12 +251,7 @@ public: repl::ReplicationConsistencyMarkers* _consistencyMarkers; StorageTimestampTest() - : ServiceContextMongoDTest("wiredTiger", - ServiceContextMongoDTest::RepairAction::kNoRepair, - kDefaultStorageEngineInitFlags, - true, // useReplSettings - true // useMockClock - ) { + : ServiceContextMongoDTest(Options{}.useReplSettings(true).useMockClock(true)) { // Set up mongod. ServiceContextMongoDTest::setUp(); diff --git a/src/mongo/db/repl/tenant_migration_donor_service_test.cpp b/src/mongo/db/repl/tenant_migration_donor_service_test.cpp index 765c8de625f..e76097432b6 100644 --- a/src/mongo/db/repl/tenant_migration_donor_service_test.cpp +++ b/src/mongo/db/repl/tenant_migration_donor_service_test.cpp @@ -60,11 +60,6 @@ class TenantMigrationDonorServiceTest : public ServiceContextMongoDTest { ServiceContextMongoDTest::setUp(); auto serviceContext = getServiceContext(); - // Set up clocks. - serviceContext->setFastClockSource(std::make_unique<SharedClockSourceAdapter>(_clkSource)); - serviceContext->setPreciseClockSource( - std::make_unique<SharedClockSourceAdapter>(_clkSource)); - WaitForMajorityService::get(getServiceContext()).startup(getServiceContext()); { @@ -130,9 +125,11 @@ class TenantMigrationDonorServiceTest : public ServiceContextMongoDTest { } protected: + TenantMigrationDonorServiceTest() : ServiceContextMongoDTest(Options{}.useMockClock(true)) {} + PrimaryOnlyServiceRegistry* _registry; PrimaryOnlyService* _service; - std::shared_ptr<ClockSourceMock> _clkSource = std::make_shared<ClockSourceMock>(); + ClockSourceMock _clkSource; long long _term = 0; const TenantMigrationPEMPayload kDonorPEMPayload = [&] { @@ -170,7 +167,7 @@ protected: TEST_F(TenantMigrationDonorServiceTest, CheckSettingMigrationStartDate) { // Advance the clock by some arbitrary amount of time so we are not starting at 0 seconds. - _clkSource->advance(Milliseconds(10000)); + _clkSource.advance(Milliseconds(10000)); auto taskFp = globalFailPointRegistry().find("pauseTenantMigrationAfterPersistingInitialDonorStateDoc"); diff --git a/src/mongo/db/repl/tenant_migration_recipient_service_test.cpp b/src/mongo/db/repl/tenant_migration_recipient_service_test.cpp index 2763fe2bfd6..589b3e63cd0 100644 --- a/src/mongo/db/repl/tenant_migration_recipient_service_test.cpp +++ b/src/mongo/db/repl/tenant_migration_recipient_service_test.cpp @@ -181,11 +181,6 @@ public: ConnectionString::setConnectionHook(mongo::MockConnRegistry::get()->getConnStrHook()); - // Set up clocks. - serviceContext->setFastClockSource(std::make_unique<SharedClockSourceAdapter>(_clkSource)); - serviceContext->setPreciseClockSource( - std::make_unique<SharedClockSourceAdapter>(_clkSource)); - WaitForMajorityService::get(serviceContext).startup(serviceContext); // Automatically mark the state doc garbage collectable after data sync completion. @@ -256,10 +251,6 @@ public: auto fetchCommittedTransactionsFp = globalFailPointRegistry().find("skipFetchingCommittedTransactions"); fetchCommittedTransactionsFp->setMode(FailPoint::alwaysOn); - - // Timestamps of "0 seconds" are not allowed, so we must advance our clock mock to the first - // real second. - _clkSource->advance(Milliseconds(1000)); } void tearDown() override { @@ -305,6 +296,9 @@ public: } protected: + TenantMigrationRecipientServiceTest() + : ServiceContextMongoDTest(Options{}.useMockClock(true)) {} + PrimaryOnlyServiceRegistry* _registry; PrimaryOnlyService* _service; long long _term = 0; @@ -416,14 +410,14 @@ protected: * Advance the time by millis on both clock source mocks. */ void advanceTime(Milliseconds millis) { - _clkSource->advance(millis); + _clkSource.advance(millis); } /** * Assumes that the times on both clock source mocks is the same. */ Date_t now() { - return _clkSource->now(); + return _clkSource.now(); }; /* @@ -456,11 +450,11 @@ protected: } ClockSource* clock() { - return _clkSource.get(); + return &_clkSource; } private: - std::shared_ptr<ClockSourceMock> _clkSource = std::make_shared<ClockSourceMock>(); + ClockSourceMock _clkSource; unittest::MinimumLoggedSeverityGuard _replicationSeverityGuard{ logv2::LogComponent::kReplication, logv2::LogSeverity::Debug(1)}; diff --git a/src/mongo/db/s/balancer/migration_test_fixture.h b/src/mongo/db/s/balancer/migration_test_fixture.h index 669133e0f36..76d13c587d7 100644 --- a/src/mongo/db/s/balancer/migration_test_fixture.h +++ b/src/mongo/db/s/balancer/migration_test_fixture.h @@ -46,6 +46,9 @@ namespace mongo { class MigrationTestFixture : public ConfigServerTestFixture { protected: + explicit MigrationTestFixture(Options options = {}) + : ConfigServerTestFixture(std::move(options)) {} + void setUp() override; /** diff --git a/src/mongo/db/s/config/config_server_test_fixture.cpp b/src/mongo/db/s/config/config_server_test_fixture.cpp index 6d378adf68d..6efbdd6d15c 100644 --- a/src/mongo/db/s/config/config_server_test_fixture.cpp +++ b/src/mongo/db/s/config/config_server_test_fixture.cpp @@ -92,7 +92,8 @@ namespace { ReadPreferenceSetting kReadPref(ReadPreference::PrimaryOnly); } // namespace -ConfigServerTestFixture::ConfigServerTestFixture() = default; +ConfigServerTestFixture::ConfigServerTestFixture(Options options, bool setUpMajorityReads) + : ShardingMongodTestFixture(std::move(options), setUpMajorityReads) {} ConfigServerTestFixture::~ConfigServerTestFixture() = default; diff --git a/src/mongo/db/s/config/config_server_test_fixture.h b/src/mongo/db/s/config/config_server_test_fixture.h index f2d2d0a1ef0..2a990b778d7 100644 --- a/src/mongo/db/s/config/config_server_test_fixture.h +++ b/src/mongo/db/s/config/config_server_test_fixture.h @@ -48,7 +48,7 @@ class Shard; */ class ConfigServerTestFixture : public ShardingMongodTestFixture { protected: - ConfigServerTestFixture(); + explicit ConfigServerTestFixture(Options options = {}, bool setUpMajorityReads = true); ~ConfigServerTestFixture(); void setUp() override; diff --git a/src/mongo/db/s/config/sharding_catalog_manager_config_initialization_test.cpp b/src/mongo/db/s/config/sharding_catalog_manager_config_initialization_test.cpp index 943be85f854..6c95002292b 100644 --- a/src/mongo/db/s/config/sharding_catalog_manager_config_initialization_test.cpp +++ b/src/mongo/db/s/config/sharding_catalog_manager_config_initialization_test.cpp @@ -81,6 +81,9 @@ void assertBSONObjsSame(const std::vector<BSONObj>& expectedBSON, class ConfigInitializationTest : public ConfigServerTestFixture { protected: + // TODO (SERVER-65308): Use wiredTiger. + ConfigInitializationTest() : ConfigServerTestFixture(Options{}.engine("ephemeralForTest")) {} + /* * Initializes the sharding state and locks both the config db and rstl. */ diff --git a/src/mongo/db/s/migration_chunk_cloner_source_legacy_test.cpp b/src/mongo/db/s/migration_chunk_cloner_source_legacy_test.cpp index 3f01fe4d3a7..0ec28d4b862 100644 --- a/src/mongo/db/s/migration_chunk_cloner_source_legacy_test.cpp +++ b/src/mongo/db/s/migration_chunk_cloner_source_legacy_test.cpp @@ -67,6 +67,8 @@ const ConnectionString kRecipientConnStr = class MigrationChunkClonerSourceLegacyTest : public ShardServerTestFixture { protected: + MigrationChunkClonerSourceLegacyTest() : ShardServerTestFixture(Options{}.useMockClock(true)) {} + void setUp() override { ShardServerTestFixture::setUp(); @@ -98,14 +100,6 @@ protected: ->setFindHostReturnValue(kRecipientConnStr.getServers()[0]); } - auto clockSource = std::make_unique<ClockSourceMock>(); - - // Timestamps of "0 seconds" are not allowed, so we must advance our clock mock to the first - // real second. - clockSource->advance(Seconds(1)); - - operationContext()->getServiceContext()->setFastClockSource(std::move(clockSource)); - _lsid = makeLogicalSessionId(operationContext()); } diff --git a/src/mongo/db/s/resharding/resharding_coordinator_service_test.cpp b/src/mongo/db/s/resharding/resharding_coordinator_service_test.cpp index 716a7278a36..e3b59a8068f 100644 --- a/src/mongo/db/s/resharding/resharding_coordinator_service_test.cpp +++ b/src/mongo/db/s/resharding/resharding_coordinator_service_test.cpp @@ -127,6 +127,10 @@ class ReshardingCoordinatorServiceTest : public ConfigServerTestFixture { public: using ReshardingCoordinator = ReshardingCoordinatorService::ReshardingCoordinator; + // TODO (SERVER-65302): Use wiredTiger. + ReshardingCoordinatorServiceTest() + : ConfigServerTestFixture(Options{}.engine("ephemeralForTest")) {} + std::unique_ptr<repl::PrimaryOnlyService> makeService(ServiceContext* serviceContext) { return std::make_unique<ReshardingCoordinatorServiceForTest>(serviceContext); } diff --git a/src/mongo/db/s/resharding/resharding_donor_recipient_common_test.cpp b/src/mongo/db/s/resharding/resharding_donor_recipient_common_test.cpp index f4437351785..3fccff9812c 100644 --- a/src/mongo/db/s/resharding/resharding_donor_recipient_common_test.cpp +++ b/src/mongo/db/s/resharding/resharding_donor_recipient_common_test.cpp @@ -32,6 +32,7 @@ #include "mongo/db/db_raii.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/persistent_task_store.h" +#include "mongo/db/repl/oplog.h" #include "mongo/db/repl/wait_for_majority_service.h" #include "mongo/db/s/collection_sharding_runtime.h" #include "mongo/db/s/operation_sharding_state.h" @@ -315,8 +316,11 @@ public: ASSERT_OK(replCoord->setFollowerMode(repl::MemberState::RS_PRIMARY)); ASSERT_OK(replCoord->updateTerm(operationContext(), _term)); - replCoord->setMyLastAppliedOpTimeAndWallTime( - repl::OpTimeAndWallTime(repl::OpTime(Timestamp(1, 1), _term), Date_t())); + + WriteUnitOfWork wuow{operationContext()}; + replCoord->setMyLastAppliedOpTimeAndWallTime(repl::OpTimeAndWallTime( + repl::OpTime(repl::getNextOpTime(operationContext()).getTimestamp(), _term), Date_t())); + wuow.commit(); _primaryOnlyServiceRegistry->onStepUpComplete(operationContext(), _term); } diff --git a/src/mongo/db/s/resharding/resharding_oplog_batch_applier_test.cpp b/src/mongo/db/s/resharding/resharding_oplog_batch_applier_test.cpp index f1361bfb165..c9d188252a8 100644 --- a/src/mongo/db/s/resharding/resharding_oplog_batch_applier_test.cpp +++ b/src/mongo/db/s/resharding/resharding_oplog_batch_applier_test.cpp @@ -298,6 +298,11 @@ public: << sessionTxnRecord.toBSON() << ", " << foundOp; } +protected: + // TODO (SERVER-65307): Use wiredTiger. + ReshardingOplogBatchApplierTest() + : ServiceContextMongoDTest(Options{}.engine("ephemeralForTest")) {} + private: ChunkManager makeChunkManagerForSourceCollection() { const OID epoch = OID::gen(); diff --git a/src/mongo/db/s/resharding/resharding_oplog_session_application_test.cpp b/src/mongo/db/s/resharding/resharding_oplog_session_application_test.cpp index dfaa498e4f6..00d3f6af370 100644 --- a/src/mongo/db/s/resharding/resharding_oplog_session_application_test.cpp +++ b/src/mongo/db/s/resharding/resharding_oplog_session_application_test.cpp @@ -310,6 +310,11 @@ public: return _oplogBufferNss; } +protected: + // TODO (SERVER-65306): Use wiredTiger. + ReshardingOplogSessionApplicationTest() + : ServiceContextMongoDTest(Options{}.engine("ephemeralForTest")) {} + private: // Used for pre/post image oplog entry lookup. const ShardId _donorShardId{"donor-0"}; diff --git a/src/mongo/db/s/resharding/resharding_txn_cloner_test.cpp b/src/mongo/db/s/resharding/resharding_txn_cloner_test.cpp index c41f4ce7db3..290a8837e9f 100644 --- a/src/mongo/db/s/resharding/resharding_txn_cloner_test.cpp +++ b/src/mongo/db/s/resharding/resharding_txn_cloner_test.cpp @@ -150,6 +150,9 @@ class ReshardingTxnClonerTest : public ShardServerTestFixture { } protected: + // TODO (SERVER-65303): Use wiredTiger. + ReshardingTxnClonerTest() : ShardServerTestFixture(Options{}.engine("ephemeralForTest")) {} + const UUID kDefaultReshardingId = UUID::gen(); const std::vector<ShardId> kTwoShardIdList{_myShardName, {"otherShardName"}}; const std::vector<ReshardingSourceId> kTwoSourceIdList = { diff --git a/src/mongo/db/s/shard_local_test.cpp b/src/mongo/db/s/shard_local_test.cpp index 9cbed53e039..427c624b0e8 100644 --- a/src/mongo/db/s/shard_local_test.cpp +++ b/src/mongo/db/s/shard_local_test.cpp @@ -39,6 +39,7 @@ #include "mongo/db/repl/replication_coordinator_mock.h" #include "mongo/db/s/shard_local.h" #include "mongo/db/service_context_d_test_fixture.h" +#include "mongo/db/storage/snapshot_manager.h" #include "mongo/db/write_concern_options.h" #include "mongo/s/client/shard_registry.h" @@ -90,6 +91,14 @@ void ShardLocalTest::setUp() { new repl::ReplicationCoordinatorMock(_opCtx->getServiceContext(), replSettings))); ASSERT_OK(repl::ReplicationCoordinator::get(getGlobalServiceContext()) ->setFollowerMode(repl::MemberState::RS_PRIMARY)); + + repl::createOplog(_opCtx.get()); + + // Set a committed snapshot so that we can perform majority reads. + WriteUnitOfWork wuow{_opCtx.get()}; + _opCtx->getServiceContext()->getStorageEngine()->getSnapshotManager()->setCommittedSnapshot( + repl::getNextOpTime(_opCtx.get()).getTimestamp()); + wuow.commit(); } void ShardLocalTest::tearDown() { diff --git a/src/mongo/db/s/shard_server_test_fixture.cpp b/src/mongo/db/s/shard_server_test_fixture.cpp index 2c65e268b1b..fe4de75a820 100644 --- a/src/mongo/db/s/shard_server_test_fixture.cpp +++ b/src/mongo/db/s/shard_server_test_fixture.cpp @@ -44,7 +44,8 @@ namespace mongo { const HostAndPort ShardServerTestFixture::kConfigHostAndPort("dummy", 123); -ShardServerTestFixture::ShardServerTestFixture() = default; +ShardServerTestFixture::ShardServerTestFixture(Options options, bool setUpMajorityReads) + : ShardingMongodTestFixture(std::move(options), setUpMajorityReads) {} ShardServerTestFixture::~ShardServerTestFixture() = default; diff --git a/src/mongo/db/s/shard_server_test_fixture.h b/src/mongo/db/s/shard_server_test_fixture.h index 57adbc030ba..a92316ed1f9 100644 --- a/src/mongo/db/s/shard_server_test_fixture.h +++ b/src/mongo/db/s/shard_server_test_fixture.h @@ -40,7 +40,7 @@ namespace mongo { */ class ShardServerTestFixture : public ShardingMongodTestFixture { protected: - ShardServerTestFixture(); + ShardServerTestFixture(Options options = {}, bool setUpMajorityReads = true); ~ShardServerTestFixture(); void setUp() override; diff --git a/src/mongo/db/s/sharding_mongod_test_fixture.cpp b/src/mongo/db/s/sharding_mongod_test_fixture.cpp index abf6df3ff4f..a05fddaa213 100644 --- a/src/mongo/db/s/sharding_mongod_test_fixture.cpp +++ b/src/mongo/db/s/sharding_mongod_test_fixture.cpp @@ -56,6 +56,7 @@ #include "mongo/db/s/op_observer_sharding_impl.h" #include "mongo/db/s/shard_local.h" #include "mongo/db/s/shard_server_op_observer.h" +#include "mongo/db/storage/snapshot_manager.h" #include "mongo/executor/task_executor_pool.h" #include "mongo/executor/thread_pool_task_executor_test_fixture.h" #include "mongo/rpc/metadata/repl_set_metadata.h" @@ -85,7 +86,8 @@ using repl::ReplicationCoordinatorMock; using repl::ReplSettings; using unittest::assertGet; -ShardingMongodTestFixture::ShardingMongodTestFixture() {} +ShardingMongodTestFixture::ShardingMongodTestFixture(Options options, bool setUpMajorityReads) + : ServiceContextMongoDTest(std::move(options)), _setUpMajorityReads(setUpMajorityReads) {} ShardingMongodTestFixture::~ShardingMongodTestFixture() = default; @@ -281,6 +283,13 @@ void ShardingMongodTestFixture::setUp() { // testing this release's code, not backwards compatibility code. // (Generic FCV reference): This FCV reference should exist across LTS binary versions. serverGlobalParams.mutableFeatureCompatibility.setVersion(multiversion::GenericFCV::kLatest); + + if (_setUpMajorityReads && service->getStorageEngine()->getSnapshotManager()) { + WriteUnitOfWork wuow{operationContext()}; + service->getStorageEngine()->getSnapshotManager()->setCommittedSnapshot( + repl::getNextOpTime(operationContext()).getTimestamp()); + wuow.commit(); + } } void ShardingMongodTestFixture::tearDown() { diff --git a/src/mongo/db/s/sharding_mongod_test_fixture.h b/src/mongo/db/s/sharding_mongod_test_fixture.h index b80d234a9ac..1c3a64e5a70 100644 --- a/src/mongo/db/s/sharding_mongod_test_fixture.h +++ b/src/mongo/db/s/sharding_mongod_test_fixture.h @@ -52,7 +52,7 @@ namespace mongo { class ShardingMongodTestFixture : public ShardingTestFixtureCommon, public ServiceContextMongoDTest { protected: - ShardingMongodTestFixture(); + ShardingMongodTestFixture(Options options = {}, bool allowMajorityReads = true); ~ShardingMongodTestFixture(); void setUp() override; @@ -143,6 +143,10 @@ private: // Records if a component has been shut down, so that it is only shut down once. bool _executorPoolShutDown = false; + + // Whether the test fixture should set a committed snapshot during setup so that tests can + // perform majority reads without doing any writes. + bool _setUpMajorityReads; }; } // namespace mongo diff --git a/src/mongo/db/s/vector_clock_config_server_test.cpp b/src/mongo/db/s/vector_clock_config_server_test.cpp index 14694f697d0..f304b321323 100644 --- a/src/mongo/db/s/vector_clock_config_server_test.cpp +++ b/src/mongo/db/s/vector_clock_config_server_test.cpp @@ -42,12 +42,12 @@ namespace { class VectorClockConfigServerTest : public ConfigServerTestFixture { protected: + VectorClockConfigServerTest() + : ConfigServerTestFixture(Options{}.useMockClock(true), false /* setUpMajorityReads */) {} + void setUp() override { ConfigServerTestFixture::setUp(); - auto clockSource = std::make_unique<ClockSourceMock>(); - getServiceContext()->setFastClockSource(std::move(clockSource)); - auto keysCollectionClient = std::make_unique<KeysCollectionClientSharded>( Grid::get(operationContext())->catalogClient()); diff --git a/src/mongo/db/s/vector_clock_shard_server_test.cpp b/src/mongo/db/s/vector_clock_shard_server_test.cpp index 0674419fc34..de575294c3c 100644 --- a/src/mongo/db/s/vector_clock_shard_server_test.cpp +++ b/src/mongo/db/s/vector_clock_shard_server_test.cpp @@ -44,12 +44,12 @@ namespace { class VectorClockShardServerTest : public ShardServerTestFixture { protected: + VectorClockShardServerTest() + : ShardServerTestFixture(Options{}.useMockClock(true), false /* setUpMajorityReads */) {} + void setUp() override { ShardServerTestFixture::setUp(); - auto clockSource = std::make_unique<ClockSourceMock>(); - getServiceContext()->setFastClockSource(std::move(clockSource)); - auto keysCollectionClient = std::make_unique<KeysCollectionClientDirect>(); VectorClockMutable::get(getServiceContext()) diff --git a/src/mongo/db/serverless/shard_split_donor_service_test.cpp b/src/mongo/db/serverless/shard_split_donor_service_test.cpp index 2e0e3822c64..ad51e94e358 100644 --- a/src/mongo/db/serverless/shard_split_donor_service_test.cpp +++ b/src/mongo/db/serverless/shard_split_donor_service_test.cpp @@ -228,6 +228,10 @@ public: } protected: + // TODO (SERVER-65218): Use wiredTiger. + ShardSplitDonorServiceTest() + : repl::PrimaryOnlyServiceMongoDTest(Options{}.engine("ephemeralForTest")) {} + std::unique_ptr<repl::PrimaryOnlyService> makeService(ServiceContext* serviceContext) override { return std::make_unique<ShardSplitDonorService>(serviceContext); } diff --git a/src/mongo/db/service_context_d_test_fixture.cpp b/src/mongo/db/service_context_d_test_fixture.cpp index c5aaed6c870..96b521111f8 100644 --- a/src/mongo/db/service_context_d_test_fixture.cpp +++ b/src/mongo/db/service_context_d_test_fixture.cpp @@ -46,6 +46,7 @@ #include "mongo/db/index_builds_coordinator_mongod.h" #include "mongo/db/op_observer_registry.h" #include "mongo/db/repl/repl_settings.h" +#include "mongo/db/repl/replication_coordinator_mock.h" #include "mongo/db/s/collection_sharding_state_factory_shard.h" #include "mongo/db/service_entry_point_mongod.h" #include "mongo/db/storage/control/storage_control.h" @@ -58,31 +59,22 @@ namespace mongo { -ServiceContextMongoDTest::ServiceContextMongoDTest() - : ServiceContextMongoDTest("ephemeralForTest") {} - -ServiceContextMongoDTest::ServiceContextMongoDTest(std::string engine) - : ServiceContextMongoDTest(engine, RepairAction::kNoRepair) {} - -ServiceContextMongoDTest::ServiceContextMongoDTest(std::string engine, - RepairAction repair, - StorageEngineInitFlags initFlags, - bool useReplSettings, - bool useMockClock) +ServiceContextMongoDTest::ServiceContextMongoDTest(Options options) : _tempDir("service_context_d_test_fixture") { - if (useReplSettings) { + if (options._useReplSettings) { repl::ReplSettings replSettings; replSettings.setOplogSizeBytes(10 * 1024 * 1024); replSettings.setReplSetString("rs0"); setGlobalReplSettings(replSettings); } - _stashedStorageParams.engine = std::exchange(storageGlobalParams.engine, std::move(engine)); + _stashedStorageParams.engine = + std::exchange(storageGlobalParams.engine, std::move(options._engine)); _stashedStorageParams.engineSetByUser = std::exchange(storageGlobalParams.engineSetByUser, true); _stashedStorageParams.repair = - std::exchange(storageGlobalParams.repair, (repair == RepairAction::kRepair)); + std::exchange(storageGlobalParams.repair, (options._repair == RepairAction::kRepair)); _stashedServerParams.enableMajorityReadConcern = serverGlobalParams.enableMajorityReadConcern; if (storageGlobalParams.engine == "ephemeralForTest" || @@ -95,7 +87,7 @@ ServiceContextMongoDTest::ServiceContextMongoDTest(std::string engine, } auto const serviceContext = getServiceContext(); - if (useMockClock) { + if (options._useMockClock) { // Copied from dbtests.cpp. DBTests sets up a controlled mock clock while // ServiceContextMongoDTest uses the system clock. Tests moved from dbtests to unittests may // depend on a deterministic clock. @@ -125,7 +117,7 @@ ServiceContextMongoDTest::ServiceContextMongoDTest(std::string engine, // Since unit tests start in their own directories, by default skip lock file and metadata file // for faster startup. auto opCtx = serviceContext->makeOperationContext(getClient()); - initializeStorageEngine(opCtx.get(), initFlags); + initializeStorageEngine(opCtx.get(), options._initFlags); StorageControl::startStorageControls(serviceContext, true /*forTestOnly*/); DatabaseHolder::set(serviceContext, std::make_unique<DatabaseHolderImpl>()); @@ -142,10 +134,16 @@ ServiceContextMongoDTest::~ServiceContextMongoDTest() { CollectionShardingStateFactory::clear(getServiceContext()); { - auto opCtx = getClient()->makeOperationContext(); - Lock::GlobalLock glk(opCtx.get(), MODE_X); - auto databaseHolder = DatabaseHolder::get(opCtx.get()); - databaseHolder->closeAll(opCtx.get()); + ServiceContext::UniqueOperationContext uniqueOpCtx; + auto opCtx = getClient()->getOperationContext(); + if (!opCtx) { + uniqueOpCtx = getClient()->makeOperationContext(); + opCtx = uniqueOpCtx.get(); + } + + Lock::GlobalLock glk(opCtx, MODE_X); + auto databaseHolder = DatabaseHolder::get(opCtx); + databaseHolder->closeAll(opCtx); } shutdownGlobalStorageEngineCleanly(getServiceContext()); diff --git a/src/mongo/db/service_context_d_test_fixture.h b/src/mongo/db/service_context_d_test_fixture.h index 539bf087634..f615ffe31d8 100644 --- a/src/mongo/db/service_context_d_test_fixture.h +++ b/src/mongo/db/service_context_d_test_fixture.h @@ -46,17 +46,43 @@ public: protected: enum class RepairAction { kNoRepair, kRepair }; - ServiceContextMongoDTest(); - - /** - * Build a ServiceContextMongoDTest, using the named storage engine. - */ - explicit ServiceContextMongoDTest(std::string engine); - ServiceContextMongoDTest(std::string engine, - RepairAction repair, - StorageEngineInitFlags initFlags = kDefaultStorageEngineInitFlags, - bool useReplSettings = false, - bool useMockClock = false); + class Options { + public: + Options(){}; + + Options& engine(std::string engine) { + _engine = std::move(engine); + return *this; + } + Options& repair(RepairAction repair) { + _repair = repair; + return *this; + } + Options& initFlags(StorageEngineInitFlags initFlags) { + _initFlags = initFlags; + return *this; + } + Options& useReplSettings(bool useReplSettings) { + _useReplSettings = useReplSettings; + return *this; + } + Options& useMockClock(bool useMockClock) { + _useMockClock = useMockClock; + return *this; + } + + private: + std::string _engine = "wiredTiger"; + RepairAction _repair = RepairAction::kNoRepair; + StorageEngineInitFlags _initFlags = kDefaultStorageEngineInitFlags; + bool _useReplSettings = false; + bool _useMockClock = false; + + friend class ServiceContextMongoDTest; + }; + + explicit ServiceContextMongoDTest(Options options = {}); + virtual ~ServiceContextMongoDTest(); void tearDown() override; diff --git a/src/mongo/db/service_context_devnull_test_fixture.cpp b/src/mongo/db/service_context_devnull_test_fixture.cpp index d1f69034f5e..c420b7eaa2c 100644 --- a/src/mongo/db/service_context_devnull_test_fixture.cpp +++ b/src/mongo/db/service_context_devnull_test_fixture.cpp @@ -34,6 +34,6 @@ namespace mongo { ServiceContextDevnullTestFixture::ServiceContextDevnullTestFixture() - : ServiceContextMongoDTest("devnull") {} + : ServiceContextMongoDTest(Options{}.engine("devnull")) {} } // namespace mongo diff --git a/src/mongo/db/session_catalog_mongod_test.cpp b/src/mongo/db/session_catalog_mongod_test.cpp index e7519fc6f9c..1b4ea856605 100644 --- a/src/mongo/db/session_catalog_mongod_test.cpp +++ b/src/mongo/db/session_catalog_mongod_test.cpp @@ -42,6 +42,8 @@ namespace { class MongoDSessionCatalogTest : public ServiceContextMongoDTest { protected: + MongoDSessionCatalogTest() : ServiceContextMongoDTest(Options{}.useMockClock(true)) {} + void setUp() override { ServiceContextMongoDTest::setUp(); const auto service = getServiceContext(); @@ -50,8 +52,6 @@ protected: repl::ReplicationCoordinator::set(service, std::move(replCoord)); repl::createOplog(_opCtx); - - service->setFastClockSource(std::make_unique<ClockSourceMock>()); } ClockSourceMock* clock() { diff --git a/src/mongo/db/storage/kv/durable_catalog_test.cpp b/src/mongo/db/storage/kv/durable_catalog_test.cpp index 787a6c3b353..14994f957e8 100644 --- a/src/mongo/db/storage/kv/durable_catalog_test.cpp +++ b/src/mongo/db/storage/kv/durable_catalog_test.cpp @@ -62,6 +62,9 @@ static const long kExpectedVersion = 1; class DurableCatalogTest : public CatalogTestFixture { public: + // TODO (SERVER-65189): Use wiredTiger. + DurableCatalogTest() : CatalogTestFixture(Options{}.engine("ephemeralForTest")) {} + void setUp() final { CatalogTestFixture::setUp(); @@ -224,6 +227,7 @@ TEST_F(DurableCatalogTest, CanSetIndividualPathComponentOfBtreeIndexAsMultikey) auto collection = getCollection(); { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(collection->setIndexIsMultikey(operationContext(), indexEntry->descriptor()->indexName(), @@ -244,6 +248,7 @@ TEST_F(DurableCatalogTest, MultikeyPathsAccumulateOnDifferentFields) { auto collection = getCollection(); { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(collection->setIndexIsMultikey(operationContext(), indexEntry->descriptor()->indexName(), @@ -259,6 +264,7 @@ TEST_F(DurableCatalogTest, MultikeyPathsAccumulateOnDifferentFields) { } { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(collection->setIndexIsMultikey(operationContext(), indexEntry->descriptor()->indexName(), @@ -279,6 +285,7 @@ TEST_F(DurableCatalogTest, MultikeyPathsAccumulateOnDifferentComponentsOfTheSame auto collection = getCollection(); { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(collection->setIndexIsMultikey( operationContext(), indexEntry->descriptor()->indexName(), {{0U}})); @@ -293,6 +300,7 @@ TEST_F(DurableCatalogTest, MultikeyPathsAccumulateOnDifferentComponentsOfTheSame } { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(collection->setIndexIsMultikey( operationContext(), indexEntry->descriptor()->indexName(), {{1U}})); @@ -312,6 +320,7 @@ TEST_F(DurableCatalogTest, NoOpWhenSpecifiedPathComponentsAlreadySetAsMultikey) auto collection = getCollection(); { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(collection->setIndexIsMultikey( operationContext(), indexEntry->descriptor()->indexName(), {{0U}})); @@ -326,6 +335,7 @@ TEST_F(DurableCatalogTest, NoOpWhenSpecifiedPathComponentsAlreadySetAsMultikey) } { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(!collection->setIndexIsMultikey( operationContext(), indexEntry->descriptor()->indexName(), {{0U}})); @@ -344,6 +354,7 @@ TEST_F(DurableCatalogTest, CanSetMultipleFieldsAndComponentsAsMultikey) { auto indexEntry = createIndex(BSON("a.b.c" << 1 << "a.b.d" << 1)); auto collection = getCollection(); { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(collection->setIndexIsMultikey( operationContext(), indexEntry->descriptor()->indexName(), {{0U, 1U}, {0U, 1U}})); @@ -364,6 +375,7 @@ DEATH_TEST_REGEX_F(DurableCatalogTest, auto indexEntry = createIndex(BSON("a" << 1 << "b" << 1)); auto collection = getCollection(); + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); collection->setIndexIsMultikey( operationContext(), indexEntry->descriptor()->indexName(), MultikeyPaths{}); @@ -375,6 +387,7 @@ DEATH_TEST_REGEX_F(DurableCatalogTest, auto indexEntry = createIndex(BSON("a" << 1 << "b" << 1)); auto collection = getCollection(); + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); collection->setIndexIsMultikey(operationContext(), @@ -415,6 +428,7 @@ TEST_F(DurableCatalogTest, CanSetEntireTextIndexAsMultikey) { auto collection = getCollection(); { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(collection->setIndexIsMultikey( operationContext(), indexEntry->descriptor()->indexName(), MultikeyPaths{})); @@ -435,6 +449,7 @@ TEST_F(DurableCatalogTest, NoOpWhenEntireIndexAlreadySetAsMultikey) { auto collection = getCollection(); { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(collection->setIndexIsMultikey( operationContext(), indexEntry->descriptor()->indexName(), MultikeyPaths{})); @@ -449,6 +464,7 @@ TEST_F(DurableCatalogTest, NoOpWhenEntireIndexAlreadySetAsMultikey) { } { + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); ASSERT(!collection->setIndexIsMultikey( operationContext(), indexEntry->descriptor()->indexName(), MultikeyPaths{})); @@ -515,6 +531,7 @@ DEATH_TEST_REGEX_F(DurableCatalogTest, auto indexEntry = createIndex(BSON("a" << indexType << "b" << 1), indexType); auto collection = getCollection(); + Lock::GlobalLock globalLock{operationContext(), MODE_IX}; WriteUnitOfWork wuow(operationContext()); collection->setIndexIsMultikey( operationContext(), indexEntry->descriptor()->indexName(), {{0U}, {0U}}); @@ -656,6 +673,7 @@ TEST_F(DurableCatalogTest, CheckTimeseriesBucketsMayHaveMixedSchemaDataFlagFCVLa const NamespaceString regularNss = NamespaceString("test.regular"); createCollection(regularNss, CollectionOptions()); + Lock::GlobalLock globalLock{operationContext(), MODE_IS}; auto collection = CollectionCatalog::get(operationContext()) ->lookupCollectionByNamespace(operationContext(), regularNss); RecordId catalogId = collection->getCatalogId(); @@ -670,6 +688,7 @@ TEST_F(DurableCatalogTest, CheckTimeseriesBucketsMayHaveMixedSchemaDataFlagFCVLa options.timeseries = TimeseriesOptions(/*timeField=*/"t"); createCollection(bucketsNss, options); + Lock::GlobalLock globalLock{operationContext(), MODE_IS}; auto collection = CollectionCatalog::get(operationContext()) ->lookupCollectionByNamespace(operationContext(), bucketsNss); RecordId catalogId = collection->getCatalogId(); diff --git a/src/mongo/db/storage/kv/storage_engine_test.cpp b/src/mongo/db/storage/kv/storage_engine_test.cpp index 46f5ffe64d7..1fbdc65188d 100644 --- a/src/mongo/db/storage/kv/storage_engine_test.cpp +++ b/src/mongo/db/storage/kv/storage_engine_test.cpp @@ -130,7 +130,6 @@ TEST_F(StorageEngineTest, TemporaryRecordStoreClustered) { WriteUnitOfWork wuow(opCtx.get()); StatusWith<RecordId> s = rs->insertRecord(opCtx.get(), rid, data, strlen(data), Timestamp()); ASSERT_TRUE(s.isOK()); - ASSERT_EQUALS(1, rs->numRecords(opCtx.get())); wuow.commit(); // Read the record back. @@ -175,13 +174,7 @@ TEST_F(StorageEngineTest, ReconcileKeepsTemporary) { ASSERT_EQUALS(0UL, reconcileResult.indexesToRebuild.size()); ASSERT_EQUALS(0UL, reconcileResult.indexBuildsToRestart.size()); - if (_storageEngine->supportsResumableIndexBuilds()) { - // The storage engine does not drop its temporary idents outside of starting up after an - // unclean shutdown. - ASSERT(identExists(opCtx.get(), ident)); - } else { - ASSERT_FALSE(identExists(opCtx.get(), ident)); - } + ASSERT_FALSE(identExists(opCtx.get(), ident)); } class StorageEngineTimestampMonitorTest : public StorageEngineTest { @@ -685,7 +678,7 @@ TEST_F(TimestampKVEngineTest, TimestampAdvancesOnNotification) { _storageEngine->getTimestampMonitor()->clearListeners(); } -TEST_F(StorageEngineDurableTest, UseAlternateStorageLocation) { +TEST_F(StorageEngineTest, UseAlternateStorageLocation) { auto opCtx = cc().makeOperationContext(); const NamespaceString coll1Ns("db.coll1"); diff --git a/src/mongo/db/storage/storage_engine_test_fixture.h b/src/mongo/db/storage/storage_engine_test_fixture.h index 266052614a7..db8f499f42b 100644 --- a/src/mongo/db/storage/storage_engine_test_fixture.h +++ b/src/mongo/db/storage/storage_engine_test_fixture.h @@ -48,23 +48,14 @@ namespace mongo { class StorageEngineTest : public ServiceContextMongoDTest { public: - StorageEngineTest(RepairAction repair, - const std::string& storageEngine, - StorageEngineInitFlags initFlags) - : ServiceContextMongoDTest(storageEngine, repair, initFlags), - _storageEngine(getServiceContext()->getStorageEngine()) {} - - StorageEngineTest(const std::string& storageEngine, StorageEngineInitFlags initFlags) - : StorageEngineTest(RepairAction::kNoRepair, storageEngine, initFlags) { - auto serviceCtx = getServiceContext(); + explicit StorageEngineTest(Options options = {}) + : ServiceContextMongoDTest(std::move(options)), + _storageEngine(getServiceContext()->getStorageEngine()) { repl::ReplicationCoordinator::set( - serviceCtx, std::make_unique<repl::ReplicationCoordinatorMock>(serviceCtx)); + getServiceContext(), + std::make_unique<repl::ReplicationCoordinatorMock>(getServiceContext())); } - StorageEngineTest() - : StorageEngineTest("ephemeralForTest", - ServiceContextMongoDTest::kDefaultStorageEngineInitFlags) {} - StatusWith<DurableCatalog::Entry> createCollection(OperationContext* opCtx, NamespaceString ns) { AutoGetDb db(opCtx, ns.db(), LockMode::MODE_X); @@ -121,6 +112,7 @@ public: } StatusWith<StorageEngine::ReconcileResult> reconcile(OperationContext* opCtx) { + Lock::GlobalLock globalLock{opCtx, MODE_IX}; return _storageEngine->reconcileCatalogAndIdents(opCtx, StorageEngine::LastShutdownState::kClean); } @@ -207,10 +199,9 @@ public: class StorageEngineRepairTest : public StorageEngineTest { public: + // TODO (SERVER-65191): Use wiredTiger. StorageEngineRepairTest() - : StorageEngineTest(RepairAction::kRepair, - "ephemeralForTest", - ServiceContextMongoDTest::kDefaultStorageEngineInitFlags) {} + : StorageEngineTest(Options{}.engine("ephemeralForTest").repair(RepairAction::kRepair)) {} void tearDown() { auto repairObserver = StorageRepairObserver::get(getGlobalServiceContext()); @@ -228,9 +219,4 @@ public: } }; -class StorageEngineDurableTest : public StorageEngineTest { -public: - StorageEngineDurableTest() : StorageEngineTest("wiredTiger", StorageEngineInitFlags()) {} -}; - } // namespace mongo diff --git a/src/mongo/db/storage/storage_repair_observer_test.cpp b/src/mongo/db/storage/storage_repair_observer_test.cpp index 2fe3cc786d0..204ba7fc7de 100644 --- a/src/mongo/db/storage/storage_repair_observer_test.cpp +++ b/src/mongo/db/storage/storage_repair_observer_test.cpp @@ -53,8 +53,7 @@ using boost::filesystem::path; class StorageRepairObserverTest : public ServiceContextMongoDTest { public: - StorageRepairObserverTest() : ServiceContextMongoDTest("ephemeralForTest") { - + StorageRepairObserverTest() { repl::ReplicationCoordinator::set( getServiceContext(), std::make_unique<repl::ReplicationCoordinatorMock>(getServiceContext())); diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp index 3cb2aca26a3..b13db8fd9d4 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp @@ -150,8 +150,7 @@ bool WiredTigerFileVersion::shouldDowngrade(bool readOnly, bool hasRecoveryTimes } const auto replCoord = repl::ReplicationCoordinator::get(getGlobalServiceContext()); - const auto memberState = replCoord->getMemberState(); - if (memberState.arbiter()) { + if (replCoord && replCoord->getMemberState().arbiter()) { // SERVER-35361: Arbiters will no longer downgrade their data files. To downgrade // binaries, the user must delete the dbpath. It's not particularly expensive for a // replica set to re-initialize an arbiter that comes online. diff --git a/src/mongo/db/transaction_participant_test.cpp b/src/mongo/db/transaction_participant_test.cpp index f53255e9ce4..25fd8403f2c 100644 --- a/src/mongo/db/transaction_participant_test.cpp +++ b/src/mongo/db/transaction_participant_test.cpp @@ -3360,8 +3360,14 @@ std::string buildTransactionInfoString(OperationContext* opCtx, << singleTransactionStatsInfo.str() << " terminationCause:" << terminationCause << timeActiveAndInactiveInfo.str() << " numYields:" << 0 - << " locks:" << locks.done().toString() - << " wasPrepared:" << wasPrepared; + << " locks:" << locks.done().toString(); + + if (auto& storageStats = CurOp::get(opCtx)->debug().storageStats) { + expectedTransactionInfo << " storage:" << storageStats->toBSON(); + } + + expectedTransactionInfo << " wasPrepared:" << wasPrepared; + if (wasPrepared) { StringBuilder totalPreparedDuration; buildPreparedDurationString( diff --git a/src/mongo/db/vector_clock_mongod_test.cpp b/src/mongo/db/vector_clock_mongod_test.cpp index 51166840c89..9351b6438ff 100644 --- a/src/mongo/db/vector_clock_mongod_test.cpp +++ b/src/mongo/db/vector_clock_mongod_test.cpp @@ -50,12 +50,12 @@ namespace { */ class VectorClockMongoDTest : public ShardingMongodTestFixture { protected: + VectorClockMongoDTest() + : ShardingMongodTestFixture(Options{}.useMockClock(true), false /* setUpMajorityReads */) {} + void setUp() override { ShardingMongodTestFixture::setUp(); - auto clockSource = std::make_unique<ClockSourceMock>(); - getServiceContext()->setFastClockSource(std::move(clockSource)); - auto keysCollectionClient = std::make_unique<KeysCollectionClientDirect>(); VectorClockMutable::get(getServiceContext()) diff --git a/src/mongo/db/vector_clock_test.cpp b/src/mongo/db/vector_clock_test.cpp index 9d462b85c10..77e194e8478 100644 --- a/src/mongo/db/vector_clock_test.cpp +++ b/src/mongo/db/vector_clock_test.cpp @@ -65,7 +65,7 @@ TEST_F(VectorClockTest, roundtrip) { VectorClockMutable::get(getServiceContext())->tickClusterTimeTo(time); auto storedTime(getClusterTime()); - ASSERT_TRUE(storedTime == time); + ASSERT_EQ(storedTime, time); } // Verify the reserve ticks functionality. @@ -75,28 +75,28 @@ TEST_F(VectorClockTest, reserveTicks) { auto t1 = VectorClockMutable::get(getServiceContext())->tickClusterTime(1); auto t2(getClusterTime()); - ASSERT_TRUE(t1 == t2); + ASSERT_EQ(t1, t2); // Make sure we synchronized with the wall clock. - ASSERT_TRUE(t2.asTimestamp().getSecs() == 10); + ASSERT_EQ(t2.asTimestamp().getSecs(), 10); auto t3 = VectorClockMutable::get(getServiceContext())->tickClusterTime(1); t1.addTicks(1); - ASSERT_TRUE(t3 == t1); + ASSERT_EQ(t3, t1); t3 = VectorClockMutable::get(getServiceContext())->tickClusterTime(100); t1.addTicks(1); - ASSERT_TRUE(t3 == t1); + ASSERT_EQ(t3, t1); t3 = VectorClockMutable::get(getServiceContext())->tickClusterTime(1); t1.addTicks(100); - ASSERT_TRUE(t3 == t1); + ASSERT_EQ(t3, t1); // Ensure overflow to a new second. auto initTimeSecs = getClusterTime().asTimestamp().getSecs(); VectorClockMutable::get(getServiceContext())->tickClusterTime((1U << 31) - 1); auto newTimeSecs = getClusterTime().asTimestamp().getSecs(); - ASSERT_TRUE(newTimeSecs == initTimeSecs + 1); + ASSERT_EQ(newTimeSecs, initTimeSecs + 1); } // Verify the advanceClusterTime functionality. @@ -104,7 +104,7 @@ TEST_F(VectorClockTest, advanceClusterTime) { auto t1 = VectorClockMutable::get(getServiceContext())->tickClusterTime(1); t1.addTicks(100); advanceClusterTime(t1); - ASSERT_TRUE(t1 == getClusterTime()); + ASSERT_EQ(t1, getClusterTime()); } // Verify rate limiter rejects cluster times whose seconds values are too far ahead. @@ -124,8 +124,8 @@ TEST_F(VectorClockTest, RateLimiterRejectsLogicalTimesTooFarAhead) { // Verify cluster time can be initialized to a very old time. TEST_F(VectorClockTest, InitFromTrustedSourceCanAcceptVeryOldLogicalTime) { - setMockClockSourceTime(Date_t::fromMillisSinceEpoch( - durationCount<Seconds>(Seconds(kMaxAcceptableLogicalClockDriftSecsDefault)) * 10 * 1000)); + setMockClockSourceTime(Date_t::fromDurationSinceEpoch( + Seconds(kMaxAcceptableLogicalClockDriftSecsDefault) * 10 * 1000)); Timestamp veryOldTimestamp( durationCount<Seconds>(getMockClockSourceTime().toDurationSinceEpoch()) - @@ -133,7 +133,7 @@ TEST_F(VectorClockTest, InitFromTrustedSourceCanAcceptVeryOldLogicalTime) { auto veryOldTime = LogicalTime(veryOldTimestamp); VectorClockMutable::get(getServiceContext())->tickClusterTimeTo(veryOldTime); - ASSERT_TRUE(getClusterTime() == veryOldTime); + ASSERT_EQ(getClusterTime(), veryOldTime); } // Verify writes to the oplog advance cluster time. @@ -142,10 +142,10 @@ TEST_F(VectorClockTest, WritesToOplogAdvanceClusterTime) { auto initialTime = LogicalTime(tX); VectorClockMutable::get(getServiceContext())->tickClusterTimeTo(initialTime); - ASSERT_TRUE(getClusterTime() == initialTime); + ASSERT_EQ(getClusterTime(), initialTime); getDBClient()->insert(kDummyNamespaceString.ns(), BSON("x" << 1)); - ASSERT_TRUE(getClusterTime() > initialTime); + ASSERT_GT(getClusterTime(), initialTime); ASSERT_EQ(getClusterTime().asTimestamp(), replicationCoordinator()->getMyLastAppliedOpTime().getTimestamp()); } @@ -170,9 +170,9 @@ TEST_F(VectorClockTest, WallClockSetTooFarInPast) { // If cluster time is either uninitialized or even farther in the past, a write would set // cluster time more than maxAcceptableLogicalClockDriftSecs in the past. getDBClient()->insert(kDummyNamespaceString.ns(), BSON("x" << 1)); - ASSERT_TRUE(getClusterTime() < - LogicalTime(Timestamp( - currentSecs - Seconds(kMaxAcceptableLogicalClockDriftSecsDefault), 0))); + ASSERT_LT(getClusterTime(), + LogicalTime( + Timestamp(currentSecs - Seconds(kMaxAcceptableLogicalClockDriftSecsDefault), 0))); // Set wall clock to the current time on the affected node. setMockClockSourceTime(Date_t::fromDurationSinceEpoch(currentSecs)); @@ -180,7 +180,7 @@ TEST_F(VectorClockTest, WallClockSetTooFarInPast) { // Verify that maxAcceptableLogicalClockDriftSecs parameter does not need to be increased to // advance cluster time through metadata back to the current time. advanceClusterTime(currentTime); - ASSERT_TRUE(getClusterTime() == currentTime); + ASSERT_EQ(getClusterTime(), currentTime); } // Tests the scenario where an admin incorrectly sets the wall clock more than @@ -201,9 +201,9 @@ TEST_F(VectorClockTest, WallClockSetTooFarInFuture) { // A write gets through and advances cluster time more than maxAcceptableLogicalClockDriftSecs // in the future. getDBClient()->insert(kDummyNamespaceString.ns(), BSON("x" << 1)); - ASSERT_TRUE(getClusterTime() > - LogicalTime(Timestamp( - currentSecs + Seconds(kMaxAcceptableLogicalClockDriftSecsDefault), 0))); + ASSERT_GT(getClusterTime(), + LogicalTime( + Timestamp(currentSecs + Seconds(kMaxAcceptableLogicalClockDriftSecsDefault), 0))); // Set wall clock to the current time on the affected node. setMockClockSourceTime(Date_t::fromDurationSinceEpoch(currentSecs)); @@ -222,11 +222,14 @@ TEST_F(VectorClockTest, WallClockSetTooFarInFuture) { setMockClockSourceTime(Date_t::fromDurationSinceEpoch(currentSecs + oneDay)); advanceClusterTime(nextTime); - ASSERT_TRUE(getClusterTime() == nextTime); + ASSERT_EQ(getClusterTime(), nextTime); } // Verify the behavior of advancing cluster time around the max allowed values. TEST_F(VectorClockTest, ReserveTicksBehaviorAroundMaxTime) { + // Ensure the clock starts at 0. + setMockClockSourceTime(Date_t{}); + // Verify clock can be advanced near the max values. // Can always advance to the max value for the inc field. @@ -320,7 +323,7 @@ TEST_F(VectorClockTest, RejectsLogicalTimesGreaterThanMaxTime) { resetClock(); ASSERT_THROWS(VectorClockMutable::get(getServiceContext())->tickClusterTimeTo(beyondMaxTime), DBException); - ASSERT_TRUE(getClusterTime() == VectorClock::kInitialComponentTime); + ASSERT_EQ(getClusterTime(), VectorClock::kInitialComponentTime); // The time can't be advanced through metadata to a time greater than the max possible. // Advance the wall clock close enough to the new value so the rate check is passed. @@ -328,7 +331,7 @@ TEST_F(VectorClockTest, RejectsLogicalTimesGreaterThanMaxTime) { Seconds(maxVal) - Seconds(kMaxAcceptableLogicalClockDriftSecsDefault) + Seconds(10); setMockClockSourceTime(Date_t::fromDurationSinceEpoch(almostMaxSecs)); ASSERT_THROWS(advanceClusterTime(beyondMaxTime), DBException); - ASSERT_TRUE(getClusterTime() == VectorClock::kInitialComponentTime); + ASSERT_EQ(getClusterTime(), VectorClock::kInitialComponentTime); } } // unnamed namespace diff --git a/src/mongo/db/vector_clock_test_fixture.cpp b/src/mongo/db/vector_clock_test_fixture.cpp index c25324339db..13814076081 100644 --- a/src/mongo/db/vector_clock_test_fixture.cpp +++ b/src/mongo/db/vector_clock_test_fixture.cpp @@ -47,7 +47,8 @@ namespace mongo { -VectorClockTestFixture::VectorClockTestFixture() = default; +VectorClockTestFixture::VectorClockTestFixture() + : ShardingMongodTestFixture(Options{}.useMockClock(true), false /* setUpMajorityReads */) {} VectorClockTestFixture::~VectorClockTestFixture() = default; @@ -58,9 +59,6 @@ void VectorClockTestFixture::setUp() { _clock = VectorClock::get(service); - service->setFastClockSource(std::make_unique<SharedClockSourceAdapter>(_mockClockSource)); - service->setPreciseClockSource(std::make_unique<SharedClockSourceAdapter>(_mockClockSource)); - _dbDirectClient = std::make_unique<DBDirectClient>(operationContext()); ASSERT_OK(replicationCoordinator()->setFollowerMode(repl::MemberState::RS_PRIMARY)); @@ -90,16 +88,16 @@ LogicalTime VectorClockTestFixture::getClusterTime() const { return now.clusterTime(); } -ClockSourceMock* VectorClockTestFixture::getMockClockSource() const { - return _mockClockSource.get(); +ClockSourceMock* VectorClockTestFixture::getMockClockSource() { + return &_mockClockSource; } -void VectorClockTestFixture::setMockClockSourceTime(Date_t time) const { - _mockClockSource->reset(time); +void VectorClockTestFixture::setMockClockSourceTime(Date_t time) { + _mockClockSource.reset(time); } -Date_t VectorClockTestFixture::getMockClockSourceTime() const { - return _mockClockSource->now(); +Date_t VectorClockTestFixture::getMockClockSourceTime() { + return _mockClockSource.now(); } DBDirectClient* VectorClockTestFixture::getDBClient() const { diff --git a/src/mongo/db/vector_clock_test_fixture.h b/src/mongo/db/vector_clock_test_fixture.h index 3fc5b043744..4ae782cbb7a 100644 --- a/src/mongo/db/vector_clock_test_fixture.h +++ b/src/mongo/db/vector_clock_test_fixture.h @@ -45,11 +45,10 @@ class VectorClockMutable; * ShardingMongodTestFixture. */ class VectorClockTestFixture : public ShardingMongodTestFixture { -public: +protected: VectorClockTestFixture(); ~VectorClockTestFixture(); -protected: /** * Sets up this fixture as the primary node in a shard server replica set with a VectorClock * (with a TimeProofService), storage engine, DBClient, OpObserver, and a mocked clock source. @@ -66,20 +65,19 @@ protected: LogicalTime getClusterTime() const; - ClockSourceMock* getMockClockSource() const; + ClockSourceMock* getMockClockSource(); - void setMockClockSourceTime(Date_t time) const; + void setMockClockSourceTime(Date_t time); - Date_t getMockClockSourceTime() const; + Date_t getMockClockSourceTime(); DBDirectClient* getDBClient() const; -protected: void setupOpObservers() override; private: VectorClock* _clock; - std::shared_ptr<ClockSourceMock> _mockClockSource = std::make_shared<ClockSourceMock>(); + ClockSourceMock _mockClockSource; std::unique_ptr<DBDirectClient> _dbDirectClient; }; |