diff options
author | Maria van Keulen <maria@mongodb.com> | 2020-03-09 11:31:34 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-03-12 16:12:40 +0000 |
commit | 90c3f10389519068eb685db72b7ff13f93c71dc6 (patch) | |
tree | c99bc77ca9b713b73270deeabd4c0c47dfac3d84 /jstests | |
parent | db3a17bbfe2e265722ed88df961e79f3e1a68067 (diff) | |
download | mongo-90c3f10389519068eb685db72b7ff13f93c71dc6.tar.gz |
SERVER-44409 Add FSM and nice-to-have collection and index test cases
Diffstat (limited to 'jstests')
-rw-r--r-- | jstests/concurrency/fsm_workloads/CRUD_and_commands.js | 265 | ||||
-rw-r--r-- | jstests/core/txns/create_collection.js | 29 | ||||
-rw-r--r-- | jstests/core/txns/create_collection_parallel.js | 56 | ||||
-rw-r--r-- | jstests/core/txns/create_indexes_parallel.js | 32 | ||||
-rw-r--r-- | jstests/libs/create_collection_txn_helpers.js | 19 | ||||
-rw-r--r-- | jstests/libs/create_index_txn_helpers.js | 3 | ||||
-rw-r--r-- | jstests/sharding/create_existing_indexes_prepared_transactions.js | 112 | ||||
-rw-r--r-- | jstests/sharding/create_new_collections_prepared_transactions.js | 64 |
8 files changed, 556 insertions, 24 deletions
diff --git a/jstests/concurrency/fsm_workloads/CRUD_and_commands.js b/jstests/concurrency/fsm_workloads/CRUD_and_commands.js new file mode 100644 index 00000000000..5e7b24c9c0a --- /dev/null +++ b/jstests/concurrency/fsm_workloads/CRUD_and_commands.js @@ -0,0 +1,265 @@ +'use strict'; + +/** + * Perform CRUD operations, some of which may implicitly create collections. Also perform index + * creations which may implicitly create collections. Performs these in parallel with collection- + * dropping operations. + */ + +var $config = (function() { + const data = {numIds: 10}; + + const states = { + init: function init(db, collName) { + this.session = db.getMongo().startSession({causalConsistency: true}); + this.sessionDb = this.session.getDatabase(db.getName()); + this.docValue = "mydoc"; + }, + + insertDocs: function insertDocs(db, collName) { + try { + for (let i = 0; i < 5; i++) { + const res = db[collName].insert({value: this.docValue, num: 1}); + assertWhenOwnColl.commandWorked(res); + assertWhenOwnColl.eq(1, res.nInserted); + } + } catch (e) { + if (e.code == ErrorCodes.ConflictingOperationInProgress) { + // dropCollection in sharding can disrupt routing cache refreshes. + if (TestData.runInsideTransaction) { + e["errorLabels"] = ["TransientTransactionError"]; + throw e; + } + } + } + }, + + updateDocs: function updateDocs(db, collName) { + for (let i = 0; i < 5; ++i) { + let indexToUpdate = Math.floor(Math.random() * this.numIds); + let res; + try { + res = + db[collName].update({_id: indexToUpdate}, {$inc: {num: 1}}, {upsert: true}); + assertWhenOwnColl.commandWorked(res); + } catch (e) { + // We propagate TransientTransactionErrors to allow the state function to + // automatically be retried when TestData.runInsideTransaction=true + if (e.hasOwnProperty('errorLabels') && + e.errorLabels.includes('TransientTransactionError')) { + throw e; + } else if (e.code == ErrorCodes.ConflictingOperationInProgress) { + // dropCollection in sharding can disrupt routing cache refreshes. + if (TestData.runInsideTransaction) { + e["errorLabels"] = ["TransientTransactionError"]; + throw e; + } + } else if (e.code == ErrorCodes.QueryPlanKilled || + e.code == ErrorCodes.OperationFailed) { + // dropIndex can cause queries to throw if these queries + // yield. + } else { + // TODO(SERVER-46651) upsert with concurrent dropCollection can result in + // writeErrors if queries yield. + assertAlways.writeError(res, "unexpected error: " + tojsononeline(e)); + } + } + } + }, + + readDocs: function readDocs(db, collName) { + for (let i = 0; i < 5; ++i) { + try { + let res = db[collName].findOne({value: this.docValue}); + if (res !== null) { + assertAlways.eq(this.docValue, res.value); + } + } catch (e) { + // We propagate TransientTransactionErrors to allow the state function to + // automatically be retried when TestData.runInsideTransaction=true + if (e.hasOwnProperty('errorLabels') && + e.errorLabels.includes('TransientTransactionError')) { + throw e; + } else if (e.code == ErrorCodes.ConflictingOperationInProgress) { + // dropCollection in sharding can disrupt routing cache refreshes. + if (TestData.runInsideTransaction) { + e["errorLabels"] = ["TransientTransactionError"]; + throw e; + } + } else { + // dropIndex can cause queries to throw if these queries yield. + assertAlways.contains( + e.code, + [ + ErrorCodes.QueryPlanKilled, + ErrorCodes.OperationFailed, + ], + 'unexpected error code: ' + e.code + ': ' + e.message); + } + } + } + }, + + deleteDocs: function deleteDocs(db, collName) { + let indexToDelete = Math.floor(Math.random() * this.numIds); + try { + db[collName].deleteOne({_id: indexToDelete}); + } catch (e) { + // We propagate TransientTransactionErrors to allow the state function to + // automatically be retried when TestData.runInsideTransaction=true + if (e.hasOwnProperty('errorLabels') && + e.errorLabels.includes('TransientTransactionError')) { + throw e; + } else if (e.code == ErrorCodes.ConflictingOperationInProgress) { + if (TestData.runInsideTransaction) { + e["errorLabels"] = ["TransientTransactionError"]; + throw e; + } + } else { + // dropIndex can cause queries to throw if these queries yield. + assertAlways.contains(e.code, + [ErrorCodes.QueryPlanKilled, ErrorCodes.OperationFailed], + 'unexpected error code: ' + e.code + ': ' + e.message); + } + } + }, + + createIndex: function createIndex(db, collName) { + db[collName].createIndex({value: 1}); + }, + + createIdIndex: function createIdIndex(db, collName) { + try { + assertWhenOwnColl.commandWorked(db[collName].createIndex({_id: 1})); + } catch (e) { + if (e.code == ErrorCodes.ConflictingOperationInProgress) { + // createIndex concurrently with dropCollection can throw. + if (TestData.runInsideTransaction) { + e["errorLabels"] = ["TransientTransactionError"]; + throw e; + } + } + } + }, + + dropIndex: function dropIndex(db, collName) { + db[collName].dropIndex({value: 1}); + }, + + dropCollection: function dropCollection(db, collName) { + db[collName].drop(); + } + }; + + const transitions = { + init: { + insertDocs: 0.10, + updateDocs: 0.10, + readDocs: 0.10, + deleteDocs: 0.10, + createIndex: 0.10, + createIdIndex: 0.10, + dropIndex: 0.10, + dropCollection: 0.10, + }, + insertDocs: { + insertDocs: 0.10, + updateDocs: 0.10, + readDocs: 0.10, + deleteDocs: 0.10, + createIndex: 0.10, + createIdIndex: 0.10, + dropIndex: 0.10, + dropCollection: 0.30, + }, + updateDocs: { + insertDocs: 0.10, + updateDocs: 0.10, + readDocs: 0.10, + deleteDocs: 0.10, + createIndex: 0.10, + createIdIndex: 0.10, + dropIndex: 0.10, + dropCollection: 0.30, + }, + readDocs: { + insertDocs: 0.10, + updateDocs: 0.10, + readDocs: 0.10, + deleteDocs: 0.10, + createIndex: 0.10, + createIdIndex: 0.10, + dropIndex: 0.10, + dropCollection: 0.30, + }, + deleteDocs: { + insertDocs: 0.10, + updateDocs: 0.10, + readDocs: 0.10, + deleteDocs: 0.10, + createIndex: 0.10, + createIdIndex: 0.10, + dropIndex: 0.10, + dropCollection: 0.30, + }, + createIndex: { + insertDocs: 0.10, + updateDocs: 0.10, + readDocs: 0.10, + deleteDocs: 0.10, + createIndex: 0.10, + createIdIndex: 0.10, + dropIndex: 0.10, + dropCollection: 0.30, + }, + createIdIndex: { + insertDocs: 0.10, + updateDocs: 0.10, + readDocs: 0.10, + deleteDocs: 0.10, + createIndex: 0.10, + createIdIndex: 0.10, + dropIndex: 0.10, + dropCollection: 0.30, + }, + dropIndex: { + insertDocs: 0.10, + updateDocs: 0.10, + readDocs: 0.10, + deleteDocs: 0.10, + createIndex: 0.10, + createIdIndex: 0.10, + dropIndex: 0.10, + dropCollection: 0.30, + }, + dropCollection: { + insertDocs: 0.10, + updateDocs: 0.10, + readDocs: 0.10, + deleteDocs: 0.10, + createIndex: 0.10, + createIdIndex: 0.10, + dropIndex: 0.10, + dropCollection: 0.10, + } + }; + + function setup(db, collName, cluster) { + assertAlways.commandWorked(db.runCommand({create: collName})); + for (let i = 0; i < this.numIds; i++) { + const res = db[collName].insert({_id: i, value: this.docValue, num: 1}); + assertAlways.commandWorked(res); + assert.eq(1, res.nInserted); + } + } + + return { + threadCount: 5, + iterations: 20, + startState: 'init', + states: states, + transitions: transitions, + setup: setup, + data: data, + }; +})(); diff --git a/jstests/core/txns/create_collection.js b/jstests/core/txns/create_collection.js index 4f49e1d4ad9..be96ebdb740 100644 --- a/jstests/core/txns/create_collection.js +++ b/jstests/core/txns/create_collection.js @@ -11,7 +11,7 @@ load("jstests/libs/create_collection_txn_helpers.js"); -function runCollectionCreateTest(explicitCreate) { +function runCollectionCreateTest(explicitCreate, upsert) { const session = db.getMongo().startSession({causalConsistency: false}); const collName = "create_new_collection"; const secondCollName = collName + "_second"; @@ -24,7 +24,14 @@ function runCollectionCreateTest(explicitCreate) { jsTest.log("Testing createCollection in a transaction"); session.startTransaction({writeConcern: {w: "majority"}}); - createCollAndCRUDInTxn(sessionDB, collName, explicitCreate); + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); + session.commitTransaction(); + assert.eq(sessionColl.find({}).itcount(), 1); + + jsTest.log("Testing createCollection in a transaction, implicitly creating database"); + assert.commandWorked(sessionDB.dropDatabase()); + session.startTransaction({writeConcern: {w: "majority"}}); + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); session.commitTransaction(); assert.eq(sessionColl.find({}).itcount(), 1); @@ -32,8 +39,8 @@ function runCollectionCreateTest(explicitCreate) { jsTest.log("Testing multiple createCollections in a transaction"); session.startTransaction({writeConcern: {w: "majority"}}); - createCollAndCRUDInTxn(sessionDB, collName, explicitCreate); - createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate); + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); + createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate, upsert); session.commitTransaction(); assert.eq(sessionColl.find({}).itcount(), 1); assert.eq(secondSessionColl.find({}).itcount(), 1); @@ -43,15 +50,15 @@ function runCollectionCreateTest(explicitCreate) { jsTest.log("Testing createCollection in a transaction that aborts"); session.startTransaction({writeConcern: {w: "majority"}}); - createCollAndCRUDInTxn(sessionDB, collName, explicitCreate); + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); assert.commandWorked(session.abortTransaction_forTesting()); assert.eq(sessionColl.find({}).itcount(), 0); jsTest.log("Testing multiple createCollections in a transaction that aborts"); session.startTransaction({writeConcern: {w: "majority"}}); - createCollAndCRUDInTxn(sessionDB, collName, explicitCreate); - createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate); + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); + createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate, upsert); session.abortTransaction(); assert.eq(sessionColl.find({}).itcount(), 0); assert.eq(secondSessionColl.find({}).itcount(), 0); @@ -63,7 +70,7 @@ function runCollectionCreateTest(explicitCreate) { "Testing createCollection on an existing collection in a transaction (SHOULD ABORT)"); assert.commandWorked(sessionDB.runCommand({create: collName, writeConcern: {w: "majority"}})); session.startTransaction({writeConcern: {w: "majority"}}); - createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate); + createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate, upsert); assert.commandFailedWithCode(sessionDB.runCommand({create: collName}), ErrorCodes.NamespaceExists); assert.commandFailedWithCode(session.abortTransaction_forTesting(), @@ -75,6 +82,8 @@ function runCollectionCreateTest(explicitCreate) { session.endSession(); } -runCollectionCreateTest(true /*explicitCreate*/); -runCollectionCreateTest(false /*explicitCreate*/); +runCollectionCreateTest(true /*explicitCreate*/, true /*upsert*/); +runCollectionCreateTest(false /*explicitCreate*/, false /*upsert*/); +runCollectionCreateTest(true /*explicitCreate*/, false /*upsert*/); +runCollectionCreateTest(false /*explicitCreate*/, true /*upsert*/); }()); diff --git a/jstests/core/txns/create_collection_parallel.js b/jstests/core/txns/create_collection_parallel.js index 0f034f8e713..5ff28a06001 100644 --- a/jstests/core/txns/create_collection_parallel.js +++ b/jstests/core/txns/create_collection_parallel.js @@ -10,7 +10,7 @@ load("jstests/libs/create_collection_txn_helpers.js"); -function runParallelCollectionCreateTest(explicitCreate) { +function runParallelCollectionCreateTest(explicitCreate, upsert) { const dbName = "test"; const collName = "create_new_collection"; const distinctCollName = collName + "_second"; @@ -30,7 +30,7 @@ function runParallelCollectionCreateTest(explicitCreate) { session.startTransaction({writeConcern: {w: "majority"}}); // txn 1 secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2 - createCollAndCRUDInTxn(sessionDB, collName, explicitCreate); + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); jsTest.log("Committing transaction 1"); session.commitTransaction(); assert.eq(sessionColl.find({}).itcount(), 1); @@ -43,14 +43,26 @@ function runParallelCollectionCreateTest(explicitCreate) { sessionColl.drop({writeConcern: {w: "majority"}}); + jsTest.log("Testing duplicate createCollections, one inside and one outside a txn"); + session.startTransaction({writeConcern: {w: "majority"}}); + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); + assert.commandWorked(secondSessionDB.runCommand({create: collName})); // outside txn + assert.commandWorked(secondSessionDB.getCollection(collName).insert({a: 1})); + + jsTest.log("Committing transaction (SHOULD FAIL)"); + assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.WriteConflict); + assert.eq(sessionColl.find({}).itcount(), 1); + + sessionColl.drop({writeConcern: {w: "majority"}}); + jsTest.log( "Testing duplicate createCollections in parallel, both attempt to commit, second to commit fails"); secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2 - createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate); + createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate, upsert); session.startTransaction({writeConcern: {w: "majority"}}); // txn 1 - createCollAndCRUDInTxn(sessionDB, collName, explicitCreate); + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); jsTest.log("Committing transaction 2"); secondSession.commitTransaction(); @@ -59,17 +71,35 @@ function runParallelCollectionCreateTest(explicitCreate) { assert.eq(sessionColl.find({}).itcount(), 1); sessionColl.drop({writeConcern: {w: "majority"}}); + + jsTest.log("Testing duplicate createCollections which implicitly create databases in parallel" + + ", both attempt to commit, second to commit fails"); + + assert.commandWorked(sessionDB.dropDatabase()); + secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2 + createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate, upsert); + + session.startTransaction({writeConcern: {w: "majority"}}); // txn 1 + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); + + jsTest.log("Committing transaction 2"); + secondSession.commitTransaction(); + jsTest.log("Committing transaction 1 (SHOULD FAIL)"); + assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.WriteConflict); + assert.eq(sessionColl.find({}).itcount(), 1); + sessionColl.drop({writeConcern: {w: "majority"}}); distinctSessionColl.drop({writeConcern: {w: "majority"}}); jsTest.log("Testing createCollection conflict during commit, where the conflict rolls back a " + "previously committed collection."); secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2 - createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate); + createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate, upsert); - session.startTransaction({writeConcern: {w: "majority"}}); // txn 1 - createCollAndCRUDInTxn(sessionDB, distinctCollName, explicitCreate); // does not conflict - createCollAndCRUDInTxn(sessionDB, collName, explicitCreate); // conflicts + session.startTransaction({writeConcern: {w: "majority"}}); // txn 1 + createCollAndCRUDInTxn( + sessionDB, distinctCollName, explicitCreate, upsert); // does not conflict + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); // conflicts jsTest.log("Committing transaction 2"); secondSession.commitTransaction(); @@ -83,10 +113,10 @@ function runParallelCollectionCreateTest(explicitCreate) { jsTest.log("Testing distinct createCollections in parallel, both successfully commit."); session.startTransaction({writeConcern: {w: "majority"}}); // txn 1 - createCollAndCRUDInTxn(sessionDB, collName, explicitCreate); + createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2 - createCollAndCRUDInTxn(secondSessionDB, distinctCollName, explicitCreate); + createCollAndCRUDInTxn(secondSessionDB, distinctCollName, explicitCreate, upsert); session.commitTransaction(); secondSession.commitTransaction(); @@ -94,6 +124,8 @@ function runParallelCollectionCreateTest(explicitCreate) { secondSession.endSession(); session.endSession(); } -runParallelCollectionCreateTest(true /*explicitCreate*/); -runParallelCollectionCreateTest(false /*explicitCreate*/); +runParallelCollectionCreateTest(true /*explicitCreate*/, true /*upsert*/); +runParallelCollectionCreateTest(false /*explicitCreate*/, true /*upsert*/); +runParallelCollectionCreateTest(true /*explicitCreate*/, false /*upsert*/); +runParallelCollectionCreateTest(false /*explicitCreate*/, false /*upsert*/); }()); diff --git a/jstests/core/txns/create_indexes_parallel.js b/jstests/core/txns/create_indexes_parallel.js index 80d5d3dbe31..6741fd80632 100644 --- a/jstests/core/txns/create_indexes_parallel.js +++ b/jstests/core/txns/create_indexes_parallel.js @@ -88,6 +88,38 @@ let doParallelCreateIndexesTest = function(explicitCollectionCreate, multikeyInd assert.eq(sessionColl.getIndexes().length, 2); sessionColl.drop({writeConcern: {w: "majority"}}); + + jsTest.log("Testing createIndexes inside txn and createCollection on conflicting collection " + + "in parallel."); + session.startTransaction({writeConcern: {w: "majority"}}); // txn 1 + createIndexAndCRUDInTxn(sessionDB, collName, explicitCollectionCreate, multikeyIndex); + assert.commandWorked(secondSessionDB.createCollection(collName)); + assert.commandWorked(secondSessionDB.getCollection(collName).insert({a: 1})); + + jsTest.log("Committing transaction (SHOULD FAIL)"); + assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.WriteConflict); + assert.eq(sessionColl.find({}).itcount(), 1); + assert.eq(sessionColl.getIndexes().length, 1); + + assert.commandWorked(sessionDB.dropDatabase()); + jsTest.log("Testing duplicate createIndexes which implicitly create a database in parallel" + + ", both attempt to commit, second to commit fails"); + + secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2 + createIndexAndCRUDInTxn(secondSessionDB, collName, explicitCollectionCreate, multikeyIndex); + + session.startTransaction({writeConcern: {w: "majority"}}); // txn 1 + createIndexAndCRUDInTxn(sessionDB, collName, explicitCollectionCreate, multikeyIndex); + + jsTest.log("Committing transaction 2"); + secondSession.commitTransaction(); + + jsTest.log("Committing transaction 1 (SHOULD FAIL)"); + assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.WriteConflict); + assert.eq(sessionColl.find({}).itcount(), 1); + assert.eq(sessionColl.getIndexes().length, 2); + + sessionColl.drop({writeConcern: {w: "majority"}}); secondSessionColl.drop({writeConcern: {w: "majority"}}); distinctSessionColl.drop({writeConcern: {w: "majority"}}); diff --git a/jstests/libs/create_collection_txn_helpers.js b/jstests/libs/create_collection_txn_helpers.js index 9b6ba15eac8..08d7093a5c7 100644 --- a/jstests/libs/create_collection_txn_helpers.js +++ b/jstests/libs/create_collection_txn_helpers.js @@ -1,12 +1,27 @@ /** * Helper function shared by createCollection inside txns tests. */ -const createCollAndCRUDInTxn = function(sessionDB, collName, explicitCreate) { +const createCollAndCRUDInTxn = function(sessionDB, collName, explicitCreate, upsert) { + if (undefined === explicitCreate) { + doassert('createCollAndCRUDInTxn called with undefined explicitCreate'); + } + if (undefined === upsert) { + doassert('createCollAndCRUDInTxn called with undefined upsert'); + } if (explicitCreate) { assert.commandWorked(sessionDB.runCommand({create: collName})); } let sessionColl = sessionDB[collName]; - assert.commandWorked(sessionColl.insert({a: 1})); + if (upsert) { + assert.commandWorked(sessionColl.update({_id: 1}, {$inc: {a: 1}}, {upsert: true})); + } else { + assert.commandWorked(sessionColl.insert({a: 1})); + } assert.eq(sessionColl.find({a: 1}).itcount(), 1); + assert.commandWorked(sessionColl.insert({_id: 2})); + let resDoc = sessionColl.findAndModify({query: {_id: 2}, update: {$inc: {a: 1}}}); + assert.eq(resDoc._id, 2); + assert.commandWorked(sessionColl.update({_id: 2}, {$inc: {a: 1}})); + assert.commandWorked(sessionColl.deleteOne({_id: 2})); assert.eq(sessionColl.find({}).itcount(), 1); }; diff --git a/jstests/libs/create_index_txn_helpers.js b/jstests/libs/create_index_txn_helpers.js index fdc2863a675..73e1ee94fe2 100644 --- a/jstests/libs/create_index_txn_helpers.js +++ b/jstests/libs/create_index_txn_helpers.js @@ -23,11 +23,14 @@ const createIndexAndCRUDInTxn = function(sessionDB, collName, explicitCollCreate } let sessionColl = sessionDB[collName]; assert.commandWorked(sessionColl.runCommand({createIndexes: collName, indexes: [indexSpecs]})); + assert.commandWorked(sessionColl.createIndex({_id: 1})); if (multikeyIndex) { assert.commandWorked(sessionColl.insert({a: [1, 2, 3]})); } else { assert.commandWorked(sessionColl.insert({a: 1})); } assert.eq(sessionColl.find({a: 1}).itcount(), 1); + assert.commandWorked(sessionColl.insert({_id: 1})); + assert.commandWorked(sessionColl.deleteOne({_id: 1})); assert.eq(sessionColl.find({}).itcount(), 1); }; diff --git a/jstests/sharding/create_existing_indexes_prepared_transactions.js b/jstests/sharding/create_existing_indexes_prepared_transactions.js new file mode 100644 index 00000000000..7dda83c7158 --- /dev/null +++ b/jstests/sharding/create_existing_indexes_prepared_transactions.js @@ -0,0 +1,112 @@ +// Test that ensuring index existence (createIndexes on an existing index) successfully runs in a +// cross-shard transaction and that attempting to do createIndexes when only a subset of shards is +// aware of the existing index fails. +// +// @tags: [ +// requires_find_command, +// requires_fcv_44, +// requires_sharding, +// uses_multi_shard_transaction, +// uses_transactions, +// ] +(function() { +"use strict"; + +function expectChunks(st, ns, chunks) { + for (let i = 0; i < chunks.length; i++) { + assert.eq(chunks[i], + st.s.getDB("config").chunks.count({ns: ns, shard: st["shard" + i].shardName}), + "unexpected number of chunks on shard " + i); + } +} + +const dbName = "test"; +const dbNameShard2 = "testOther"; +const collName = "foo"; +const ns = dbName + '.' + collName; + +const st = new ShardingTest({ + shards: 3, + mongos: 1, +}); + +// Set up one sharded collection with 2 chunks, both on the primary shard. + +assert.commandWorked( + st.s.getDB(dbName)[collName].insert({_id: -5}, {writeConcern: {w: "majority"}})); +assert.commandWorked( + st.s.getDB(dbName)[collName].insert({_id: 5}, {writeConcern: {w: "majority"}})); + +assert.commandWorked(st.s.adminCommand({enableSharding: dbName})); +st.ensurePrimaryShard(dbName, st.shard0.shardName); +assert.commandWorked(st.s.getDB(dbName).runCommand({ + createIndexes: collName, + indexes: [{key: {a: 1}, name: "a_1"}], + writeConcern: {w: "majority"} +})); + +// Set up another collection with a different shard (shard 2) as its primary shard. +assert.commandWorked( + st.s.getDB(dbNameShard2)[collName].insert({_id: 4}, {writeConcern: {w: "majority"}})); +st.ensurePrimaryShard(dbNameShard2, st.shard2.shardName); + +const session = st.s.getDB(dbName).getMongo().startSession({causalConsistency: false}); + +let sessionDB = session.getDatabase(dbName); +let sessionColl = sessionDB[collName]; +let sessionDBShard2 = session.getDatabase(dbNameShard2); +let sessionCollShard2 = sessionDBShard2[collName]; + +assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {_id: 1}})); +assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 0}})); + +expectChunks(st, ns, [2, 0, 0]); + +st.stopBalancer(); + +// Ensure collection `ns` has chunks distributed across two shards. +assert.commandWorked(st.s.adminCommand({moveChunk: ns, find: {_id: 5}, to: st.shard1.shardName})); +expectChunks(st, ns, [1, 1, 0]); + +// Ensure no stale version errors occur. +let doc = st.s.getDB(dbName).getCollection(collName).findOne({_id: 5}); +assert.eq(doc._id, 5); +let doc2 = st.s.getDB(dbNameShard2).getCollection(collName).findOne({_id: 4}); +assert.eq(doc2._id, 4); + +jsTest.log("Testing createIndexes on an existing index in a transaction"); +session.startTransaction({writeConcern: {w: "majority"}}); + +assert.commandWorked( + sessionColl.runCommand({createIndexes: collName, indexes: [{key: {a: 1}, name: "a_1"}]})); +// Perform cross-shard writes to execute prepare path. +assert.commandWorked(sessionColl.insert({n: 2})); +assert.commandWorked(sessionCollShard2.insert({m: 1})); +assert.eq(sessionCollShard2.findOne({m: 1}).m, 1); +assert.eq(sessionColl.findOne({n: 2}).n, 2); + +session.commitTransaction(); + +jsTest.log("Testing createIndexes on an existing index in a transaction when not all shards are" + + " aware of that index (should abort)"); + +// Simulate a scenario where one shard with chunks for a collection is unaware of one of the +// collection's indexes. +st.shard1.getDB(dbName).getCollection(collName).dropIndexes("a_1"); + +session.startTransaction({writeConcern: {w: "majority"}}); + +assert.commandFailedWithCode( + sessionColl.runCommand({createIndexes: collName, indexes: [{key: {a: 1}, name: "a_1"}]}), + ErrorCodes.OperationNotSupportedInTransaction); + +assert.commandFailedWithCode(session.abortTransaction_forTesting(), ErrorCodes.NoSuchTransaction); + +// Resolve index inconsistency to pass consistency checks. +st.shard1.getDB(dbName).getCollection(collName).runCommand( + {createIndexes: collName, indexes: [{key: {a: 1}, name: "a_1"}]}); + +st.startBalancer(); + +st.stop(); +})(); diff --git a/jstests/sharding/create_new_collections_prepared_transactions.js b/jstests/sharding/create_new_collections_prepared_transactions.js new file mode 100644 index 00000000000..7c56dcd87ba --- /dev/null +++ b/jstests/sharding/create_new_collections_prepared_transactions.js @@ -0,0 +1,64 @@ +// Test that new collection creation fails in a cross-shard write transaction, but succeeds in a +// single-shard write transaction. +// +// @tags: [ +// requires_find_command, +// requires_fcv_44, +// requires_sharding, +// uses_multi_shard_transaction, +// uses_transactions, +// ] +(function() { +"use strict"; + +const dbNameShard0 = "test"; +const dbNameShard2 = "testOther"; +const collName = "foo"; + +const st = new ShardingTest({ + shards: 3, + mongos: 1, +}); + +// Create two databases with different shards as their primaries. + +assert.commandWorked( + st.s.getDB(dbNameShard0)[collName].insert({_id: 5}, {writeConcern: {w: "majority"}})); + +assert.commandWorked(st.s.adminCommand({enableSharding: dbNameShard0})); +st.ensurePrimaryShard(dbNameShard0, st.shard0.shardName); + +// Set up another collection with a different shard (shard2) as its primary shard. +assert.commandWorked( + st.s.getDB(dbNameShard2)[collName].insert({_id: 4}, {writeConcern: {w: "majority"}})); +st.ensurePrimaryShard(dbNameShard2, st.shard2.shardName); + +const session = st.s.getDB(dbNameShard0).getMongo().startSession({causalConsistency: false}); + +let sessionDBShard0 = session.getDatabase(dbNameShard0); +let sessionDBShard2 = session.getDatabase(dbNameShard2); +let newCollName = "newColl"; + +// Ensure no stale version errors occur. +let doc = st.s.getDB(dbNameShard0).getCollection(collName).findOne({_id: 5}); +assert.eq(doc._id, 5); +let doc2 = st.s.getDB(dbNameShard2).getCollection(collName).findOne({_id: 4}); +assert.eq(doc2._id, 4); + +jsTest.log("Testing collection creation in a cross-shard write transaction."); +session.startTransaction({writeConcern: {w: "majority"}}); +assert.commandWorked(sessionDBShard0.createCollection(newCollName)); +assert.commandWorked(sessionDBShard2.createCollection(newCollName)); + +// TODO(SERVER-46796) Replace NoSuchTransaction with OperationNotSupportedInTransaction +assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.NoSuchTransaction); + +jsTest.log("Testing collection creation in a single-shard write transaction."); +session.startTransaction({writeConcern: {w: "majority"}}); +assert.commandWorked(sessionDBShard0.createCollection(newCollName)); +doc2 = sessionDBShard2.getCollection(collName).findOne({_id: 4}); +assert.eq(doc2._id, 4); +assert.commandWorked(session.commitTransaction_forTesting()); + +st.stop(); +})(); |