diff options
author | Maria van Keulen <maria@mongodb.com> | 2017-08-15 15:35:01 -0400 |
---|---|---|
committer | Maria van Keulen <maria@mongodb.com> | 2017-08-17 14:30:24 -0400 |
commit | c1aaff64cdf88d3ff2f0220033964fa6fcdb5513 (patch) | |
tree | 6325002431b486d7a5e1b912e6bec7fca632da91 | |
parent | 1bef1a5c3a5ed09b2efc899cf2ddba93e1ec1079 (diff) | |
download | mongo-c1aaff64cdf88d3ff2f0220033964fa6fcdb5513.tar.gz |
SERVER-30665 Only accept nonexistent UUIDs for UUID upgrade collMods
-rw-r--r-- | jstests/noPassthrough/coll_mod_apply_ops.js | 43 | ||||
-rw-r--r-- | src/mongo/db/repl/oplog.cpp | 38 |
2 files changed, 67 insertions, 14 deletions
diff --git a/jstests/noPassthrough/coll_mod_apply_ops.js b/jstests/noPassthrough/coll_mod_apply_ops.js new file mode 100644 index 00000000000..5755e1ddf30 --- /dev/null +++ b/jstests/noPassthrough/coll_mod_apply_ops.js @@ -0,0 +1,43 @@ +// SERVER-30665 Ensure that a non-empty collMod with a nonexistent UUID is not applied +// in applyOps. + +(function() { + "use strict"; + const conn = MongoRunner.runMongod(); + assert.neq(null, conn, "mongod was unable to start up with empty options"); + + let dbCollModName = "db_coll_mod"; + const dbCollMod = conn.getDB(dbCollModName); + dbCollMod.dropDatabase(); + let collName = "collModTest"; + let coll = dbCollMod[collName]; + + // Generate a random UUID that is distinct from collModTest's UUID. + const randomUUID = UUID(); + assert.neq(randomUUID, coll.uuid); + + // Perform a collMod to initialize validationLevel to "off". + assert.commandWorked(dbCollMod.createCollection(collName)); + let cmd = {"collMod": collName, "validationLevel": "off"}; + let res = dbCollMod.runCommand(cmd); + assert.commandWorked(res, 'could not run ' + tojson(cmd)); + let collectionInfosOriginal = dbCollMod.getCollectionInfos()[0]; + assert.eq(collectionInfosOriginal.options.validationLevel, "off"); + + // Perform an applyOps command with a nonexistent UUID and the same name as an existing + // collection. applyOps should succeed because of idempotency but a NamespaceNotFound + // uassert should be thrown during collMod application. + let collModApplyOpsEntry = { + "v": 2, + "op": "c", + "ns": dbCollModName + ".$cmd", + "ui": randomUUID, + "o2": {"collectionOptions_old": {"uuid": randomUUID}}, + "o": {"collMod": collName, "validationLevel": "moderate"} + }; + assert.commandWorked(dbCollMod.adminCommand({"applyOps": [collModApplyOpsEntry]})); + + // Ensure the collection options of the existing collection were not affected. + assert.eq(dbCollMod.getCollectionInfos()[0].name, collName); + assert.eq(dbCollMod.getCollectionInfos()[0].options.validationLevel, "off"); +}()); diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp index e7e6de49b11..d8877504c04 100644 --- a/src/mongo/db/repl/oplog.cpp +++ b/src/mongo/db/repl/oplog.cpp @@ -624,6 +624,29 @@ NamespaceString parseNs(const string& ns, const BSONObj& cmdObj) { return NamespaceString(NamespaceString(ns).db().toString(), coll); } +std::pair<OptionalCollectionUUID, NamespaceString> parseCollModUUIDAndNss(OperationContext* opCtx, + const BSONElement& ui, + const char* ns, + BSONObj& cmd) { + if (ui.eoo()) { + return std::pair<OptionalCollectionUUID, NamespaceString>(boost::none, parseNs(ns, cmd)); + } + CollectionUUID uuid = uassertStatusOK(UUID::parse(ui)); + auto& catalog = UUIDCatalog::get(opCtx); + if (catalog.lookupCollectionByUUID(uuid)) { + return std::pair<OptionalCollectionUUID, NamespaceString>(uuid, + catalog.lookupNSSByUUID(uuid)); + } else { + uassert(ErrorCodes::NamespaceNotFound, + str::stream() << "Failed to apply operation due to missing collection (" << uuid + << "): " + << redact(cmd.toString()), + cmd.nFields() == 1); + // If cmd is an empty collMod, i.e., nFields is 1, this is a UUID upgrade collMod. + return std::pair<OptionalCollectionUUID, NamespaceString>(uuid, parseNs(ns, cmd)); + } +} + NamespaceString parseUUID(OperationContext* opCtx, const BSONElement& ui) { auto statusWithUUID = UUID::parse(ui); uassertStatusOK(statusWithUUID); @@ -715,22 +738,9 @@ std::map<std::string, ApplyOpMetadata> opsMap = { const BSONElement& ui, BSONObj& cmd, const OpTime& opTime) -> Status { - // Get UUID from cmd, if it exists. OptionalCollectionUUID uuid; NamespaceString nss; - if (ui.eoo()) { - uuid = boost::none; - nss = parseNs(ns, cmd); - } else { - uuid = uassertStatusOK(UUID::parse(ui)); - // We need to see whether a collection with UUID ui exists before attempting to do - // a collMod on it. This is because we add UUIDs during upgrade to - // featureCompatibilityVersion 3.6 with a collMod command, so the collection will - // not have a UUID at the time we attempt to look it up by UUID. - auto& catalog = UUIDCatalog::get(opCtx); - nss = catalog.lookupCollectionByUUID(uuid.get()) ? catalog.lookupNSSByUUID(uuid.get()) - : parseNs(ns, cmd); - } + std::tie(uuid, nss) = parseCollModUUIDAndNss(opCtx, ui, ns, cmd); return collModForUUIDUpgrade(opCtx, nss, cmd, uuid); }, {ErrorCodes::IndexNotFound, ErrorCodes::NamespaceNotFound}}}, |