diff options
author | Spencer Jackson <spencer.jackson@mongodb.com> | 2016-07-11 13:50:21 -0400 |
---|---|---|
committer | Spencer Jackson <spencer.jackson@mongodb.com> | 2016-07-29 19:27:08 -0400 |
commit | 62d931bf4ba6a4d881e53e10dd176a80d8f3b8b3 (patch) | |
tree | 676409e08781056977c858e6015775edb820b665 /jstests | |
parent | 5f288387f694706a14eedde8ab910ce234bc47b9 (diff) | |
download | mongo-62d931bf4ba6a4d881e53e10dd176a80d8f3b8b3.tar.gz |
SERVER-17856: Allow mongod users to currentOp and killOp own operations
(cherry picked from commit 9380a1c12a19a061eaafabb5f6b9e87f16a28179)
Diffstat (limited to 'jstests')
-rw-r--r-- | jstests/auth/killop_own_ops.js | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/jstests/auth/killop_own_ops.js b/jstests/auth/killop_own_ops.js new file mode 100644 index 00000000000..b4dc9085263 --- /dev/null +++ b/jstests/auth/killop_own_ops.js @@ -0,0 +1,148 @@ +/** + * 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. + */ + +(function() { + 'use strict'; + + function runTest(m) { + 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}); + admin.logout(); + + /** + * 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) { + var p = db.currentOp({$ownOps: ownOps}).inprog; + var ids = []; + for (var i in p) { + var o = p[i]; + // We *can't* check for ns, b/c it's not guaranteed to be there unless the query is + // active, which it may not be in our polling cycle - particularly b/c we sleep + // every + // second in both the query and the assert + if ((o.active || o.waitingForLock) && o.query && o.query.query && + o.query.query.$where && o.query.count == "jstests_killop") { + print("OP: " + tojson(o)); + ids.push(o.opid); + } + } + return ids; + } + + var countWithWhereOp = + 'db = db.getSiblingDB("foo"); db.auth("reader", "reader"); db.jstests_killop.count({ $where: function() { while (1) { sleep(500); } } });'; + + db.auth('reader', 'reader'); + jsTestLog("Starting long-running $where operation"); + var s1 = startParallelShell(countWithWhereOp, m.port); + + jsTestLog("Finding ops in currentOp() output"); + var o = []; + assert.soon( + function() { + o = ops(); + return o.length == 1; + }, + { + toString: function() { + return tojson(db.currentOp().inprog); + } + }, + 60000); + + jsTestLog("Checking that another user cannot see or kill the op"); + db.logout(); + db.auth('otherReader', 'otherReader'); + assert.eq([], ops()); + db.killOp(o[0]); + db.logout(); + sleep(10); + 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'); + db.killOp(o[0]); + + jsTestLog("Waiting for ops to terminate"); + var exitCode = s1({checkExitSuccess: false}); + assert.neq( + 0, exitCode, "expected shell to exit abnormally due to JS 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 $where operation"); + var s2 = startParallelShell(countWithWhereOp, m.port); + jsTestLog("Finding ops in currentOp() output"); + var o2 = []; + assert.soon( + function() { + o2 = ops(); + return o2.length == 1; + }, + { + toString: function() { + return tojson(db.currentOp().inprog); + } + }, + 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(); + db.killOp(o2[0]); + 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 m = MongoRunner.runMongod({auth: ""}); + runTest(m); + MongoRunner.stopMongod(m); + + // TODO: This feature is currently not supported on sharded clusters. + /*var st = + new ShardingTest({shards: 2, config: 3, keyFile: 'jstests/libs/key1', useHostname: false}); + runTest(st.s); + st.stop();*/ +})(); |