summaryrefslogtreecommitdiff
path: root/jstests/multiVersion/change_streams_pre_and_post_images_upgrade_downgrade.js
blob: da6d6e1f957c089d4be85ce2184a2369daf6a20f (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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/**
 * Verifies that it is possible to upgrade a replica set with collections with 'recordPreImages'
 * option to use 'changeStreamPreAndPostImages' option, and to do a corresponding downgrade.
 * @tags: [
 * requires_fcv_52,
 * featureFlagChangeStreamPreAndPostImages,
 * ]
 */
(function() {
'use strict';

load("jstests/libs/collection_drop_recreate.js");  // For assertCreateCollection.
load("jstests/libs/collection_options.js");        // For assertCollectionOptionIsEnabled,
                                                   // assertCollectionOptionIsAbsent.
load("jstests/multiVersion/libs/multi_rs.js");     // For upgradeSet.
load(
    "jstests/libs/change_stream_util.js");  // For
                                            // assertChangeStreamPreAndPostImagesCollectionOptionIsEnabled,
                                            // assertChangeStreamPreAndPostImagesCollectionOptionIsAbsent.
                                            // assertPreImagesCollectionIsAbsent,
                                            // assertPreImagesCollectionExists.
load("jstests/libs/fail_point_util.js");  // For configureFailPoint.

const collName = "test";
const latestBinVersion = "latest";

// Checks that the pre-image of the next change event in the change stream equals to the
// 'expectedPreImage'.
function assertNextPreImage(changeStream, expectedPreImage) {
    assert.soon(() => changeStream.hasNext());
    assert.eq(changeStream.next().fullDocumentBeforeChange, expectedPreImage);
}

// Tests "changeStreamPreAndPostImages" option for the "create" and "collMod" commands in downgraded
// and upgraded FCV states. Tests an FCV downgrade succeeds when no collection with
// changeStreamPreImages: {enabled: true} exists.
function testCreateAndCollModCommandsInUpgradedDowngradedFCVStates(downgradeFCV) {
    const rst = new ReplSetTest({
        nodes: 2,
        nodeOptions: {binVersion: downgradeFCV},
    });
    rst.startSet();
    rst.initiate();

    // Create the collection with recorded pre-images enabled.
    let testDB = rst.getPrimary().getDB(jsTestName());
    assertCreateCollection(testDB, collName, {"recordPreImages": true});

    // Upgrade the replica set.
    rst.upgradeSet({binVersion: latestBinVersion});
    testDB = rst.getPrimary().getDB(jsTestName());

    // Verify that an attempt to set 'changeStreamPreAndPostImages' option fails for the downgraded
    // FCV version.
    assert.commandFailedWithCode(
        testDB.createCollection("anotherTestCollection",
                                {"changeStreamPreAndPostImages": {enabled: false}}),
        5846900);
    assert.commandFailedWithCode(
        testDB.runCommand({"collMod": collName, "changeStreamPreAndPostImages": {enabled: false}}),
        5846901);

    // Set the FCV to the latest.
    assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: latestFCV}));

    // 'changeStreamPreAndPostImages' option must be absent and 'recordPreImages' option must be set
    // to true.
    assertCollectionOptionIsEnabled(testDB, collName, "recordPreImages");
    assertChangeStreamPreAndPostImagesCollectionOptionIsAbsent(testDB, collName);

    // Enable pre-/post-images for the collection with "recordPreImages" enabled.
    assert.commandWorked(
        testDB.runCommand({"collMod": collName, "changeStreamPreAndPostImages": {enabled: true}}));

    // 'changeStreamPreAndPostImages' option must be enabled and 'recordPreImages' should be absent.
    assertCollectionOptionIsAbsent(testDB, collName, "recordPreImages");
    assertChangeStreamPreAndPostImagesCollectionOptionIsEnabled(testDB, collName);

    // Set 'recordPreImages: true' to disable 'changeStreamPreAndPostImages' option.
    assert.commandWorked(testDB.runCommand({"collMod": collName, "recordPreImages": true}));

    // 'changeStreamPreAndPostImages' option must be absent and 'recordPreImages' should be set to
    // true.
    assertCollectionOptionIsEnabled(testDB, collName, "recordPreImages");
    assertChangeStreamPreAndPostImagesCollectionOptionIsAbsent(testDB, collName);

    // Downgrade the FCV.
    assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: downgradeFCV}));

    // Downgrade the replica set.
    rst.upgradeSet({binVersion: downgradeFCV});
    rst.stopSet();
}

