summaryrefslogtreecommitdiff
path: root/jstests/sharding/read_pref_cmd.js
blob: 11646bd8e03f21d919615c4aa51788afa541cac0 (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
var NODE_COUNT = 2;

/**
 * Performs a series of tests on commands with read preference.
 *
 * @param conn {Mongo} the connection object of which to test the read
 *     preference functionality.
 * @param hostList {Array.<Mongo>} list of the replica set host members.
 * @param isMongos {boolean} true if conn is a mongos connection.
 */
var doTest = function(conn, hostList, isMongos) {
    var testDB = conn.getDB('test');
    conn.setSlaveOk(false); // purely rely on readPref
    conn.setReadPref('secondary');

    /**
     * Performs the command and checks whether the command was routed to the
     * appropriate node.
     *
     * @param cmdObj the cmd to send.
     * @param secOk true if command should be routed to a secondary.
     * @param profileQuery the query to perform agains the profile collection to
     *     look for the cmd just sent.
     */
    var cmdTest = function(cmdObj, secOk, profileQuery) {
        var cmdResult = testDB.runCommand(cmdObj, { readPref: { mode: 'secondary' }});
        jsTest.log('cmd result: ' + tojson(cmdResult));
        assert(cmdResult.ok);

        var testedAtLeastOnce = false;
        var query = { op: 'command', ns: 'test.$cmd' };
        Object.extend(query, profileQuery);

        hostList.forEach(function(node) {
            var testDB = node.getDB('test');
            var result = testDB.system.profile.findOne(query);

            if (result != null) {
                if (secOk) {
                    assert(testDB.adminCommand({ isMaster: 1 }).secondary);
                }
                else {
                    assert(testDB.adminCommand({ isMaster: 1 }).ismaster);
                }

                testedAtLeastOnce = true;
            }
        });

        assert(testedAtLeastOnce);
    };

    /**
     * Assumption: all values are native types (no objects)
     */
    var formatProfileQuery = function(queryObj, isEmbedded) {
        var prefix = isEmbedded? 'command.query.' : 'command.';
        var newObj = {};

        for (var field in queryObj) {
            newObj[prefix + field] = queryObj[field];
        }

        return newObj;
    };

    // Test command that can be sent to secondary
    cmdTest({ distinct: 'user', key: { x: 1 }, query: { x: 1 }}, true,
        formatProfileQuery({ distinct: 'user' }, !isMongos));

    // Test command that can't be sent to secondary
    cmdTest({ create: 'mrIn' }, false, formatProfileQuery({ create: 'mrIn' }, !isMongos));
    // Make sure mrIn is propagated to secondaries before proceeding
    testDB.runCommand({ getLastError: 1, w: NODE_COUNT });

    var mapFunc = function(doc) {};
    var reduceFunc = function(key, values) { return values; };

    // Test inline mapReduce on sharded collection.
    // Note that in sharded map reduce, it will output the result in a temp collection
    // even if out is inline.
    if (isMongos) {
        cmdTest({ mapreduce: 'user', map: mapFunc, reduce: reduceFunc, out: { inline: 1 }},
            false, formatProfileQuery({ mapreduce: 'user', shardedFirstPass: true }, false));
    }

    // Test inline mapReduce on unsharded collection.
    cmdTest({ mapreduce: 'mrIn', map: mapFunc, reduce: reduceFunc, out: { inline: 1 }}, true,
        formatProfileQuery({ mapreduce: 'mrIn', 'out.inline': 1 }, !isMongos));

    // Test non-inline mapReduce on sharded collection.
    if (isMongos) {
        cmdTest({ mapreduce: 'user', map: mapFunc, reduce: reduceFunc,
            out: { replace: 'mrOut' }}, false,
            formatProfileQuery({ mapreduce: 'user', shardedFirstPass: true }, false));
    }

    // Test non-inline mapReduce on unsharded collection.
    cmdTest({ mapreduce: 'mrIn', map: mapFunc, reduce: reduceFunc, out: { replace: 'mrOut' }},
        false, formatProfileQuery({ mapreduce: 'mrIn', 'out.replace': 'mrOut' }, !isMongos));

    // Test other commands that can be sent to secondary.
    cmdTest({ count: 'user' }, true, formatProfileQuery({ count: 'user' }, !isMongos));
    cmdTest({ group: { key: { x: true }, '$reduce': function(a, b) {}, ns: 'mrIn',
        initial: { x: 0  }}}, true, formatProfileQuery({ 'group.ns': 'mrIn' }, !isMongos));

    cmdTest({ collStats: 'user' }, true, formatProfileQuery({ count: 'user' }, !isMongos));
    cmdTest({ dbStats: 1 }, true, formatProfileQuery({ dbStats: 1 }, !isMongos));

    testDB.user.ensureIndex({ loc: '2d' });
    testDB.runCommand({ getLastError: 1, w: NODE_COUNT });
    cmdTest({ geoNear: 'user', near: [1, 1] }, true,
        formatProfileQuery({ geoNear: 'user' }, !isMongos));

    // Test on sharded
    cmdTest({ aggregate: 'user', pipeline: [{ $project: { x: 1 }}] }, true,
        formatProfileQuery({ aggregate: 'user' }, !isMongos));

    // Test on non-sharded
    cmdTest({ aggregate: 'mrIn', pipeline: [{ $project: { x: 1 }}] }, true,
        formatProfileQuery({ aggregate: 'mrIn' }, !isMongos));
};

var st = new ShardingTest({ shards: { rs0: { nodes: NODE_COUNT }}});
st.stopBalancer();

var configDB = st.s.getDB('config');
configDB.adminCommand({ enableSharding: 'test' });
configDB.adminCommand({ shardCollection: 'test.user', key: { x: 1 }});

ReplSetTest.awaitRSClientHosts(st.s, st.rs0.nodes);

st.rs0.nodes.forEach(function(node) {
    node.getDB('test').setProfilingLevel(2);
});

var replConn = new Mongo(st.rs0.getURL());

// TODO: use api in SERVER-7533 once available.
// Make sure replica set connection is ready by repeatedly performing a dummy query
// against the secondary until it succeeds. This hack is needed because awaitRSClientHosts
// won't work on the shell's instance of the ReplicaSetMonitor.
assert.soon(function() {
    try {
        replConn.getDB('test').user.find().readPref('secondary').hasNext();
        return true;
    }
    catch (x) {
        // Intentionally caused an error that forces the monitor to refresh.
        print('Caught exception while doing dummy query: ' + tojson(x));
        return false;
    }
});

doTest(new Mongo(st.rs0.getURL()), st.rs0.nodes, false);

st.s.getDB('test').dropDatabase();
// Hack until SERVER-7739 gets fixed
st.rs0.awaitReplication();

configDB.adminCommand({ enableSharding: 'test' });
configDB.adminCommand({ shardCollection: 'test.user', key: { x: 1 }});

st.rs0.nodes.forEach(function(node) {
    node.getDB('test').setProfilingLevel(2);
});

jsTest.log('Starting test for mongos connection');

doTest(st.s, st.rs0.nodes, true);

st.stop();