1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
/**
* Tests that the migration recipient will retrieve committed transactions on the donor
* with lastWriteOpTime <= the stored startApplyingOpTime. The recipient should store
* these committed transaction entries in its own 'config.transactions' collection.
*
* @tags: [
* incompatible_with_macos,
* incompatible_with_windows_tls,
* requires_majority_read_concern,
* requires_persistence,
* serverless,
* incompatible_with_shard_merge,
* requires_fcv_53
* ]
*/
(function() {
"use strict";
load("jstests/core/txns/libs/prepare_helpers.js");
load("jstests/replsets/libs/tenant_migration_test.js");
load("jstests/replsets/libs/tenant_migration_util.js");
load("jstests/replsets/rslib.js");
load("jstests/libs/uuid_util.js");
const tenantId = "testTenantId";
const transactionsNS = "config.transactions";
const collName = "testColl";
const tenantMigrationTest = new TenantMigrationTest({name: jsTestName()});
const tenantDB = tenantMigrationTest.tenantDB(tenantId, "testDB");
const nonTenantDB = tenantMigrationTest.nonTenantDB(tenantId, "testDB");
const tenantNS = `${tenantDB}.${collName}`;
const donorPrimary = tenantMigrationTest.getDonorPrimary();
const recipientPrimary = tenantMigrationTest.getRecipientPrimary();
function validateTransactionEntryonRecipient(sessionId) {
const donorTxnEntry =
donorPrimary.getCollection(transactionsNS).findOne({"_id.id": sessionId.id});
const recipientTxnEntry =
recipientPrimary.getCollection(transactionsNS).findOne({"_id.id": sessionId.id});
assert.eq(donorTxnEntry.txnNum, recipientTxnEntry.txnNum);
assert.eq(donorTxnEntry.state, recipientTxnEntry.state);
// The recipient should have replaced the 'lastWriteOpTime' and 'lastWriteDate' fields.
assert.neq(donorTxnEntry.lastWriteOpTime, recipientTxnEntry.lastWriteOpTime);
assert.neq(donorTxnEntry.lastWriteDate, recipientTxnEntry.lastWriteDate);
// Test that the client can retry the first 'commitTransaction' on the recipient.
assert.commandWorked(recipientPrimary.adminCommand({
commitTransaction: 1,
lsid: recipientTxnEntry._id,
txnNumber: recipientTxnEntry.txnNum,
autocommit: false,
}));
}
assert.commandWorked(donorPrimary.getCollection(tenantNS).insert([{_id: 0, x: 0}, {_id: 1, x: 1}],
{writeConcern: {w: "majority"}}));
let sessionIdBeforeMigration;
{
jsTestLog("Run and commit a transaction prior to the migration");
const session = donorPrimary.startSession({causalConsistency: false});
sessionIdBeforeMigration = session.getSessionId();
const sessionDb = session.getDatabase(tenantDB);
const sessionColl = sessionDb.getCollection(collName);
session.startTransaction({writeConcern: {w: "majority"}});
const findAndModifyRes0 = sessionColl.findAndModify({query: {x: 0}, remove: true});
assert.eq({_id: 0, x: 0}, findAndModifyRes0);
assert.commandWorked(session.commitTransaction_forTesting());
assert.sameMembers(sessionColl.find({}).toArray(), [{_id: 1, x: 1}]);
session.endSession();
}
assert.eq(1, donorPrimary.getCollection(transactionsNS).find().itcount());
{
jsTestLog("Run and abort a transaction prior to the migration");
const session = donorPrimary.startSession({causalConsistency: false});
const sessionDb = session.getDatabase(tenantDB);
const sessionColl = sessionDb.getCollection(collName);
session.startTransaction({writeConcern: {w: "majority"}});
const findAndModifyRes0 = sessionColl.findAndModify({query: {x: 1}, remove: true});
assert.eq({_id: 1, x: 1}, findAndModifyRes0);
// We prepare the transaction so that 'abortTransaction' will update the transactions table.
// We should later see that the recipient will not update its transactions table with this
// entry, since we only fetch committed transactions.
PrepareHelpers.prepareTransaction(session);
assert.commandWorked(session.abortTransaction_forTesting());
assert.sameMembers(sessionColl.find({}).toArray(), [{_id: 1, x: 1}]);
session.endSession();
}
assert.eq(2, donorPrimary.getCollection(transactionsNS).find().itcount());
{
jsTestLog("Run and commit a transaction that does not belong to the tenant");
const session = donorPrimary.startSession({causalConsistency: false});
const sessionDb = session.getDatabase(nonTenantDB);
const sessionColl = sessionDb.getCollection(collName);
session.startTransaction({writeConcern: {w: "majority"}});
assert.commandWorked(sessionColl.insert([{_id: 0, x: 0}, {_id: 1, x: 1}]));
assert.commandWorked(session.commitTransaction_forTesting());
session.endSession();
}
assert.eq(3, donorPrimary.getCollection(transactionsNS).find().itcount());
jsTestLog("Running a migration");
const migrationId = UUID();
const migrationOpts = {
migrationIdString: extractUUIDFromObject(migrationId),
tenantId,
};
const fpAfterFetchingCommittedTransactions =
configureFailPoint(recipientPrimary, "fpAfterFetchingCommittedTransactions", {action: "hang"});
assert.commandWorked(tenantMigrationTest.startMigration(migrationOpts));
fpAfterFetchingCommittedTransactions.wait();
// Verify that the recipient has fetched and written the committed transaction entry
// belonging to the migrating tenant from the donor.
assert.eq(1, recipientPrimary.getCollection(transactionsNS).find().itcount());
fpAfterFetchingCommittedTransactions.off();
TenantMigrationTest.assertCommitted(tenantMigrationTest.waitForMigrationToComplete(migrationOpts));
validateTransactionEntryonRecipient(sessionIdBeforeMigration);
tenantMigrationTest.stop();
})();
|