// Tests that when change stream pre-images are recorded on a collection using option
// recordPreImages: true and, after FCV upgrade, recordPreImages: true option is replaced with
// changeStreamPreAndPostImages: {enabled: true} , then pre-images are available for all change
// events in a change stream without interruption. Subsequently, tests a FCV downgrade and switching
// back from option changeStreamPreAndPostImages: {enabled: true} to recordPreImages: true.
function testUpgradeDowngradeFromRecordPreImageOptionToChangeStreamPreAndPostImages(downgradeFCV) {
    // Upgrade scenario.
    // Upgrade server binary.
    const rst = new ReplSetTest({
        nodes: 2,
        nodeOptions: {binVersion: downgradeFCV},
    });
    rst.startSet();
    rst.initiate();
    rst.upgradeSet({binVersion: latestBinVersion});

    // Create the collection with recorded pre-images enabled and insert one document.
    const testDB = rst.getPrimary().getDB(jsTestName());
    const coll = assertCreateCollection(testDB, collName, {recordPreImages: true});
    assert.commandWorked(coll.insert({_id: 1, eventId: 1}));

    // Open a change stream with fullDocumentBeforeChange: "required".
    const changeStream = coll.watch([], {fullDocumentBeforeChange: "required"});

    // Perform an "update" command. Pre-image will be recorded in the oplog.
    assert.commandWorked(coll.update({_id: 1}, {$inc: {eventId: 1}}));

    // Upgrade to the latest FCV.
    assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: latestFCV}));

    // Verify that the pre-images collection is created.
    assertPreImagesCollectionExists(testDB);

    // Enable change stream pre-images recording for the collection.
    assert.commandWorked(
        testDB.runCommand({collMod: collName, changeStreamPreAndPostImages: {enabled: true}}));

    // Issue an "update" command for which the pre-image won't be available after the FCV downgrade.
    assert.commandWorked(coll.update({_id: 1}, {$inc: {eventId: 1}}));

    // Verify that change stream receives change event with pre-image being set.
    assertNextPreImage(changeStream, {_id: 1, eventId: 1});

    // Issue an "update" command for which the pre-image won't be available after the FCV downgrade.
    assert.commandWorked(coll.update({_id: 1}, {$inc: {eventId: 1}}));

    // Downgrade scenario.
    // Revert to the previous pre-image recording capability available in 5.0.
    assert.commandWorked(testDB.runCommand({collMod: collName, recordPreImages: true}));

    // Verify that the change stream returns a change event with a pre-image set.
    assertNextPreImage(changeStream, {_id: 1, eventId: 2});

    // Downgrade the FCV version. Pre-images collection is dropped during the downgrade.
    assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: downgradeFCV}));

    // Verify that pre-images collection is dropped.
    assertPreImagesCollectionIsAbsent(testDB);

    // Verify that reading the next change event fails for change stream with
    // fullDocumentBeforeChange: "required", as pre-image for this event was recorded in the
    // pre-images collection.
    assert.throwsWithCode(() => changeStream.hasNext(), ErrorCodes.NoMatchingDocument);

    rst.stopSet();
}

