summaryrefslogtreecommitdiff
path: root/jstests/replsets/tenant_migration_cloner_stats.js
blob: 54a6dbf28a844c56614a90f5bb8f29c6dfd49ed9 (plain)
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/**
 * Tests tenant migration cloner stats such as 'approxTotalDataSize', 'approxTotalBytesCopied'
 * across multiple databases and collections in the absence of failovers.
 *
 * @tags: [requires_fcv_49, requires_majority_read_concern, requires_persistence,
 * incompatible_with_eft, incompatible_with_windows_tls, incompatible_with_macos]
 */

(function() {
"use strict";
load("jstests/libs/uuid_util.js");        // For extractUUIDFromObject().
load("jstests/libs/fail_point_util.js");  // For configureFailPoint().
load("jstests/replsets/libs/tenant_migration_test.js");
load("jstests/replsets/libs/tenant_migration_util.js");

// Limit the batch size to test the stat in between batches.
const tenantMigrationTest = new TenantMigrationTest(
    {name: jsTestName(), sharedOptions: {setParameter: {collectionClonerBatchSize: 10}}});

if (!tenantMigrationTest.isFeatureFlagEnabled()) {
    jsTestLog("Skipping test because the tenant migrations feature flag is disabled");
    return;
}

const kMigrationId = UUID();
const kTenantId = 'testTenantId';
const kReadPreference = {
    mode: "primary"
};
const migrationOpts = {
    migrationIdString: extractUUIDFromObject(kMigrationId),
    tenantId: kTenantId,
    readPreference: kReadPreference
};

const dbName = tenantMigrationTest.tenantDB(kTenantId, "testDB");
const collName = "testColl";

const dbName1 = dbName + '_db_1';
const dbName2 = dbName + '_db_2';
const collName1 = collName + "_coll_1";
const collName2 = collName + "_coll_2";
const collNameDb2 = collName + "_only_coll";

const dataForEachCollection = [...Array(100).keys()].map((i) => ({a: i, b: 'A very long string.'}));
tenantMigrationTest.insertDonorDB(dbName1, collName1, dataForEachCollection);
tenantMigrationTest.insertDonorDB(dbName1, collName2, dataForEachCollection);
tenantMigrationTest.insertDonorDB(dbName2, collNameDb2, dataForEachCollection);

// Assert that the number of databases and collections cloned before failover is 0, as no failovers
// occur during this test.
function assertNothingClonedBeforeFailover(currOpResult) {
    const currOp = currOpResult.inprog[0];
    const dbInfo = currOp.databases;
    assert.eq(dbInfo.databasesClonedBeforeFailover, 0, currOpResult);
    assert.eq(dbInfo[dbName1].clonedCollectionsBeforeFailover, 0, currOpResult);
    assert.eq(dbInfo[dbName2].clonedCollectionsBeforeFailover, 0, currOpResult);
}

jsTestLog("Set up fail points on recipient.");
const recipientPrimary = tenantMigrationTest.getRecipientPrimary();
const fpAfterPersistingStateDoc =
    configureFailPoint(recipientPrimary,
                       "fpAfterPersistingTenantMigrationRecipientInstanceStateDoc",
                       {action: "hang"});
const fpAfterCreateFirstCollection = configureFailPoint(
    recipientPrimary, "tenantCollectionClonerHangAfterCreateCollection", {action: "hang"});

const donorPrimary = tenantMigrationTest.getDonorPrimary();
const donorDB1 = donorPrimary.getDB(dbName1);

const db1Size = assert.commandWorked(donorDB1.runCommand({dbStats: 1})).dataSize;
const db2Size = assert.commandWorked(donorPrimary.getDB(dbName2).runCommand({dbStats: 1})).dataSize;

const db1Collection1Size = assert.commandWorked(donorDB1.runCommand({collStats: collName1})).size;
const db1Collection2Size = assert.commandWorked(donorDB1.runCommand({collStats: collName2})).size;

const donorStats = {
    db1Size,
    db2Size,
    db1Collection1Size,
    db1Collection2Size
};

jsTestLog("Collected the following stats on the donor: " + tojson(donorStats));

jsTestLog("Starting tenant migration with migrationId: " + kMigrationId +
          ", tenantId: " + kTenantId);
assert.commandWorked(tenantMigrationTest.startMigration(migrationOpts));

// In this case, we do not expect the stats to exist yet, as the cloner has not been started.
jsTestLog("Waiting until the state doc has been persisted.");
fpAfterPersistingStateDoc.wait();
let res = recipientPrimary.adminCommand({currentOp: true, desc: "tenant recipient migration"});
let currOp = res.inprog[0];
assert(!currOp.hasOwnProperty("approxTotalDataSize"), res);
assert(!currOp.hasOwnProperty("approxTotalBytesCopied"), res);
assert(!currOp.hasOwnProperty("totalReceiveElapsedMillis"), res);
assert(!currOp.hasOwnProperty("remainingReceiveEstimatedMillis"), res);
assert(!currOp.hasOwnProperty("databases"), res);
fpAfterPersistingStateDoc.off();

// At this point, the total data size stat will have been obtained. However, nothing has been
// copied yet.
jsTestLog("Wait until the cloner has created the first collection");
fpAfterCreateFirstCollection.wait();
res = recipientPrimary.adminCommand({currentOp: true, desc: "tenant recipient migration"});
currOp = res.inprog[0];
assert.eq(currOp.approxTotalDataSize, db1Size + db2Size, res);
assert.eq(currOp.approxTotalBytesCopied, 0, res);
assert.gt(currOp.totalReceiveElapsedMillis, 0, res);
assert.gt(currOp.remainingReceiveEstimatedMillis, 0, res);
assertNothingClonedBeforeFailover(res);

// Before proceeding, set the failpoint to pause after cloning a single batch.
jsTestLog("Setting failpoint to pause after cloning single batch.");
const fpAfterFirstBatch = configureFailPoint(
    recipientPrimary, "tenantMigrationHangCollectionClonerAfterHandlingBatchResponse");
fpAfterCreateFirstCollection.off();

// After copying one batch, the amount of data copied should be non-zero, but less than the size
// of the collection.
jsTestLog("Waiting for a single batch of documents to have been cloned.");
fpAfterFirstBatch.wait();

// Since documents are inserted on a separate thread, wait until the expected stats are seen. The
// failpoint needs to be maintained so that the next batch isn't processed.
assert.soon(() => {
    res = recipientPrimary.adminCommand({currentOp: true, desc: "tenant recipient migration"});
    currOp = res.inprog[0];

    // Wait until one batch of documents has been copied.
    return currOp.approxTotalBytesCopied > 0;
}, res);

assert.eq(currOp.approxTotalDataSize, db1Size + db2Size, res);
assert.gt(currOp.approxTotalBytesCopied, 0, res);
assert.lt(currOp.approxTotalBytesCopied, db1Collection1Size, res);
assert.gt(currOp.totalReceiveElapsedMillis, 0, res);
assertNothingClonedBeforeFailover(res);
// At this point, most of the data is un-cloned.
assert.gt(currOp.remainingReceiveEstimatedMillis, currOp.totalReceiveElapsedMillis, res);

// Before proceeding, set fail point to pause at the next create collection boundary.
const fpAfterCreateSecondCollection = configureFailPoint(
    recipientPrimary, "tenantCollectionClonerHangAfterCreateCollection", {action: "hang"});
fpAfterFirstBatch.off();

// One collection should have been cloned completely. The stats should reflect this.
jsTestLog("Waiting for the second collection to be created.");
fpAfterCreateSecondCollection.wait();
res = recipientPrimary.adminCommand({currentOp: true, desc: "tenant recipient migration"});
currOp = res.inprog[0];
assert.eq(currOp.approxTotalDataSize, db1Size + db2Size, res);
assert.eq(currOp.approxTotalBytesCopied, db1Collection1Size, res);
assert.gt(currOp.totalReceiveElapsedMillis, 0, res);
assert.gt(currOp.remainingReceiveEstimatedMillis, currOp.totalReceiveElapsedMillis, res);
assertNothingClonedBeforeFailover(res);
let prevTotalElapsedMillis = currOp.totalReceiveElapsedMillis;
const prevRemainingMillis = currOp.remainingReceiveEstimatedMillis;

// Before proceeding, set fail point to pause before copying the second database.
const fpBeforeCopyingSecondDB =
    configureFailPoint(recipientPrimary, "tenantDatabaseClonerHangAfterGettingOperationTime");
fpAfterCreateSecondCollection.off();

jsTestLog("Wait until the second database is about to be cloned.");
fpBeforeCopyingSecondDB.wait();
res = recipientPrimary.adminCommand({currentOp: true, desc: "tenant recipient migration"});
currOp = res.inprog[0];
assert.eq(currOp.approxTotalDataSize, db1Size + db2Size, res);
assert.eq(currOp.approxTotalBytesCopied, db1Size, res);
assert.gt(currOp.totalReceiveElapsedMillis, prevTotalElapsedMillis, res);
assertNothingClonedBeforeFailover(res);
// We have copied most of the data.
assert.lt(currOp.remainingReceiveEstimatedMillis, currOp.totalReceiveElapsedMillis, res);
assert.lt(currOp.remainingReceiveEstimatedMillis, prevRemainingMillis);
prevTotalElapsedMillis = currOp.totalReceiveElapsedMillis;
fpBeforeCopyingSecondDB.off();

// After the migration completes, the total bytes copied should be equal to the total data size.
jsTestLog("Waiting for migration to complete.");
assert.commandWorked(tenantMigrationTest.waitForMigrationToComplete(migrationOpts));
res = recipientPrimary.adminCommand({currentOp: true, desc: "tenant recipient migration"});
currOp = res.inprog[0];
assert.eq(currOp.approxTotalDataSize, db1Size + db2Size, res);
assert.eq(currOp.approxTotalBytesCopied, db1Size + db2Size, res);
assert.gt(currOp.totalReceiveElapsedMillis, prevTotalElapsedMillis, res);
assertNothingClonedBeforeFailover(res);
// We have finished cloning, therefore time remaining is zero.
assert.eq(currOp.remainingReceiveEstimatedMillis, 0, res);

tenantMigrationTest.stop();
})();