summaryrefslogtreecommitdiff
path: root/jstests
diff options
context:
space:
mode:
authorHaley Connelly <haley.connelly@mongodb.com>2020-07-09 19:58:59 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-07-14 17:15:51 +0000
commit17e468583ec1a7e9755b34b50eaa7cf017a1c96e (patch)
tree56d4af2a4c82aa4d9f5f73a75edce0a193f14bbb /jstests
parentaaeb549ed1e6804e229c1db09fc60260bfd82a62 (diff)
downloadmongo-17e468583ec1a7e9755b34b50eaa7cf017a1c96e.tar.gz
SERVER-46811 multi=true updates can modify the shard key of orphan documents and cause them to become owned
Diffstat (limited to 'jstests')
-rw-r--r--jstests/sharding/multi_update_orphan_shard_key.js60
1 files changed, 60 insertions, 0 deletions
diff --git a/jstests/sharding/multi_update_orphan_shard_key.js b/jstests/sharding/multi_update_orphan_shard_key.js
new file mode 100644
index 00000000000..3c71d0f8f35
--- /dev/null
+++ b/jstests/sharding/multi_update_orphan_shard_key.js
@@ -0,0 +1,60 @@
+/**
+ * Tests it isn't possible to update an orphan document's shard key. Only multi=true updates skip
+ * shard versioning. They are therefore the only case which skips ownership filtering.
+ * @tags: [requires_fcv_46]
+ */
+(function() {
+"use strict";
+
+load("jstests/libs/fail_point_util.js");
+
+const st = new ShardingTest({mongos: 1, config: 1, shards: 2, rs: {nodes: 1}});
+const dbName = "test";
+const collName = "update_orphan_shard_key";
+const collection = st.s.getDB(dbName).getCollection(collName);
+
+// Create a sharded collection with two chunks on shard0, split at the key {x: -1}.
+assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
+assert.commandWorked(st.s.adminCommand({movePrimary: dbName, to: st.shard0.shardName}));
+assert.commandWorked(st.s.adminCommand({shardCollection: collection.getFullName(), key: {x: 1}}));
+assert.commandWorked(st.s.adminCommand({split: collection.getFullName(), middle: {x: -1}}));
+
+// Insert some documents into the collection, but only into the higher of the two chunks.
+assert.commandWorked(collection.insert(Array.from({length: 100}, (_, i) => ({_id: i, x: i}))));
+
+// Enable the failpoint to cause range deletion to hang indefinitely.
+const suspendRangeDeletionFailpoint = configureFailPoint(st.shard0, "suspendRangeDeletion");
+
+// Note: Use _waitForDelete=false to ensure the command completes since the test intentionally
+// causes range deletion to hang.
+assert.commandWorked(st.s.adminCommand({
+ moveChunk: collection.getFullName(),
+ find: {x: 1},
+ to: st.shard1.shardName,
+ _waitForDelete: false,
+}));
+
+let res = assert.commandWorked(collection.update({x: 0}, {$set: {y: 1}}));
+assert.eq(1, res.nMatched, res);
+assert.eq(1, res.nModified, res);
+
+// Do a multi=true update that will target both shards but not update any documents on the shard
+// which owns the range [-1, MaxKey].
+res = assert.commandFailedWithCode(
+ collection.update({x: {$lte: 0}, y: {$exists: false}}, {$set: {x: -10, y: 2}}, {multi: true}),
+ 31025);
+assert.eq(0, res.nMatched, res);
+assert.eq(0, res.nModified, res);
+assert.eq(0, res.nUpserted, res);
+
+// Wait for range deletion to happen on the donor.
+suspendRangeDeletionFailpoint.off();
+assert.soon(() => {
+ return st.shard0.getCollection(collection.getFullName()).findOne({x: 1}) === null;
+});
+
+// Run a $out aggregation as a simple way to check for duplicate _id values.
+assert.doesNotThrow(() => collection.aggregate([{$out: "output"}]));
+
+st.stop();
+})();