summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Noma <gregory.noma@gmail.com>2022-02-09 15:56:24 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-02-09 17:16:30 +0000
commita9bdb9af6ee3b7edd6a3c932633035011ddf7b93 (patch)
tree7479f8301db1e20e3c48401db2b62907926fee8a
parent116a38b637faf897e1a6014abdaadff137cdb496 (diff)
downloadmongo-a9bdb9af6ee3b7edd6a3c932633035011ddf7b93.tar.gz
SERVER-63286 Ensure `CollectionUUIDMismatch` from `createIndexes` and `dropIndexes` commands does not omit the actual collection even if unsharded
-rw-r--r--jstests/sharding/collection_uuid_create_indexes.js65
-rw-r--r--jstests/sharding/collection_uuid_drop_indexes.js68
-rw-r--r--src/mongo/s/SConscript1
-rw-r--r--src/mongo/s/cluster_commands_helpers.cpp23
4 files changed, 155 insertions, 2 deletions
diff --git a/jstests/sharding/collection_uuid_create_indexes.js b/jstests/sharding/collection_uuid_create_indexes.js
new file mode 100644
index 00000000000..e95d1644d67
--- /dev/null
+++ b/jstests/sharding/collection_uuid_create_indexes.js
@@ -0,0 +1,65 @@
+/**
+ * Tests the collectionUUID parameter of the createIndexes command when one collection is sharded
+ * and the other collection is unsharded.
+ *
+ * @tags: [
+ * featureFlagCommandsAcceptCollectionUUID,
+ * ]
+ */
+(function() {
+'use strict';
+
+const st = new ShardingTest({shards: 2});
+const mongos = st.s;
+
+const db = mongos.getDB(jsTestName());
+assert.commandWorked(mongos.adminCommand({enableSharding: db.getName()}));
+assert.commandWorked(mongos.adminCommand({movePrimary: db.getName(), to: st.shard0.name}));
+
+const shardedColl = db.sharded;
+const unshardedColl = db.unsharded;
+
+assert.commandWorked(shardedColl.insert({_id: 0}));
+assert.commandWorked(shardedColl.insert({_id: 1}));
+assert.commandWorked(unshardedColl.insert({_id: 2}));
+
+const uuid = function(coll) {
+ return assert.commandWorked(db.runCommand({listCollections: 1}))
+ .cursor.firstBatch.find(c => c.name === coll.getName())
+ .info.uuid;
+};
+
+assert.commandWorked(
+ mongos.adminCommand({shardCollection: shardedColl.getFullName(), key: {_id: 1}}));
+
+// Move {_id: 0} to shard0 and {_id: 1} to shard1.
+assert.commandWorked(st.splitAt(shardedColl.getFullName(), {_id: 1}));
+assert.commandWorked(mongos.adminCommand(
+ {moveChunk: shardedColl.getFullName(), find: {_id: 0}, to: st.shard0.shardName}));
+assert.commandWorked(mongos.adminCommand(
+ {moveChunk: shardedColl.getFullName(), find: {_id: 1}, to: st.shard1.shardName}));
+
+// Run the command on the collection which is sharded, while specifying the UUID of the unsharded
+// collection that exists only on shard0.
+let res = assert.commandFailedWithCode(db.runCommand({
+ createIndexes: shardedColl.getName(),
+ indexes: [{key: {a: 1}, name: 'a_1'}],
+ collectionUUID: uuid(unshardedColl),
+}),
+ ErrorCodes.CollectionUUIDMismatch);
+assert.eq(res.collectionUUID, uuid(unshardedColl));
+assert.eq(res.actualNamespace, unshardedColl.getFullName());
+
+// Run the command on the collection which is unsharded and exists only on shard0, while specifying
+// the UUID of the collection that is sharded.
+res = assert.commandFailedWithCode(db.runCommand({
+ createIndexes: unshardedColl.getName(),
+ indexes: [{key: {a: 1}, name: 'a_1'}],
+ collectionUUID: uuid(shardedColl),
+}),
+ ErrorCodes.CollectionUUIDMismatch);
+assert.eq(res.collectionUUID, uuid(shardedColl));
+assert.eq(res.actualNamespace, shardedColl.getFullName());
+
+st.stop();
+})();
diff --git a/jstests/sharding/collection_uuid_drop_indexes.js b/jstests/sharding/collection_uuid_drop_indexes.js
new file mode 100644
index 00000000000..d2bb5703353
--- /dev/null
+++ b/jstests/sharding/collection_uuid_drop_indexes.js
@@ -0,0 +1,68 @@
+/**
+ * Tests the collectionUUID parameter of the dropIndexes command when one collection is sharded and
+ * the other collection is unsharded.
+ *
+ * @tags: [
+ * featureFlagCommandsAcceptCollectionUUID,
+ * ]
+ */
+(function() {
+'use strict';
+
+const st = new ShardingTest({shards: 2});
+const mongos = st.s;
+
+const db = mongos.getDB(jsTestName());
+assert.commandWorked(mongos.adminCommand({enableSharding: db.getName()}));
+assert.commandWorked(mongos.adminCommand({movePrimary: db.getName(), to: st.shard0.name}));
+
+const shardedColl = db.sharded;
+const unshardedColl = db.unsharded;
+
+assert.commandWorked(shardedColl.insert({_id: 0}));
+assert.commandWorked(shardedColl.insert({_id: 1}));
+assert.commandWorked(unshardedColl.insert({_id: 2}));
+
+const uuid = function(coll) {
+ return assert.commandWorked(db.runCommand({listCollections: 1}))
+ .cursor.firstBatch.find(c => c.name === coll.getName())
+ .info.uuid;
+};
+
+assert.commandWorked(
+ mongos.adminCommand({shardCollection: shardedColl.getFullName(), key: {_id: 1}}));
+
+// Move {_id: 0} to shard0 and {_id: 1} to shard1.
+assert.commandWorked(st.splitAt(shardedColl.getFullName(), {_id: 1}));
+assert.commandWorked(mongos.adminCommand(
+ {moveChunk: shardedColl.getFullName(), find: {_id: 0}, to: st.shard0.shardName}));
+assert.commandWorked(mongos.adminCommand(
+ {moveChunk: shardedColl.getFullName(), find: {_id: 1}, to: st.shard1.shardName}));
+
+assert.commandWorked(shardedColl.createIndex({a: 1}));
+assert.commandWorked(unshardedColl.createIndex({a: 1}));
+
+// Run the command on the collection which is sharded, while specifying the UUID of the unsharded
+// collection that exists only on shard0.
+let res = assert.commandFailedWithCode(db.runCommand({
+ dropIndexes: shardedColl.getName(),
+ index: {a: 1},
+ collectionUUID: uuid(unshardedColl),
+}),
+ ErrorCodes.CollectionUUIDMismatch);
+assert.eq(res.collectionUUID, uuid(unshardedColl));
+assert.eq(res.actualNamespace, unshardedColl.getFullName());
+
+// Run the command on the collection which is unsharded and exists only on shard0, while specifying
+// the UUID of the collection that is sharded.
+res = assert.commandFailedWithCode(db.runCommand({
+ dropIndexes: unshardedColl.getName(),
+ index: {a: 1},
+ collectionUUID: uuid(shardedColl),
+}),
+ ErrorCodes.CollectionUUIDMismatch);
+assert.eq(res.collectionUUID, uuid(shardedColl));
+assert.eq(res.actualNamespace, shardedColl.getFullName());
+
+st.stop();
+})();
diff --git a/src/mongo/s/SConscript b/src/mongo/s/SConscript
index 6410d32a0df..df8ccfb00be 100644
--- a/src/mongo/s/SConscript
+++ b/src/mongo/s/SConscript
@@ -65,6 +65,7 @@ env.Library(
'grid',
],
LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/db/catalog/collection_uuid_mismatch_info',
'$BUILD_DIR/mongo/db/internal_transactions_feature_flag',
'$BUILD_DIR/mongo/db/sessions_collection',
],
diff --git a/src/mongo/s/cluster_commands_helpers.cpp b/src/mongo/s/cluster_commands_helpers.cpp
index a4d47c09825..32bc6e50137 100644
--- a/src/mongo/s/cluster_commands_helpers.cpp
+++ b/src/mongo/s/cluster_commands_helpers.cpp
@@ -36,6 +36,7 @@
#include "mongo/s/cluster_commands_helpers.h"
#include "mongo/bson/util/bson_extract.h"
+#include "mongo/db/catalog/collection_uuid_mismatch_info.h"
#include "mongo/db/commands.h"
#include "mongo/db/curop.h"
#include "mongo/db/error_labels.h"
@@ -622,13 +623,31 @@ RawResponsesResult appendRawResponses(
}
// There was an error. Choose the first error as the top-level error.
- const auto& firstError = genericErrorsReceived.front().second;
+ auto& firstError = genericErrorsReceived.front().second;
+
+ if (firstError.code() == ErrorCodes::CollectionUUIDMismatch &&
+ firstError.extraInfo<CollectionUUIDMismatchInfo>()->actualNamespace().isEmpty()) {
+ // The first error is a CollectionUUIDMismatchInfo but it doesn't contain an actual
+ // namespace. It's possible that the acutal namespace is unsharded, in which case only the
+ // error from the primary shard will contain this information. Iterate through the errors to
+ // see if this is the case.
+ for (const auto& error : genericErrorsReceived) {
+ if (error.second.code() == ErrorCodes::CollectionUUIDMismatch &&
+ !error.second.extraInfo<CollectionUUIDMismatchInfo>()
+ ->actualNamespace()
+ .isEmpty()) {
+ firstError = error.second;
+ break;
+ }
+ }
+ }
+
output->append("code", firstError.code());
output->append("codeName", ErrorCodes::errorString(firstError.code()));
- *errmsg = firstError.reason();
if (auto extra = firstError.extraInfo()) {
extra->serialize(output);
}
+ *errmsg = firstError.reason();
return {false, shardsWithSuccessResponses, successARSResponses, firstStaleConfigErrorReceived};
}