/** * Copyright (C) 2017 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * 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 GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/platform/basic.h" #include "mongo/db/repl/rollback_test_fixture.h" #include #include "mongo/db/catalog/collection_catalog_entry.h" #include "mongo/db/catalog/database_holder.h" #include "mongo/db/client.h" #include "mongo/db/db_raii.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/repl/replication_consistency_markers_mock.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/repl/replication_coordinator_mock.h" #include "mongo/db/repl/replication_process.h" #include "mongo/db/repl/replication_recovery.h" #include "mongo/db/repl/rs_rollback.h" #include "mongo/db/session_catalog.h" #include "mongo/logger/log_component.h" #include "mongo/logger/logger.h" #include "mongo/stdx/memory.h" #include "mongo/util/mongoutils/str.h" namespace mongo { namespace repl { namespace { /** * Creates ReplSettings for ReplicationCoordinatorRollbackMock. */ ReplSettings createReplSettings() { ReplSettings settings; settings.setOplogSizeBytes(5 * 1024 * 1024); settings.setReplSetString("mySet/node1:12345"); return settings; } } // namespace void RollbackTest::setUp() { _storageInterface = new StorageInterfaceRollback(); auto serviceContext = getServiceContext(); auto consistencyMarkers = stdx::make_unique(); auto recovery = stdx::make_unique(_storageInterface, consistencyMarkers.get()); _replicationProcess = stdx::make_unique( _storageInterface, std::move(consistencyMarkers), std::move(recovery)); _dropPendingCollectionReaper = new DropPendingCollectionReaper(_storageInterface); DropPendingCollectionReaper::set( serviceContext, std::unique_ptr(_dropPendingCollectionReaper)); StorageInterface::set(serviceContext, std::unique_ptr(_storageInterface)); _coordinator = new ReplicationCoordinatorRollbackMock(serviceContext); ReplicationCoordinator::set(serviceContext, std::unique_ptr(_coordinator)); setOplogCollectionName(serviceContext); _opCtx = makeOperationContext(); _replicationProcess->getConsistencyMarkers()->clearAppliedThrough(_opCtx.get(), {}); _replicationProcess->getConsistencyMarkers()->setMinValid(_opCtx.get(), OpTime{}); _replicationProcess->initializeRollbackID(_opCtx.get()).transitional_ignore(); // Increase rollback log component verbosity for unit tests. mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( logger::LogComponent::kReplicationRollback, logger::LogSeverity::Debug(2)); } RollbackTest::ReplicationCoordinatorRollbackMock::ReplicationCoordinatorRollbackMock( ServiceContext* service) : ReplicationCoordinatorMock(service, createReplSettings()) {} void RollbackTest::ReplicationCoordinatorRollbackMock::failSettingFollowerMode( const MemberState& transitionToFail, ErrorCodes::Error codeToFailWith) { _failSetFollowerModeOnThisMemberState = transitionToFail; _failSetFollowerModeWithThisCode = codeToFailWith; } Status RollbackTest::ReplicationCoordinatorRollbackMock::setFollowerMode( const MemberState& newState) { if (newState == _failSetFollowerModeOnThisMemberState) { return Status(_failSetFollowerModeWithThisCode, str::stream() << "ReplicationCoordinatorRollbackMock set to fail on setting state to " << _failSetFollowerModeOnThisMemberState.toString()); } return ReplicationCoordinatorMock::setFollowerMode(newState); } std::pair RollbackTest::makeCRUDOp(OpTypeEnum opType, Timestamp ts, UUID uuid, StringData nss, BSONObj o, boost::optional o2, int recordId) { invariant(opType != OpTypeEnum::kCommand); BSONObjBuilder bob; bob.append("ts", ts); bob.append("h", 1LL); bob.append("op", OpType_serializer(opType)); uuid.appendToBuilder(&bob, "ui"); bob.append("ns", nss); bob.append("o", o); if (o2) { bob.append("o2", *o2); } return std::make_pair(bob.obj(), RecordId(recordId)); } std::pair RollbackTest::makeCommandOp( Timestamp ts, OptionalCollectionUUID uuid, StringData nss, BSONObj cmdObj, int recordId) { BSONObjBuilder bob; bob.append("ts", ts); bob.append("h", 1LL); bob.append("op", "c"); if (uuid) { // Not all ops have UUID fields. uuid.get().appendToBuilder(&bob, "ui"); } bob.append("ns", nss); bob.append("o", cmdObj); return std::make_pair(bob.obj(), RecordId(recordId)); } Collection* RollbackTest::_createCollection(OperationContext* opCtx, const NamespaceString& nss, const CollectionOptions& options) { Lock::DBLock dbLock(opCtx, nss.db(), MODE_X); mongo::WriteUnitOfWork wuow(opCtx); auto db = DatabaseHolder::getDatabaseHolder().openDb(opCtx, nss.db()); ASSERT_TRUE(db); db->dropCollection(opCtx, nss.ns()).transitional_ignore(); auto coll = db->createCollection(opCtx, nss.ns(), options); ASSERT_TRUE(coll); wuow.commit(); return coll; } Collection* RollbackTest::_createCollection(OperationContext* opCtx, const std::string& nss, const CollectionOptions& options) { return _createCollection(opCtx, NamespaceString(nss), options); } Status RollbackTest::_insertOplogEntry(const BSONObj& doc) { TimestampedBSONObj obj; obj.obj = doc; return _storageInterface->insertDocument( _opCtx.get(), NamespaceString::kRsOplogNamespace, obj, 0); } RollbackSourceMock::RollbackSourceMock(std::unique_ptr oplog) : _oplog(std::move(oplog)) {} const OplogInterface& RollbackSourceMock::getOplog() const { return *_oplog; } const HostAndPort& RollbackSourceMock::getSource() const { return _source; } int RollbackSourceMock::getRollbackId() const { return 0; } BSONObj RollbackSourceMock::getLastOperation() const { auto iter = _oplog->makeIterator(); auto result = iter->next(); ASSERT_OK(result.getStatus()); return result.getValue().first; } BSONObj RollbackSourceMock::findOne(const NamespaceString& nss, const BSONObj& filter) const { return BSONObj(); } std::pair RollbackSourceMock::findOneByUUID(const std::string& db, UUID uuid, const BSONObj& filter) const { return {BSONObj(), NamespaceString()}; } void RollbackSourceMock::copyCollectionFromRemote(OperationContext* opCtx, const NamespaceString& nss) const {} StatusWith RollbackSourceMock::getCollectionInfo(const NamespaceString& nss) const { return BSON("name" << nss.ns() << "options" << BSONObj()); } StatusWith RollbackSourceMock::getCollectionInfoByUUID(const std::string& db, const UUID& uuid) const { return BSON("options" << BSONObj() << "info" << BSON("uuid" << uuid)); } RollbackResyncsCollectionOptionsTest::RollbackSourceWithCollectionOptions:: RollbackSourceWithCollectionOptions(std::unique_ptr oplog, BSONObj collOptionsObj) : RollbackSourceMock(std::move(oplog)), collOptionsObj(collOptionsObj) {} StatusWith 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" << "noPadding" << false), "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) << "h" << 1LL << "t" << 1LL << "op" << "n" << "o" << BSONObj() << "ns" << "rollback_test.test" << "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(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.getCollection()->getCatalogEntry()->getCollectionOptions(_opCtx.get()); 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