/** * 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/db/op_observer_impl.h" #include "mongo/db/client.h" #include "mongo/db/db_raii.h" #include "mongo/db/field_parser.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/repl/oplog_interface_local.h" #include "mongo/db/repl/repl_client_info.h" #include "mongo/db/repl/replication_coordinator_mock.h" #include "mongo/db/service_context_d_test_fixture.h" namespace mongo { namespace { class OpObserverTest : public ServiceContextMongoDTest { private: void setUp() override { // Set up mongod. ServiceContextMongoDTest::setUp(); auto service = getServiceContext(); auto opCtx = cc().makeOperationContext(); // Set up ReplicationCoordinator and create oplog. repl::ReplicationCoordinator::set( service, stdx::make_unique(service)); repl::setOplogCollectionName(); repl::createOplog(opCtx.get()); // Ensure that we are primary. auto replCoord = repl::ReplicationCoordinator::get(opCtx.get()); ASSERT_OK(replCoord->setFollowerMode(repl::MemberState::RS_PRIMARY)); } protected: // Assert that oplog only has a single entry and return that oplog entry. BSONObj getSingleOplogEntry(OperationContext* opCtx) { repl::OplogInterfaceLocal oplogInterface(opCtx, NamespaceString::kRsOplogNamespace.ns()); auto oplogIter = oplogInterface.makeIterator(); auto opEntry = unittest::assertGet(oplogIter->next()); ASSERT_EQUALS(ErrorCodes::CollectionIsEmpty, oplogIter->next().getStatus()); return opEntry.first; } }; TEST_F(OpObserverTest, CollModWithCollectionOptionsAndTTLInfo) { OpObserverImpl opObserver; auto opCtx = cc().makeOperationContext(); auto uuid = CollectionUUID::gen(); // Create 'collMod' command. NamespaceString nss("test.coll"); BSONObj collModCmd = BSON("collMod" << nss.coll() << "validationLevel" << "off" << "validationAction" << "warn" // We verify that 'onCollMod' ignores this field. << "index" << "indexData"); CollectionOptions oldCollOpts; oldCollOpts.validationLevel = "strict"; oldCollOpts.validationAction = "error"; oldCollOpts.flags = 2; oldCollOpts.flagsSet = true; TTLCollModInfo ttlInfo; ttlInfo.expireAfterSeconds = Seconds(10); ttlInfo.oldExpireAfterSeconds = Seconds(5); ttlInfo.indexName = "name_of_index"; // Write to the oplog. { AutoGetDb autoDb(opCtx.get(), nss.db(), MODE_X); WriteUnitOfWork wunit(opCtx.get()); opObserver.onCollMod(opCtx.get(), nss, uuid, collModCmd, oldCollOpts, ttlInfo); wunit.commit(); } auto oplogEntry = getSingleOplogEntry(opCtx.get()); // Ensure that collMod fields were properly added to the oplog entry. auto o = oplogEntry.getObjectField("o"); auto oExpected = BSON("collMod" << nss.coll() << "validationLevel" << "off" << "validationAction" << "warn" << "index" << BSON("name" << ttlInfo.indexName << "expireAfterSeconds" << durationCount(ttlInfo.expireAfterSeconds))); ASSERT_BSONOBJ_EQ(oExpected, o); // Ensure that the old collection metadata was saved. auto o2 = oplogEntry.getObjectField("o2"); auto o2Expected = BSON("collectionOptions_old" << BSON("flags" << oldCollOpts.flags << "validationLevel" << oldCollOpts.validationLevel << "validationAction" << oldCollOpts.validationAction) << "expireAfterSeconds_old" << durationCount(ttlInfo.oldExpireAfterSeconds)); ASSERT_BSONOBJ_EQ(o2Expected, o2); } TEST_F(OpObserverTest, CollModWithOnlyCollectionOptions) { OpObserverImpl opObserver; auto opCtx = cc().makeOperationContext(); auto uuid = CollectionUUID::gen(); // Create 'collMod' command. NamespaceString nss("test.coll"); BSONObj collModCmd = BSON("collMod" << nss.coll() << "validationLevel" << "off" << "validationAction" << "warn"); CollectionOptions oldCollOpts; oldCollOpts.validationLevel = "strict"; oldCollOpts.validationAction = "error"; // Write to the oplog. { AutoGetDb autoDb(opCtx.get(), nss.db(), MODE_X); WriteUnitOfWork wunit(opCtx.get()); opObserver.onCollMod(opCtx.get(), nss, uuid, collModCmd, oldCollOpts, boost::none); wunit.commit(); } auto oplogEntry = getSingleOplogEntry(opCtx.get()); // Ensure that collMod fields were properly added to oplog entry. auto o = oplogEntry.getObjectField("o"); auto oExpected = collModCmd; ASSERT_BSONOBJ_EQ(oExpected, o); // Ensure that the old collection metadata was saved and that TTL info is not present. auto o2 = oplogEntry.getObjectField("o2"); auto o2Expected = BSON("collectionOptions_old" << BSON("validationLevel" << oldCollOpts.validationLevel << "validationAction" << oldCollOpts.validationAction)); ASSERT_BSONOBJ_EQ(o2Expected, o2); } TEST_F(OpObserverTest, OnDropCollectionReturnsDropOpTime) { OpObserverImpl opObserver; auto opCtx = cc().makeOperationContext(); auto uuid = CollectionUUID::gen(); // Create 'drop' command. NamespaceString nss("test.coll"); auto dropCmd = BSON("drop" << nss.coll()); // Write to the oplog. repl::OpTime dropOpTime; { AutoGetDb autoDb(opCtx.get(), nss.db(), MODE_X); WriteUnitOfWork wunit(opCtx.get()); dropOpTime = opObserver.onDropCollection(opCtx.get(), nss, uuid); wunit.commit(); } auto oplogEntry = getSingleOplogEntry(opCtx.get()); // Ensure that drop fields were properly added to oplog entry. auto o = oplogEntry.getObjectField("o"); auto oExpected = dropCmd; ASSERT_BSONOBJ_EQ(oExpected, o); // Ensure that the drop optime returned is the same as the last optime in the ReplClientInfo. ASSERT_EQUALS(repl::ReplClientInfo::forClient(&cc()).getLastOp(), dropOpTime); } TEST_F(OpObserverTest, OnRenameCollectionReturnsRenameOpTime) { OpObserverImpl opObserver; auto opCtx = cc().makeOperationContext(); // Create 'renameCollection' command. auto dropTarget = false; auto stayTemp = false; NamespaceString sourceNss("test.foo"); NamespaceString targetNss("test.bar"); auto renameCmd = BSON( "renameCollection" << sourceNss.ns() << "to" << targetNss.ns() << "stayTemp" << stayTemp << "dropTarget" << dropTarget); // Write to the oplog. repl::OpTime renameOpTime; { AutoGetDb autoDb(opCtx.get(), sourceNss.db(), MODE_X); WriteUnitOfWork wunit(opCtx.get()); renameOpTime = opObserver.onRenameCollection( opCtx.get(), sourceNss, targetNss, {}, dropTarget, {}, stayTemp); wunit.commit(); } auto oplogEntry = getSingleOplogEntry(opCtx.get()); // Ensure that renameCollection fields were properly added to oplog entry. auto o = oplogEntry.getObjectField("o"); auto oExpected = renameCmd; ASSERT_BSONOBJ_EQ(oExpected, o); // Ensure that the rename optime returned is the same as the last optime in the ReplClientInfo. ASSERT_EQUALS(repl::ReplClientInfo::forClient(&cc()).getLastOp(), renameOpTime); } } // namespace } // namespace mongo