/** * Copyright 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/catalog/index_create.h" #include "mongo/db/client.h" #include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/db_raii.h" #include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" #include "mongo/db/op_observer.h" #include "mongo/db/op_observer_impl.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/drop_pending_collection_reaper.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/repl/optime.h" #include "mongo/db/repl/repl_client_info.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/repl/replication_coordinator_mock.h" #include "mongo/db/repl/storage_interface_mock.h" #include "mongo/db/service_context_d_test_fixture.h" #include "mongo/stdx/memory.h" #include "mongo/unittest/unittest.h" #include "mongo/util/scopeguard.h" namespace { using namespace mongo; ServiceContext::UniqueOperationContext makeOpCtx() { return cc().makeOperationContext(); } class DatabaseTest : public ServiceContextMongoDTest { private: void setUp() override; void tearDown() override; protected: ServiceContext::UniqueOperationContext _opCtx; NamespaceString _nss; }; void DatabaseTest::setUp() { // Set up mongod. ServiceContextMongoDTest::setUp(); auto service = getServiceContext(); _opCtx = cc().makeOperationContext(); repl::StorageInterface::set(service, stdx::make_unique()); repl::DropPendingCollectionReaper::set( service, stdx::make_unique(repl::StorageInterface::get(service))); // 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_TRUE(replCoord->setFollowerMode(repl::MemberState::RS_PRIMARY)); // Set up OpObserver so that Database will append actual oplog entries to the oplog using // repl::logOp(). repl::logOp() will also store the oplog entry's optime in ReplClientInfo. service->setOpObserver(stdx::make_unique()); _nss = NamespaceString("test.foo"); } void DatabaseTest::tearDown() { _nss = {}; _opCtx = {}; auto service = getServiceContext(); repl::DropPendingCollectionReaper::set(service, {}); repl::StorageInterface::set(service, {}); ServiceContextMongoDTest::tearDown(); } void _testDropCollection(OperationContext* opCtx, const NamespaceString& nss, bool createCollectionBeforeDrop, const repl::OpTime& dropOpTime = {}) { writeConflictRetry(opCtx, "testDropCollection", nss.ns(), [=] { AutoGetOrCreateDb autoDb(opCtx, nss.db(), MODE_X); auto db = autoDb.getDb(); ASSERT_TRUE(db); WriteUnitOfWork wuow(opCtx); if (createCollectionBeforeDrop) { ASSERT_TRUE(db->createCollection(opCtx, nss.ns())); } else { ASSERT_FALSE(db->getCollection(opCtx, nss)); } ASSERT_OK(db->dropCollection(opCtx, nss.ns(), dropOpTime)); ASSERT_FALSE(db->getCollection(opCtx, nss)); wuow.commit(); }); } TEST_F(DatabaseTest, DropCollectionReturnsOKIfCollectionDoesNotExist) { _testDropCollection(_opCtx.get(), _nss, false); // Check last optime for this client to ensure no entries were appended to the oplog. ASSERT_EQUALS(repl::OpTime(), repl::ReplClientInfo::forClient(&cc()).getLastOp()); } TEST_F(DatabaseTest, DropCollectionDropsCollectionButDoesNotLogOperationIfWritesAreNotReplicated) { repl::UnreplicatedWritesBlock uwb(_opCtx.get()); ASSERT_FALSE(_opCtx->writesAreReplicated()); ASSERT_TRUE( repl::ReplicationCoordinator::get(_opCtx.get())->isOplogDisabledFor(_opCtx.get(), _nss)); _testDropCollection(_opCtx.get(), _nss, true); // Drop optime is null because no op was written to the oplog. auto dropOpTime = repl::ReplClientInfo::forClient(&cc()).getLastOp(); 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 .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); } /** * Sets up ReplicationCoordinator for master/slave. */ void _setUpMasterSlave(ServiceContext* service) { repl::ReplSettings settings; settings.setOplogSizeBytes(10 * 1024 * 1024); settings.setMaster(true); repl::ReplicationCoordinator::set( service, stdx::make_unique(service, settings)); auto replCoord = repl::ReplicationCoordinator::get(service); ASSERT_TRUE(repl::ReplicationCoordinator::modeMasterSlave == replCoord->getReplicationMode()); } TEST_F(DatabaseTest, DropCollectionDropsCollectionAndLogsOperationIfWritesAreReplicatedAndReplModeIsMasterSlave) { _setUpMasterSlave(getServiceContext()); 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 should not be renamed under master/slave. auto dpns = _nss.makeDropPendingNamespace(dropOpTime); ASSERT_FALSE(mongo::AutoGetCollectionForRead(_opCtx.get(), dpns).getCollection()); // Reaper should not have the drop optime of the collection. auto reaperEarliestDropOpTime = repl::DropPendingCollectionReaper::get(_opCtx.get())->getEarliestDropOpTime(); ASSERT_FALSE(reaperEarliestDropOpTime); } TEST_F(DatabaseTest, DropCollectionRejectsProvidedDropOpTimeIfWritesAreReplicated) { ASSERT_TRUE(_opCtx->writesAreReplicated()); ASSERT_FALSE( repl::ReplicationCoordinator::get(_opCtx.get())->isOplogDisabledFor(_opCtx.get(), _nss)); auto opCtx = _opCtx.get(); auto nss = _nss; writeConflictRetry(opCtx, "testDropOpTimeWithReplicated", nss.ns(), [opCtx, nss] { AutoGetOrCreateDb autoDb(opCtx, nss.db(), MODE_X); auto db = autoDb.getDb(); ASSERT_TRUE(db); WriteUnitOfWork wuow(opCtx); ASSERT_TRUE(db->createCollection(opCtx, nss.ns())); repl::OpTime dropOpTime(Timestamp(Seconds(100), 0), 1LL); ASSERT_EQUALS(ErrorCodes::BadValue, db->dropCollection(opCtx, nss.ns(), 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 .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); } TEST_F( DatabaseTest, DropCollectionIgnoresProvidedDropOpTimeAndDropsCollectionButDoesNotLogOperationIfReplModeIsMasterSlave) { _setUpMasterSlave(getServiceContext()); 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()); // Collection is not renamed under master/slave. auto dpns = _nss.makeDropPendingNamespace(dropOpTime); ASSERT_FALSE(mongo::AutoGetCollectionForRead(_opCtx.get(), dpns).getCollection()); // Reaper should not have the drop optime of the collection. auto reaperEarliestDropOpTime = repl::DropPendingCollectionReaper::get(_opCtx.get())->getEarliestDropOpTime(); ASSERT_FALSE(reaperEarliestDropOpTime); } void _testDropCollectionThrowsExceptionIfThereAreIndexesInProgress(OperationContext* opCtx, const NamespaceString& nss) { writeConflictRetry(opCtx, "testDropCollectionWithIndexesInProgress", nss.ns(), [opCtx, nss] { AutoGetOrCreateDb autoDb(opCtx, nss.db(), MODE_X); auto db = autoDb.getDb(); ASSERT_TRUE(db); Collection* collection = nullptr; { WriteUnitOfWork wuow(opCtx); ASSERT_TRUE(collection = db->createCollection(opCtx, nss.ns())); wuow.commit(); } MultiIndexBlock indexer(opCtx, collection); ON_BLOCK_EXIT([&indexer, opCtx] { WriteUnitOfWork wuow(opCtx); indexer.commit(); wuow.commit(); }); auto indexCatalog = collection->getIndexCatalog(); ASSERT_EQUALS(indexCatalog->numIndexesInProgress(opCtx), 0); auto indexInfoObj = BSON( "v" << int(IndexDescriptor::kLatestIndexVersion) << "key" << BSON("a" << 1) << "name" << "a_1" << "ns" << nss.ns()); ASSERT_OK(indexer.init(indexInfoObj).getStatus()); ASSERT_GREATER_THAN(indexCatalog->numIndexesInProgress(opCtx), 0); WriteUnitOfWork wuow(opCtx); ASSERT_THROWS_CODE(db->dropCollection(opCtx, nss.ns()), MsgAssertionException, 40461); }); } TEST_F(DatabaseTest, DropCollectionThrowsExceptionIfThereAreIndexesInProgressAndWritesAreNotReplicated) { repl::UnreplicatedWritesBlock uwb(_opCtx.get()); ASSERT_FALSE(_opCtx->writesAreReplicated()); _testDropCollectionThrowsExceptionIfThereAreIndexesInProgress(_opCtx.get(), _nss); } TEST_F(DatabaseTest, DropCollectionThrowsExceptionIfThereAreIndexesInProgressAndWritesAreReplicated) { ASSERT_TRUE(_opCtx->writesAreReplicated()); _testDropCollectionThrowsExceptionIfThereAreIndexesInProgress(_opCtx.get(), _nss); } } // namespace