summaryrefslogtreecommitdiff
path: root/jstests/sharding/merge_from_stale_mongos.js
blob: d91d92dcb626e842f040f470a3c45c871fcfe442 (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
// Tests for $merge against a stale mongos with combinations of sharded/unsharded source and target
// collections.
(function() {
    "use strict";

    load("jstests/aggregation/extras/merge_helpers.js");  // For withEachMergeMode.
    load("jstests/aggregation/extras/utils.js");          // For assertErrorCode.

    const st = new ShardingTest({
        shards: 2,
        mongos: 4,
    });

    const freshMongos = st.s0.getDB(jsTestName());
    const staleMongosSource = st.s1.getDB(jsTestName());
    const staleMongosTarget = st.s2.getDB(jsTestName());
    const staleMongosBoth = st.s3.getDB(jsTestName());

    const sourceColl = freshMongos.getCollection("source");
    const targetColl = freshMongos.getCollection("target");

    // Enable sharding on the test DB and ensure its primary is shard 0.
    assert.commandWorked(
        staleMongosSource.adminCommand({enableSharding: staleMongosSource.getName()}));
    st.ensurePrimaryShard(staleMongosSource.getName(), st.rs0.getURL());

    // Shards the collection 'coll' through 'mongos'.
    function shardCollWithMongos(mongos, coll) {
        coll.drop();
        // Shard the given collection on _id, split the collection into 2 chunks: [MinKey, 0) and
        // [0, MaxKey), then move the [0, MaxKey) chunk to shard 1.
        assert.commandWorked(
            mongos.adminCommand({shardCollection: coll.getFullName(), key: {_id: 1}}));
        assert.commandWorked(mongos.adminCommand({split: coll.getFullName(), middle: {_id: 0}}));
        assert.commandWorked(mongos.adminCommand(
            {moveChunk: coll.getFullName(), find: {_id: 1}, to: st.rs1.getURL()}));
    }

    // Configures the two mongos, staleMongosSource and staleMongosTarget, to be stale on the source
    // and target collections, respectively. For instance, if 'shardedSource' is true then
    // staleMongosSource will believe that the source collection is unsharded.
    function setupStaleMongos({shardedSource, shardedTarget}) {
        // Initialize both mongos to believe the collections are unsharded.
        sourceColl.drop();
        targetColl.drop();
        assert.commandWorked(staleMongosSource[sourceColl.getName()].insert(
            {_id: "insert when unsharded (source)"}));
        assert.commandWorked(staleMongosSource[targetColl.getName()].insert(
            {_id: "insert when unsharded (source)"}));
        assert.commandWorked(staleMongosTarget[sourceColl.getName()].insert(
            {_id: "insert when unsharded (target)"}));
        assert.commandWorked(staleMongosTarget[targetColl.getName()].insert(
            {_id: "insert when unsharded (target)"}));

        if (shardedSource) {
            // Shard the source collection through the staleMongosTarget mongos, keeping the
            // staleMongosSource unaware.
            shardCollWithMongos(staleMongosTarget, sourceColl);
        } else {
            // Shard the collection through staleMongosSource.
            shardCollWithMongos(staleMongosSource, sourceColl);

            // Then drop the collection, but do not recreate it yet as that will happen on the next
            // insert later in the test.
            sourceColl.drop();
        }

        if (shardedTarget) {
            // Shard the target collection through the staleMongosSource mongos, keeping the
            // staleMongosTarget unaware.
            shardCollWithMongos(staleMongosSource, targetColl);
        } else {
            // Shard the collection through staleMongosTarget.
            shardCollWithMongos(staleMongosTarget, targetColl);

            // Then drop the collection, but do not recreate it yet as that will happen on the next
            // insert later in the test.
            targetColl.drop();
        }
    }

    // Runs a $merge with the given modes against each mongos in 'mongosList'. This method will wrap
    // 'mongosList' into a list if it is not an array.
    function runMergeTest(whenMatchedMode, whenNotMatchedMode, mongosList) {
        if (!(mongosList instanceof Array)) {
            mongosList = [mongosList];
        }

        mongosList.forEach(mongos => {
            targetColl.remove({});
            sourceColl.remove({});
            // Insert several documents into the source and target collection without any conflicts.
            // Note that the chunk split point is at {_id: 0}.
            assert.commandWorked(sourceColl.insert([{_id: -1}, {_id: 0}, {_id: 1}]));
            assert.commandWorked(targetColl.insert([{_id: -2}, {_id: 2}, {_id: 3}]));

            mongos[sourceColl.getName()].aggregate([{
                $merge: {
                    into: targetColl.getName(),
                    whenMatched: whenMatchedMode,
                    whenNotMatched: whenNotMatchedMode
                }
            }]);

            // If whenNotMatchedMode is "discard", then the documents in the source collection will
            // not get written to the target since none of them match.
            assert.eq(whenNotMatchedMode == "discard" ? 3 : 6, targetColl.find().itcount());
        });
    }

    withEachMergeMode(({whenMatchedMode, whenNotMatchedMode}) => {
        // Skip the combination of merge modes which will fail depending on the contents of the
        // source and target collection, as this will cause the assertion below to trip.
        if (whenNotMatchedMode == "fail")
            return;

        // For each mode, test the following scenarios:
        // * Both the source and target collections are sharded.
        // * Both the source and target collections are unsharded.
        // * Source collection is sharded and the target collection is unsharded.
        // * Source collection is unsharded and the target collection is sharded.
        setupStaleMongos({shardedSource: false, shardedTarget: false});
        runMergeTest(whenMatchedMode, whenNotMatchedMode, [staleMongosSource, staleMongosTarget]);

        setupStaleMongos({shardedSource: true, shardedTarget: true});
        runMergeTest(whenMatchedMode, whenNotMatchedMode, [staleMongosSource, staleMongosTarget]);

        setupStaleMongos({shardedSource: true, shardedTarget: false});
        runMergeTest(whenMatchedMode, whenNotMatchedMode, [staleMongosSource, staleMongosTarget]);

        setupStaleMongos({shardedSource: false, shardedTarget: true});
        runMergeTest(whenMatchedMode, whenNotMatchedMode, [staleMongosSource, staleMongosTarget]);

        //
        // The remaining tests run against a mongos which is stale with respect to BOTH the source
        // and target collections.
        //
        const sourceCollStale = staleMongosBoth.getCollection(sourceColl.getName());
        const targetCollStale = staleMongosBoth.getCollection(targetColl.getName());

        //
        // 1. Both source and target collections are sharded.
        //
        sourceCollStale.drop();
        targetCollStale.drop();

        // Insert into both collections through the stale mongos such that it believes the
        // collections exist and are unsharded.
        assert.commandWorked(sourceCollStale.insert({_id: 0}));
        assert.commandWorked(targetCollStale.insert({_id: 0}));

        shardCollWithMongos(freshMongos, sourceColl);
        shardCollWithMongos(freshMongos, targetColl);

        // Test against the stale mongos, which believes both collections are unsharded.
        runMergeTest(whenMatchedMode, whenNotMatchedMode, staleMongosBoth);

        //
        // 2. Both source and target collections are unsharded.
        //
        sourceColl.drop();
        targetColl.drop();

        // The collections were both dropped through a different mongos, so the stale mongos still
        // believes that they're sharded.
        runMergeTest(whenMatchedMode, whenNotMatchedMode, staleMongosBoth);

        //
        // 3. Source collection is sharded and target collection is unsharded.
        //
        sourceCollStale.drop();

        // Insert into the source collection through the stale mongos such that it believes the
        // collection exists and is unsharded.
        assert.commandWorked(sourceCollStale.insert({_id: 0}));

        // Shard the source collection through the fresh mongos.
        shardCollWithMongos(freshMongos, sourceColl);

        // Shard the target through the stale mongos, but then drop and recreate it as unsharded
        // through a different mongos.
        shardCollWithMongos(staleMongosBoth, targetColl);
        targetColl.drop();

        // At this point, the stale mongos believes the source collection is unsharded and the
        // target collection is sharded when in fact the reverse is true.
        runMergeTest(whenMatchedMode, whenNotMatchedMode, staleMongosBoth);

        //
        // 4. Source collection is unsharded and target collection is sharded.
        //
        sourceCollStale.drop();
        targetCollStale.drop();

        // Insert into the target collection through the stale mongos such that it believes the
        // collection exists and is unsharded.
        assert.commandWorked(targetCollStale.insert({_id: 0}));

        shardCollWithMongos(freshMongos, targetColl);

        // Shard the source through the stale mongos, but then drop and recreate it as unsharded
        // through a different mongos.
        shardCollWithMongos(staleMongosBoth, sourceColl);
        sourceColl.drop();

        // At this point, the stale mongos believes the source collection is sharded and the target
        // collection is unsharded when in fact the reverse is true.
        runMergeTest(whenMatchedMode, whenNotMatchedMode, staleMongosBoth);
    });

    // Runs a legacy $out against each mongos in 'mongosList'. This method will wrap 'mongosList'
    // into a list if it is not an array.
    function runOutTest(mongosList) {
        if (!(mongosList instanceof Array)) {
            mongosList = [mongosList];
        }

        mongosList.forEach(mongos => {
            targetColl.remove({});
            sourceColl.remove({});
            // Insert several documents into the source and target collection without any conflicts.
            // Note that the chunk split point is at {_id: 0}.
            assert.commandWorked(sourceColl.insert([{_id: -1}, {_id: 0}, {_id: 1}]));
            assert.commandWorked(targetColl.insert([{_id: -2}, {_id: 2}, {_id: 3}]));

            mongos[sourceColl.getName()].aggregate([{$out: targetColl.getName()}]);
            assert.eq(3, targetColl.find().itcount());
        });
    }

    // Legacy $out will fail if the target collection is sharded.
    setupStaleMongos({shardedSource: false, shardedTarget: false});
    runOutTest([staleMongosSource, staleMongosTarget]);

    setupStaleMongos({shardedSource: true, shardedTarget: true});
    assert.eq(assert.throws(() => runOutTest(staleMongosSource)).code, 28769);
    assert.eq(assert.throws(() => runOutTest(staleMongosTarget)).code, 17017);

    setupStaleMongos({shardedSource: true, shardedTarget: false});
    runOutTest([staleMongosSource, staleMongosTarget]);

    setupStaleMongos({shardedSource: false, shardedTarget: true});
    assert.eq(assert.throws(() => runOutTest(staleMongosSource)).code, 28769);
    assert.eq(assert.throws(() => runOutTest(staleMongosTarget)).code, 17017);

    st.stop();
}());