summaryrefslogtreecommitdiff
path: root/jstests
diff options
context:
space:
mode:
authorSpencer Jackson <spencer.jackson@mongodb.com>2016-07-11 13:50:21 -0400
committerSpencer Jackson <spencer.jackson@mongodb.com>2016-07-29 19:27:08 -0400
commit62d931bf4ba6a4d881e53e10dd176a80d8f3b8b3 (patch)
tree676409e08781056977c858e6015775edb820b665 /jstests
parent5f288387f694706a14eedde8ab910ce234bc47b9 (diff)
downloadmongo-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.js148
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();*/
+})();