// kv_storage_engine.cpp /** * Copyright (C) 2014 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage #include "mongo/db/storage/kv/kv_storage_engine.h" #include "mongo/db/operation_context_noop.h" #include "mongo/db/storage/kv/kv_database_catalog_entry.h" #include "mongo/db/storage/kv/kv_engine.h" #include "mongo/util/log.h" namespace mongo { namespace { const std::string catalogInfo = "_mdb_catalog"; } class KVStorageEngine::RemoveDBChange : public RecoveryUnit::Change { public: RemoveDBChange(KVStorageEngine* engine, const StringData& db, KVDatabaseCatalogEntry* entry) : _engine(engine) , _db(db.toString()) , _entry(entry) {} virtual void commit() { delete _entry; } virtual void rollback() { boost::mutex::scoped_lock lk(_engine->_dbsLock); _engine->_dbs[_db] = _entry; } KVStorageEngine* const _engine; const std::string _db; KVDatabaseCatalogEntry* const _entry; }; KVStorageEngine::KVStorageEngine( KVEngine* engine, const KVStorageEngineOptions& options ) : _options( options ) , _engine( engine ) , _supportsDocLocking(_engine->supportsDocLocking()) { OperationContextNoop opCtx( _engine->newRecoveryUnit() ); { WriteUnitOfWork uow( &opCtx ); Status status = _engine->createRecordStore( &opCtx, catalogInfo, catalogInfo, CollectionOptions() ); // BadValue is usually caused by invalid configuration string. // We still fassert() but without a stack trace. if (status.code() == ErrorCodes::BadValue) { fassertFailedNoTrace(28562); } fassert( 28520, status ); _catalogRecordStore.reset( _engine->getRecordStore( &opCtx, catalogInfo, catalogInfo, CollectionOptions() ) ); _catalog.reset( new KVCatalog( _catalogRecordStore.get(), _supportsDocLocking, _options.directoryPerDB, _options.directoryForIndexes) ); _catalog->init( &opCtx ); std::vector collections; _catalog->getAllCollections( &collections ); for ( size_t i = 0; i < collections.size(); i++ ) { std::string coll = collections[i]; NamespaceString nss( coll ); string dbName = nss.db().toString(); // No rollback since this is only for committed dbs. KVDatabaseCatalogEntry*& db = _dbs[dbName]; if ( !db ) { db = new KVDatabaseCatalogEntry( dbName, this ); } db->initCollection( &opCtx, coll ); } uow.commit(); } opCtx.recoveryUnit()->commitAndRestart(); // now clean up orphaned idents { // get all idents std::set allIdents; { std::vector v = _engine->getAllIdents( &opCtx ); allIdents.insert( v.begin(), v.end() ); allIdents.erase( catalogInfo ); } // remove ones still in use { vector idents = _catalog->getAllIdents( &opCtx ); for ( size_t i = 0; i < idents.size(); i++ ) { allIdents.erase( idents[i] ); } } for ( std::set::const_iterator it = allIdents.begin(); it != allIdents.end(); ++it ) { const std::string& toRemove = *it; if ( !_catalog->isUserDataIdent( toRemove ) ) continue; log() << "dropping unused ident: " << toRemove; WriteUnitOfWork wuow( &opCtx ); _engine->dropIdent( &opCtx, toRemove ); wuow.commit(); } } } void KVStorageEngine::cleanShutdown() { for ( DBMap::const_iterator it = _dbs.begin(); it != _dbs.end(); ++it ) { delete it->second; } _dbs.clear(); _catalog.reset( NULL ); _catalogRecordStore.reset( NULL ); _engine->cleanShutdown(); // intentionally not deleting _engine } KVStorageEngine::~KVStorageEngine() { } void KVStorageEngine::finishInit() { } RecoveryUnit* KVStorageEngine::newRecoveryUnit( OperationContext* opCtx ) { if ( !_engine ) { // shutdown return NULL; } return _engine->newRecoveryUnit(); } void KVStorageEngine::listDatabases( std::vector* out ) const { boost::mutex::scoped_lock lk( _dbsLock ); for ( DBMap::const_iterator it = _dbs.begin(); it != _dbs.end(); ++it ) { if ( it->second->isEmpty() ) continue; out->push_back( it->first ); } } DatabaseCatalogEntry* KVStorageEngine::getDatabaseCatalogEntry( OperationContext* opCtx, const StringData& dbName ) { boost::mutex::scoped_lock lk( _dbsLock ); KVDatabaseCatalogEntry*& db = _dbs[dbName.toString()]; if ( !db ) { // Not registering change since db creation is implicit and never rolled back. db = new KVDatabaseCatalogEntry( dbName, this ); } return db; } Status KVStorageEngine::closeDatabase( OperationContext* txn, const StringData& db ) { // This is ok to be a no-op as there is no database layer in kv. return Status::OK(); } Status KVStorageEngine::dropDatabase( OperationContext* txn, const StringData& db ) { KVDatabaseCatalogEntry* entry; { boost::mutex::scoped_lock lk( _dbsLock ); DBMap::const_iterator it = _dbs.find( db.toString() ); if ( it == _dbs.end() ) return Status( ErrorCodes::NamespaceNotFound, "db not found to drop" ); entry = it->second; } // This is called outside of a WUOW since MMAPv1 has unfortunate behavior around dropping // databases. We need to create one here since we want db dropping to all-or-nothing // wherever possible. Eventually we want to move this up so that it can include the logOp // inside of the WUOW, but that would require making DB dropping happen inside the Dur // system for MMAPv1. WriteUnitOfWork wuow(txn); std::list toDrop; entry->getCollectionNamespaces( &toDrop ); for ( std::list::iterator it = toDrop.begin(); it != toDrop.end(); ++it ) { string coll = *it; entry->dropCollection( txn, coll ); } toDrop.clear(); entry->getCollectionNamespaces( &toDrop ); invariant( toDrop.empty() ); { boost::mutex::scoped_lock lk( _dbsLock ); txn->recoveryUnit()->registerChange(new RemoveDBChange(this, db, entry)); _dbs.erase( db.toString() ); } wuow.commit(); return Status::OK(); } int KVStorageEngine::flushAllFiles( bool sync ) { return _engine->flushAllFiles( sync ); } bool KVStorageEngine::isDurable() const { return _engine->isDurable(); } Status KVStorageEngine::repairDatabase( OperationContext* txn, const std::string& dbName, bool preserveClonedFilesOnFailure, bool backupOriginalFiles ) { if ( preserveClonedFilesOnFailure ) { return Status( ErrorCodes::BadValue, "preserveClonedFilesOnFailure not supported" ); } if ( backupOriginalFiles ) { return Status( ErrorCodes::BadValue, "backupOriginalFiles not supported" ); } vector idents = _catalog->getAllIdentsForDB( dbName ); for ( size_t i = 0; i < idents.size(); i++ ) { Status status = _engine->repairIdent( txn, idents[i] ); if ( !status.isOK() ) return status; } return Status::OK(); } }