Files
mongo/jstests/auth/pinned_users.js
2018-10-01 15:43:49 -04:00

147 lines
5.5 KiB
JavaScript

/*
* This test checks that we can recover from an operation that has taken out a write lock
* for a long time (potentially deadlocking the server). This comes out of a number of BF's
* related to jstests/core/mr_killop.js in the parallel suite (see BF-6259 for the full
* write-up).
*
* @tags: [requires_replication]
*
*/
(function() {
'use strict';
jsTest.setOption("enableTestCommands", true);
// Start a mongod with the user cache size set to zero, so we know that users who have
// logged out always get fetched cleanly from disk.
const rs = new ReplSetTest({
nodes: 3,
nodeOptions: {auth: "", setParameter: "authorizationManagerCacheSize=0"},
keyFile: "jstests/libs/key1"
});
rs.startSet();
rs.initiate();
const mongod = rs.getPrimary();
const admin = mongod.getDB("admin");
admin.createUser({user: "admin", pwd: "admin", roles: ["root"]});
admin.auth("admin", "admin");
// Mark the "admin2" user as pinned in memory, we'll use this later on to recover from
// the deadlock
assert.commandWorked(admin.runCommand({
setParameter: 1,
authorizationManagerPinnedUsers: [
{user: "admin2", db: "admin"},
],
logLevel: 1
}));
admin.createUser({user: "admin2", pwd: "admin", roles: ["root"]});
let secondConn = new Mongo(mongod.host);
let secondAdmin = secondConn.getDB("admin");
secondAdmin.auth("admin2", "admin");
// Invalidate the user cache so we know only "admin" is in there
assert.commandWorked(admin.runCommand({invalidateUserCache: 1}));
print("User cache after initialization: ",
tojson(admin.aggregate([{$listCachedAndActiveUsers: {}}]).toArray()));
const waitForCommand = function(waitingFor, opFilter) {
let opId = -1;
assert.soon(function() {
print(`Checking for ${waitingFor}`);
const curopRes = admin.currentOp();
assert.commandWorked(curopRes);
const foundOp = curopRes["inprog"].filter(opFilter);
if (foundOp.length == 1) {
opId = foundOp[0]["opid"];
}
return (foundOp.length == 1);
});
return opId;
};
// The deadlock happens in two phases. First we run a command that acquires a read lock and
// holds it for forever.
let readLockShell = startParallelShell(function() {
assert.eq(db.getSiblingDB("admin").auth("admin", "admin"), 1);
assert.commandFailed(db.adminCommand(
{sleep: 1, secs: 500, lock: "r", lockTarget: "admin", $comment: "Read lock sleep"}));
}, mongod.port);
// Wait for that command to appear in currentOp
const readID = waitForCommand(
"readlock",
op => (op["ns"] == "admin.$cmd" && op["command"]["$comment"] == "Read lock sleep"));
// Then we run a command that tries to acquire a write lock, which will wait for forever
// because we're already holding a read lock, but will also prevent any new read locks from
// being taken.
let writeLockShell = startParallelShell(function() {
assert.eq(db.getSiblingDB("admin").auth("admin", "admin"), 1);
assert.commandFailed(db.adminCommand(
{sleep: 1, secs: 500, lock: "w", lockTarget: "admin", $comment: "Write lock sleep"}));
}, mongod.port);
// Wait for that to appear in currentOp
const writeID = waitForCommand(
"writeLock",
op => (op["ns"] == "admin.$cmd" && op["command"]["$comment"] == "Write lock sleep"));
print("killing ops and moving on!");
// If "admin2" wasn't pinned in memory, then these would hang.
assert.commandWorked(secondAdmin.currentOp());
assert.commandWorked(secondAdmin.killOp(readID));
assert.commandWorked(secondAdmin.killOp(writeID));
readLockShell();
writeLockShell();
admin.logout();
secondAdmin.logout();
rs.stopSet();
})();
// This checks that removing a user document actually unpins a user. This is a round-about way
// of making sure that updates to the authz manager by the opObserver correctly invalidates the
// cache and that pinned users don't stick around after they're removed.
(function() {
'use strict';
jsTest.setOption("enableTestCommands", true);
// Start a mongod with the user cache size set to zero, so we know that users who have
// logged out always get fetched cleanly from disk.
const mongod =
MongoRunner.runMongod({auth: "", setParameter: "authorizationManagerCacheSize=0"});
let admin = mongod.getDB("admin");
admin.createUser({user: "admin", pwd: "admin", roles: ["root"]});
admin.auth("admin", "admin");
// Mark the "admin2" user as pinned in memory
assert.commandWorked(admin.runCommand({
setParameter: 1,
logLevel: 1,
authorizationManagerPinnedUsers: [
{user: "admin2", db: "admin"},
],
}));
admin.createUser({user: "admin2", pwd: "admin", roles: ["root"]});
// Invalidate the user cache so we know only "admin" is in there
assert.commandWorked(admin.runCommand({invalidateUserCache: 1}));
print("User cache after initialization: ",
tojson(admin.aggregate([{$listCachedAndActiveUsers: {}}]).toArray()));
assert.commandWorked(admin.getCollection("system.users").remove({user: "admin2"}));
print("User cache after removing user doc: ",
tojson(admin.aggregate([{$listCachedAndActiveUsers: {}}]).toArray()));
assert.eq(admin.auth("admin2", "admin"), 0);
MongoRunner.stopMongod(mongod);
})();