summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/change_stream_preimages_standalone_mode.js
blob: bc2b746c8a54483cf7777ce06c8268cbfb640f6a (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
/**
 * Test that nodes are able to startup with 'recordPreImages' and 'changeStreamPreAndPostImages'
 * options set in collection metadata and no pre-images are recorded while being in standalone mode.
 *
 * @tags: [
 *   # Servers are restarted in this test and the data must be retained.
 *   requires_persistence,
 *   # This test uses a replica set and must avoid replica set incompatible test suites, like the
 *   # test suite that turns journaling off.
 *   requires_replication,
 *   requires_fcv_60,
 *   featureFlagChangeStreamPreAndPostImages,
 * ]
 */

(function() {
'use strict';

load("jstests/libs/collection_drop_recreate.js");  // For assertDropAndRecreateCollection.
load(
    "jstests/libs/change_stream_util.js");  // For
                                            // assertChangeStreamPreAndPostImagesCollectionOptionIsEnabled,
                                            // preImagesForOps.

// Fetches the collection with name 'collName' from database 'nodeDB'. Expects the collection to
// exist.
const findCollectionInfo = function(nodeDB, collName) {
    const collInfos = nodeDB.getCollectionInfos();
    assert.gt(collInfos.length, 0, "The database is empty");

    const collInfo = collInfos.filter(collInfo => collInfo.name == collName);
    assert.eq(collInfo.length, 1);
    return collInfo[0];
};

// Returns the oplog entries written while performing the write operations.
function oplogEntriesForOps(db, writeOps) {
    const oplogColl = db.getSiblingDB('local').oplog.rs;
    const numOplogEntriesBefore = oplogColl.find().itcount();

    // Perform the write operations.
    writeOps();

    // Check the number of oplog entries written.
    const numOplogEntriesAfter = oplogColl.find().itcount();
    const numberOfNewOplogEntries = numOplogEntriesAfter - numOplogEntriesBefore;
    if (numberOfNewOplogEntries == 0) {
        return [];
    }
    return oplogColl.find().sort({ts: -1}).limit(numberOfNewOplogEntries).toArray();
}

/**
 * Tests the pre-image recording behavior when the server transitions to and from the stand-alone
 * mode for a collection with specified 'collectionOptions'.
 *
 * @param {function} assertPreImagesRecordingEnabledFunc - asserts that pre-images
 *     recording collection option is enabled for the specified collection.
 * @param {function} assertPreImagesRecordedFunc - asserts that pre-images are recorded while
 *     executing the 'writeOps' (update/replace/delete operations) on the collection.
 * @param {function} assertNoPreImagesRecordedFunc - asserts that no pre-images are recorded while
 *     executing the 'writeOps' (update/replace/delete operations) on the collection.
 */
function testStandaloneMode({
    collectionOptions = {},
    assertPreImagesRecordingEnabledFunc = (db, collName) => {},
    assertPreImagesRecordedFunc = (db, writeOps) => {},
    assertNoPreImagesRecordedFunc = (db, writeOps) => {}
}) {
    const rst = new ReplSetTest({nodes: 1});
    rst.startSet();
    rst.initiate();

    const collName = "coll";
    const dbName = jsTestName();
    let primary = rst.getPrimary();
    let testDB = primary.getDB(dbName);

    // Create a collection with pre-images recording collection option enabled.
    let testColl = assertDropAndRecreateCollection(testDB, collName, collectionOptions);
    assertPreImagesRecordingEnabledFunc(testDB, collName);

    // Verify that pre-images are recorded for the specified operations.
    const writeOpsForReplSetMode = () => {
        assert.commandWorked(testColl.insert({a: 1, b: 1}));
        assert.commandWorked(testColl.update({a: 1}, {a: 2, b: 2}));
        assert.commandWorked(
            testColl.update({a: 2}, {a: 3, b: 3}, {writeConcern: {w: 1, j: true}}));

        // Ensure that the last write with j:true write concern has reached the disk, and now fsync
        // will checkpoint that data.
        assert.commandWorked(testDB.adminCommand({fsync: 1}));
    };
    assertPreImagesRecordedFunc(testDB, writeOpsForReplSetMode);

    // Restart the replica set member as a standalone node.
    const replicaSetNodeId = rst.getNodeId(primary);
    const replicaSetNodeDBPath = primary.dbpath;
    rst.stop(replicaSetNodeId);

    const standaloneConn = MongoRunner.runMongod({
        dbpath: replicaSetNodeDBPath,
        noCleanData: true,
    });
    const standaloneDB = standaloneConn.getDB(dbName);
    const standaloneColl = standaloneDB.getCollection(collName);

    // The collection must have pre-images recording option enabled even when running in standalone
    // mode.
    assertPreImagesRecordingEnabledFunc(standaloneDB, collName);

    // Verify that no pre-images are recorded while running in standalone mode.
    const writeOpsForStandaloneMode = () => {
        assert.commandWorked(standaloneColl.insert({c: 1, d: 1}));
        assert.commandWorked(standaloneColl.update({c: 1}, {c: 2, d: 2}));
        assert.commandWorked(standaloneColl.update({c: 2}, {c: 3, d: 3}));
        assert.commandWorked(standaloneColl.insert({c: 1, d: 1}));
        assert.commandWorked(standaloneColl.remove({c: 1, d: 1}));
    };
    assertNoPreImagesRecordedFunc(standaloneDB, writeOpsForStandaloneMode);

    // Shut down standalone server.
    MongoRunner.stopMongod(standaloneConn);

    // Restart the node as a replica set member.
    rst.start(replicaSetNodeId, {}, true /*restart*/);
    primary = rst.getPrimary();
    testDB = primary.getDB(dbName);
    testColl = testDB.getCollection(collName);

    // Check that everything is still working properly after being in standalone mode.
    assertPreImagesRecordingEnabledFunc(testDB, collName);
    const writeOpsForReplSetModeAfterStandalone = () => {
        assert.commandWorked(testColl.update({a: 3}, {a: 4, b: 4}));
        assert.commandWorked(testColl.update({a: 4}, {a: 5, b: 5}));
    };
    assertPreImagesRecordedFunc(testDB, writeOpsForReplSetModeAfterStandalone);

    rst.stopSet();
}

// Run the test for 'recordPreImages' option.
testStandaloneMode({
    collectionOptions: {recordPreImages: true},
    assertPreImagesRecordingEnabledFunc: (db, collName) => {
        assert.eq(findCollectionInfo(db, collName).options.recordPreImages, true);
    },
    assertPreImagesRecordedFunc: (db, writerOps) => {
        const writtenOplogEntries = oplogEntriesForOps(db, writerOps);
        assert.gt(writtenOplogEntries.length, 0, writtenOplogEntries);
    },
    assertNoPreImagesRecordedFunc: (db, writerOps) => {
        const writtenOplogEntries = oplogEntriesForOps(db, writerOps);
        assert.eq(writtenOplogEntries.length, 0, writtenOplogEntries);
    }
});

// Run the test for 'changeStreamPreAndPostImages' option.
testStandaloneMode({
    collectionOptions: {changeStreamPreAndPostImages: {enabled: true}},
    assertPreImagesRecordingEnabledFunc:
        assertChangeStreamPreAndPostImagesCollectionOptionIsEnabled,
    assertPreImagesRecordedFunc: (db, writerOps) => {
        const writtenPreImages = preImagesForOps(db, writerOps);
        assert.gt(writtenPreImages.length, 0, writtenPreImages);
    },
    assertNoPreImagesRecordedFunc: (db, writerOps) => {
        const writtenPreImages = preImagesForOps(db, writerOps);
        assert.eq(writtenPreImages.length, 0, writtenPreImages);
    }
});
}());