diff options
author | Jason Chan <jason.chan@mongodb.com> | 2019-10-02 22:06:42 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-10-02 22:06:42 +0000 |
commit | 292c390a47413e90623314fb777b05edc36750c6 (patch) | |
tree | 6f5790d85beb9227acc7dc7559c82a79e2416f7d | |
parent | c8bbd339b94d6a61fb4d5d1d7676ef0f21bd37fa (diff) | |
download | mongo-292c390a47413e90623314fb777b05edc36750c6.tar.gz |
SERVER-42946 Prevent FCV upgrade/downgrade when in standalone mode with a non-empty config.transactions table.
-rw-r--r-- | jstests/replsets/set_fcv_42_on_standalone_with_replica_set_data.js | 74 | ||||
-rw-r--r-- | src/mongo/db/commands/set_feature_compatibility_version_command.cpp | 21 |
2 files changed, 95 insertions, 0 deletions
diff --git a/jstests/replsets/set_fcv_42_on_standalone_with_replica_set_data.js b/jstests/replsets/set_fcv_42_on_standalone_with_replica_set_data.js new file mode 100644 index 00000000000..acf9f99f1ca --- /dev/null +++ b/jstests/replsets/set_fcv_42_on_standalone_with_replica_set_data.js @@ -0,0 +1,74 @@ +/** + * Tests that standalone nodes with replica set data are unable to upgrade or downgrade FCV while + * the config.transactions collection is non-empty. + */ +(function() { + +"use strict"; +load("jstests/libs/feature_compatibility_version.js"); + +let replTest = new ReplSetTest({nodes: 1}); +replTest.startSet(); +replTest.initiate(); + +const primary = replTest.getPrimary(); +const dbName = "test"; +const collName = "set_fcv_42_on_standalone"; +let adminDB = primary.getDB('admin'); + +assert.commandWorked(primary.getDB(dbName).createCollection(collName)); + +jsTestLog("Downgrade the featureCompatibilityVersion."); +assert.commandWorked(adminDB.adminCommand({setFeatureCompatibilityVersion: lastStableFCV})); +checkFCV(adminDB, lastStableFCV); +const session = primary.startSession(); +const sessionDB = session.getDatabase(dbName); + +session.startTransaction(); +assert.commandWorked(sessionDB[collName].insert({_id: 1})); +assert.commandWorked(session.commitTransaction_forTesting()); +// Restarting as a standalone causes the node to restart from the stable timestamp without applying +// operations from the oplog. +replTest.awaitLastStableRecoveryTimestamp(); + +jsTestLog( + "Test upgrade on a standalone with replica set data and a non-empty config.transactions table."); +const standalone = replTest.restart(0, {noReplSet: true}); +adminDB = standalone.getDB('admin'); +const localDB = standalone.getDB('local'); +const replSetData = localDB.getCollection('system.replset').findOne(); +assert.neq(null, replSetData); + +// Make sure the config.transactions table is not empty. +const configDB = standalone.getDB('config'); +const txnRecord = configDB.getCollection('transactions').findOne(); +assert.neq(null, txnRecord); + +// Should fail on featureCompatibilityVersion upgrade attempt. +assert.commandFailedWithCode( + standalone.getDB('admin').adminCommand({setFeatureCompatibilityVersion: latestFCV}), + ErrorCodes.IllegalOperation); + +jsTestLog( + "Empty the config.transactions table and successfully upgrade featureCompatibilityVersion."); +assert.commandWorked(configDB.getCollection('transactions').remove({})); +assert.commandWorked(configDB.adminCommand({setFeatureCompatibilityVersion: latestFCV})); +checkFCV(adminDB, latestFCV); + +jsTestLog( + "Test downgrade on a standalone with replica set data and a non-empty config.transactions table."); +assert.commandWorked(configDB.getCollection('transactions').insertOne(txnRecord)); + +// Should fail on featureCompatibilityVersion downgrade attempt. +assert.commandFailedWithCode( + standalone.getDB('admin').adminCommand({setFeatureCompatibilityVersion: lastStableFCV}), + ErrorCodes.IllegalOperation); + +jsTestLog( + "Empty the config.transactions table and successfully downgrade featureCompatibilityVersion."); +assert.commandWorked(configDB.getCollection('transactions').remove({})); +assert.commandWorked(configDB.adminCommand({setFeatureCompatibilityVersion: lastStableFCV})); +checkFCV(adminDB, lastStableFCV); + +replTest.stopSet(); +})();
\ No newline at end of file diff --git a/src/mongo/db/commands/set_feature_compatibility_version_command.cpp b/src/mongo/db/commands/set_feature_compatibility_version_command.cpp index 50196ec7aed..0127b1f401a 100644 --- a/src/mongo/db/commands/set_feature_compatibility_version_command.cpp +++ b/src/mongo/db/commands/set_feature_compatibility_version_command.cpp @@ -48,6 +48,7 @@ #include "mongo/db/namespace_string.h" #include "mongo/db/ops/write_ops.h" #include "mongo/db/repl/repl_client_info.h" +#include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/s/config/sharding_catalog_manager.h" #include "mongo/db/server_options.h" #include "mongo/db/session_catalog_mongod.h" @@ -290,6 +291,26 @@ public: CommandHelpers::appendCommandWCStatus(result, waitForWCStatus, res); }); + const bool isReplSet = repl::ReplicationCoordinator::get(opCtx)->getReplicationMode() == + repl::ReplicationCoordinator::modeReplSet; + if (!isReplSet) { + // Only allow changing the FCV on standalone nodes when the config.transactions table + // is empty. + const auto txnTableNss = NamespaceString::kSessionTransactionsTableNamespace; + const auto statusObj = + repl::StorageInterface::get(opCtx)->findSingleton(opCtx, txnTableNss); + const auto status = statusObj.getStatus(); + const bool isStandaloneWithEmptyTransactionsTable = + (status == ErrorCodes::CollectionIsEmpty || + status == ErrorCodes::NamespaceNotFound); + + uassert(ErrorCodes::IllegalOperation, + "cannot change featureCompatibilityVersion when in standalone mode with a " + "non-empty config.transactions table. Perform the upgrade/downgrade as " + "a replica set member or empty the config.transactions table to retry.", + isStandaloneWithEmptyTransactionsTable); + } + // Only allow one instance of setFeatureCompatibilityVersion to run at a time. invariant(!opCtx->lockState()->isLocked()); Lock::ExclusiveLock lk(opCtx->lockState(), FeatureCompatibilityVersion::fcvLock); |