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
|
/**
* Test that in tenant migration, the recipient does not change sync source
* even after its current sync source steps down as primary.
*
* @tags: [
* requires_majority_read_concern,
* requires_fcv_49,
* incompatible_with_windows_tls,
* incompatible_with_macos, requires_persistence,
* incompatible_with_eft
* ]
*/
(function() {
"use strict";
load("jstests/libs/fail_point_util.js");
load("jstests/libs/uuid_util.js");
load("jstests/replsets/libs/tenant_migration_test.js");
load("jstests/replsets/libs/tenant_migration_util.js");
// Verify the recipient's current sync source is the expected one.
const verifySyncSource = function(conn, migrationId, expectedSyncSource) {
const res = conn.adminCommand({currentOp: true, desc: "tenant recipient migration"});
assert.eq(res.inprog.length, 1);
const currOp = res.inprog[0];
assert.eq(bsonWoCompare(currOp.instanceID.uuid, migrationId), 0);
assert.eq(currOp.donorSyncSource, expectedSyncSource, tojson(res));
};
const batchSize = 2;
const recipientRst = new ReplSetTest({
nodes: 2,
name: jsTestName() + "_recipient",
nodeOptions: Object.assign(TenantMigrationUtil.makeX509OptionsForTest().recipient, {
setParameter: {
// Use a batch size of 2 so that collection cloner requires more than a single
// batch to complete.
collectionClonerBatchSize: batchSize,
// Allow reads on recipient before migration completes for testing.
'failpoint.tenantMigrationRecipientNotRejectReads': tojson({mode: 'alwaysOn'}),
}
})
});
recipientRst.startSet();
recipientRst.initiateWithHighElectionTimeout();
if (!TenantMigrationUtil.isFeatureFlagEnabled(recipientRst.getPrimary())) {
jsTestLog("Skipping test because the tenant migrations feature flag is disabled");
recipientRst.stopSet();
return;
}
const tenantMigrationTest =
new TenantMigrationTest({name: jsTestName(), recipientRst: recipientRst});
const donorRst = tenantMigrationTest.getDonorRst();
const donorPrimary = donorRst.getPrimary();
const tenantId = "testTenantId";
const dbName = tenantMigrationTest.tenantDB(tenantId, "testDB");
const collName = "testColl";
const recipientPrimary = tenantMigrationTest.getRecipientPrimary();
const docs1 = [{_id: 0}, {_id: 1}, {_id: 2}, {_id: 3}];
const docs2 = [{_id: 4}, {_id: 5}];
tenantMigrationTest.insertDonorDB(dbName, collName, docs1);
const migrationId = UUID();
const migrationIdString = extractUUIDFromObject(migrationId);
const migrationOpts = {
migrationIdString: migrationIdString,
recipientConnString: tenantMigrationTest.getRecipientConnString(),
tenantId: tenantId,
readPreference: {mode: "primary"}, // only sync from donor's primary
};
const recipientDb = recipientPrimary.getDB(dbName);
const recipientColl = recipientDb.getCollection(collName);
// Fail point to have the recipient primary hang creating connections to donor.
const hangRecipientPrimaryAfterCreatingConnections = configureFailPoint(
recipientPrimary, "fpAfterStartingOplogFetcherMigrationRecipientInstance", {action: "hang"});
// Fail point to have the recipient primary hang after cloning 2 documents.
const hangDuringCollectionClone =
configureFailPoint(recipientPrimary,
"tenantMigrationHangCollectionClonerAfterHandlingBatchResponse",
{nss: recipientColl.getFullName()});
jsTestLog("Starting the tenant migration");
assert.commandWorked(tenantMigrationTest.startMigration(migrationOpts));
// Wait for the connection to donor's primary to be created and verify recipient's
// sync source is donor's primary as specified by the read preference.
hangRecipientPrimaryAfterCreatingConnections.wait();
verifySyncSource(recipientPrimary, migrationId, donorPrimary.host);
hangRecipientPrimaryAfterCreatingConnections.off();
// Wait for recipient to hang after cloning 2 documents.
hangDuringCollectionClone.wait();
assert.soon(() => recipientColl.find().itcount() === batchSize);
verifySyncSource(recipientPrimary, migrationId, donorPrimary.host);
// Steps down the current donor's primary and wait for the new primary to be discovered.
donorRst.awaitLastOpCommitted();
assert.commandWorked(donorRst.getSecondary().adminCommand({replSetStepUp: 1}));
const newDonorPrimary = donorRst.getPrimary();
assert.neq(newDonorPrimary.host, donorPrimary.host);
// Insert some new documents so that the recipient's oplog fetcher needs to continue
// fetching documents after donor replSet changes primary in order to be consistent.
tenantMigrationTest.insertDonorDB(dbName, collName, docs2);
hangDuringCollectionClone.off();
// After recipient syncs new documents, becomes consistent, and finishes migration,
// verify the sync source is still the donor's old primary.
const stateRes =
assert.commandWorked(tenantMigrationTest.waitForMigrationToComplete(migrationOpts));
assert.eq(stateRes.state, TenantMigrationTest.DonorState.kCommitted);
assert.eq(recipientColl.find().itcount(), docs1.length + docs2.length);
assert.docEq(recipientColl.find().sort({_id: 1}).toArray(), docs1.concat(docs2));
verifySyncSource(recipientPrimary, migrationId, donorPrimary.host);
tenantMigrationTest.stop();
recipientRst.stopSet();
})();
|