0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-11-24 00:17:37 +01:00
mongodb/jstests/sharding/transactions_error_labels.js
Matt Broadstone 771dabd098 SERVER-81339 Convert ReplSetTest and ShardingTest to modules (#26332)
GitOrigin-RevId: 744aa110a53786b23c62ff53f87a1418b5991e8d
2024-08-20 22:00:49 +00:00

213 lines
9.3 KiB
JavaScript

// Test TransientTransactionErrors error label in mongos write commands.
//
// @tags: [
// uses_multi_shard_transaction,
// uses_transactions,
// ]
import {ShardingTest} from "jstests/libs/shardingtest.js";
import {
failCommandWithError,
failCommandWithWriteConcernError,
turnOffFailCommand
} from "jstests/sharding/libs/failpoint_helpers.js";
import {
flushRoutersAndRefreshShardMetadata
} from "jstests/sharding/libs/sharded_transactions_helpers.js";
const dbName = "test";
const collName = "foo";
const ns = dbName + "." + collName;
let numCalls = 0;
const startTransaction = function(mongosSession, dbName, collName) {
numCalls++;
mongosSession.startTransaction();
return mongosSession.getDatabase(dbName).runCommand({
insert: collName,
// Target both chunks, wherever they may be
documents: [{_id: -1 * numCalls}, {_id: numCalls}],
readConcern: {level: "snapshot"},
});
};
const abortTransactionDirectlyOnParticipant = function(rst, lsid, txnNumber) {
assert.commandWorked(rst.getPrimary().adminCommand({
abortTransaction: 1,
lsid: lsid,
txnNumber: NumberLong(txnNumber),
autocommit: false,
}));
};
const checkMongosResponse = function(
res, expectedErrorCode, expectedErrorLabel, writeConcernErrorExpected) {
if (expectedErrorCode) {
assert.eq(0, res.ok, tojson(res));
assert.eq(expectedErrorCode, res.code, tojson(res));
} else {
assert.eq(1, res.ok, tojson(res));
}
if (expectedErrorLabel) {
assert.neq(null, res.errorLabels, tojson(res));
assert.contains(expectedErrorLabel, res.errorLabels, tojson(res));
} else {
assert.eq(null, res.errorLabels, tojson(res));
}
if (writeConcernErrorExpected) {
assert.neq(null, res.writeConcernError, tojson(res));
} else {
assert.eq(null, res.writeConcernError, tojson(res));
}
};
const runCommitTests = function(commandSentToShard) {
jsTest.log("Mongos does not attach any error label if " + commandSentToShard +
" returns success.");
assert.commandWorked(startTransaction(mongosSession, dbName, collName));
res = mongosSession.commitTransaction_forTesting();
checkMongosResponse(res, null, null, null);
jsTest.log("Mongos does not attach any error label if " + commandSentToShard +
" returns success with writeConcern error.");
failCommandWithWriteConcernError(st.rs0, commandSentToShard);
assert.commandWorked(startTransaction(mongosSession, dbName, collName));
res = mongosSession.commitTransaction_forTesting();
checkMongosResponse(res, null, null, true);
turnOffFailCommand(st.rs0);
jsTest.log("Mongos attaches 'TransientTransactionError' label if " + commandSentToShard +
" returns NoSuchTransaction.");
assert.commandWorked(startTransaction(mongosSession, dbName, collName));
abortTransactionDirectlyOnParticipant(
st.rs0, mongosSession.getSessionId(), mongosSession.getTxnNumber_forTesting());
res = mongosSession.commitTransaction_forTesting();
checkMongosResponse(res, ErrorCodes.NoSuchTransaction, "TransientTransactionError", null);
turnOffFailCommand(st.rs0);
jsTest.log("failCommand with errorLabels but without errorCode or writeConcernError should " +
"not interfere with mongos' error labels attaching");
assert.commandWorked(st.s.adminCommand({
configureFailPoint: "failCommand",
mode: "alwaysOn",
data: {failCommands: ["insert"], errorLabels: ["foo"]}
}));
assert.commandWorked(startTransaction(mongosSession, dbName, collName));
abortTransactionDirectlyOnParticipant(
st.rs0, mongosSession.getSessionId(), mongosSession.getTxnNumber_forTesting());
res = mongosSession.commitTransaction_forTesting();
checkMongosResponse(res, ErrorCodes.NoSuchTransaction, "TransientTransactionError", null);
assert.commandWorked(st.s.adminCommand({configureFailPoint: "failCommand", mode: "off"}));
jsTest.log("Mongos does not attach any error label if " + commandSentToShard +
" returns NoSuchTransaction with writeConcern error.");
failCommandWithWriteConcernError(st.rs0, commandSentToShard);
assert.commandWorked(startTransaction(mongosSession, dbName, collName));
abortTransactionDirectlyOnParticipant(
st.rs0, mongosSession.getSessionId(), mongosSession.getTxnNumber_forTesting());
res = mongosSession.commitTransaction_forTesting();
checkMongosResponse(res, ErrorCodes.NoSuchTransaction, null, true);
turnOffFailCommand(st.rs0);
jsTest.log("No error label for network error if " + commandSentToShard +
" returns network error");
assert.commandWorked(startTransaction(mongosSession, dbName, collName));
failCommandWithError(st.rs0, {
commandToFail: commandSentToShard,
errorCode: ErrorCodes.InternalError,
closeConnection: true
});
res = mongosSession.commitTransaction_forTesting();
checkMongosResponse(res, ErrorCodes.HostUnreachable, false /* expectedErrorLabel */, null);
turnOffFailCommand(st.rs0);
};
let st = new ShardingTest({shards: 2, mongosOptions: {verbose: 3}});
// Create a sharded collection with a chunk on each shard:
// shard0: [-inf, 0)
// shard1: [0, +inf)
assert.commandWorked(
st.s.adminCommand({enableSharding: dbName, primaryShard: st.shard0.shardName}));
assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {_id: 1}}));
assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 0}}));
st.refreshCatalogCacheForNs(st.s, ns);
// These forced refreshes are not strictly necessary; they just prevent extra TXN log lines
// from the shards starting, aborting, and restarting the transaction due to needing to
// refresh after the transaction has started.
assert.commandWorked(st.shard0.adminCommand({_flushRoutingTableCacheUpdates: ns}));
assert.commandWorked(st.shard1.adminCommand({_flushRoutingTableCacheUpdates: ns}));
let mongosSession = st.s.startSession();
let res;
// write statement
jsTest.log(
"'TransientTransactionError' label is attached if write statement returns WriteConflict");
failCommandWithError(
st.rs0, {commandToFail: "insert", errorCode: ErrorCodes.WriteConflict, closeConnection: false});
res = startTransaction(mongosSession, dbName, collName);
checkMongosResponse(res, ErrorCodes.WriteConflict, "TransientTransactionError", null);
turnOffFailCommand(st.rs0);
assert.commandFailedWithCode(mongosSession.abortTransaction_forTesting(),
ErrorCodes.NoSuchTransaction);
jsTest.log("'TransientTransactionError' label is attached if write statement returns " +
"WriteConflict via failCommand on mongos");
assert.commandWorked(st.s.adminCommand({
configureFailPoint: "failCommand",
mode: "alwaysOn",
data: {failCommands: ["insert"], errorCode: ErrorCodes.WriteConflict}
}));
res = startTransaction(mongosSession, dbName, collName);
checkMongosResponse(res, ErrorCodes.WriteConflict, "TransientTransactionError", null);
assert.commandWorked(st.s.adminCommand({configureFailPoint: "failCommand", mode: "off"}));
assert.commandFailedWithCode(mongosSession.abortTransaction_forTesting(),
ErrorCodes.NoSuchTransaction);
jsTest.log("failCommand with errorLabels should override labels attached by mongos");
assert.commandWorked(st.s.adminCommand({
configureFailPoint: "failCommand",
mode: "alwaysOn",
data: {failCommands: ["insert"], errorCode: ErrorCodes.WriteConflict, errorLabels: ["foo"]}
}));
res = startTransaction(mongosSession, dbName, collName);
checkMongosResponse(res, ErrorCodes.WriteConflict, "foo", null);
assert.commandWorked(st.s.adminCommand({configureFailPoint: "failCommand", mode: "off"}));
assert.commandFailedWithCode(mongosSession.abortTransaction_forTesting(),
ErrorCodes.NoSuchTransaction);
jsTest.log("failCommand with empty errorLabels should suppress labels attached by mongos");
assert.commandWorked(st.s.adminCommand({
configureFailPoint: "failCommand",
mode: "alwaysOn",
data: {failCommands: ["insert"], errorCode: ErrorCodes.WriteConflict, errorLabels: []}
}));
res = startTransaction(mongosSession, dbName, collName);
checkMongosResponse(res, ErrorCodes.WriteConflict, null, null);
assert.commandWorked(st.s.adminCommand({configureFailPoint: "failCommand", mode: "off"}));
assert.commandFailedWithCode(mongosSession.abortTransaction_forTesting(),
ErrorCodes.NoSuchTransaction);
// statements prior to commit network error
failCommandWithError(
st.rs0, {commandToFail: "insert", errorCode: ErrorCodes.InternalError, closeConnection: true});
res = startTransaction(mongosSession, dbName, collName);
checkMongosResponse(res, ErrorCodes.HostUnreachable, "TransientTransactionError", null);
turnOffFailCommand(st.rs0);
assert.commandFailedWithCode(mongosSession.abortTransaction_forTesting(),
ErrorCodes.NoSuchTransaction);
// commitTransaction for single-shard transaction (mongos sends commitTransaction)
runCommitTests("commitTransaction");
// commitTransaction for multi-shard transaction (mongos sends coordinateCommitTransaction)
assert.commandWorked(st.s.adminCommand({moveChunk: ns, find: {_id: 0}, to: st.shard1.shardName}));
flushRoutersAndRefreshShardMetadata(st, {ns});
runCommitTests("coordinateCommitTransaction");
st.stop();