// Tests that when change stream pre-images are recorded on a collection using option
// changeStreamPreAndPostImages: {enabled: true} and, after changeStreamPreAndPostImages option is
// disabled, then pre-images are unavailable to change stream change events after FCV downgrade.
function testDowngrade(downgradeFCV) {
    const rst = new ReplSetTest({
        nodes: 2,
        nodeOptions: {binVersion: latestBinVersion},
    });
    rst.startSet();
    rst.initiate();

    // Create the collection with changeStreamPreAndPostImages: {enabled: true} and perform insert
    // and update operations.
    const testDB = rst.getPrimary().getDB(jsTestName());
    const coll =
        assertCreateCollection(testDB, collName, {changeStreamPreAndPostImages: {enabled: true}});
    const changeStream = coll.watch([], {fullDocumentBeforeChange: "required"});
    assert.commandWorked(coll.insert({_id: 1, eventId: 1}));
    assert.commandWorked(coll.update({_id: 1}, {$inc: {eventId: 1}}));

    // Downgrade scenario.
    // Issue "collMod" command in order to disable changeStreamPreAndPostImages option.
    assert.commandWorked(
        testDB.runCommand({"collMod": collName, changeStreamPreAndPostImages: {enabled: false}}));

    // Downgrade the FCV version.
    assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: downgradeFCV}));

    // Verify that the pre-images collection is dropped.
    assertPreImagesCollectionIsAbsent(testDB);

    // Verify that reading the next change event fails for change stream with
    // fullDocumentBeforeChange: "required", as pre-image for this event was recorded in the
    // pre-images collection that no longer exists.
    assert.throwsWithCode(() => changeStream.hasNext(), ErrorCodes.NoMatchingDocument);

    rst.stopSet();
}

// Tests that downgrading of the FCV fails if there exists a collection with
// changeStreamPreAndPostImages: {enabled: true}.
function testFCVDowngradeFailureWhenChangeStreamPreAndPostImagesEnabledForCollection(downgradeFCV) {
    const rst = new ReplSetTest({
        nodes: 2,
        nodeOptions: {binVersion: latestBinVersion},
    });
    rst.startSet();
    rst.initiate();
    const testDB = rst.getPrimary().getDB(jsTestName());

    // Pre-images collection must exist upon start-up with the latest FCV.
    assertPreImagesCollectionExists(testDB);
    assert.commandWorked(
        testDB.createCollection("testCollection", {changeStreamPreAndPostImages: {enabled: true}}));

    // Verify that a downgrade of the FCV fails when there is at least one collection with
    // {changeStreamPreAndPostImages: {enabled: true}} option set.
    assert.commandFailedWithCode(
        testDB.adminCommand({setFeatureCompatibilityVersion: downgradeFCV}),
        ErrorCodes.CannotDowngrade);

    // Verify that the pre-images collection is not dropped in case of a failed FCV downgrade.
    assertPreImagesCollectionExists(testDB);

    rst.stopSet();
}

// Tests that FCV upgrade fails if there is an error creating pre-images collection.
function testFCVUpgradeFailureWhenCreationOfPreImagesCollectionFails(downgradeFCV) {
    const rst = new ReplSetTest({
        nodes: 2,
        nodeOptions: {binVersion: binVersionFromFCV(downgradeFCV)},
    });
    rst.startSet();
    rst.initiate();
    rst.upgradeSet({binVersion: latestBinVersion});
    const testDB = rst.getPrimary().getDB(jsTestName());
    configureFailPoint(rst.getPrimary(), "failPreimagesCollectionCreation", {}, {times: 1});

    // Verify that FCV upgrade fails when creation of the pre-images collection fails.
    assert.commandFailedWithCode(testDB.adminCommand({setFeatureCompatibilityVersion: latestFCV}),
                                 5868501);

    // Verfiy that FCV version remains unchanged.
    const fcvDoc = testDB.adminCommand({getParameter: 1, featureCompatibilityVersion: 1});
    assert.eq(fcvDoc.featureCompatibilityVersion.version, downgradeFCV, fcvDoc);

    // Verify that the pre-images collection is not created.
    assertPreImagesCollectionIsAbsent(testDB);

    rst.stopSet();
}

runFeatureFlagMultiversionTest('featureFlagChangeStreamPreAndPostImages',
                               testCreateAndCollModCommandsInUpgradedDowngradedFCVStates);
runFeatureFlagMultiversionTest(
    'featureFlagChangeStreamPreAndPostImages',
    testUpgradeDowngradeFromRecordPreImageOptionToChangeStreamPreAndPostImages);
runFeatureFlagMultiversionTest('featureFlagChangeStreamPreAndPostImages', testDowngrade);
runFeatureFlagMultiversionTest(
    'featureFlagChangeStreamPreAndPostImages',
    testFCVDowngradeFailureWhenChangeStreamPreAndPostImagesEnabledForCollection);
runFeatureFlagMultiversionTest('featureFlagChangeStreamPreAndPostImages',
                               testFCVUpgradeFailureWhenCreationOfPreImagesCollectionFails);
})();