summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSanika Phanse <sanika.phanse@mongodb.com>2022-04-19 17:10:16 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-04-19 18:47:05 +0000
commite5bd4972cea9311177529fb7a5e4744cf93eb0a8 (patch)
tree6c2a306c1035599bc67f409b339c8059b8d4bed5
parent2fabc0cc49f78109a444ad909e99bd0c3c43470e (diff)
downloadmongo-e5bd4972cea9311177529fb7a5e4744cf93eb0a8.tar.gz
SERVER-65048 Support retryable writes in internal_transactions_test_command
(cherry picked from commit e2ae4c71e1c4e32e3c2d191c9bc07583926dcd5c)
-rw-r--r--jstests/sharding/internal_txns/transaction_api_test_command_basic.js130
-rw-r--r--src/mongo/db/transaction_validation.cpp1
-rw-r--r--src/mongo/s/commands/internal_transactions_test_command.h10
3 files changed, 125 insertions, 16 deletions
diff --git a/jstests/sharding/internal_txns/transaction_api_test_command_basic.js b/jstests/sharding/internal_txns/transaction_api_test_command_basic.js
index 5b86cb2e961..16ac90ea5ba 100644
--- a/jstests/sharding/internal_txns/transaction_api_test_command_basic.js
+++ b/jstests/sharding/internal_txns/transaction_api_test_command_basic.js
@@ -23,27 +23,81 @@ let primary = rst.getPrimary();
let db = primary.getDB(kDbName);
let rstColl = db.getCollection(kCollName);
-function runTxn(connection, commandInfos, collection) {
- const res = assert.commandWorked(
- connection.adminCommand({testInternalTransactions: 1, commandInfos: commandInfos}));
- jsTest.log(res);
+let stColl = st.s.getCollection(kNs);
+function verifyCompletedInsertCommandResult(commandInfos, response, collection) {
let i = 0;
commandInfos.forEach(commandInfo => {
- assert.eq(1, res.responses[i].ok);
- assert.eq(commandInfo.command.documents.length, res.responses[i].n);
+ // Verifies command response.
+ assert.eq(1, response.responses[i].ok);
+ assert.eq(commandInfo.command.documents.length, response.responses[i].n);
commandInfo.command.documents.forEach(document => {
+ // Verifies documents were successfully inserted.
assert.eq(document, collection.findOne(document));
});
++i;
});
}
+function runTxn(connection, commandInfos, collection) {
+ const res = assert.commandWorked(
+ connection.adminCommand({testInternalTransactions: 1, commandInfos: commandInfos}));
+ verifyCompletedInsertCommandResult(commandInfos, res, collection);
+}
+
+function runRetryableWrite(connection, commandInfos, lsid) {
+ const txnNumber = NumberLong(0);
+ return assert.commandWorked(connection.adminCommand({
+ testInternalTransactions: 1,
+ commandInfos: commandInfos,
+ lsid: {id: lsid},
+ txnNumber: txnNumber
+ }));
+}
+
+function runRetryableInsert(connection, commandInfos, lsid, collection) {
+ const originalRes = runRetryableWrite(connection, commandInfos, lsid);
+ verifyCompletedInsertCommandResult(commandInfos, originalRes, collection);
+
+ // This retryable write is expected to return a response of length 1 as the command list should
+ // stop executing after the first insert.
+ const retryRes = runRetryableWrite(connection, commandInfos, lsid);
+ assert.eq(1, retryRes.responses.length);
+ assert.eq(1, retryRes.ok);
+ assert.eq(0, retryRes.responses[0].retriedStmtIds[0]);
+}
+
+function runRetryableFindAndModify(connection, commandInfos, lsid, collection) {
+ const originalRes = runRetryableWrite(connection, commandInfos, lsid);
+ assert.eq(commandInfos.length, originalRes.responses.length);
+
+ // Verify command response.
+ assert.eq(1, originalRes.responses[0].lastErrorObject.n);
+ assert.eq(commandInfos[0].command.query._id, originalRes.responses[0].lastErrorObject.upserted);
+
+ // Verify document from commandInfos[0] was upserted into database.
+ let document = commandInfos[0].command.query;
+ assert.eq(document, collection.findOne(document));
+
+ // Verify document from commandInfos[1] was inserted into database.
+ assert.eq(1, originalRes.responses[1].n);
+ document = commandInfos[1].command.documents[0];
+ assert.eq(document, collection.findOne(document));
+
+ // Retry transaction. This retryable write is expected to return a response of length 1 as the
+ // command list should stop executing after the first findAndModify command.
+ const retryRes = runRetryableWrite(connection, commandInfos, lsid);
+ assert.eq(1, retryRes.responses.length);
+ assert.eq(1, retryRes.ok);
+ assert.eq(0, retryRes.responses[0].retriedStmtId);
+}
+
// Insert initial data.
-assert.commandWorked(st.s.getCollection(kNs).insert([{_id: 0}]));
+assert.commandWorked(stColl.insert([{_id: 0}]));
assert.commandWorked(rstColl.insert([{_id: 0}]));
-const commandInfos0 = [{
+// Set of commandInfos that will be used in tests below.
+const commandInfosSingleInsert = [{
dbName: kDbName,
command: {
insert: kCollName,
@@ -51,7 +105,7 @@ const commandInfos0 = [{
}
}];
-const commandInfos1 = [
+const commandInfosBatchInsert = [
{
dbName: kDbName,
command: {
@@ -68,17 +122,65 @@ const commandInfos1 = [
}
];
+const commandInfosRetryableBatchInsert = [
+ {
+ dbName: kDbName,
+ command: {
+ insert: kCollName,
+ documents: [{_id: 5}],
+ stmtId: NumberInt(0),
+ }
+ },
+ {
+ dbName: kDbName,
+ command: {
+ insert: kCollName,
+ documents: [{_id: 6}, {_id: 7}],
+ stmtId: NumberInt(1),
+ }
+ }
+];
+
+function commandInfosRetryableFindAndModify(collection) {
+ return [
+ {
+ dbName: kDbName,
+ command: {
+ findandmodify: collection.getName(),
+ query: {_id: 8},
+ update: {},
+ upsert: true,
+ stmtId: NumberInt(0),
+ }
+ },
+ {
+ dbName: kDbName,
+ command: {
+ insert: collection.getName(),
+ documents: [{_id: 9}],
+ stmtId: NumberInt(1),
+ }
+ }
+ ];
+}
+
jsTest.log(
"Insert documents without a session into a sharded cluster, using internal transactions test command.");
-runTxn(st.s, commandInfos0, st.s.getCollection(kNs));
-runTxn(st.s, commandInfos1, st.s.getCollection(kNs));
+runTxn(st.s, commandInfosSingleInsert, stColl);
+runTxn(st.s, commandInfosBatchInsert, stColl);
jsTest.log(
"Insert documents without a session into a replica set, using internal transactions test command.");
-runTxn(primary, commandInfos0, rstColl);
-runTxn(primary, commandInfos1, rstColl);
+runTxn(primary, commandInfosSingleInsert, rstColl);
+runTxn(primary, commandInfosBatchInsert, rstColl);
+
+jsTest.log("Testing retryable write targeting a mongos.");
+runRetryableInsert(st.s, commandInfosRetryableBatchInsert, UUID(), stColl);
+runRetryableFindAndModify(st.s, commandInfosRetryableFindAndModify(stColl), UUID(), stColl);
-// TODO SERVER-65048: Add testing for retryable writes and txns run in sessions.
+jsTest.log("Testing retryable write targeting a mongod.");
+runRetryableInsert(primary, commandInfosRetryableBatchInsert, UUID(), rstColl);
+runRetryableFindAndModify(primary, commandInfosRetryableFindAndModify(rstColl), UUID(), rstColl);
rst.stopSet();
st.stop();
diff --git a/src/mongo/db/transaction_validation.cpp b/src/mongo/db/transaction_validation.cpp
index cc136f0fa6f..6571711cc76 100644
--- a/src/mongo/db/transaction_validation.cpp
+++ b/src/mongo/db/transaction_validation.cpp
@@ -53,6 +53,7 @@ const StringMap<int> retryableWriteCommands = {{"clusterDelete", 1},
{"findandmodify", 1},
{"findAndModify", 1},
{"insert", 1},
+ {"testInternalTransactions", 1},
{"update", 1},
{"_recvChunkStart", 1},
{"_configsvrRemoveChunks", 1},
diff --git a/src/mongo/s/commands/internal_transactions_test_command.h b/src/mongo/s/commands/internal_transactions_test_command.h
index dc4b7fc00c8..416a2a2ca94 100644
--- a/src/mongo/s/commands/internal_transactions_test_command.h
+++ b/src/mongo/s/commands/internal_transactions_test_command.h
@@ -116,8 +116,14 @@ public:
// commands.
uassertStatusOK(getStatusFromWriteCommandReply(res));
}
- // TODO SERVER-65048: Check if result has retriedStmtId & retriedStmtIds
- // field, exit.
+
+ // Exit if we are reexecuting commands in a retryable write, identified by a
+ // populated retriedStmtId. eoo() is false if field is found.
+ const auto isRetryStmt = !(res.getField("retriedStmtIds").eoo() &&
+ res.getField("retriedStmtId").eoo());
+ if (isRetryStmt) {
+ break;
+ }
}
return SemiFuture<void>::makeReady();
});