summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaria van Keulen <maria@mongodb.com>2017-08-15 15:35:01 -0400
committerMaria van Keulen <maria@mongodb.com>2017-08-17 14:30:24 -0400
commitc1aaff64cdf88d3ff2f0220033964fa6fcdb5513 (patch)
tree6325002431b486d7a5e1b912e6bec7fca632da91
parent1bef1a5c3a5ed09b2efc899cf2ddba93e1ec1079 (diff)
downloadmongo-c1aaff64cdf88d3ff2f0220033964fa6fcdb5513.tar.gz
SERVER-30665 Only accept nonexistent UUIDs for UUID upgrade collMods
-rw-r--r--jstests/noPassthrough/coll_mod_apply_ops.js43
-rw-r--r--src/mongo/db/repl/oplog.cpp38
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}}},