2018-09-05 16:14:46 -04:00
|
|
|
/**
|
|
|
|
|
* Exercises the coordinator commands logic by simulating a basic two phase commit and basic two
|
|
|
|
|
* phase abort.
|
2018-09-07 16:58:34 -04:00
|
|
|
*
|
2018-09-26 11:26:36 -04:00
|
|
|
* @tags: [uses_transactions, uses_prepare_transaction]
|
2018-09-05 16:14:46 -04:00
|
|
|
*/
|
|
|
|
|
(function() {
|
|
|
|
|
const dbName = "test";
|
|
|
|
|
const collName = "foo";
|
|
|
|
|
const ns = dbName + "." + collName;
|
|
|
|
|
|
2018-09-21 15:42:10 -04:00
|
|
|
let st = new ShardingTest({shards: 3, causallyConsistent: true});
|
2018-09-05 16:14:46 -04:00
|
|
|
|
|
|
|
|
let coordinator = st.shard0;
|
|
|
|
|
let participant1 = st.shard1;
|
|
|
|
|
let participant2 = st.shard2;
|
|
|
|
|
|
|
|
|
|
// Create a sharded collection with a chunk on each shard:
|
|
|
|
|
// shard0: [-inf, 0)
|
|
|
|
|
// shard1: [0, 10)
|
|
|
|
|
// shard2: [10, +inf)
|
|
|
|
|
assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
|
|
|
|
|
assert.commandWorked(st.s.adminCommand({movePrimary: dbName, to: coordinator.shardName}));
|
|
|
|
|
assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {_id: 1}}));
|
|
|
|
|
assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 0}}));
|
|
|
|
|
assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 10}}));
|
|
|
|
|
assert.commandWorked(
|
|
|
|
|
st.s.adminCommand({moveChunk: ns, find: {_id: 0}, to: participant1.shardName}));
|
|
|
|
|
assert.commandWorked(
|
|
|
|
|
st.s.adminCommand({moveChunk: ns, find: {_id: 10}, to: participant2.shardName}));
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sends an insert on 'collName' to each shard with 'coordinator: true' on the first shard.
|
|
|
|
|
*
|
|
|
|
|
* Then calls 'coordinateCommitTransaction' on the coordinator with a participant list
|
|
|
|
|
* containing all the shards.
|
|
|
|
|
*
|
|
|
|
|
* lsid should be an object with format {id: <UUID>}
|
|
|
|
|
* txnNumber should be an integer
|
|
|
|
|
*/
|
|
|
|
|
let setUp = function(lsid, txnNumber) {
|
|
|
|
|
// Simulate that the first statement in the transaction touched 'shard0', so mongos
|
|
|
|
|
// forwarded the statement with 'coordinator: true' to shard0.
|
|
|
|
|
assert.commandWorked(coordinator.getDB(dbName).runCommand({
|
|
|
|
|
insert: collName,
|
|
|
|
|
documents: [{_id: -5}],
|
|
|
|
|
lsid: lsid,
|
|
|
|
|
txnNumber: NumberLong(txnNumber),
|
|
|
|
|
stmtId: NumberInt(0),
|
|
|
|
|
startTransaction: true,
|
|
|
|
|
autocommit: false,
|
|
|
|
|
coordinator: true,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Simulate that some statement in the transaction touched shards 'shard1' and 'shard2'.
|
|
|
|
|
assert.commandWorked(participant1.getDB(dbName).runCommand({
|
|
|
|
|
insert: collName,
|
|
|
|
|
documents: [{_id: 5}],
|
|
|
|
|
lsid: lsid,
|
|
|
|
|
txnNumber: NumberLong(txnNumber),
|
|
|
|
|
stmtId: NumberInt(0),
|
|
|
|
|
startTransaction: true,
|
|
|
|
|
autocommit: false,
|
|
|
|
|
}));
|
|
|
|
|
assert.commandWorked(participant2.getDB(dbName).runCommand({
|
|
|
|
|
insert: collName,
|
|
|
|
|
documents: [{_id: 15}],
|
|
|
|
|
lsid: lsid,
|
|
|
|
|
txnNumber: NumberLong(txnNumber),
|
|
|
|
|
stmtId: NumberInt(0),
|
|
|
|
|
startTransaction: true,
|
|
|
|
|
autocommit: false,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Simulate that mongos sends the participant list to the coordinator.
|
|
|
|
|
assert.commandWorked(coordinator.adminCommand({
|
|
|
|
|
coordinateCommitTransaction: 1,
|
|
|
|
|
participants: [
|
|
|
|
|
{shardId: coordinator.shardName},
|
|
|
|
|
{shardId: participant1.shardName},
|
|
|
|
|
{shardId: participant2.shardName}
|
|
|
|
|
],
|
|
|
|
|
lsid: lsid,
|
|
|
|
|
txnNumber: NumberLong(txnNumber),
|
|
|
|
|
stmtId: NumberInt(0),
|
|
|
|
|
autocommit: false,
|
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Test two-phase abort
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
let lsid = {id: UUID()};
|
|
|
|
|
let txnNumber = 0;
|
|
|
|
|
|
|
|
|
|
setUp(lsid, txnNumber);
|
|
|
|
|
|
|
|
|
|
// Simulate that some participant votes to abort.
|
|
|
|
|
assert.commandWorked(coordinator.adminCommand({
|
|
|
|
|
voteAbortTransaction: 1,
|
|
|
|
|
shardId: participant2.shardName,
|
|
|
|
|
lsid: lsid,
|
|
|
|
|
txnNumber: NumberLong(txnNumber),
|
|
|
|
|
stmtId: NumberInt(0),
|
|
|
|
|
autocommit: false,
|
|
|
|
|
}));
|
|
|
|
|
// Manually abort the transaction on this participant, since the coordinator will not send
|
|
|
|
|
// abortTransaction to a participant that voted to abort.
|
|
|
|
|
assert.commandWorked(participant2.adminCommand({
|
|
|
|
|
abortTransaction: 1,
|
|
|
|
|
lsid: lsid,
|
|
|
|
|
txnNumber: NumberLong(txnNumber),
|
|
|
|
|
stmtId: NumberInt(0),
|
|
|
|
|
autocommit: false,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Verify that the transaction was aborted on all shards.
|
|
|
|
|
assert.eq(0, st.s.getDB(dbName).getCollection(collName).find().itcount());
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Test two-phase commit
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
txnNumber++;
|
|
|
|
|
|
|
|
|
|
setUp(lsid, txnNumber);
|
|
|
|
|
|
2018-09-11 16:49:04 -04:00
|
|
|
// Simulate that mongos sends 'prepare' with the coordinator's id to the non-coordinator
|
|
|
|
|
// participants.
|
2018-09-26 16:12:35 -04:00
|
|
|
assert.commandWorked(participant1.adminCommand({
|
|
|
|
|
prepareTransaction: 1,
|
|
|
|
|
coordinatorId: coordinator.shardName,
|
2018-09-05 16:14:46 -04:00
|
|
|
lsid: lsid,
|
|
|
|
|
txnNumber: NumberLong(txnNumber),
|
|
|
|
|
stmtId: NumberInt(0),
|
|
|
|
|
autocommit: false,
|
|
|
|
|
}));
|
2018-09-26 16:12:35 -04:00
|
|
|
assert.commandWorked(participant2.adminCommand({
|
|
|
|
|
prepareTransaction: 1,
|
|
|
|
|
coordinatorId: coordinator.shardName,
|
2018-09-05 16:14:46 -04:00
|
|
|
lsid: lsid,
|
|
|
|
|
txnNumber: NumberLong(txnNumber),
|
|
|
|
|
stmtId: NumberInt(0),
|
|
|
|
|
autocommit: false,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Verify that the transaction was committed on all shards.
|
2018-09-26 16:12:35 -04:00
|
|
|
// Use assert.soon(), because none of the above commands (prepareTransaction or
|
|
|
|
|
// coordinateCommitTransaction) currently block until the decision is made. Once
|
|
|
|
|
// coordinateCommitTransaction *blocks* until a commit decision is *written*, the test can pass
|
|
|
|
|
// the operationTime returned by coordinateCommitTransaction as 'afterClusterTime' in the read
|
|
|
|
|
// to ensure the read sees the transaction's writes (TODO SERVER-37165).
|
|
|
|
|
assert.soon(function() {
|
|
|
|
|
return 3 === st.s.getDB(dbName).getCollection(collName).find().itcount();
|
|
|
|
|
});
|
2018-09-05 16:14:46 -04:00
|
|
|
|
|
|
|
|
st.stop();
|
|
|
|
|
|
|
|
|
|
})();
|