diff options
-rw-r--r-- | src/mongo/db/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection.h | 3 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_mock.h | 276 | ||||
-rw-r--r-- | src/mongo/db/catalog/database_impl.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/catalog/rename_collection.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/commands/mr.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/op_observer.h | 1 | ||||
-rw-r--r-- | src/mongo/db/op_observer_impl.cpp | 22 | ||||
-rw-r--r-- | src/mongo/db/op_observer_impl.h | 1 | ||||
-rw-r--r-- | src/mongo/db/op_observer_noop.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/op_observer_noop.h | 1 | ||||
-rw-r--r-- | src/mongo/util/SConscript | 26 | ||||
-rw-r--r-- | src/mongo/util/namespace_uuid_cache.cpp | 10 | ||||
-rw-r--r-- | src/mongo/util/namespace_uuid_cache.h | 21 | ||||
-rw-r--r-- | src/mongo/util/namespace_uuid_cache_test.cpp | 4 | ||||
-rw-r--r-- | src/mongo/util/uuid_catalog.cpp | 79 | ||||
-rw-r--r-- | src/mongo/util/uuid_catalog.h | 85 | ||||
-rw-r--r-- | src/mongo/util/uuid_catalog_test.cpp | 85 |
18 files changed, 624 insertions, 21 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 05c31829d07..63119c54019 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -602,6 +602,7 @@ env.Library( 'commands/dcommands', 'repl/serveronly', 'views/views_mongod', + '$BUILD_DIR/mongo/util/uuid_catalog', ], ) diff --git a/src/mongo/db/catalog/collection.h b/src/mongo/db/catalog/collection.h index 1f4bd4bf6bb..25fb8930290 100644 --- a/src/mongo/db/catalog/collection.h +++ b/src/mongo/db/catalog/collection.h @@ -340,6 +340,9 @@ public: this->_impl().init(opCtx); } + // Use this constructor only for testing/mocks + explicit inline Collection(std::unique_ptr<Impl> mock) : _pimpl(std::move(mock)) {} + inline ~Collection() = default; inline bool ok() const { diff --git a/src/mongo/db/catalog/collection_mock.h b/src/mongo/db/catalog/collection_mock.h new file mode 100644 index 00000000000..7b46367a766 --- /dev/null +++ b/src/mongo/db/catalog/collection_mock.h @@ -0,0 +1,276 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * 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. + */ + +#pragma once + +#include "mongo/db/catalog/collection.h" + +namespace mongo { + +/** + * This class comprises a mock Collection for use by UUIDCatalog unit tests. + */ +class CollectionMock : virtual public Collection::Impl, + virtual CappedCallback, + virtual UpdateNotifier { +public: + CollectionMock(const NamespaceString& ns) : _ns(ns) {} + ~CollectionMock() = default; + + void init(OperationContext* opCtx) { + std::abort(); + } + +private: + DatabaseCatalogEntry* dbce() const { + std::abort(); + } + + CollectionCatalogEntry* details() const { + std::abort(); + } + + Status aboutToDeleteCapped(OperationContext* opCtx, const RecordId& loc, RecordData data) { + std::abort(); + } + + Status recordStoreGoingToUpdateInPlace(OperationContext* opCtx, const RecordId& loc) { + std::abort(); + } + const NamespaceString _ns; + +public: + const NamespaceString& ns() const { + return _ns; + } + bool ok() const { + std::abort(); + } + + CollectionCatalogEntry* getCatalogEntry() { + std::abort(); + } + const CollectionCatalogEntry* getCatalogEntry() const { + std::abort(); + } + + CollectionInfoCache* infoCache() { + std::abort(); + } + const CollectionInfoCache* infoCache() const { + std::abort(); + } + + const IndexCatalog* getIndexCatalog() const { + std::abort(); + } + IndexCatalog* getIndexCatalog() { + std::abort(); + } + + const RecordStore* getRecordStore() const { + std::abort(); + } + RecordStore* getRecordStore() { + std::abort(); + } + + CursorManager* getCursorManager() const { + std::abort(); + } + + bool requiresIdIndex() const { + std::abort(); + } + + Snapshotted<BSONObj> docFor(OperationContext* opCtx, const RecordId& loc) const { + std::abort(); + } + + bool findDoc(OperationContext* opCtx, const RecordId& loc, Snapshotted<BSONObj>* out) const { + std::abort(); + } + + std::unique_ptr<SeekableRecordCursor> getCursor(OperationContext* opCtx, bool forward) const { + std::abort(); + } + + std::vector<std::unique_ptr<RecordCursor>> getManyCursors(OperationContext* opCtx) const { + std::abort(); + } + + void deleteDocument(OperationContext* opCtx, + const RecordId& loc, + OpDebug* opDebug, + bool fromMigrate, + bool noWarn) { + std::abort(); + } + + Status insertDocuments(OperationContext* opCtx, + std::vector<BSONObj>::const_iterator begin, + std::vector<BSONObj>::const_iterator end, + OpDebug* opDebug, + bool enforceQuota, + bool fromMigrate) { + std::abort(); + } + + Status insertDocument(OperationContext* opCtx, + const BSONObj& doc, + OpDebug* opDebug, + bool enforceQuota, + bool fromMigrate) { + std::abort(); + } + + Status insertDocumentsForOplog(OperationContext* opCtx, + const DocWriter* const* docs, + size_t nDocs) { + std::abort(); + } + + Status insertDocument(OperationContext* opCtx, + const BSONObj& doc, + const std::vector<MultiIndexBlock*>& indexBlocks, + bool enforceQuota) { + std::abort(); + } + + StatusWith<RecordId> updateDocument(OperationContext* opCtx, + const RecordId& oldLocation, + const Snapshotted<BSONObj>& oldDoc, + const BSONObj& newDoc, + bool enforceQuota, + bool indexesAffected, + OpDebug* opDebug, + OplogUpdateEntryArgs* args) { + std::abort(); + } + + bool updateWithDamagesSupported() const { + std::abort(); + } + + StatusWith<RecordData> updateDocumentWithDamages(OperationContext* opCtx, + const RecordId& loc, + const Snapshotted<RecordData>& oldRec, + const char* damageSource, + const mutablebson::DamageVector& damages, + OplogUpdateEntryArgs* args) { + std::abort(); + } + + StatusWith<CompactStats> compact(OperationContext* opCtx, const CompactOptions* options) { + std::abort(); + } + Status truncate(OperationContext* opCtx) { + std::abort(); + } + + Status validate(OperationContext* opCtx, + ValidateCmdLevel level, + ValidateResults* results, + BSONObjBuilder* output) { + std::abort(); + } + + Status touch(OperationContext* opCtx, + bool touchData, + bool touchIndexes, + BSONObjBuilder* output) const { + std::abort(); + } + + void cappedTruncateAfter(OperationContext* opCtx, RecordId end, bool inclusive) { + std::abort(); + } + + StatusWithMatchExpression parseValidator(const BSONObj& validator) const { + std::abort(); + } + + Status setValidator(OperationContext* opCtx, BSONObj validator) { + std::abort(); + } + + Status setValidationLevel(OperationContext* opCtx, StringData newLevel) { + std::abort(); + } + Status setValidationAction(OperationContext* opCtx, StringData newAction) { + std::abort(); + } + + StringData getValidationLevel() const { + std::abort(); + } + StringData getValidationAction() const { + std::abort(); + } + + bool isCapped() const { + std::abort(); + } + + std::shared_ptr<CappedInsertNotifier> getCappedInsertNotifier() const { + std::abort(); + } + + uint64_t numRecords(OperationContext* opCtx) const { + std::abort(); + } + + uint64_t dataSize(OperationContext* opCtx) const { + std::abort(); + } + + uint64_t getIndexSize(OperationContext* opCtx, BSONObjBuilder* details, int scale) { + std::abort(); + } + + boost::optional<SnapshotName> getMinimumVisibleSnapshot() { + std::abort(); + } + + void setMinimumVisibleSnapshot(SnapshotName name) { + std::abort(); + } + + void notifyCappedWaitersIfNeeded() { + std::abort(); + } + + const CollatorInterface* getDefaultCollator() const { + std::abort(); + } + + OptionalCollectionUUID uuid(OperationContext* opCtx) const { + std::abort(); + } +}; +} // namespace mongo diff --git a/src/mongo/db/catalog/database_impl.cpp b/src/mongo/db/catalog/database_impl.cpp index 59ba8c1ed53..ab8ba32ca7f 100644 --- a/src/mongo/db/catalog/database_impl.cpp +++ b/src/mongo/db/catalog/database_impl.cpp @@ -66,6 +66,7 @@ #include "mongo/util/assert_util.h" #include "mongo/util/log.h" #include "mongo/util/namespace_uuid_cache.h" +#include "mongo/util/uuid_catalog.h" namespace mongo { namespace { @@ -462,11 +463,6 @@ Status DatabaseImpl::dropCollectionEvenIfSystem(OperationContext* opCtx, getGlobalServiceContext()->getOpObserver()->onDropCollection(opCtx, fullns, uuid); - // Evict namespace entry from the namespace/uuid cache. - if (enableCollectionUUIDs) { - NamespaceUUIDCache& cache = NamespaceUUIDCache::get(opCtx); - cache.evictNamespace(fullns); - } return Status::OK(); } @@ -548,11 +544,6 @@ Status DatabaseImpl::renameCollection(OperationContext* opCtx, Status s = _dbEntry->renameCollection(opCtx, fromNS, toNS, stayTemp); _collections[toNS] = _getOrCreateCollectionInstance(opCtx, toNSS); - // Evict namespace entry from the namespace/uuid cache. - if (enableCollectionUUIDs) { - NamespaceUUIDCache& cache = NamespaceUUIDCache::get(opCtx); - cache.evictNamespace(fromNSS); - } return s; } @@ -648,7 +639,7 @@ Collection* DatabaseImpl::createCollection(OperationContext* opCtx, } getGlobalServiceContext()->getOpObserver()->onCreateCollection( - opCtx, nss, options, fullIdIndexSpec); + opCtx, collection, nss, options, fullIdIndexSpec); return collection; } diff --git a/src/mongo/db/catalog/rename_collection.cpp b/src/mongo/db/catalog/rename_collection.cpp index d9240302534..37e35387249 100644 --- a/src/mongo/db/catalog/rename_collection.cpp +++ b/src/mongo/db/catalog/rename_collection.cpp @@ -190,14 +190,16 @@ Status renameCollection(OperationContext* opCtx, // write lock acquired at the top. NamespaceString tmpName(target.db(), "tmp.renameCollection"); Collection* tmpColl = nullptr; - OptionalCollectionUUID tmpUUID; + OptionalCollectionUUID newUUID; { CollectionOptions options = sourceColl->getCatalogEntry()->getCollectionOptions(opCtx); // Renaming across databases will result in a new UUID, as otherwise we'd require // two collections with the same uuid (temporarily). options.temp = true; - if (enableCollectionUUIDs) - tmpUUID = UUID::gen(); + if (enableCollectionUUIDs) { + newUUID = UUID::gen(); + options.uuid = newUUID; + } MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { WriteUnitOfWork wunit(opCtx); @@ -294,7 +296,7 @@ Status renameCollection(OperationContext* opCtx, opCtx, source, target, - tmpUUID, + newUUID, dropTarget, dropTargetUUID, /*dropSourceUUID*/ sourceUUID, diff --git a/src/mongo/db/commands/mr.cpp b/src/mongo/db/commands/mr.cpp index 7b64dadbbdb..8993e12db51 100644 --- a/src/mongo/db/commands/mr.cpp +++ b/src/mongo/db/commands/mr.cpp @@ -425,6 +425,9 @@ void State::prepTempCollection() { CollectionOptions options; options.setNoIdIndex(); options.temp = true; + if (enableCollectionUUIDs) { + options.uuid.emplace(UUID::gen()); + } incColl = incCtx.db()->createCollection(_opCtx, _config.incLong.ns(), options); invariant(incColl); @@ -496,6 +499,9 @@ void State::prepTempCollection() { CollectionOptions options = finalOptions; options.temp = true; + if (enableCollectionUUIDs) { + options.uuid.emplace(UUID::gen()); + } tempColl = tempCtx.db()->createCollection(_opCtx, _config.tempNamespace.ns(), options); for (vector<BSONObj>::iterator it = indexesToInsert.begin(); it != indexesToInsert.end(); diff --git a/src/mongo/db/op_observer.h b/src/mongo/db/op_observer.h index 5a3e089f261..33d2a76ba16 100644 --- a/src/mongo/db/op_observer.h +++ b/src/mongo/db/op_observer.h @@ -106,6 +106,7 @@ public: bool fromMigrate) = 0; virtual void onOpMessage(OperationContext* opCtx, const BSONObj& msgObj) = 0; virtual void onCreateCollection(OperationContext* opCtx, + Collection* coll, const NamespaceString& collectionName, const CollectionOptions& options, const BSONObj& idIndex) = 0; diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp index 2f0cf6eef00..bde44ed01ca 100644 --- a/src/mongo/db/op_observer_impl.cpp +++ b/src/mongo/db/op_observer_impl.cpp @@ -43,6 +43,8 @@ #include "mongo/db/server_options.h" #include "mongo/db/views/durable_view_catalog.h" #include "mongo/scripting/engine.h" +#include "mongo/util/namespace_uuid_cache.h" +#include "mongo/util/uuid_catalog.h" namespace mongo { @@ -185,6 +187,7 @@ void OpObserverImpl::onOpMessage(OperationContext* opCtx, const BSONObj& msgObj) } void OpObserverImpl::onCreateCollection(OperationContext* opCtx, + Collection* coll, const NamespaceString& collectionName, const CollectionOptions& options, const BSONObj& idIndex) { @@ -217,6 +220,11 @@ void OpObserverImpl::onCreateCollection(OperationContext* opCtx, getGlobalAuthorizationManager()->logOp(opCtx, "c", dbName, cmdObj, nullptr); logOpForDbHash(opCtx, dbName); + + if (options.uuid) { + UUIDCatalog& catalog = UUIDCatalog::get(opCtx->getServiceContext()); + catalog.onCreateCollection(opCtx, coll, options.uuid.get()); + } } namespace { @@ -320,6 +328,16 @@ void OpObserverImpl::onDropCollection(OperationContext* opCtx, auto css = CollectionShardingState::get(opCtx, collectionName); css->onDropCollection(opCtx, collectionName); + // Evict namespace entry from the namespace/uuid cache if it exists. + NamespaceUUIDCache& cache = NamespaceUUIDCache::get(opCtx); + cache.onDropCollection(collectionName); + + // Remove collection from the uuid catalog. + if (uuid) { + UUIDCatalog& catalog = UUIDCatalog::get(opCtx->getServiceContext()); + catalog.onDropCollection(opCtx, uuid.get()); + } + logOpForDbHash(opCtx, dbName); } @@ -368,6 +386,10 @@ void OpObserverImpl::onRenameCollection(OperationContext* opCtx, getGlobalAuthorizationManager()->logOp(opCtx, "c", cmdNss, cmdObj, nullptr); logOpForDbHash(opCtx, cmdNss); + + // Evict namespace entry from the namespace/uuid cache if it exists. + NamespaceUUIDCache& cache = NamespaceUUIDCache::get(opCtx); + cache.onRenameCollection(fromCollection); } void OpObserverImpl::onApplyOps(OperationContext* opCtx, diff --git a/src/mongo/db/op_observer_impl.h b/src/mongo/db/op_observer_impl.h index aaff2de5f64..525a94bda71 100644 --- a/src/mongo/db/op_observer_impl.h +++ b/src/mongo/db/op_observer_impl.h @@ -61,6 +61,7 @@ public: bool fromMigrate) override; void onOpMessage(OperationContext* opCtx, const BSONObj& msgObj) override; void onCreateCollection(OperationContext* opCtx, + Collection* coll, const NamespaceString& collectionName, const CollectionOptions& options, const BSONObj& idIndex) override; diff --git a/src/mongo/db/op_observer_noop.cpp b/src/mongo/db/op_observer_noop.cpp index 34234940816..5a5b04ecc53 100644 --- a/src/mongo/db/op_observer_noop.cpp +++ b/src/mongo/db/op_observer_noop.cpp @@ -59,6 +59,7 @@ void OpObserverNoop::onDelete(OperationContext*, void OpObserverNoop::onOpMessage(OperationContext*, const BSONObj&) {} void OpObserverNoop::onCreateCollection(OperationContext*, + Collection*, const NamespaceString&, const CollectionOptions&, const BSONObj&) {} diff --git a/src/mongo/db/op_observer_noop.h b/src/mongo/db/op_observer_noop.h index 1701a875f3a..b624282fd7d 100644 --- a/src/mongo/db/op_observer_noop.h +++ b/src/mongo/db/op_observer_noop.h @@ -61,6 +61,7 @@ public: bool fromMigrate) override; void onOpMessage(OperationContext* opCtx, const BSONObj& msgObj) override; void onCreateCollection(OperationContext* opCtx, + Collection* coll, const NamespaceString& collectionName, const CollectionOptions& options, const BSONObj& idIndex) override; diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript index 87f7226d33b..ebe9ae87d3d 100644 --- a/src/mongo/util/SConscript +++ b/src/mongo/util/SConscript @@ -100,6 +100,20 @@ env.Library( ], LIBDEPS=[ '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/db/catalog/collection', + '$BUILD_DIR/mongo/util/decorable', + '$BUILD_DIR/third_party/murmurhash3/murmurhash3', + ], +) + +env.Library( + target='uuid_catalog', + source=[ + 'uuid_catalog.cpp' + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/db/catalog/collection', '$BUILD_DIR/mongo/util/decorable', '$BUILD_DIR/third_party/murmurhash3/murmurhash3', ], @@ -126,6 +140,18 @@ env.CppUnitTest( ], ) +env.CppUnitTest( + target='uuid_catalog_test', + source=[ + 'uuid_catalog_test.cpp', + ], + LIBDEPS=[ + 'uuid_catalog', + 'uuid', + '$BUILD_DIR/mongo/db/service_context', + ] + ) + env.Library( target='summation', source=[ diff --git a/src/mongo/util/namespace_uuid_cache.cpp b/src/mongo/util/namespace_uuid_cache.cpp index 0cf1d9c30a1..59e40b73166 100644 --- a/src/mongo/util/namespace_uuid_cache.cpp +++ b/src/mongo/util/namespace_uuid_cache.cpp @@ -49,7 +49,15 @@ void NamespaceUUIDCache::ensureNamespaceInCache(const NamespaceString& nss, Coll it->second == uuid); } } -void NamespaceUUIDCache::evictNamespace(const NamespaceString& nss) { +void NamespaceUUIDCache::onDropCollection(const NamespaceString& nss) { + _evictNamespace(nss); +} + +void NamespaceUUIDCache::onRenameCollection(const NamespaceString& nss) { + _evictNamespace(nss); +} + +void NamespaceUUIDCache::_evictNamespace(const NamespaceString& nss) { invariant(_cache.erase(nss.ns()) <= 1); } } // namespace mongo diff --git a/src/mongo/util/namespace_uuid_cache.h b/src/mongo/util/namespace_uuid_cache.h index a38394787de..10f4bd75206 100644 --- a/src/mongo/util/namespace_uuid_cache.h +++ b/src/mongo/util/namespace_uuid_cache.h @@ -52,17 +52,32 @@ public: /** * This function adds the pair nss.ns(), uuid to the namespace uuid cache * if it does not yet exist. If nss.ns() already exists in the cache with - * a different uuid, a UserException is thrown. + * a different uuid, a UserException is thrown, so we can guarantee that + * an operation will always resolve the same name to the same collection, + * even in presence of drops and renames. */ void ensureNamespaceInCache(const NamespaceString& nss, CollectionUUID uuid); /** * This function removes the entry for nss.ns() from the namespace uuid - * cache. Does nothing if the entry doesn't exist. + * cache. Does nothing if the entry doesn't exist. It is called via the + * op observer when a collection is dropped. + */ + void onDropCollection(const NamespaceString& nss); + + /** + * This function removes the entry for nss.ns() from the namespace uuid + * cache. Does nothing if the entry doesn't exist. It is called via the + * op observer when a collection is renamed. */ - void evictNamespace(const NamespaceString& nss); + void onRenameCollection(const NamespaceString& nss); private: + /** + * This function removes the entry for nss.ns() from the namespace uuid + * cache. Does nothing if the entry doesn't exist. + */ + void _evictNamespace(const NamespaceString& nss); using CollectionUUIDMap = StringMap<CollectionUUID>; CollectionUUIDMap _cache; }; diff --git a/src/mongo/util/namespace_uuid_cache_test.cpp b/src/mongo/util/namespace_uuid_cache_test.cpp index 906cfebaebb..7834dcb4968 100644 --- a/src/mongo/util/namespace_uuid_cache_test.cpp +++ b/src/mongo/util/namespace_uuid_cache_test.cpp @@ -47,13 +47,13 @@ TEST(NamespaceUUIDCache, ensureNamespaceInCache) { ASSERT_THROWS(cache.ensureNamespaceInCache(nss, uuidConflict), UserException); } -TEST(NamespaceUUIDCache, evictEntry) { +TEST(NamespaceUUIDCache, onDropCollection) { NamespaceUUIDCache cache; CollectionUUID uuid = CollectionUUID::gen(); CollectionUUID newUuid = CollectionUUID::gen(); NamespaceString nss("test", "test_collection_ns"); cache.ensureNamespaceInCache(nss, uuid); - cache.evictNamespace(nss); + cache.onDropCollection(nss); // Add nss to the cache with a different uuid. This should not throw since // we evicted the previous entry from the cache. cache.ensureNamespaceInCache(nss, newUuid); diff --git a/src/mongo/util/uuid_catalog.cpp b/src/mongo/util/uuid_catalog.cpp new file mode 100644 index 00000000000..6ec799414c1 --- /dev/null +++ b/src/mongo/util/uuid_catalog.cpp @@ -0,0 +1,79 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * 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 "uuid_catalog.h" + +#include "mongo/db/storage/recovery_unit.h" +#include "mongo/util/uuid.h" + +namespace mongo { + +const ServiceContext::Decoration<UUIDCatalog> UUIDCatalog::get = + ServiceContext::declareDecoration<UUIDCatalog>(); + +void UUIDCatalog::onCreateCollection(OperationContext* opCtx, + Collection* coll, + CollectionUUID uuid) { + _registerUUIDCatalogEntry(uuid, coll); + opCtx->recoveryUnit()->onRollback([this, uuid] { _removeUUIDCatalogEntry(uuid); }); +} + +Collection* UUIDCatalog::lookupCollectionByUUID(CollectionUUID uuid) { + stdx::lock_guard<stdx::mutex> lock(_catalogLock); + Collection* foundCol = _catalog[uuid]; + return foundCol; +} + +NamespaceString UUIDCatalog::lookupNSSByUUID(CollectionUUID uuid) { + stdx::lock_guard<stdx::mutex> lock(_catalogLock); + Collection* foundCol = _catalog[uuid]; + NamespaceString nss = foundCol ? foundCol->ns() : NamespaceString(); + return nss; +} + +void UUIDCatalog::onDropCollection(OperationContext* opCtx, CollectionUUID uuid) { + Collection* foundCol = _removeUUIDCatalogEntry(uuid); + opCtx->recoveryUnit()->onRollback( + [this, foundCol, uuid] { _registerUUIDCatalogEntry(uuid, foundCol); }); +} + +void UUIDCatalog::_registerUUIDCatalogEntry(CollectionUUID uuid, Collection* coll) { + stdx::lock_guard<stdx::mutex> lock(_catalogLock); + if (coll) { + std::pair<CollectionUUID, Collection*> entry = std::make_pair(uuid, coll); + invariant(_catalog.insert(entry).second == true); + } +} + +Collection* UUIDCatalog::_removeUUIDCatalogEntry(CollectionUUID uuid) { + stdx::lock_guard<stdx::mutex> lock(_catalogLock); + Collection* foundCol = _catalog[uuid]; + invariant(_catalog.erase(uuid) <= 1); + return foundCol; +} +} // namespace mongo diff --git a/src/mongo/util/uuid_catalog.h b/src/mongo/util/uuid_catalog.h new file mode 100644 index 00000000000..f79b2ded90f --- /dev/null +++ b/src/mongo/util/uuid_catalog.h @@ -0,0 +1,85 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * 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. + */ + +#pragma once + +#include <unordered_map> + +#include "mongo/base/disallow_copying.h" +#include "mongo/db/catalog/collection.h" +#include "mongo/db/service_context.h" +#include "mongo/util/uuid.h" + +namespace mongo { + +/** + * This class comprises a UUID to collection catalog, allowing for efficient + * collection lookup by UUID. + */ +using CollectionUUID = UUID; + +class UUIDCatalog { + MONGO_DISALLOW_COPYING(UUIDCatalog); + +public: + static const ServiceContext::Decoration<UUIDCatalog> get; + UUIDCatalog() = default; + + /* This function inserts the entry for uuid, coll into the UUID + * Collection. It is called by the op observer when a collection + * is created. + */ + void onCreateCollection(OperationContext* opCtx, Collection* coll, CollectionUUID uuid); + + /* This function gets the Collection* pointer that corresponds to + * CollectionUUID uuid. The required locks should be obtained prior + * to calling this function, or else the found Collection pointer + * might no longer be valid when the call returns. + */ + Collection* lookupCollectionByUUID(CollectionUUID uuid); + + /* This function gets the NamespaceString from the Collection* pointer that + * corresponds to CollectionUUID uuid. If there is no such pointer, an empty + * NamespaceString is returned. + */ + NamespaceString lookupNSSByUUID(CollectionUUID uuid); + + /* This function removes the entry for uuid from the UUID catalog. It + * is called by the op observer when a collection is dropped. + */ + void onDropCollection(OperationContext* opCtx, CollectionUUID uuid); + +private: + mongo::stdx::mutex _catalogLock; + mongo::stdx::unordered_map<CollectionUUID, Collection*, CollectionUUID::Hash> _catalog; + + void _registerUUIDCatalogEntry(CollectionUUID uuid, Collection* coll); + Collection* _removeUUIDCatalogEntry(CollectionUUID uuid); +}; + +} // namespace mongo diff --git a/src/mongo/util/uuid_catalog_test.cpp b/src/mongo/util/uuid_catalog_test.cpp new file mode 100644 index 00000000000..eb1afba9b99 --- /dev/null +++ b/src/mongo/util/uuid_catalog_test.cpp @@ -0,0 +1,85 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * 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/util/uuid_catalog.h" + +#include "mongo/db/catalog/collection_mock.h" +#include "mongo/db/operation_context_noop.h" +#include "mongo/unittest/unittest.h" + +using namespace mongo; + +/** + * A test fixture that creates a UUID Catalog and Collection* pointer to store in it. + */ +class UUIDCatalogTest : public unittest::Test { +public: + UUIDCatalogTest() + : uuid(CollectionUUID::gen()), + nss("testdb", "testcol"), + col(stdx::make_unique<CollectionMock>(nss)) { + // Register dummy collection in catalog. + catalog.onCreateCollection(&opCtx, &col, uuid); + } + +protected: + UUIDCatalog catalog; + OperationContextNoop opCtx; + CollectionUUID uuid; + NamespaceString nss; + Collection col; +}; + +namespace { + +TEST_F(UUIDCatalogTest, onCreateCollection) { + ASSERT(catalog.lookupCollectionByUUID(uuid) != nullptr); +} + +TEST_F(UUIDCatalogTest, lookupCollectionByUUID) { + ASSERT(catalog.lookupCollectionByUUID(uuid) != nullptr); + // Ensure the string value of the NamespaceString of the obtained Collection is equal to + // nss.ns(). + ASSERT_EQUALS(catalog.lookupCollectionByUUID(uuid)->ns().ns(), nss.ns()); + // Ensure lookups of unknown UUIDs result in null pointers. + ASSERT(catalog.lookupCollectionByUUID(CollectionUUID::gen()) == nullptr); +} + +TEST_F(UUIDCatalogTest, lookupNSSByUUID) { + // Ensure the string value of the obtained NamespaceString is equal to nss.ns(). + ASSERT_EQUALS(catalog.lookupNSSByUUID(uuid).ns(), nss.ns()); + // Ensure namespace lookups of unknown UUIDs result in empty NamespaceStrings. + ASSERT_EQUALS(catalog.lookupNSSByUUID(CollectionUUID::gen()).ns(), NamespaceString().ns()); +} + +TEST_F(UUIDCatalogTest, onDropCollection) { + catalog.onDropCollection(&opCtx, uuid); + // Ensure the lookup returns a null pointer upon removing the uuid entry. + ASSERT(catalog.lookupCollectionByUUID(uuid) == nullptr); +} +} // namespace |