summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Chan <jason.chan@mongodb.com>2019-10-02 22:06:42 +0000
committerevergreen <evergreen@mongodb.com>2019-10-02 22:06:42 +0000
commit292c390a47413e90623314fb777b05edc36750c6 (patch)
tree6f5790d85beb9227acc7dc7559c82a79e2416f7d
parentc8bbd339b94d6a61fb4d5d1d7676ef0f21bd37fa (diff)
downloadmongo-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.js74
-rw-r--r--src/mongo/db/commands/set_feature_compatibility_version_command.cpp21
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);