summaryrefslogtreecommitdiff
path: root/jstests/libs/check_routing_table_consistency_helpers.js
blob: ac2631e62f4f6782e828d1bd79b05c0cf540eb1d (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
'use strict';

var RoutingTableConsistencyChecker = (function() {
    const sameObjectFields = (lhsObjFields, rhsObjFields) => {
        if (lhsObjFields.length !== rhsObjFields.length) {
            return false;
        }

        for (let i = 0; i != lhsObjFields.length; ++i) {
            if (lhsObjFields[i] !== rhsObjFields[i]) {
                return false;
            }
        }

        return true;
    };

    const fetchRoutingTableData = (mongos) => {
        // Group docs in config.chunks by coll UUID (sorting by minKey),
        // then join with docs in config.collections.
        // NOTE: the query may throw an exception if the collected data size is bigger than 16MB.
        // To skip the execution of the hook, set the `TestData.skipCheckRoutingTableConsistency`
        // flag in the failing test.
        return mongos.getDB('config')
                                .chunks
                                .aggregate([
                                    {$sort: {min: 1}},
                                    {
                                        $group: {
                                            _id: '$uuid',
                                            routingTable: {$push: '$$ROOT'},
                                        }
                                    },
                                    {$lookup: {
                                        from: 'collections',
                                        localField: '_id',
                                        foreignField: 'uuid',
                                        as: 'details'
                                    },
                                    }
                                ])
                                .toArray();
    };

    const checkCollRoutingTable = (nss, shardKeyPattern, routingTable) => {
        if (!routingTable) {
            jsTest.log(`config.collections entry '${nss}' has no chunks`);
            return false;
        }

        const shardKeyfields = Object.keys(shardKeyPattern);
        let lastLowerBound = {};
        for (const key of shardKeyfields) {
            lastLowerBound[key] = MinKey;
        }
        let expectedLastBound = {};
        for (const key of shardKeyfields) {
            expectedLastBound[key] = MaxKey;
        }
        for (const chunk of routingTable) {
            if (!sameObjectFields(Object.keys(chunk.min), shardKeyfields) ||
                !sameObjectFields(Object.keys(chunk.max), shardKeyfields)) {
                jsTest.log(`Shard key pattern violation found in config.chunks for ${
                    nss}! Expected: ${tojson(shardKeyPattern)}, found chunk ${tojson(chunk)}`);
                return false;
            }

            if (bsonWoCompare(chunk.min, lastLowerBound) !== 0) {
                jsTest.log(`Found gap or range overlap in config.chunks for collection ${
                    nss}, chunk ${tojson(chunk._id)}! Expected ${tojson(lastLowerBound)}, found ${
                    tojson(chunk.min)}`);
                return false;
            }
            lastLowerBound = chunk.max;
        }

        if (bsonWoCompare(routingTable[routingTable.length - 1].max, expectedLastBound) !== 0) {
            jsTest.log(
                `Incomplete range key detected in config.chunks for ${nss} (MaxKey missing)`);
            return false;
        }

        jsTest.log(`${nss} with ${routingTable.length} chunks passed the check`);
        return true;
    };

    const run = (mongos) => {
        try {
            jsTest.log('Checking routing table consistency');
            // Group docs in config.chunks by coll UUID (sorting by minKey),
            // then join with docs in config.collections.
            const testCollectionsWithRoutingTable = fetchRoutingTableData(mongos);

            for (const collData of testCollectionsWithRoutingTable) {
                // Test invariant
                assert.lte(
                    1,
                    collData.details.length,
                    'Possible bug in aggregation generating testCollectionsWithRoutingTable');

                // Test checks
                assert.eq(
                    1,
                    collData.details.length,
                    `Found entries in config.chunks with no match in config.collections! Details: ${
                        tojson(collData)}`);
                assert(checkCollRoutingTable(
                           collData.details[0]._id, collData.details[0].key, collData.routingTable),
                       `Corrupted routing table detected for ${collData._id}! Details: ${
                           tojson(collData)}`);
            }
        } catch (e) {
            if (e.code !== ErrorCodes.Unauthorized) {
                throw e;
            }
            jsTest.log(
                'Skipping check of routing table consistency - access to admin collections is not authorized');
        }
        jsTest.log('Routing table consistency check completed');
    };

    return {
        run: run,
    };
})();