summaryrefslogtreecommitdiff
path: root/jstests/auth/killop_own_ops.js
blob: dbb1689708c4883aee474ec7f4dc2a8fa18ab58e (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
/**
 * Test that a user may $currentOp, and then killOp their own operations.
 *
 * Theory of operation: Create a long running operation from a user which does not have the killOp
 * or inProg privileges. Using the same user, run currentOp to get the opId, and then run killOp
 * against it.
 * @tags: [requires_sharding]
 */

(function() {
'use strict';

load("jstests/libs/fixture_helpers.js");  // For isMongos.

function runTest(m, failPointName) {
    var db = m.getDB("foo");
    var admin = m.getDB("admin");

    admin.createUser({user: 'admin', pwd: 'password', roles: jsTest.adminUserRoles});
    admin.auth('admin', 'password');
    db.createUser({user: 'reader', pwd: 'reader', roles: [{db: 'foo', role: 'read'}]});
    db.createUser({user: 'otherReader', pwd: 'otherReader', roles: [{db: 'foo', role: 'read'}]});
    admin.createRole({
        role: 'opAdmin',
        roles: [],
        privileges: [{resource: {cluster: true}, actions: ['inprog', 'killop']}]
    });
    db.createUser({user: 'opAdmin', pwd: 'opAdmin', roles: [{role: 'opAdmin', db: 'admin'}]});

    var t = db.jstests_killop;
    t.save({x: 1});

    if (!FixtureHelpers.isMongos(db)) {
        assert.commandWorked(
            db.adminCommand({setParameter: 1, internalQueryExecYieldIterations: 1}));
    }

    admin.logout();

    // Only used for nice error messages.
    function getAllLocalOps() {
        admin.aggregate([{$currentOp: {allUsers: true, localOps: true}}]).toArray();
    }

    /**
     * This function filters for the operations that we're looking for, based on their state and
     * the contents of their query object.
     */
    function ops(ownOps = true) {
        const ops = admin.aggregate([{$currentOp: {allUsers: !ownOps, localOps: true}}]).toArray();

        var ids = [];
        for (let o of ops) {
            if ((o.active || o.waitingForLock) && o.command &&
                o.command.find === "jstests_killop" && o.command.comment === "kill_own_ops") {
                ids.push(o.opid);
            }
        }
        return ids;
    }

    var queryAsReader =
        'db = db.getSiblingDB("foo"); db.auth("reader", "reader"); db.jstests_killop.find().comment("kill_own_ops").toArray()';

    jsTestLog("Starting long-running operation");
    db.auth('reader', 'reader');
    assert.commandWorked(db.adminCommand({configureFailPoint: failPointName, mode: "alwaysOn"}));
    var s1 = startParallelShell(queryAsReader, m.port);
    jsTestLog("Finding ops in $currentOp output");
    var o = [];
    assert.soon(
        function() {
            o = ops();
            return o.length == 1;
        },
        () => {
            return tojson(getAllLocalOps());
        },
        60000);
    jsTestLog("Checking that another user cannot see or kill the op");
    db.logout();
    db.auth('otherReader', 'otherReader');
    assert.eq([], ops());
    assert.commandFailed(db.killOp(o[0]));
    db.logout();
    db.auth('reader', 'reader');
    assert.eq(1, ops().length);
    db.logout();
    jsTestLog("Checking that originating user can kill operation");
    var start = new Date();
    db.auth('reader', 'reader');
    assert.commandWorked(db.killOp(o[0]));
    assert.commandWorked(db.adminCommand({configureFailPoint: failPointName, mode: "off"}));

    jsTestLog("Waiting for ops to terminate");
    var exitCode = s1({checkExitSuccess: false});
    assert.neq(0,
               exitCode,
               "expected shell to exit abnormally due to operation execution being terminated");

    // don't want to pass if timeout killed the js function.
    var end = new Date();
    var diff = end - start;
    assert.lt(diff, 30000, "Start: " + start + "; end: " + end + "; diff: " + diff);

    jsTestLog("Starting a second long-running operation");
    assert.commandWorked(db.adminCommand({configureFailPoint: failPointName, mode: "alwaysOn"}));
    var s2 = startParallelShell(queryAsReader, m.port);
    jsTestLog("Finding ops in $currentOp output");
    var o2 = [];
    assert.soon(
        function() {
            o2 = ops();
            return o2.length == 1;
        },
        () => {
            return tojson(getAllLocalOps());
        },
        60000);

    db.logout();
    db.auth('opAdmin', 'opAdmin');

    jsTestLog("Checking that an administrative user can find others' operations");
    assert.eq(o2, ops(false));

    jsTestLog("Checking that an administrative user cannot find others' operations with ownOps");
    assert.eq([], ops());

    jsTestLog("Checking that an administrative user can kill others' operations");
    var start = new Date();
    assert.commandWorked(db.killOp(o2[0]));
    assert.commandWorked(db.adminCommand({configureFailPoint: failPointName, mode: "off"}));
    jsTestLog("Waiting for ops to terminate");
    var exitCode = s2({checkExitSuccess: false});
    assert.neq(
        0, exitCode, "expected shell to exit abnormally due to JS execution being terminated");

    var end = new Date();
    var diff = end - start;
    assert.lt(diff, 30000, "Start: " + start + "; end: " + end + "; diff: " + diff);
}

var conn = MongoRunner.runMongod({auth: ""});
runTest(conn, "setYieldAllLocksHang");
MongoRunner.stopMongod(conn);

// TODO: Remove 'shardAsReplicaSet: false' when SERVER-32672 is fixed.
var st =
    new ShardingTest({shards: 1, keyFile: 'jstests/libs/key1', other: {shardAsReplicaSet: false}});
// Use a different failpoint in the sharded version, since the mongos does not have a
// setYieldAlllocksHang failpoint.
runTest(st.s, "waitInFindBeforeMakingBatch");
st.stop();
})();