diff options
author | Kyle Suarez <kyle.suarez@mongodb.com> | 2018-03-20 15:30:41 -0400 |
---|---|---|
committer | Kyle Suarez <kyle.suarez@mongodb.com> | 2018-03-20 15:43:29 -0400 |
commit | 76cef6675d80ab57b98af11e6e47868a60e11cbb (patch) | |
tree | 6cdf9c90415265502ae25c436d894ce5d781e7d0 /src/mongo/db/repl/rollback_impl_test.cpp | |
parent | 2ee6908f1c73dd50d6425e3462ccac2582deb2f3 (diff) | |
download | mongo-76cef6675d80ab57b98af11e6e47868a60e11cbb.tar.gz |
SERVER-29051 create data files during rollback via recover to timestamp
Diffstat (limited to 'src/mongo/db/repl/rollback_impl_test.cpp')
-rw-r--r-- | src/mongo/db/repl/rollback_impl_test.cpp | 745 |
1 files changed, 696 insertions, 49 deletions
diff --git a/src/mongo/db/repl/rollback_impl_test.cpp b/src/mongo/db/repl/rollback_impl_test.cpp index 7e3d0528f5a..2ad96e7be70 100644 --- a/src/mongo/db/repl/rollback_impl_test.cpp +++ b/src/mongo/db/repl/rollback_impl_test.cpp @@ -25,9 +25,16 @@ * exception statement from all source files in the program, then also delete * it in the license file. */ +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kReplicationRollback #include "mongo/platform/basic.h" +#include <boost/optional.hpp> + +#include "mongo/db/catalog/collection_mock.h" +#include "mongo/db/catalog/drop_collection.h" +#include "mongo/db/catalog/uuid_catalog.h" +#include "mongo/db/repl/drop_pending_collection_reaper.h" #include "mongo/db/repl/oplog_entry.h" #include "mongo/db/repl/oplog_interface_local.h" #include "mongo/db/repl/oplog_interface_mock.h" @@ -37,8 +44,8 @@ #include "mongo/db/s/type_shard_identity.h" #include "mongo/unittest/death_test.h" #include "mongo/util/assert_util.h" +#include "mongo/util/log.h" #include "mongo/util/uuid.h" -#include <boost/optional.hpp> namespace { @@ -47,6 +54,87 @@ using namespace mongo::repl; NamespaceString nss("local.oplog.rs"); +BSONObj makeInsertOplogEntry(long long time, BSONObj obj, StringData ns, UUID uuid) { + return BSON("ts" << Timestamp(time, time) << "h" << time << "t" << time << "op" + << "i" + << "o" + << obj + << "ns" + << ns + << "ui" + << uuid); +} + +BSONObj makeUpdateOplogEntry( + long long time, BSONObj query, BSONObj update, StringData ns, UUID uuid) { + return BSON("ts" << Timestamp(time, time) << "h" << time << "t" << time << "op" + << "u" + << "ns" + << ns + << "ui" + << uuid + << "o2" + << query + << "o" + << BSON("$set" << update)); +} + +BSONObj makeDeleteOplogEntry(long long time, BSONObj id, StringData ns, UUID uuid) { + return BSON("ts" << Timestamp(time, time) << "h" << time << "t" << time << "op" + << "d" + << "ns" + << ns + << "ui" + << uuid + << "o" + << id); +} + +class RollbackImplForTest final : public RollbackImpl { +public: + using RollbackImpl::RollbackImpl; + + static const std::vector<BSONObj> kEmptyVector; + + /** + * Returns a reference to a vector containing all of the BSONObjs deleted from the namespace + * represented by 'uuid', or kEmptyVector if that namespace wasn't found in '_uuidToObjsMap'. + */ + const std::vector<BSONObj>& docsDeletedForNamespace_forTest(UUID uuid) const& final { + auto iter = _uuidToObjsMap.find(uuid); + if (iter == _uuidToObjsMap.end()) { + return kEmptyVector; + } + return iter->second; + } + +protected: + /** + * Saves documents that would be deleted in '_uuidToObjsMap', rather than writing them out to a + * file. + */ + void _writeRollbackFileForNamespace(OperationContext* opCtx, + UUID uuid, + NamespaceString nss, + const SimpleBSONObjUnorderedSet& idSet) final { + log() << "Simulating writing a rollback file for namespace " << nss.ns() << " with uuid " + << uuid; + for (auto&& id : idSet) { + log() << "Looking up " << id.jsonString(); + auto document = _findDocumentById(opCtx, uuid, nss, id.firstElement()); + if (document) { + _uuidToObjsMap[uuid].push_back(*document); + } + } + _listener->onRollbackFileWrittenForNamespace(std::move(uuid), std::move(nss)); + } + +private: + stdx::unordered_map<UUID, std::vector<BSONObj>, UUID::Hash> _uuidToObjsMap; +}; + +const std::vector<BSONObj> RollbackImplForTest::kEmptyVector; + /** * Unit test for rollback implementation introduced in 3.6. */ @@ -64,9 +152,76 @@ private: friend class RollbackImplTest::Listener; protected: + /** + * Creates a new mock collection with name 'nss' via the StorageInterface and associates 'uuid' + * with the new collection in the UUIDCatalog. There must not already exist a collection with + * name 'nss'. + */ + std::unique_ptr<Collection> _initializeCollection(OperationContext* opCtx, + UUID uuid, + const NamespaceString& nss) { + // Create a new collection in the storage interface. + CollectionOptions options; + options.uuid = uuid; + ASSERT_OK(_storageInterface->createCollection(opCtx, nss, options)); + + // Initialize a mock collection. + std::unique_ptr<Collection> coll = + std::make_unique<Collection>(std::make_unique<CollectionMock>(nss)); + + // Register the UUID to that collection in the UUIDCatalog. + UUIDCatalog::get(opCtx).registerUUIDCatalogEntry(uuid, coll.get()); + return coll; + } + + /** + * Creates an oplog entry that represents the insertion of 'doc' into the namespace 'nss' with + * UUID 'uuid', and inserts the document into the storage interface. + * + * Unless 'time' is explicitly given, this insert is timestamped with an arbitrary time that + * monotonically increases with each successive call to this function. + */ + void _insertDocAndGenerateOplogEntry(BSONObj doc, + UUID uuid, + NamespaceString nss, + boost::optional<long> time = boost::none) { + const auto optime = time.value_or(_counter++); + ASSERT_OK(_insertOplogEntry(makeInsertOplogEntry(optime, doc, nss.ns(), uuid))); + ASSERT_OK(_storageInterface->insertDocument( + _opCtx.get(), nss, {doc, Timestamp(optime, optime)}, optime)); + } + + /** + * Creates an oplog entry that represents updating an object matched by 'query' to be 'newDoc' + * in the namespace 'nss, with UUID 'uuid'. It also inserts 'newDoc' into the storage interface. + * This update is timestamped with an arbitrary time that monotonically increases with each + * successive call. + */ + void _updateDocAndGenerateOplogEntry(BSONObj query, + BSONObj newDoc, + UUID uuid, + NamespaceString nss) { + const auto time = _counter++; + ASSERT_OK(_insertOplogEntry(makeUpdateOplogEntry(time, query, newDoc, nss.ns(), uuid))); + ASSERT_OK(_storageInterface->insertDocument( + _opCtx.get(), nss, {newDoc, Timestamp(time, time)}, time)); + } + + /** + * Creates an oplog entry that represents deleting an object with _id 'id' in the namespace + * 'nss' with UUID 'uuid'. This function also removes that document from the storage interface. + * This update is timestamped with an arbitrary time that monotonically increases with each + * successive call. + */ + void _deleteDocAndGenerateOplogEntry(BSONElement id, UUID uuid, NamespaceString nss) { + const auto time = _counter++; + ASSERT_OK(_insertOplogEntry(makeDeleteOplogEntry(time, id.wrap(), nss.ns(), uuid))); + ASSERT_OK(_storageInterface->deleteById(_opCtx.get(), nss, id)); + } + std::unique_ptr<OplogInterfaceLocal> _localOplog; std::unique_ptr<OplogInterfaceMock> _remoteOplog; - std::unique_ptr<RollbackImpl> _rollback; + std::unique_ptr<RollbackImplForTest> _rollback; bool _transitionedToRollback = false; stdx::function<void()> _onTransitionToRollbackFn = [this]() { _transitionedToRollback = true; }; @@ -94,7 +249,13 @@ protected: stdx::function<void(const OpObserver::RollbackObserverInfo& rbInfo)> _onRollbackOpObserverFn = [this](const OpObserver::RollbackObserverInfo& rbInfo) {}; + stdx::function<void(UUID, NamespaceString)> _onRollbackFileWrittenForNamespaceFn = + [this](UUID, NamespaceString) {}; + std::unique_ptr<Listener> _listener; + +private: + long _counter = 100; }; void RollbackImplTest::setUp() { @@ -104,12 +265,12 @@ void RollbackImplTest::setUp() { NamespaceString::kRsOplogNamespace.ns()); _remoteOplog = stdx::make_unique<OplogInterfaceMock>(); _listener = stdx::make_unique<Listener>(this); - _rollback = stdx::make_unique<RollbackImpl>(_localOplog.get(), - _remoteOplog.get(), - _storageInterface, - _replicationProcess.get(), - _coordinator, - _listener.get()); + _rollback = stdx::make_unique<RollbackImplForTest>(_localOplog.get(), + _remoteOplog.get(), + _storageInterface, + _replicationProcess.get(), + _coordinator, + _listener.get()); createOplog(_opCtx.get()); } @@ -134,6 +295,10 @@ public: _test->_onCommonPointFoundFn(commonPoint); } + void onRollbackFileWrittenForNamespace(UUID uuid, NamespaceString nss) noexcept final { + _test->_onRollbackFileWrittenForNamespaceFn(std::move(uuid), std::move(nss)); + } + void onRecoverToStableTimestamp(Timestamp stableTimestamp) noexcept override { _test->_onRecoverToStableTimestampFn(stableTimestamp); } @@ -256,6 +421,8 @@ TEST_F(RollbackImplTest, RollbackPersistsDocumentAfterCommonPointToOplogTruncate auto nextTime = 3; ASSERT_OK(_insertOplogEntry(makeOp(nextTime))); + const auto nss = NamespaceString("test.coll"); + auto coll = _initializeCollection(_opCtx.get(), UUID::gen(), nss); ASSERT_OK(_rollback->runRollback(_opCtx.get())); ASSERT_EQUALS(_truncatePoint, Timestamp(3, 3)); @@ -587,6 +754,366 @@ TEST_F(RollbackImplTest, RollbackSkipsTriggerOpObserverWhenShutDownEarly) { ASSERT_EQUALS(_coordinator->getMemberState(), MemberState::RS_SECONDARY); } +TEST_F(RollbackImplTest, RollbackDoesNotWriteRollbackFilesIfNoInsertsOrUpdatesAfterCommonPoint) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + const auto uuid = UUID::gen(); + const auto nss = NamespaceString("db.coll"); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + const auto oplogEntry = BSON("ts" << Timestamp(3, 3) << "h" << 3L << "t" << 3L << "op" + << "c" + << "o" + << BSON("create" << nss.coll()) + << "ns" + << nss.ns() + << "ui" + << uuid); + ASSERT_OK(_insertOplogEntry(oplogEntry)); + + ASSERT_OK(_rollback->runRollback(_opCtx.get())); + ASSERT(_rollback->docsDeletedForNamespace_forTest(uuid).empty()); +} + +TEST_F(RollbackImplTest, RollbackSavesInsertedDocumentToFile) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + const auto nss = NamespaceString("db.people"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + + const auto obj = BSON("_id" << 0 << "name" + << "kyle"); + _insertDocAndGenerateOplogEntry(obj, uuid, nss); + + ASSERT_OK(_rollback->runRollback(_opCtx.get())); + + const auto& deletedObjs = _rollback->docsDeletedForNamespace_forTest(uuid); + ASSERT_EQ(deletedObjs.size(), 1UL); + ASSERT_BSONOBJ_EQ(deletedObjs.front(), obj); +} + +TEST_F(RollbackImplTest, RollbackSavesLatestVersionOfDocumentWhenThereAreMultipleInserts) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + const auto nss = NamespaceString("db.people"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + + const auto oldObj = BSON("_id" << 0 << "name" + << "kyle"); + const auto newObj = BSON("_id" << 0 << "name" + << "jungsoo"); + _insertDocAndGenerateOplogEntry(oldObj, uuid, nss); + _deleteDocAndGenerateOplogEntry(oldObj["_id"], uuid, nss); + _insertDocAndGenerateOplogEntry(newObj, uuid, nss); + + ASSERT_OK(_rollback->runRollback(_opCtx.get())); + + const auto& deletedObjs = _rollback->docsDeletedForNamespace_forTest(uuid); + ASSERT_EQ(deletedObjs.size(), 1UL); + ASSERT_BSONOBJ_EQ(deletedObjs.front(), newObj); +} + +TEST_F(RollbackImplTest, RollbackSavesUpdatedDocumentToFile) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + const auto nss = NamespaceString("db.people"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + + const auto query = BSON("_id" << 0); + const auto obj = BSON("_id" << 0 << "name" + << "kyle"); + _updateDocAndGenerateOplogEntry(query, obj, uuid, nss); + + ASSERT_OK(_rollback->runRollback(_opCtx.get())); + + const auto& deletedObjs = _rollback->docsDeletedForNamespace_forTest(uuid); + ASSERT_EQ(deletedObjs.size(), 1UL); + ASSERT_BSONOBJ_EQ(deletedObjs.front(), obj); +} + +TEST_F(RollbackImplTest, RollbackSavesLatestVersionOfDocumentWhenThereAreMultipleUpdates) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + const auto nss = NamespaceString("db.people"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + + const auto query = BSON("_id" << 3.14); + const auto oldObj = BSON("_id" << 3.14 << "name" + << "kyle"); + const auto newObj = BSON("_id" << 3.14 << "name" + << "jungsoo"); + _updateDocAndGenerateOplogEntry(query, oldObj, uuid, nss); + _deleteDocAndGenerateOplogEntry(oldObj["_id"], uuid, nss); + _updateDocAndGenerateOplogEntry(query, newObj, uuid, nss); + + ASSERT_OK(_rollback->runRollback(_opCtx.get())); + + const auto& deletedObjs = _rollback->docsDeletedForNamespace_forTest(uuid); + ASSERT_EQ(deletedObjs.size(), 1UL); + ASSERT_BSONOBJ_EQ(deletedObjs.front(), newObj); +} + +TEST_F(RollbackImplTest, RollbackDoesNotWriteDocumentToFileIfInsertIsRevertedByDelete) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + const auto nss = NamespaceString("db.numbers"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + + const auto objToKeep = BSON("_id" << 6); + const auto objToDelete = BSON("_id" << 7); + _insertDocAndGenerateOplogEntry(objToDelete, uuid, nss); + _insertDocAndGenerateOplogEntry(objToKeep, uuid, nss); + _deleteDocAndGenerateOplogEntry(objToDelete["_id"], uuid, nss); + + ASSERT_OK(_rollback->runRollback(_opCtx.get())); + + const auto& deletedObjs = _rollback->docsDeletedForNamespace_forTest(uuid); + ASSERT_EQ(deletedObjs.size(), 1UL); + ASSERT_BSONOBJ_EQ(deletedObjs.front(), objToKeep); +} + +TEST_F(RollbackImplTest, RollbackDoesNotWriteDocumentToFileIfUpdateIsFollowedByDelete) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + const auto nss = NamespaceString("db.numbers"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + + const auto objToKeep = BSON("_id" << 6); + const auto objToDelete = BSON("_id" << 7); + _updateDocAndGenerateOplogEntry(objToDelete, objToDelete, uuid, nss); + _updateDocAndGenerateOplogEntry(objToKeep, objToKeep, uuid, nss); + _deleteDocAndGenerateOplogEntry(objToDelete["_id"], uuid, nss); + + ASSERT_OK(_rollback->runRollback(_opCtx.get())); + + const auto& deletedObjs = _rollback->docsDeletedForNamespace_forTest(uuid); + ASSERT_EQ(deletedObjs.size(), 1UL); + ASSERT_BSONOBJ_EQ(deletedObjs.front(), objToKeep); +} + +TEST_F(RollbackImplTest, RollbackProperlySavesFilesWhenCollectionIsRenamed) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + const auto nssBeforeRename = NamespaceString("db.firstColl"); + const auto uuidBeforeRename = UUID::gen(); + const auto collBeforeRename = + _initializeCollection(_opCtx.get(), uuidBeforeRename, nssBeforeRename); + + // Insert a document into the collection. + const auto objInRenamedCollection = BSON("_id" + << "kyle"); + _insertDocAndGenerateOplogEntry(objInRenamedCollection, uuidBeforeRename, nssBeforeRename, 2); + + // Rename the original collection. + const auto nssAfterRename = NamespaceString("db.secondColl"); + auto renameCmdObj = + BSON("renameCollection" << nssBeforeRename.ns() << "to" << nssAfterRename.ns()); + auto renameCmdOp = + makeCommandOp(Timestamp(3, 3), uuidBeforeRename, nssBeforeRename.ns(), renameCmdObj, 3); + ASSERT_OK(_insertOplogEntry(renameCmdOp.first)); + ASSERT_OK( + _storageInterface->renameCollection(_opCtx.get(), nssBeforeRename, nssAfterRename, true)); + + // Create a new collection with the old name. + const auto uuidAfterRename = UUID::gen(); + const auto collAfterRename = + _initializeCollection(_opCtx.get(), uuidAfterRename, nssBeforeRename); + + // Insert a different document into the new collection. + const auto objInNewCollection = BSON("_id" + << "jungsoo"); + _insertDocAndGenerateOplogEntry(objInNewCollection, uuidAfterRename, nssBeforeRename, 4); + + ASSERT_OK(_rollback->runRollback(_opCtx.get())); + + const auto& deletedObjsRenamedColl = + _rollback->docsDeletedForNamespace_forTest(uuidBeforeRename); + ASSERT_EQ(deletedObjsRenamedColl.size(), 1UL); + ASSERT_BSONOBJ_EQ(deletedObjsRenamedColl.front(), objInRenamedCollection); + + const auto& deletedObjsNewColl = _rollback->docsDeletedForNamespace_forTest(uuidAfterRename); + ASSERT_EQ(deletedObjsNewColl.size(), 1UL); + ASSERT_BSONOBJ_EQ(deletedObjsNewColl.front(), objInNewCollection); +} + +TEST_F(RollbackImplTest, RollbackProperlySavesFilesWhenInsertsAndDropOfCollectionAreRolledBack) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + // Create the collection, but as a drop-pending collection. + const auto dropOpTime = OpTime(Timestamp(200, 200), 200L); + const auto nss = NamespaceString("db.people").makeDropPendingNamespace(dropOpTime); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + DropPendingCollectionReaper::get(_opCtx.get())->addDropPendingNamespace(dropOpTime, nss); + + // Insert documents into the collection. We'll write them out even though the collection is + // later dropped. + const auto obj1 = BSON("_id" + << "kyle"); + const auto obj2 = BSON("_id" + << "glenn"); + _insertDocAndGenerateOplogEntry(obj1, uuid, nss); + _insertDocAndGenerateOplogEntry(obj2, uuid, nss); + + // Create an oplog entry for the collection drop. + const auto oplogEntry = + BSON("ts" << dropOpTime.getTimestamp() << "h" << 200L << "t" << dropOpTime.getTerm() << "op" + << "c" + << "o" + << BSON("drop" << nss.coll()) + << "ns" + << nss.ns() + << "ui" + << uuid); + ASSERT_OK(_insertOplogEntry(oplogEntry)); + + ASSERT_OK(_rollback->runRollback(_opCtx.get())); + + const auto& deletedObjs = _rollback->docsDeletedForNamespace_forTest(uuid); + ASSERT_EQ(deletedObjs.size(), 2UL); + std::vector<BSONObj> expectedObjs({obj1, obj2}); + ASSERT(std::is_permutation(deletedObjs.begin(), + deletedObjs.end(), + expectedObjs.begin(), + expectedObjs.end(), + SimpleBSONObjComparator::kInstance.makeEqualTo())); +} + +TEST_F(RollbackImplTest, RollbackProperlySavesFilesWhenCreateCollAndInsertsAreRolledBack) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + // Create the collection and make an oplog entry for the creation event. + const auto nss = NamespaceString("db.people"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + const auto oplogEntry = BSON("ts" << Timestamp(3, 3) << "h" << 3L << "t" << 3L << "op" + << "c" + << "o" + << BSON("create" << nss.coll()) + << "ns" + << nss.ns() + << "ui" + << uuid); + ASSERT_OK(_insertOplogEntry(oplogEntry)); + + // Insert documents into the collection. + const std::vector<BSONObj> objs({BSON("_id" + << "kyle"), + BSON("_id" + << "jungsoo"), + BSON("_id" + << "erjon")}); + for (auto&& obj : objs) { + _insertDocAndGenerateOplogEntry(obj, uuid, nss); + } + + ASSERT_OK(_rollback->runRollback(_opCtx.get())); + + const auto& deletedObjs = _rollback->docsDeletedForNamespace_forTest(uuid); + ASSERT_EQ(deletedObjs.size(), objs.size()); + ASSERT(std::is_permutation(deletedObjs.begin(), + deletedObjs.end(), + objs.begin(), + objs.end(), + SimpleBSONObjComparator::kInstance.makeEqualTo())); +} + +TEST_F(RollbackImplTest, RollbackStopsWritingRollbackFilesWhenShutdownIsInProgress) { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + const auto nss1 = NamespaceString("db.people"); + const auto uuid1 = UUID::gen(); + const auto coll1 = _initializeCollection(_opCtx.get(), uuid1, nss1); + const auto obj1 = BSON("_id" << 0 << "name" + << "kyle"); + _insertDocAndGenerateOplogEntry(obj1, uuid1, nss1); + + const auto nss2 = NamespaceString("db.persons"); + const auto uuid2 = UUID::gen(); + const auto coll2 = _initializeCollection(_opCtx.get(), uuid2, nss2); + const auto obj2 = BSON("_id" << 0 << "name" + << "jungsoo"); + _insertDocAndGenerateOplogEntry(obj2, uuid2, nss2); + + // Register a listener that sends rollback into shutdown. + std::vector<UUID> collsWithSuccessfullyWrittenDataFiles; + _onRollbackFileWrittenForNamespaceFn = + [this, &collsWithSuccessfullyWrittenDataFiles](UUID uuid, NamespaceString nss) { + collsWithSuccessfullyWrittenDataFiles.emplace_back(std::move(uuid)); + _rollback->shutdown(); + }; + + ASSERT_EQ(_rollback->runRollback(_opCtx.get()), ErrorCodes::ShutdownInProgress); + + ASSERT_EQ(collsWithSuccessfullyWrittenDataFiles.size(), 1UL); + const auto& uuid = collsWithSuccessfullyWrittenDataFiles.front(); + ASSERT(uuid == uuid1 || uuid == uuid2) << "wrote out a data file for unknown uuid " << uuid + << "; expected it to be either " << uuid1 << " or " + << uuid2; +} + +DEATH_TEST_F(RollbackImplTest, + InvariantFailureIfNamespaceIsMissingWhenWritingRollbackFiles, + "unexpectedly missing in the UUIDCatalog") { + const auto commonOp = makeOpAndRecordId(1); + _remoteOplog->setOperations({commonOp}); + ASSERT_OK(_insertOplogEntry(commonOp.first)); + _storageInterface->setStableTimestamp(nullptr, Timestamp(1, 1)); + + const auto nss = NamespaceString("db.people"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + + const auto obj = BSON("_id" << 0 << "name" + << "kyle"); + _insertDocAndGenerateOplogEntry(obj, uuid, nss); + + // Drop the collection (immediately; not a two-phase drop), so that the namespace can no longer + // be found. + ASSERT_OK(_storageInterface->dropCollection(_opCtx.get(), nss)); + + auto status = _rollback->runRollback(_opCtx.get()); + unittest::log() << "mongod did not crash when expected; status: " << status; +} + /** * Fixture to help test that rollback records the correct information in its RollbackObserverInfo * struct. @@ -614,13 +1141,12 @@ public: return _rollback->runRollback(_opCtx.get()); } - BSONObj makeSessionOp(NamespaceString nss, UUID sessionId, TxnNumber txnNum) { - auto uuid = UUID::gen(); + BSONObj makeSessionOp(UUID collId, NamespaceString nss, UUID sessionId, TxnNumber txnNum) { BSONObjBuilder bob; bob.append("ts", Timestamp(2, 1)); bob.append("h", 1LL); bob.append("op", "i"); - uuid.appendToBuilder(&bob, "ui"); + collId.appendToBuilder(&bob, "ui"); bob.append("ns", nss.ns()); bob.append("o", BSON("_id" << 1)); bob.append("lsid", @@ -630,6 +1156,20 @@ public: return bob.obj(); } + void assertRollbackInfoContainsObjectForUUID(UUID uuid, BSONObj bson) { + const auto& uuidToIdMap = _rbInfo.rollbackDeletedIdsMap; + auto search = uuidToIdMap.find(uuid); + ASSERT(search != uuidToIdMap.end()) << "map is unexpectedly missing an entry for uuid " + << uuid.toString() << " containing object " + << bson.jsonString(); + const auto& idObjSet = search->second; + const auto iter = idObjSet.find(bson); + ASSERT(iter != idObjSet.end()) << "_id object set is unexpectedly missing object " + << bson.jsonString() << " in namespace with uuid " + << uuid.toString(); + } + + protected: OpObserver::RollbackObserverInfo _rbInfo; }; @@ -869,37 +1409,37 @@ TEST_F(RollbackImplObserverInfoTest, RollbackFailsOnMalformedApplyOpsOplogEntry) } TEST_F(RollbackImplObserverInfoTest, RollbackRecordsNamespaceOfSingleOplogEntry) { - auto nss = NamespaceString("test", "coll"); - auto insertOp = makeCRUDOp(OpTypeEnum::kInsert, - Timestamp(2, 2), - UUID::gen(), - nss.ns(), - BSON("_id" << 1), - boost::none, - 2); + const auto nss = NamespaceString("test", "coll"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + const auto insertOp = makeCRUDOp( + OpTypeEnum::kInsert, Timestamp(2, 2), uuid, nss.ns(), BSON("_id" << 1), boost::none, 2); ASSERT_OK(rollbackOps({insertOp})); std::set<NamespaceString> expectedNamespaces = {nss}; ASSERT(expectedNamespaces == _rbInfo.rollbackNamespaces); } TEST_F(RollbackImplObserverInfoTest, RollbackRecordsMultipleNamespacesOfOplogEntries) { - auto makeInsertOp = [&](NamespaceString nss, Timestamp ts, int recordId) { - return makeCRUDOp(OpTypeEnum::kInsert, - ts, - UUID::gen(), - nss.ns(), - BSON("_id" << 1), - boost::none, - recordId); + const auto makeInsertOp = [&](UUID uuid, NamespaceString nss, Timestamp ts, int recordId) { + return makeCRUDOp( + OpTypeEnum::kInsert, ts, uuid, nss.ns(), BSON("_id" << 1), boost::none, recordId); }; - auto nss1 = NamespaceString("test", "coll1"); - auto nss2 = NamespaceString("test", "coll2"); - auto nss3 = NamespaceString("test", "coll3"); + const auto nss1 = NamespaceString("test", "coll1"); + const auto nss2 = NamespaceString("test", "coll2"); + const auto nss3 = NamespaceString("test", "coll3"); - auto insertOp1 = makeInsertOp(nss1, Timestamp(2, 1), 2); - auto insertOp2 = makeInsertOp(nss2, Timestamp(3, 1), 3); - auto insertOp3 = makeInsertOp(nss3, Timestamp(4, 1), 4); + const auto uuid1 = UUID::gen(); + const auto uuid2 = UUID::gen(); + const auto uuid3 = UUID::gen(); + + const auto coll1 = _initializeCollection(_opCtx.get(), uuid1, nss1); + const auto coll2 = _initializeCollection(_opCtx.get(), uuid2, nss2); + const auto coll3 = _initializeCollection(_opCtx.get(), uuid3, nss3); + + auto insertOp1 = makeInsertOp(uuid1, nss1, Timestamp(2, 1), 2); + auto insertOp2 = makeInsertOp(uuid2, nss2, Timestamp(3, 1), 3); + auto insertOp3 = makeInsertOp(uuid3, nss3, Timestamp(4, 1), 4); ASSERT_OK(rollbackOps({insertOp3, insertOp2, insertOp1})); std::set<NamespaceString> expectedNamespaces = {nss1, nss2, nss3}; @@ -924,10 +1464,11 @@ DEATH_TEST_F(RollbackImplObserverInfoTest, } TEST_F(RollbackImplObserverInfoTest, RollbackRecordsSessionIdFromOplogEntry) { - NamespaceString nss("test.coll"); + const auto collId = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), collId, nss); auto sessionId = UUID::gen(); - auto sessionOpObj = makeSessionOp(nss, sessionId, 1L); + auto sessionOpObj = makeSessionOp(collId, nss, sessionId, 1L); auto sessionOp = std::make_pair(sessionOpObj, RecordId(recordId)); // Run the rollback and make sure the correct session id was recorded. @@ -938,23 +1479,21 @@ TEST_F(RollbackImplObserverInfoTest, RollbackRecordsSessionIdFromOplogEntry) { TEST_F(RollbackImplObserverInfoTest, RollbackDoesntRecordSessionIdFromOplogEntryWithoutSessionInfo) { - auto nss = NamespaceString("test", "coll"); - auto insertOp = makeCRUDOp(OpTypeEnum::kInsert, - Timestamp(2, 2), - UUID::gen(), - nss.ns(), - BSON("_id" << 1), - boost::none, - 2); + const auto nss = NamespaceString("test", "coll"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + auto insertOp = makeCRUDOp( + OpTypeEnum::kInsert, Timestamp(2, 2), uuid, nss.ns(), BSON("_id" << 1), boost::none, 2); ASSERT_OK(rollbackOps({insertOp})); ASSERT(_rbInfo.rollbackSessionIds.empty()); } TEST_F(RollbackImplObserverInfoTest, RollbackRecordsSessionIdFromApplyOpsSubOp) { - NamespaceString nss("test.coll"); + const auto collId = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), collId, nss); auto sessionId = UUID::gen(); - auto sessionOpObj = makeSessionOp(nss, sessionId, 1L); + auto sessionOpObj = makeSessionOp(collId, nss, sessionId, 1L); // Create the applyOps command object. BSONArrayBuilder subops; @@ -970,10 +1509,12 @@ TEST_F(RollbackImplObserverInfoTest, RollbackRecordsSessionIdFromApplyOpsSubOp) TEST_F(RollbackImplObserverInfoTest, RollbackRecordsShardIdentityRollback) { serverGlobalParams.clusterRole = ClusterRole::ShardServer; - auto nss = NamespaceString::kServerConfigurationNamespace; + const auto uuid = UUID::gen(); + const auto nss = NamespaceString::kServerConfigurationNamespace; + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); auto insertShardIdOp = makeCRUDOp(OpTypeEnum::kInsert, Timestamp(2, 2), - UUID::gen(), + uuid, nss.ns(), BSON("_id" << ShardIdentityType::IdName), boost::none, @@ -986,10 +1527,12 @@ TEST_F(RollbackImplObserverInfoTest, RollbackRecordsShardIdentityRollback) { TEST_F(RollbackImplObserverInfoTest, RollbackDoesntRecordShardIdentityRollbackForNormalDocument) { serverGlobalParams.clusterRole = ClusterRole::ShardServer; - auto nss = NamespaceString::kServerConfigurationNamespace; + const auto nss = NamespaceString::kServerConfigurationNamespace; + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); auto deleteOp = makeCRUDOp(OpTypeEnum::kDelete, Timestamp(2, 2), - UUID::gen(), + uuid, nss.ns(), BSON("_id" << "not_the_shard_id_document"), @@ -998,4 +1541,108 @@ TEST_F(RollbackImplObserverInfoTest, RollbackDoesntRecordShardIdentityRollbackFo ASSERT_OK(rollbackOps({deleteOp})); ASSERT_FALSE(_rbInfo.shardIdentityRolledBack); } + +TEST_F(RollbackImplObserverInfoTest, RollbackRecordsInsertOpsInUUIDToIdMap) { + const auto nss1 = NamespaceString("db.people"); + const auto uuid1 = UUID::gen(); + const auto coll1 = _initializeCollection(_opCtx.get(), uuid1, nss1); + const auto obj1 = BSON("_id" + << "kyle"); + const auto insertOp1 = + makeCRUDOp(OpTypeEnum::kInsert, Timestamp(2, 2), uuid1, nss.ns(), obj1, boost::none, 2); + + const auto nss2 = NamespaceString("db.persons"); + const auto uuid2 = UUID::gen(); + const auto coll2 = _initializeCollection(_opCtx.get(), uuid2, nss2); + const auto obj2 = BSON("_id" + << "jungsoo"); + const auto insertOp2 = + makeCRUDOp(OpTypeEnum::kInsert, Timestamp(3, 3), uuid2, nss2.ns(), obj2, boost::none, 3); + + ASSERT_OK(rollbackOps({insertOp2, insertOp1})); + ASSERT_EQ(_rbInfo.rollbackDeletedIdsMap.size(), 2UL); + + assertRollbackInfoContainsObjectForUUID(uuid1, obj1); + assertRollbackInfoContainsObjectForUUID(uuid2, obj2); +} + +TEST_F(RollbackImplObserverInfoTest, RollbackRecordsUpdateOpsInUUIDToIdMap) { + const auto nss1 = NamespaceString("db.coll1"); + const auto uuid1 = UUID::gen(); + const auto coll1 = _initializeCollection(_opCtx.get(), uuid1, nss1); + const auto id1 = BSON("_id" << 1); + const auto updateOp1 = makeCRUDOp(OpTypeEnum::kUpdate, + Timestamp(2, 2), + uuid1, + nss1.ns(), + BSON("$set" << BSON("foo" << 1)), + id1, + 2); + + const auto nss2 = NamespaceString("db.coll2"); + const auto uuid2 = UUID::gen(); + const auto coll2 = _initializeCollection(_opCtx.get(), uuid2, nss2); + const auto id2 = BSON("_id" << 2); + const auto updateOp2 = makeCRUDOp(OpTypeEnum::kUpdate, + Timestamp(3, 3), + uuid2, + nss2.ns(), + BSON("$set" << BSON("foo" << 1)), + id2, + 3); + + ASSERT_OK(rollbackOps({updateOp2, updateOp1})); + ASSERT_EQ(_rbInfo.rollbackDeletedIdsMap.size(), 2UL); + + assertRollbackInfoContainsObjectForUUID(uuid1, id1); + assertRollbackInfoContainsObjectForUUID(uuid2, id2); +} + +TEST_F(RollbackImplObserverInfoTest, RollbackRecordsMultipleInsertOpsForSameNamespace) { + const auto nss = NamespaceString("db.coll"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + const auto obj1 = BSON("_id" << 1); + const auto insertOp1 = + makeCRUDOp(OpTypeEnum::kInsert, Timestamp(2, 2), uuid, nss.ns(), obj1, boost::none, 2); + + const auto obj2 = BSON("_id" << 2); + const auto insertOp2 = + makeCRUDOp(OpTypeEnum::kInsert, Timestamp(3, 3), uuid, nss.ns(), obj2, boost::none, 3); + + ASSERT_OK(rollbackOps({insertOp2, insertOp1})); + ASSERT_EQ(_rbInfo.rollbackDeletedIdsMap.size(), 1UL); + + assertRollbackInfoContainsObjectForUUID(uuid, obj1); + assertRollbackInfoContainsObjectForUUID(uuid, obj2); +} + +TEST_F(RollbackImplObserverInfoTest, RollbackRecordsMultipleUpdateOpsForSameNamespace) { + const auto nss = NamespaceString("db.coll"); + const auto uuid = UUID::gen(); + const auto coll = _initializeCollection(_opCtx.get(), uuid, nss); + const auto obj1 = BSON("_id" << 1); + const auto updateOp1 = makeCRUDOp(OpTypeEnum::kUpdate, + Timestamp(2, 2), + uuid, + nss.ns(), + BSON("$set" << BSON("foo" << 1)), + obj1, + 2); + + const auto obj2 = BSON("_id" << 2); + const auto updateOp2 = makeCRUDOp(OpTypeEnum::kUpdate, + Timestamp(3, 3), + uuid, + nss.ns(), + BSON("$set" << BSON("bar" << 2)), + obj2, + 3); + + ASSERT_OK(rollbackOps({updateOp2, updateOp1})); + ASSERT_EQ(_rbInfo.rollbackDeletedIdsMap.size(), 1UL); + + assertRollbackInfoContainsObjectForUUID(uuid, obj1); + assertRollbackInfoContainsObjectForUUID(uuid, obj2); +} } // namespace |