summaryrefslogtreecommitdiff
path: root/jstests/replsets/tenant_migration_timeseries_retryable_write_retry_on_recipient.js
blob: 410269bd2684d5bf69eaf83bf2b448212fa4dc0f (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
/**
 * Tests that retryable writes made on the donor during a tenant migration can be properly retried
 * on the recipient for time-series collections.
 *
 * This test is based on "tenant_migration_retryable_write_retry_on_recipient.js".
 *
 * TODO (SERVER-68159) we should no longer need to use the incompatible_with_shard_merge. Remove it.
 *
 * @tags: [
 *   incompatible_with_macos,
 *   incompatible_with_windows_tls,
 *   incompatible_with_shard_merge,
 *   requires_majority_read_concern,
 *   requires_persistence,
 *   serverless,
 * ]
 */

(function() {
"use strict";

load("jstests/replsets/libs/tenant_migration_test.js");
load("jstests/replsets/libs/tenant_migration_util.js");
load("jstests/libs/fail_point_util.js");
load("jstests/libs/parallelTester.js");  // for 'Thread'
load("jstests/libs/uuid_util.js");

function testRetryOnRecipient(ordered) {
    const tenantMigrationTest = new TenantMigrationTest({name: jsTestName()});

    const donorPrimary = tenantMigrationTest.getDonorPrimary();

    const kTenantId = "testTenantId";
    const kDbName = tenantMigrationTest.tenantDB(kTenantId, "tsDb");
    const kCollNameBefore = "tsCollBefore";
    const kCollNameDuring = "tsCollDuring";

    const donorDb = donorPrimary.getDB(kDbName);
    assert.commandWorked(donorDb.createCollection(
        kCollNameBefore, {timeseries: {timeField: "time", metaField: "meta"}}));
    assert.commandWorked(donorDb.createCollection(
        kCollNameDuring, {timeseries: {timeField: "time", metaField: "meta"}}));
    const recipientPrimary = tenantMigrationTest.getRecipientPrimary();
    const recipientDb = recipientPrimary.getDB(kDbName);

    const pauseTenantMigrationBeforeLeavingDataSyncState =
        configureFailPoint(donorPrimary, "pauseTenantMigrationBeforeLeavingDataSyncState");

    const migrationId = UUID();
    const migrationOpts = {
        migrationIdString: extractUUIDFromObject(migrationId),
        recipientConnString: tenantMigrationTest.getRecipientConnString(),
        tenantId: kTenantId,
    };
    function setupRetryableWritesForCollection(collName) {
        const kNs = `${kDbName}.${collName}`;
        assert.commandWorked(donorPrimary.getCollection(kNs).insert(
            [
                {time: ISODate(), x: 0, meta: 0},
                {time: ISODate(), x: 1, meta: 0},
                {time: ISODate(), x: 2, meta: 0},
            ],
            {writeConcern: {w: "majority"}}));

        let result = {collName: collName};
        const lsid1 = {id: UUID()};
        const insertTag = "retryable insert " + collName;
        const updateTag = "retryable update " + collName;
        result.insertTag = insertTag;
        result.updateTag = updateTag;
        result.retryableInsertCommand = {
            insert: collName,
            documents: [
                // Batched inserts resulting in "inserts".
                {x: 0, time: ISODate(), tag: insertTag, meta: 1},
                {x: 1, time: ISODate(), tag: insertTag, meta: 1},
                {x: 2, time: ISODate(), tag: insertTag, meta: 1},
                // Batched inserts resulting in "updates".
                {x: 3, time: ISODate(), tag: updateTag, meta: 0},
                {x: 4, time: ISODate(), tag: updateTag, meta: 0},
                {x: 5, time: ISODate(), tag: updateTag, meta: 0},
            ],
            txnNumber: NumberLong(0),
            lsid: lsid1,
            ordered: ordered,
        };
        return result;
    }

    const beforeWrites = setupRetryableWritesForCollection(kCollNameBefore);
    const duringWrites = setupRetryableWritesForCollection(kCollNameDuring);

    jsTestLog("Run retryable writes before the migration");
    assert.commandWorked(donorDb.runCommand(beforeWrites.retryableInsertCommand));

    const donorRstArgs = TenantMigrationUtil.createRstArgs(tenantMigrationTest.getDonorRst());
    const migrationThread =
        new Thread(TenantMigrationUtil.runMigrationAsync, migrationOpts, donorRstArgs);
    migrationThread.start();

    pauseTenantMigrationBeforeLeavingDataSyncState.wait();

    jsTestLog("Run retryable writes during the migration");
    assert.commandWorked(donorDb.runCommand(duringWrites.retryableInsertCommand));

    // Wait for the migration to complete.
    jsTest.log("Waiting for migration to complete");
    pauseTenantMigrationBeforeLeavingDataSyncState.off();
    TenantMigrationTest.assertCommitted(migrationThread.returnData());

    // Print the no-op oplog entries for debugging purposes.
    jsTestLog("Recipient oplog migration entries.");
    printjson(recipientPrimary.getDB("local")
                  .oplog.rs.find({op: 'n', fromTenantMigration: {$exists: true}})
                  .sort({'$natural': -1})
                  .toArray());

    function testRecipientRetryableWrites(db, writes) {
        const kCollName = writes.collName;
        jsTestLog("Testing retryable inserts");
        assert.commandWorked(db.runCommand(writes.retryableInsertCommand));
        // If retryable inserts don't work, we will see 6 here.
        assert.eq(3, db[kCollName].find({tag: writes.insertTag}).itcount());
        assert.eq(3, db[kCollName].find({tag: writes.updateTag}).itcount());
    }
    jsTestLog("Run retryable write on primary after the migration");
    testRecipientRetryableWrites(recipientDb, beforeWrites);
    testRecipientRetryableWrites(recipientDb, duringWrites);

    jsTestLog("Step up secondary");
    const recipientRst = tenantMigrationTest.getRecipientRst();
    recipientRst.stepUp(recipientRst.getSecondary());
    jsTestLog("Run retryable write on secondary after the migration");
    testRecipientRetryableWrites(recipientRst.getPrimary().getDB(kDbName), beforeWrites);
    testRecipientRetryableWrites(recipientRst.getPrimary().getDB(kDbName), duringWrites);

    tenantMigrationTest.forgetMigration(migrationOpts.migrationIdString);

    jsTestLog("Trying a back-to-back migration");
    const tenantMigrationTest2 = new TenantMigrationTest(
        {name: jsTestName() + "2", donorRst: tenantMigrationTest.getRecipientRst()});
    const recipient2Primary = tenantMigrationTest2.getRecipientPrimary();
    const recipient2Db = recipient2Primary.getDB(kDbName);
    const migrationOpts2 = {
        migrationIdString: extractUUIDFromObject(UUID()),
        tenantId: kTenantId,
    };

    TenantMigrationTest.assertCommitted(tenantMigrationTest2.runMigration(migrationOpts2));

    // Print the no-op oplog entries for debugging purposes.
    jsTestLog("Second recipient oplog migration entries.");
    printjson(recipient2Primary.getDB("local")
                  .oplog.rs.find({op: 'n', fromTenantMigration: {$exists: true}})
                  .sort({'$natural': -1})
                  .toArray());

    jsTestLog("Test retryable write on primary after the second migration");
    testRecipientRetryableWrites(recipient2Db, beforeWrites);
    testRecipientRetryableWrites(recipient2Db, duringWrites);

    tenantMigrationTest2.stop();
    tenantMigrationTest.stop();
}

testRetryOnRecipient(true);
testRetryOnRecipient(false);
})();