diff options
-rw-r--r-- | jstests/libs/sessions_collection.js | 6 | ||||
-rw-r--r-- | jstests/multiVersion/causal_consistency_downgrade_cluster.js | 10 | ||||
-rw-r--r-- | jstests/multiVersion/causal_consistency_upgrade_cluster.js | 6 | ||||
-rw-r--r-- | jstests/noPassthrough/sessions_collection_auto_healing.js | 34 | ||||
-rw-r--r-- | jstests/replsets/sessions_collection_auto_healing.js | 61 | ||||
-rw-r--r-- | src/mongo/db/sessions_collection.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/sessions_collection.h | 5 | ||||
-rw-r--r-- | src/mongo/db/sessions_collection_rs.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/sessions_collection_standalone.cpp | 25 |
9 files changed, 151 insertions, 39 deletions
diff --git a/jstests/libs/sessions_collection.js b/jstests/libs/sessions_collection.js index ba6d9b684c4..71ee9528d89 100644 --- a/jstests/libs/sessions_collection.js +++ b/jstests/libs/sessions_collection.js @@ -4,7 +4,8 @@ * Validates that the sessions collection exists if we expect it to, * and has a TTL index on the lastUse field, if we expect it to. */ -function validateSessionsCollection(conn, collectionExists, indexExists, assertIfNotExists = true) { +function validateSessionsCollection( + conn, collectionExists, indexExists, timeout, assertIfNotExists = true) { var config = conn.getDB("config"); var info = config.getCollectionInfos({name: "system.sessions"}); @@ -26,6 +27,9 @@ function validateSessionsCollection(conn, collectionExists, indexExists, assertI assert.eq(entry["ns"], "config.system.sessions"); assert.eq(entry["key"], {"lastUse": 1}); assert(entry.hasOwnProperty("expireAfterSeconds")); + if (timeout) { + assert.eq(entry["expireAfterSeconds"], timeout * 60); + } } } diff --git a/jstests/multiVersion/causal_consistency_downgrade_cluster.js b/jstests/multiVersion/causal_consistency_downgrade_cluster.js index e45dba25306..8571b16352e 100644 --- a/jstests/multiVersion/causal_consistency_downgrade_cluster.js +++ b/jstests/multiVersion/causal_consistency_downgrade_cluster.js @@ -55,10 +55,10 @@ // force config server to create sessions collection assert.commandWorked( st.configRS.getPrimary().getDB('admin').runCommand({refreshLogicalSessionCacheNow: 1})); - validateSessionsCollection(st.configRS.getPrimary(), false, false, true); + validateSessionsCollection(st.configRS.getPrimary(), false, false, null, true); // initially system.sessions collection has just one chunk. - assert(validateSessionsCollection(st.rs0.getPrimary(), true, false, false) || - validateSessionsCollection(st.rs1.getPrimary(), true, false, false)); + assert(validateSessionsCollection(st.rs0.getPrimary(), true, false, null, false) || + validateSessionsCollection(st.rs1.getPrimary(), true, false, null, false)); // Change featureCompatibilityVersion to 3.4. assert.commandWorked(st.s.adminCommand({setFeatureCompatibilityVersion: "3.4"})); @@ -89,8 +89,8 @@ }); // Confirm that the system.sessions was dropped on the downgrade. - validateSessionsCollection(st.rs0.getPrimary(), false, false, true); - validateSessionsCollection(st.rs1.getPrimary(), false, false, true); + validateSessionsCollection(st.rs0.getPrimary(), false, false, null, true); + validateSessionsCollection(st.rs1.getPrimary(), false, false, null, true); // Downgrade mongos first. jsTest.log("Downgrading mongos servers."); diff --git a/jstests/multiVersion/causal_consistency_upgrade_cluster.js b/jstests/multiVersion/causal_consistency_upgrade_cluster.js index 39c8c812554..b3f871c1b9d 100644 --- a/jstests/multiVersion/causal_consistency_upgrade_cluster.js +++ b/jstests/multiVersion/causal_consistency_upgrade_cluster.js @@ -163,10 +163,10 @@ assert.commandWorked( st.configRS.getPrimary().getDB('admin').runCommand({refreshLogicalSessionCacheNow: 1})); // system.sessions collection can never be on config server. - validateSessionsCollection(st.configRS.getPrimary(), false, false, true); + validateSessionsCollection(st.configRS.getPrimary(), false, false, null, true); // The system sessions collection should have been created on some shard. - assert(validateSessionsCollection(st.rs0.getPrimary(), true, true, false) || - validateSessionsCollection(st.rs1.getPrimary(), true, true, false)); + assert(validateSessionsCollection(st.rs0.getPrimary(), true, true, null, false) || + validateSessionsCollection(st.rs1.getPrimary(), true, true, null, false)); st.stop(); })(); diff --git a/jstests/noPassthrough/sessions_collection_auto_healing.js b/jstests/noPassthrough/sessions_collection_auto_healing.js index 21eb3d221a3..9de17041fce 100644 --- a/jstests/noPassthrough/sessions_collection_auto_healing.js +++ b/jstests/noPassthrough/sessions_collection_auto_healing.js @@ -7,32 +7,54 @@ load('jstests/libs/sessions_collection.js'); // implicit sessions. TestData.disableImplicitSessions = true; + let timeoutMinutes = 5; + var startSession = {startSession: 1}; - var conn = MongoRunner.runMongod({nojournal: ""}); + var conn = MongoRunner.runMongod( + {nojournal: "", setParameter: "localLogicalSessionTimeoutMinutes=" + timeoutMinutes}); var admin = conn.getDB("admin"); var config = conn.getDB("config"); // Test that we can use sessions before the sessions collection exists. { - validateSessionsCollection(conn, false, false); + validateSessionsCollection(conn, false, false, timeoutMinutes); assert.commandWorked(admin.runCommand({startSession: 1})); - validateSessionsCollection(conn, false, false); + validateSessionsCollection(conn, false, false, timeoutMinutes); } // Test that a refresh will create the sessions collection. { assert.commandWorked(admin.runCommand({refreshLogicalSessionCacheNow: 1})); - validateSessionsCollection(conn, true, true); + validateSessionsCollection(conn, true, true, timeoutMinutes); } // Test that a refresh will (re)create the TTL index on the sessions collection. { assert.commandWorked(config.system.sessions.dropIndex({lastUse: 1})); - validateSessionsCollection(conn, true, false); + validateSessionsCollection(conn, true, false, timeoutMinutes); + assert.commandWorked(admin.runCommand({refreshLogicalSessionCacheNow: 1})); + validateSessionsCollection(conn, true, true, timeoutMinutes); + } + + MongoRunner.stopMongod(conn); + + timeoutMinutes = 4; + conn = MongoRunner.runMongod({ + restart: conn, + cleanData: false, + setParameter: "localLogicalSessionTimeoutMinutes=" + timeoutMinutes + }); + admin = conn.getDB("admin"); + config = conn.getDB("config"); + + // Test that a change to the TTL index expiration on restart will generate a collMod to change + // the expiration time. + { assert.commandWorked(admin.runCommand({refreshLogicalSessionCacheNow: 1})); - validateSessionsCollection(conn, true, true); + validateSessionsCollection(conn, true, true, timeoutMinutes); } MongoRunner.stopMongod(conn); + })(); diff --git a/jstests/replsets/sessions_collection_auto_healing.js b/jstests/replsets/sessions_collection_auto_healing.js index ed332241921..b75ed876d25 100644 --- a/jstests/replsets/sessions_collection_auto_healing.js +++ b/jstests/replsets/sessions_collection_auto_healing.js @@ -21,75 +21,102 @@ load('jstests/libs/sessions_collection.js'); var secondary = replTest.getSecondary(); var secondaryAdmin = secondary.getDB("admin"); + // Get the current value of the TTL index so that we can verify it's being properly applied. + let res = assert.commandWorked( + primary.adminCommand({getParameter: 1, localLogicalSessionTimeoutMinutes: 1})); + let timeoutMinutes = res.localLogicalSessionTimeoutMinutes; + // Test that we can use sessions on the primary before the sessions collection exists. { - validateSessionsCollection(primary, false, false); + validateSessionsCollection(primary, false, false, timeoutMinutes); assert.commandWorked(primaryAdmin.runCommand({startSession: 1})); - validateSessionsCollection(primary, false, false); + validateSessionsCollection(primary, false, false, timeoutMinutes); } // Test that we can use sessions on secondaries before the sessions collection exists. { - validateSessionsCollection(primary, false, false); + validateSessionsCollection(primary, false, false, timeoutMinutes); replTest.awaitReplication(); - validateSessionsCollection(secondary, false, false); + validateSessionsCollection(secondary, false, false, timeoutMinutes); assert.commandWorked(secondaryAdmin.runCommand({startSession: 1})); - validateSessionsCollection(primary, false, false); + validateSessionsCollection(primary, false, false, timeoutMinutes); replTest.awaitReplication(); - validateSessionsCollection(secondary, false, false); + validateSessionsCollection(secondary, false, false, timeoutMinutes); } // Test that a refresh on a secondary does not create the sessions collection. { - validateSessionsCollection(primary, false, false); + validateSessionsCollection(primary, false, false, timeoutMinutes); replTest.awaitReplication(); - validateSessionsCollection(secondary, false, false); + validateSessionsCollection(secondary, false, false, timeoutMinutes); assert.commandWorked(secondaryAdmin.runCommand({refreshLogicalSessionCacheNow: 1})); - validateSessionsCollection(primary, false, false); + validateSessionsCollection(primary, false, false, timeoutMinutes); replTest.awaitReplication(); - validateSessionsCollection(secondary, false, false); + validateSessionsCollection(secondary, false, false, timeoutMinutes); } // Test that a refresh on the primary creates the sessions collection. { - validateSessionsCollection(primary, false, false); + validateSessionsCollection(primary, false, false, timeoutMinutes); replTest.awaitReplication(); - validateSessionsCollection(secondary, false, false); + validateSessionsCollection(secondary, false, false, timeoutMinutes); assert.commandWorked(primaryAdmin.runCommand({refreshLogicalSessionCacheNow: 1})); - validateSessionsCollection(primary, true, true); + validateSessionsCollection(primary, true, true, timeoutMinutes); } // Test that a refresh on a secondary will not create the TTL index on the sessions collection. { assert.commandWorked(primary.getDB("config").system.sessions.dropIndex({lastUse: 1})); - validateSessionsCollection(primary, true, false); + validateSessionsCollection(primary, true, false, timeoutMinutes); assert.commandWorked(secondaryAdmin.runCommand({refreshLogicalSessionCacheNow: 1})); - validateSessionsCollection(primary, true, false); + validateSessionsCollection(primary, true, false, timeoutMinutes); } // Test that a refresh on the primary will create the TTL index on the sessions collection. { - validateSessionsCollection(primary, true, false); + validateSessionsCollection(primary, true, false, timeoutMinutes); + + assert.commandWorked(primaryAdmin.runCommand({refreshLogicalSessionCacheNow: 1})); + + validateSessionsCollection(primary, true, true, timeoutMinutes); + } + + timeoutMinutes = 4; + replTest.restart( + 0, + {startClean: false, setParameter: "localLogicalSessionTimeoutMinutes=" + timeoutMinutes}); + + primary = replTest.getPrimary(); + primaryAdmin = primary.getDB("admin"); + secondary = replTest.getSecondary(); + + // Test that a change to the TTL index expiration on restart will generate a collMod to change + // the expiration time. + { assert.commandWorked(primaryAdmin.runCommand({refreshLogicalSessionCacheNow: 1})); - validateSessionsCollection(primary, true, true); + validateSessionsCollection(primary, true, true, timeoutMinutes); + + replTest.awaitReplication(); + validateSessionsCollection(secondary, true, true, timeoutMinutes); } replTest.stopSet(); + })(); diff --git a/src/mongo/db/sessions_collection.cpp b/src/mongo/db/sessions_collection.cpp index 05b6df1666c..e4bad86e337 100644 --- a/src/mongo/db/sessions_collection.cpp +++ b/src/mongo/db/sessions_collection.cpp @@ -318,4 +318,20 @@ BSONObj SessionsCollection::generateCreateIndexesCmd() { return createIndexes.toBSON(); } + +BSONObj SessionsCollection::generateCollModCmd() { + BSONObjBuilder collModCmdBuilder; + + collModCmdBuilder << "collMod" << kSessionsNamespaceString.coll(); + + BSONObjBuilder indexBuilder(collModCmdBuilder.subobjStart("index")); + indexBuilder << "name" << kSessionsTTLIndex; + indexBuilder << "expireAfterSeconds" << localLogicalSessionTimeoutMinutes * 60; + + indexBuilder.done(); + collModCmdBuilder.done(); + + return collModCmdBuilder.obj(); +} + } // namespace mongo diff --git a/src/mongo/db/sessions_collection.h b/src/mongo/db/sessions_collection.h index e592e9a7b5d..5cb0c595419 100644 --- a/src/mongo/db/sessions_collection.h +++ b/src/mongo/db/sessions_collection.h @@ -97,6 +97,11 @@ public: */ static BSONObj generateCreateIndexesCmd(); + /* + * Generates a collMod command for the sessions collection TTL index. + */ + static BSONObj generateCollModCmd(); + protected: /** * Makes a send function for the given client. diff --git a/src/mongo/db/sessions_collection_rs.cpp b/src/mongo/db/sessions_collection_rs.cpp index e3c729ca61c..373f1618eaf 100644 --- a/src/mongo/db/sessions_collection_rs.cpp +++ b/src/mongo/db/sessions_collection_rs.cpp @@ -161,10 +161,22 @@ Status SessionsCollectionRS::setupSessionsCollection(OperationContext* opCtx) { kSessionsNamespaceString, opCtx, [&] { - // Creating the TTL index will auto-generate the collection. + auto existsStatus = checkSessionsCollectionExists(opCtx); + if (existsStatus.isOK()) { + return Status::OK(); + } + DBDirectClient client(opCtx); + BSONObj cmd; + + if (existsStatus.code() == ErrorCodes::IndexOptionsConflict) { + cmd = generateCollModCmd(); + } else { + // Creating the TTL index will auto-generate the collection. + cmd = generateCreateIndexesCmd(); + } + BSONObj info; - auto cmd = generateCreateIndexesCmd(); if (!client.runCommand(kSessionsNamespaceString.db().toString(), cmd, info)) { return getStatusFromCommandResult(info); } @@ -183,15 +195,22 @@ Status SessionsCollectionRS::checkSessionsCollectionExists(OperationContext* opC return Status{ErrorCodes::NamespaceNotFound, "config.system.sessions does not exist"}; } - auto indexExists = std::find_if(indexes.begin(), indexes.end(), [](const BSONObj& index) { + auto index = std::find_if(indexes.begin(), indexes.end(), [](const BSONObj& index) { return index.getField("name").String() == kSessionsTTLIndex; }); - if (indexExists == indexes.end()) { + if (index == indexes.end()) { return Status{ErrorCodes::IndexNotFound, "config.system.sessions does not have the required TTL index"}; } + if (!index->hasField("expireAfterSeconds") || + index->getField("expireAfterSeconds").Int() != (localLogicalSessionTimeoutMinutes * 60)) { + return Status{ + ErrorCodes::IndexOptionsConflict, + "config.system.sessions currently has the incorrect timeout for the TTL index"}; + } + return Status::OK(); } diff --git a/src/mongo/db/sessions_collection_standalone.cpp b/src/mongo/db/sessions_collection_standalone.cpp index b8a39186c60..e5ad672b485 100644 --- a/src/mongo/db/sessions_collection_standalone.cpp +++ b/src/mongo/db/sessions_collection_standalone.cpp @@ -55,8 +55,20 @@ Status SessionsCollectionStandalone::setupSessionsCollection(OperationContext* o return {ErrorCodes::MustUpgrade, "Can not create config.system.sessions collection"}; } + auto existsStatus = checkSessionsCollectionExists(opCtx); + if (existsStatus.isOK()) { + return Status::OK(); + } + DBDirectClient client(opCtx); - auto cmd = generateCreateIndexesCmd(); + BSONObj cmd; + + if (existsStatus.code() == ErrorCodes::IndexOptionsConflict) { + cmd = generateCollModCmd(); + } else { + cmd = generateCreateIndexesCmd(); + } + BSONObj info; if (!client.runCommand(kSessionsNamespaceString.db().toString(), cmd, info)) { return getStatusFromCommandResult(info); @@ -74,15 +86,22 @@ Status SessionsCollectionStandalone::checkSessionsCollectionExists(OperationCont return Status{ErrorCodes::NamespaceNotFound, "config.system.sessions does not exist"}; } - auto indexExists = std::find_if(indexes.begin(), indexes.end(), [](const BSONObj& index) { + auto index = std::find_if(indexes.begin(), indexes.end(), [](const BSONObj& index) { return index.getField("name").String() == kSessionsTTLIndex; }); - if (indexExists == indexes.end()) { + if (index == indexes.end()) { return Status{ErrorCodes::IndexNotFound, "config.system.sessions does not have the required TTL index"}; }; + if (!index->hasField("expireAfterSeconds") || + index->getField("expireAfterSeconds").Int() != (localLogicalSessionTimeoutMinutes * 60)) { + return Status{ + ErrorCodes::IndexOptionsConflict, + "config.system.sessions currently has the incorrect timeout for the TTL index"}; + } + return Status::OK(); } |