Files
mongo/jstests/multiVersion/update_shard_key_disallowed_fcv40.js

273 lines
13 KiB
JavaScript

// Test that shard key fields cannot be updated when one or more shards is in FCV 4.0.
// @tags: [uses_transactions, uses_multi_shard_transaction]
(function() {
"use strict";
load("jstests/libs/feature_compatibility_version.js");
load("jstests/sharding/libs/update_shard_key_helpers.js");
let st = new ShardingTest({
shards: {rs0: {nodes: 3, binVersion: "latest"}, rs1: {nodes: 3, binVersion: "latest"}},
mongos: 1,
other: {mongosOptions: {binVersion: "latest"}, configOptions: {binVersion: "latest"}}
});
let mongos = st.s0;
let kDbName = "test";
let collName = "foo";
let ns = kDbName + "." + collName;
assert.commandWorked(mongos.adminCommand({enableSharding: kDbName}));
st.ensurePrimaryShard(kDbName, st.shard0.shardName);
function shardCollectionAndMoveChunks(docsToInsert, shardKey, splitDoc, moveDoc) {
for (let i = 0; i < docsToInsert.length; i++) {
assert.commandWorked(mongos.getDB(kDbName).foo.insert(docsToInsert[i]));
}
assert.commandWorked(mongos.getDB(kDbName).foo.createIndex(shardKey));
assert.commandWorked(mongos.adminCommand({shardCollection: ns, key: shardKey}));
assert.commandWorked(mongos.adminCommand({split: ns, find: splitDoc}));
assert.commandWorked(
mongos.adminCommand({moveChunk: ns, find: moveDoc, to: st.shard1.shardName}));
assert.commandWorked(mongos.adminCommand({flushRouterConfig: 1}));
st.rs0.getPrimary().adminCommand({_flushRoutingTableCacheUpdates: ns});
st.rs1.getPrimary().adminCommand({_flushRoutingTableCacheUpdates: ns});
st.rs0.getPrimary().adminCommand({_flushDatabaseCacheUpdates: kDbName});
st.rs1.getPrimary().adminCommand({_flushDatabaseCacheUpdates: kDbName});
}
function assertCannotUpdateShardKey(isMixedCluster) {
// ------------------------------------------------
// Test changes to shard key run as retryable write
// ------------------------------------------------
let session = st.s.startSession({retryWrites: true});
let sessionDB = session.getDatabase(kDbName);
// Updates to full shard key
shardCollectionMoveChunks(
st, kDbName, ns, {x: 1}, [{x: 30}, {x: 50}, {x: 80}], {x: 50}, {x: 80});
cleanupOrphanedDocs(st, ns);
// Assert that updating the shard key when the doc would remain on the same shard fails for
// both modify and replacement updates
assert.writeError(sessionDB.foo.update({x: 80}, {$set: {x: 100}}));
assert.writeError(sessionDB.foo.update({x: 80}, {x: 100}));
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 80}, update: {$set: {x: 100}}});
});
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 80}, update: {x: 100}});
});
// Assert that updating the shard key when the doc would move shards fails for both modify
// and replacement updates.
assert.writeError(sessionDB.foo.update({x: 80}, {$set: {x: 3}}));
// TODO: SERVER-39158. Currently, this update will not fail but will not update the doc.
// After SERVER-39158 is finished, this should fail.
assert.commandWorked(sessionDB.foo.update({x: 80}, {x: 3}));
assert.eq(1, mongos.getDB(kDbName).foo.find({x: 80}).itcount());
assert.eq(0, mongos.getDB(kDbName).foo.find({x: 3}).itcount());
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 80}, update: {$set: {x: 3}}});
});
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 80}, update: {x: 3}});
});
mongos.getDB(kDbName).foo.drop();
// Updates to partial shard key
shardCollectionMoveChunks(st,
kDbName,
ns,
{x: 1, y: 1},
[{x: 30, y: 4}, {x: 50, y: 50}, {x: 80, y: 100}],
{x: 50, y: 50},
{x: 80, y: 100});
cleanupOrphanedDocs(st, ns);
// Assert that updating the shard key when the doc would remain on the same shard fails for
// both modify and replacement updates
assert.writeError(sessionDB.foo.update({x: 80}, {$set: {x: 100}}));
assert.writeError(sessionDB.foo.update({x: 80}, {x: 100}));
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 80}, update: {$set: {x: 100}}});
});
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 80}, update: {x: 100}});
});
// Assert that updating the shard key when the doc would move shards fails for both modify
// and replacement updates
assert.writeError(sessionDB.foo.update({x: 80}, {$set: {x: 3}}));
assert.writeError(sessionDB.foo.update({x: 80}, {x: 3}));
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 80}, update: {$set: {x: 3}}});
});
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 80}, update: {x: 3}});
});
mongos.getDB(kDbName).foo.drop();
// -----------------------------------------------
// Test changes to shard key run in a transaction
// -----------------------------------------------
session = st.s.startSession();
sessionDB = session.getDatabase(kDbName);
// Updates to full shard key
shardCollectionMoveChunks(
st, kDbName, ns, {x: 1}, [{x: 30}, {x: 50}, {x: 80}], {x: 50}, {x: 80});
cleanupOrphanedDocs(st, ns);
// Assert that updating the shard key when the doc would remain on the same shard fails for
// both modify and replacement updates
session.startTransaction();
assert.writeError(sessionDB.foo.update({x: 80}, {$set: {x: 100}}));
session.abortTransaction();
session.startTransaction();
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 80}, update: {x: 100}});
});
session.abortTransaction();
mongos.getDB(kDbName).foo.drop();
if (isMixedCluster) {
// Assert that updating the shard key when the doc would move shards fails on commit
// because one of the participants is not in FCV 4.2. If the original write is a
// retryable write, the write will fail when mongos attempts to run commitTransaction.
// If the original write is part of a transaction, the write itself will complete
// successfully, but the transaction will fail to commit.
// Retryable write - updates to full shard key
session = st.s.startSession({retryWrites: true});
sessionDB = session.getDatabase(kDbName);
shardCollectionMoveChunks(
st, kDbName, ns, {x: 1}, [{x: 30}, {x: 50}, {x: 80}], {x: 50}, {x: 80});
cleanupOrphanedDocs(st, ns);
// Doc will move shards
assert.writeError(sessionDB.foo.update({x: 30}, {$set: {x: 100}}));
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 30}, update: {$set: {x: 100}}});
});
// Doc will remain on the same shard. Because shard 0 is on FCV 4.2, these should
// complete successfully.
sessionDB.foo.findAndModify({query: {x: 30}, update: {$set: {x: 5}}});
st.rs0.awaitReplication();
assert.eq(1, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 5}).itcount());
assert.eq(1, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 5}).itcount());
assert.eq(0, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 30}).itcount());
assert.eq(0, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 30}).itcount());
sessionDB.foo.findAndModify({query: {x: 5}, update: {x: 10}});
st.rs0.awaitReplication();
assert.eq(0, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 5}).itcount());
assert.eq(0, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 5}).itcount());
assert.eq(1, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 10}).itcount());
assert.eq(1, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 10}).itcount());
assert.commandWorked(sessionDB.foo.update({x: 10}, {$set: {x: 25}}));
st.rs0.awaitReplication();
assert.eq(1, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 25}).itcount());
assert.eq(1, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 25}).itcount());
assert.eq(0, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 10}).itcount());
assert.eq(0, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 10}).itcount());
assert.commandWorked(sessionDB.foo.update({x: 25}, {x: 3}));
st.rs0.awaitReplication();
assert.eq(0, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 25}).itcount());
assert.eq(0, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 25}).itcount());
assert.eq(1, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 3}).itcount());
assert.eq(1, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 3}).itcount());
mongos.getDB(kDbName).foo.drop();
// Retryable write - updates to partial shard key
shardCollectionMoveChunks(st,
kDbName,
ns,
{x: 1, y: 1},
[{x: 30, y: 4}, {x: 50, y: 50}, {x: 80, y: 100}],
{x: 50, y: 50},
{x: 80, y: 100});
cleanupOrphanedDocs(st, ns);
assert.writeError(sessionDB.foo.update({x: 30}, {$set: {x: 100}}));
assert.throws(function() {
sessionDB.foo.findAndModify({query: {x: 30}, update: {x: 100}});
});
mongos.getDB(kDbName).foo.drop();
// Transactional writes
session = st.s.startSession();
sessionDB = session.getDatabase(kDbName);
shardCollectionMoveChunks(
st, kDbName, ns, {x: 1}, [{x: 10}, {x: 30}, {x: 50}, {x: 80}], {x: 50}, {x: 80});
cleanupOrphanedDocs(st, ns);
session.startTransaction();
assert.commandWorked(sessionDB.foo.update({x: 30}, {$set: {x: 100}}));
assert.commandFailed(session.commitTransaction_forTesting());
assert.eq(1, mongos.getDB(kDbName).foo.find({x: 30}).itcount());
assert.eq(0, mongos.getDB(kDbName).foo.find({x: 100}).itcount());
session.startTransaction();
sessionDB.foo.findAndModify({query: {x: 30}, update: {x: 100}});
assert.commandFailed(session.commitTransaction_forTesting());
assert.eq(1, mongos.getDB(kDbName).foo.find({x: 30}).itcount());
assert.eq(0, mongos.getDB(kDbName).foo.find({x: 100}).itcount());
// Doc will remain on the same shard. Because shard 0 is on FCV 4.2, this should
// complete successfully.
session.startTransaction();
sessionDB.foo.findAndModify({query: {x: 10}, update: {x: 1}});
assert.commandWorked(sessionDB.foo.update({x: 30}, {$set: {x: 5}}));
assert.commandWorked(session.commitTransaction_forTesting());
st.rs0.awaitReplication();
assert.eq(0, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 30}).itcount());
assert.eq(0, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 30}).itcount());
assert.eq(0, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 10}).itcount());
assert.eq(0, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 10}).itcount());
assert.eq(1, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 5}).itcount());
assert.eq(1, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 5}).itcount());
assert.eq(1, st.rs0.getPrimary().getDB(kDbName).foo.find({x: 1}).itcount());
assert.eq(1, st.rs0.getSecondaries()[0].getDB(kDbName).foo.find({x: 1}).itcount());
mongos.getDB(kDbName).foo.drop();
}
}
// Check that updating the shard key fails when all shards are in FCV 4.0
assert.commandWorked(st.s.getDB("admin").runCommand({setFeatureCompatibilityVersion: "4.0"}));
checkFCV(st.configRS.getPrimary().getDB("admin"), "4.0");
checkFCV(st.rs0.getPrimary().getDB("admin"), "4.0");
checkFCV(st.rs1.getPrimary().getDB("admin"), "4.0");
assertCannotUpdateShardKey(false);
// Check that updating the shard key fails when shard0 is in FCV 4.2 but shard 1 is in FCV 4.0
assert.commandWorked(
st.rs0.getPrimary().getDB("admin").runCommand({setFeatureCompatibilityVersion: "4.2"}));
checkFCV(st.configRS.getPrimary().getDB("admin"), "4.0");
checkFCV(st.rs0.getPrimary().getDB("admin"), "4.2");
checkFCV(st.rs1.getPrimary().getDB("admin"), "4.0");
assertCannotUpdateShardKey(true);
st.stop();
})();