summaryrefslogtreecommitdiff
path: root/jstests/libs/read_write_concern_defaults_propagation_common.js
blob: 22167cd48f3d6c03555ce8b8dcbd3e840bdbdd8a (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
var ReadWriteConcernDefaultsPropagation = (function() {
    "use strict";

    const kDefaultReadConcernField = "defaultReadConcern";
    const kDefaultWriteConcernField = "defaultWriteConcern";
    const kUpdateOpTimeField = "updateOpTime";
    const kUpdateWallClockTimeField = "updateWallClockTime";
    const kLocalUpdateWallClockTimeField = "localUpdateWallClockTime";

    const kDefaultRWCFields = [kDefaultReadConcernField, kDefaultWriteConcernField];
    const kExtraSetFields = [kUpdateOpTimeField, kUpdateWallClockTimeField];
    const kExtraLocalFields = [kLocalUpdateWallClockTimeField];
    const kExtraFields = [...kExtraSetFields, ...kExtraLocalFields];
    const kSetFields = [...kDefaultRWCFields, ...kExtraSetFields];

    const timeoutSecs = 2 * 60;
    const intervalSecs = 5;

    // Check that setting the defaults on setConn propagates correctly across checkConns.
    function setDefaultsAndVerifyPropagation(setConn, checkConns, inMemory) {
        // Get the current defaults from setConn.
        var initialSetConnDefaults =
            assert.commandWorked(setConn.adminCommand({getDefaultRWConcern: 1}));

        // Ensure that all checkConns agree with this. Use a loop in case the initial defaults were
        // recently set and have not yet propagated to all nodes.
        var checkConnsDefaults = [];
        var initialCheckConnsDefaults = [];
        assert.soon(
            () => {
                initialCheckConnsDefaults = checkConns.map(
                    conn => assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})));
                return initialCheckConnsDefaults.every(
                    checkConnDefaults =>
                        kSetFields.every(field => friendlyEqual(checkConnDefaults[field],
                                                                initialSetConnDefaults[field])));
            },
            () => "expected initial defaults to be present on all nodes within" + timeoutSecs +
                " secs.  Expected defaults: " + tojson(initialSetConnDefaults) + ", checkConns: " +
                tojson(checkConns) + ", current state: " + tojson(initialCheckConnsDefaults),
            timeoutSecs * 1000,
            intervalSecs * 1000,
            {runHangAnalyzer: false});

        // Set new defaults on setConn.
        var newDefaults = {
            defaultReadConcern: {level: "majority"},
            defaultWriteConcern: {w: 2, wtimeout: 0}
        };
        // If these happen to match what's already there, adjust them.
        if (initialSetConnDefaults.defaultReadConcern &&
            friendlyEqual(initialSetConnDefaults.defaultReadConcern,
                          newDefaults.defaultReadConcern)) {
            newDefaults.defaultReadConcern.level = "local";
        }
        if (initialSetConnDefaults.defaultWriteConcern &&
            friendlyEqual(initialSetConnDefaults.defaultWriteConcern,
                          newDefaults.defaultWriteConcern)) {
            newDefaults.defaultWriteConcern.w++;
        }
        var cmd = {setDefaultRWConcern: 1};
        Object.extend(cmd, newDefaults);
        cmd.writeConcern = {
            w: 1
        };  // Prevent any existing default WC (which may be unsatisfiable) from being applied.
        var newDefaultsRes = assert.commandWorked(setConn.adminCommand(cmd));
        kDefaultRWCFields.forEach(field => assert.eq(newDefaultsRes[field],
                                                     newDefaults[field],
                                                     field + " was not set correctly"));
        assert.hasFields(
            newDefaultsRes, kExtraFields, "missing field in result of setDefaultRWConcern");

        // Check that updateOpTime has increased.  Since everything is running on one host, we can
        // also check that each of kUpdateOpTime and localUpdateWallClockTime have increased.
        kExtraFields.forEach(field => {
            if (field in initialSetConnDefaults) {
                assert.gt(newDefaultsRes[field],
                          initialSetConnDefaults[field],
                          field + " did not increase after setting new defaults");
            }
        });

        // Ensure that all checkConns agree with this.
        assert.soon(
            function() {
                // Get the defaults from all the connections.
                checkConnsDefaults = checkConns.map(conn => assert.commandWorked(conn.adminCommand(
                                                        {getDefaultRWConcern: 1, inMemory})));

                // Check if they all match the recently-set values.
                for (var connDefault of checkConnsDefaults) {
                    if (inMemory) {
                        assert.eq(true, connDefault.inMemory, tojson(connDefault));
                    } else {
                        assert.eq(undefined, connDefault.inMemory, tojson(connDefault));
                    }

                    for (var field of kSetFields) {
                        if (!friendlyEqual(connDefault[field], newDefaultsRes[field])) {
                            return false;
                        }
                    }

                    // localUpdateWallClockTime reflects which the conn updated its cache.  Since
                    // all the conns (including setConn) are running on a single host, we can check
                    // that this is later than when setDefaultRWConcern was run.
                    if (!(connDefault[kLocalUpdateWallClockTimeField] >=
                          connDefault[kUpdateWallClockTimeField])) {
                        return false;
                    }
                }
                return true;
            },
            () => "updated defaults failed to propagate to all nodes within " + timeoutSecs +
                " secs.  Expected defaults: " + tojson(newDefaults) +
                ", checkConns: " + tojson(checkConns) +
                ", current state: " + tojson(checkConnsDefaults) + ", inMemory: " + inMemory,
            timeoutSecs * 1000,
            intervalSecs * 1000,
            {runHangAnalyzer: false});
    }

    /**
     * Tests propagation of RWC defaults across an environment.  Updated RWC defaults are set using
     * setConn, and then each connection in the checkConns array is checked to ensure that it
     * becomes aware of the new defaults (within the acceptable window of 2 minutes).
     */
    var runTests = function(setConn, checkConns, inMemory) {
        // Since these connections are on a brand new replset/cluster, this checks the propagation
        // of the initial setting of defaults.
        setDefaultsAndVerifyPropagation(setConn, checkConns, inMemory);

        // Do it again to check that updating the defaults also propagates correctly.
        setDefaultsAndVerifyPropagation(setConn, checkConns, inMemory);
    };

    /**
     * Asserts eventually all given nodes have no default RWC in their in-memory cache.
     */
    function verifyPropgationOfNoDefaults(checkConns) {
        assert.soon(() => checkConns.every(checkConn => {
            const defaultsRes = assert.commandWorked(
                checkConn.adminCommand({getDefaultRWConcern: 1, inMemory: true}));

            // Note localUpdateWallClockTime is generated by the in-memory cache, so it will be
            // present even if there are no defaults
            const unexpectedFields = kDefaultRWCFields.concat(kExtraSetFields);
            return unexpectedFields.every(field => !defaultsRes.hasOwnProperty(field));
        }),
                    "deleted/dropped defaults failed to propagate to all nodes within " +
                        timeoutSecs + " secs. checkConns: " + tojson(checkConns),
                    timeoutSecs * 1000,
                    intervalSecs * 1000,
                    {runHangAnalyzer: false});
    }

    /**
     * Tests that when the RWC defaults document is removed, either through a delete or drop of
     * config.settings, the RWC defaults cache is invalidated.
     */
    var runDropAndDeleteTests = function(mainConn, checkConns) {
        // Set the defaults to some value other than the implicit server defaults. Then remove the
        // defaults document and verify the cache is invalidated on all nodes.
        setDefaultsAndVerifyPropagation(mainConn, checkConns);
        assert.commandWorked(
            mainConn.getDB("config").settings.remove({_id: "ReadWriteConcernDefaults"}));
        verifyPropgationOfNoDefaults(checkConns);
    };

    return {runTests, runDropAndDeleteTests};
})();