summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSpencer Jackson <spencer.jackson@mongodb.com>2022-04-05 17:05:03 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-04-05 18:45:10 +0000
commitc96f8dacc4c71b4774c932a07be4fac71b6db628 (patch)
tree5b153d37c19b644aef1642aca76ff28703065312
parent17c2a28282ba790157a32b97c76b516c4a426b7e (diff)
downloadmongo-c96f8dacc4c71b4774c932a07be4fac71b6db628.tar.gz
SERVER-63725 Block FCV downgrade on User Write Block Mode
-rw-r--r--jstests/noPassthrough/libs/user_write_blocking.js219
-rw-r--r--jstests/noPassthrough/set_user_write_block_mode.js194
-rw-r--r--jstests/noPassthrough/user_write_block_fcv.js83
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/set_feature_compatibility_version_command.cpp24
-rw-r--r--src/mongo/db/commands/set_user_write_block_mode_command.cpp31
-rw-r--r--src/mongo/db/s/config/configsvr_coordinator.h4
-rw-r--r--src/mongo/db/s/config/configsvr_coordinator_service.cpp16
-rw-r--r--src/mongo/db/s/config/configsvr_coordinator_service.h3
-rw-r--r--src/mongo/db/s/config/configsvr_set_user_write_block_mode_command.cpp30
10 files changed, 400 insertions, 205 deletions
diff --git a/jstests/noPassthrough/libs/user_write_blocking.js b/jstests/noPassthrough/libs/user_write_blocking.js
new file mode 100644
index 00000000000..3eb2322f38f
--- /dev/null
+++ b/jstests/noPassthrough/libs/user_write_blocking.js
@@ -0,0 +1,219 @@
+load("jstests/libs/fail_point_util.js");
+load("jstests/libs/parallel_shell_helpers.js");
+
+const UserWriteBlockHelpers = (function() {
+ 'use strict';
+
+ const WriteBlockState = {UNKNOWN: 0, DISABLED: 1, ENABLED: 2};
+
+ const bypassUser = "adminUser";
+ const noBypassUser = "user";
+ const password = "password";
+
+ const keyfile = "jstests/libs/key1";
+
+ // Define a set of classes for managing different cluster types, and allowing the
+ // test body to perform basic operations against them.
+ class Fixture {
+ // For a localhost node listening on the provided port, create a privileged and unprivileged
+ // user. Create a connection authenticated as each user.
+ spawnConnections(port) {
+ this.adminConn = new Mongo("127.0.0.1:" + port);
+ this.adminConn.port = port;
+ const admin = this.adminConn.getDB("admin");
+ // User with "__system" role has restore role and thus can bypass user write blocking.
+ // Can also run setUserWriteBlockMode.
+ if (!this.haveCreatedUsers) {
+ assert.commandWorked(admin.runCommand({
+ createUser: bypassUser,
+ pwd: password,
+ roles: [{role: "__system", db: "admin"}]
+ }));
+ }
+ assert(admin.auth(bypassUser, password));
+
+ if (!this.haveCreatedUsers) {
+ assert.commandWorked(admin.runCommand({
+ createUser: noBypassUser,
+ pwd: password,
+ roles: [{role: "readWriteAnyDatabase", db: "admin"}]
+ }));
+ this.haveCreatedUsers = true;
+ }
+
+ this.conn = new Mongo("127.0.0.1:" + port);
+ this.conn.port = port;
+ assert(this.conn.getDB("admin").auth(noBypassUser, password));
+ }
+
+ constructor(port) {
+ this.spawnConnections(port);
+ }
+
+ asUser(fun) {
+ const db = this.conn.getDB(jsTestName());
+ const coll = db.test;
+ return fun({conn: this.conn, db: db, coll: coll});
+ }
+
+ asAdmin(fun) {
+ const db = this.adminConn.getDB(jsTestName());
+ const coll = db.test;
+ return fun({conn: this.adminConn, db: db, admin: db.getSiblingDB("admin"), coll: coll});
+ }
+
+ enableWriteBlockMode() {
+ assert.commandWorked(
+ this.adminConn.getDB("admin").runCommand({setUserWriteBlockMode: 1, global: true}));
+ }
+
+ disableWriteBlockMode() {
+ assert.commandWorked(this.adminConn.getDB("admin").runCommand(
+ {setUserWriteBlockMode: 1, global: false}));
+ }
+
+ getStatus() {
+ throw "UNIMPLEMENTED";
+ }
+
+ assertWriteBlockMode(expectedUserWriteBlockMode) {
+ const status = this.getStatus();
+ assert.eq(expectedUserWriteBlockMode, status.repl.userWriteBlockMode);
+ }
+
+ _hangTransition(targetConn, failpoint) {
+ let hangWhileSetingUserWriteBlockModeFailPoint =
+ configureFailPoint(targetConn, failpoint);
+
+ const awaitShell = startParallelShell(
+ funWithArgs((username, password) => {
+ let admin = db.getSiblingDB("admin");
+ admin.auth(username, password);
+ assert.commandWorked(
+ admin.runCommand({setUserWriteBlockMode: 1, global: true}));
+ }, bypassUser, password), this.conn.port);
+
+ hangWhileSetingUserWriteBlockModeFailPoint.wait();
+
+ return {waiter: awaitShell, failpoint: hangWhileSetingUserWriteBlockModeFailPoint};
+ }
+
+ restart() {
+ throw "UNIMPLEMENTED";
+ }
+
+ stop() {
+ throw "UNIMPLEMENTED";
+ }
+ }
+
+ class ReplicaFixture extends Fixture {
+ constructor() {
+ const rst = new ReplSetTest(
+ {nodes: 3, nodeOptions: {auth: "", bind_ip_all: ""}, keyFile: keyfile});
+ rst.startSet();
+ rst.initiate();
+
+ super(rst.getPrimary().port);
+ this.rst = rst;
+ }
+
+ getStatus() {
+ return this.adminConn.getDB("admin").serverStatus();
+ }
+
+ takeGlobalLock() {
+ class LockHolder {
+ constructor(fixture, waiter, opId) {
+ this.fixture = fixture;
+ this.waiter = waiter;
+ this.opId = opId;
+ }
+
+ unlock() {
+ assert.commandWorked(this.fixture.adminConn.getDB("admin").runCommand(
+ {killOp: 1, op: this.opId}));
+ this.waiter();
+ }
+ }
+
+ const parallelShell = startParallelShell(
+ funWithArgs((connString, username, password) => {
+ let admin = db.getSiblingDB("admin");
+ admin.auth(username, password);
+ assert.commandFailedWithCode(admin.runCommand({sleep: 1, lock: "w", secs: 600}),
+ ErrorCodes.Interrupted);
+ }, "127.0.0.1:" + this.conn.port, bypassUser, password), this.conn.port);
+
+ var opId;
+
+ assert.soon(() => {
+ let result = this.adminConn.getDB("admin")
+ .aggregate([
+ {$currentOp: {}},
+ {$match: {op: "command", "command.sleep": {$exists: true}}}
+ ])
+ .toArray();
+ if (result.length !== 1)
+ return false;
+
+ opId = result[0].opid;
+
+ return true;
+ });
+
+ return new LockHolder(this, parallelShell, opId);
+ }
+
+ restart() {
+ this.rst.stopSet(undefined, /* restart */ true);
+ this.rst.startSet({}, /* restart */ true);
+ this.rst.waitForPrimary();
+
+ super.spawnConnections(this.rst.getPrimary().port);
+ }
+
+ stop() {
+ this.rst.stopSet();
+ }
+ }
+
+ class ShardingFixture extends Fixture {
+ constructor() {
+ const st = new ShardingTest(
+ {shards: 1, mongos: 1, config: 1, auth: "", other: {keyFile: keyfile}});
+
+ super(st.s.port);
+ this.st = st;
+ }
+
+ getStatus() {
+ const backend = this.st.rs0.getPrimary();
+ return authutil.asCluster(
+ backend, keyfile, () => backend.getDB('admin').serverStatus());
+ }
+
+ hangTransition() {
+ return this._hangTransition(this.st.shard0, "hangInShardsvrSetUserWriteBlockMode");
+ }
+
+ restart() {
+ this.st.restartShardRS(0);
+ this.st.restartConfigServer(0);
+ }
+
+ stop() {
+ this.st.stop();
+ }
+ }
+
+ return {
+ WriteBlockState: WriteBlockState,
+ ShardingFixture: ShardingFixture,
+ ReplicaFixture: ReplicaFixture,
+ bypassUser: bypassUser,
+ noBypassUser,
+ password: password,
+ keyfile: keyfile
+ };
+})();
diff --git a/jstests/noPassthrough/set_user_write_block_mode.js b/jstests/noPassthrough/set_user_write_block_mode.js
index f776236a687..b8974bc6eb9 100644
--- a/jstests/noPassthrough/set_user_write_block_mode.js
+++ b/jstests/noPassthrough/set_user_write_block_mode.js
@@ -9,20 +9,20 @@
// featureFlagUserWriteBlocking,
// ]
+load("jstests/noPassthrough/libs/user_write_blocking.js");
+
(function() {
'use strict';
-const WriteBlockState = {
- UNKNOWN: 0,
- DISABLED: 1,
- ENABLED: 2
-};
-
-const keyfile = "jstests/libs/key1";
-
-const bypassUser = "adminUser";
-const noBypassUser = "user";
-const password = "password";
+const {
+ WriteBlockState,
+ ShardingFixture,
+ ReplicaFixture,
+ bypassUser,
+ noBypassUser,
+ password,
+ keyfile
+} = UserWriteBlockHelpers;
function runTest(fixture) {
// For this test to work, we expect the state of the collection passed to be a single {a: 2}
@@ -106,154 +106,6 @@ function runTest(fixture) {
}
}
-// Define a set of classes for managing different cluster types, and allowing the
-// test body to perform basic operations against them.
-class Fixture {
- // For a localhost node listening on the provided port, create a privileged and unprivileged
- // user. Create a connection authenticated as each user.
- spawnConnections(port) {
- this.adminConn = new Mongo("127.0.0.1:" + port);
- this.adminConn.port = port;
- const admin = this.adminConn.getDB("admin");
- // User with "__system" role has restore role and thus can bypass user write blocking. Can
- // also run setUserWriteBlockMode.
- if (!this.haveCreatedUsers) {
- assert.commandWorked(admin.runCommand(
- {createUser: bypassUser, pwd: password, roles: [{role: "__system", db: "admin"}]}));
- }
- assert(admin.auth(bypassUser, password));
-
- if (!this.haveCreatedUsers) {
- assert.commandWorked(admin.runCommand({
- createUser: noBypassUser,
- pwd: password,
- roles: [{role: "readWriteAnyDatabase", db: "admin"}]
- }));
- this.haveCreatedUsers = true;
- }
-
- this.conn = new Mongo("127.0.0.1:" + port);
- this.conn.port = port;
- assert(this.conn.getDB("admin").auth(noBypassUser, password));
- }
-
- constructor(port) {
- this.spawnConnections(port);
- }
-
- asUser(fun) {
- const db = this.conn.getDB(jsTestName());
- const coll = db.test;
- return fun({conn: this.conn, db: db, coll: coll});
- }
-
- asAdmin(fun) {
- const db = this.adminConn.getDB(jsTestName());
- const coll = db.test;
- return fun({conn: this.adminConn, db: db, coll: coll});
- }
-
- enableWriteBlockMode() {
- assert.commandWorked(
- this.adminConn.getDB("admin").runCommand({setUserWriteBlockMode: 1, global: true}));
- }
-
- disableWriteBlockMode() {
- assert.commandWorked(
- this.adminConn.getDB("admin").runCommand({setUserWriteBlockMode: 1, global: false}));
- }
-
- getStatus() {
- throw "UNIMPLEMENTED";
- }
-
- assertWriteBlockMode(expectedUserWriteBlockMode) {
- const status = this.getStatus();
- assert.eq(expectedUserWriteBlockMode, status.repl.userWriteBlockMode);
- }
-
- restart() {
- throw "UNIMPLEMENTED";
- }
-
- stop() {
- throw "UNIMPLEMENTED";
- }
-}
-
-class ReplicaFixture extends Fixture {
- constructor() {
- const rst =
- new ReplSetTest({nodes: 3, nodeOptions: {auth: "", bind_ip_all: ""}, keyFile: keyfile});
- rst.startSet();
- rst.initiate();
-
- super(rst.getPrimary().port);
- this.rst = rst;
- }
-
- getStatus() {
- return this.adminConn.getDB("admin").serverStatus();
- }
-
- takeGlobalLock() {
- load("jstests/libs/parallel_shell_helpers.js");
-
- class LockHolder {
- constructor(fixture, waiter, opId) {
- this.fixture = fixture;
- this.waiter = waiter;
- this.opId = opId;
- }
-
- unlock() {
- assert.commandWorked(
- this.fixture.adminConn.getDB("admin").runCommand({killOp: 1, op: this.opId}));
- this.waiter();
- }
- }
-
- const parallelShell = startParallelShell(
- funWithArgs((connString, username, password) => {
- let admin = db.getSiblingDB("admin");
- admin.auth(username, password);
- assert.commandFailedWithCode(admin.runCommand({sleep: 1, lock: "w", secs: 600}),
- ErrorCodes.Interrupted);
- }, "127.0.0.1:" + this.conn.port, bypassUser, password), this.conn.port);
-
- var opId;
-
- assert.soon(() => {
- let result = this.adminConn.getDB("admin")
- .aggregate([
- {$currentOp: {}},
- {$match: {op: "command", "command.sleep": {$exists: true}}}
- ])
- .toArray();
- if (result.length !== 1)
- return false;
-
- opId = result[0].opid;
-
- return true;
- });
-
- return new LockHolder(this, parallelShell, opId);
- }
-
- restart() {
- this.rst.stopSet(undefined, /* restart */ true);
- this.rst.startSet({}, /* restart */ true);
- this.rst.waitForPrimary();
-
- super.spawnConnections(this.rst.getPrimary().port);
- }
-
- stop() {
- this.rst.stopSet();
- }
-}
-
{
// Validate that setting user write blocking fails on standalones
const conn = MongoRunner.runMongod({auth: "", bind_ip: "127.0.0.1"});
@@ -272,30 +124,6 @@ const rst = new ReplicaFixture();
runTest(rst);
rst.stop();
-class ShardingFixture extends Fixture {
- constructor() {
- const st = new ShardingTest(
- {shards: 1, mongos: 1, config: 1, auth: "", other: {keyFile: keyfile}});
-
- super(st.s.port);
- this.st = st;
- }
-
- getStatus() {
- const backend = this.st.rs0.getPrimary();
- return authutil.asCluster(backend, keyfile, () => backend.getDB('admin').serverStatus());
- }
-
- restart() {
- this.st.restartShardRS(0);
- this.st.restartConfigServer(0);
- }
-
- stop() {
- this.st.stop();
- }
-}
-
// Test on a sharded cluster
const st = new ShardingFixture();
runTest(st);
diff --git a/jstests/noPassthrough/user_write_block_fcv.js b/jstests/noPassthrough/user_write_block_fcv.js
new file mode 100644
index 00000000000..529d4cc8633
--- /dev/null
+++ b/jstests/noPassthrough/user_write_block_fcv.js
@@ -0,0 +1,83 @@
+// Test setUserWriteBlockMode command.
+//
+// @tags: [
+// creates_and_authenticates_user,
+// requires_auth,
+// requires_fcv_60,
+// requires_non_retryable_commands,
+// requires_replication,
+// featureFlagUserWriteBlocking,
+// ]
+
+load("jstests/noPassthrough/libs/user_write_blocking.js");
+
+(function() {
+'use strict';
+
+const {
+ WriteBlockState,
+ ShardingFixture,
+ ReplicaFixture,
+ bypassUser,
+ noBypassUser,
+ password,
+ keyfile
+} = UserWriteBlockHelpers;
+
+function runTest(fixture) {
+ // While the cluster is in write block mode, FCV cannot be downgraded
+ fixture.asAdmin(({admin}) => assert.commandWorked(
+ admin.runCommand({setUserWriteBlockMode: 1, global: true})));
+ fixture.asAdmin(
+ ({admin}) => assert.commandFailedWithCode(
+ admin.runCommand({setFeatureCompatibilityVersion: "5.0"}), ErrorCodes.CannotDowngrade));
+ fixture.assertWriteBlockMode(WriteBlockState.ENABLED);
+
+ // When write block mode is disabled, FCV can be downgraded
+ fixture.asAdmin(({admin}) => assert.commandWorked(
+ admin.runCommand({setUserWriteBlockMode: 1, global: false})));
+ fixture.asAdmin(({admin}) => assert.commandWorked(
+ admin.runCommand({setFeatureCompatibilityVersion: "5.0"})));
+
+ // While in a downgraded state, it is not possible to enable write blocking
+ fixture.asAdmin(({admin}) => assert.commandFailed(
+ admin.runCommand({setUserWriteBlockMode: 1, global: true})));
+
+ // When a cluster is started in a downgraded state, it's possible to upgrade FCV,
+ // and block writes on pre-existing connections without the bypass privilege
+ fixture.restart();
+ fixture.asUser(({coll}) => assert.commandWorked(coll.insert({data: 1})));
+ fixture.asAdmin(({admin}) => assert.commandWorked(
+ admin.runCommand({setFeatureCompatibilityVersion: "6.0"})));
+ fixture.asUser(({coll}) => assert.commandWorked(coll.insert({data: 2})));
+ fixture.asAdmin(({admin}) => assert.commandWorked(
+ admin.runCommand({setUserWriteBlockMode: 1, global: true})));
+ fixture.asUser(({coll}) => assert.commandFailed(coll.insert({data: 3})));
+ fixture.asAdmin(({coll}) => assert.commandWorked(coll.insert({data: 4})));
+
+ // While the cluster is in the middle of activating write block mode, FCV cannot be downgraded.
+ // This does not need to be tested on replicasets. FCV locks are uninterruptable. Waiting on a
+ // failpoint on a replicaset primary would prevent FCV from ever completing.
+ if (fixture.hangTransition) {
+ let hangWaiter = fixture.hangTransition();
+
+ fixture.asAdmin(({admin}) => assert.commandFailedWithCode(
+ admin.runCommand({setFeatureCompatibilityVersion: "5.0"}),
+ ErrorCodes.CannotDowngrade));
+ hangWaiter.failpoint.off();
+ hangWaiter.waiter();
+ }
+}
+
+{
+ const rst = new ReplicaFixture();
+ runTest(rst);
+ rst.stop();
+}
+
+{
+ const st = new ShardingFixture();
+ runTest(st);
+ st.stop();
+}
+}());
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index cd2f7b33a87..f122a05c212 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -604,6 +604,7 @@ env.Library(
'$BUILD_DIR/mongo/db/s/sharding_runtime_d',
'$BUILD_DIR/mongo/db/s/transaction_coordinator',
'$BUILD_DIR/mongo/db/s/user_writes_recoverable_critical_section',
+ '$BUILD_DIR/mongo/db/server_feature_flags',
'$BUILD_DIR/mongo/db/server_options_core',
'$BUILD_DIR/mongo/db/timeseries/timeseries_conversion_util',
'$BUILD_DIR/mongo/idl/idl_parser',
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 6c20e3ae89c..2a540ffa88b 100644
--- a/src/mongo/db/commands/set_feature_compatibility_version_command.cpp
+++ b/src/mongo/db/commands/set_feature_compatibility_version_command.cpp
@@ -64,6 +64,7 @@
#include "mongo/db/repl/tenant_migration_donor_service.h"
#include "mongo/db/repl/tenant_migration_recipient_service.h"
#include "mongo/db/s/active_migrations_registry.h"
+#include "mongo/db/s/config/configsvr_coordinator_service.h"
#include "mongo/db/s/config/sharding_catalog_manager.h"
#include "mongo/db/s/migration_coordinator_document_gen.h"
#include "mongo/db/s/migration_util.h"
@@ -74,6 +75,7 @@
#include "mongo/db/s/sharding_ddl_coordinator_service.h"
#include "mongo/db/s/sharding_util.h"
#include "mongo/db/s/transaction_coordinator_service.h"
+#include "mongo/db/server_feature_flags_gen.h"
#include "mongo/db/server_options.h"
#include "mongo/db/session_catalog.h"
#include "mongo/db/session_txn_record_gen.h"
@@ -383,6 +385,28 @@ public:
}
}
+ if (!gFeatureFlagUserWriteBlocking.isEnabledOnVersion(requestedVersion)) {
+ // TODO SERVER-65010 Remove this scope once 6.0 has branched out
+
+ if (serverGlobalParams.clusterRole == ClusterRole::ConfigServer) {
+ uassert(
+ ErrorCodes::CannotDowngrade,
+ "Cannot downgrade while user write blocking is being changed",
+ ConfigsvrCoordinatorService::getService(opCtx)
+ ->isAnyCoordinatorOfGivenTypeRunning(
+ opCtx, ConfigsvrCoordinatorTypeEnum::kSetUserWriteBlockMode));
+ }
+
+ DBDirectClient client(opCtx);
+
+ const bool isBlockingUserWrites =
+ client.count(NamespaceString::kUserWritesCriticalSectionsNamespace) != 0;
+
+ uassert(ErrorCodes::CannotDowngrade,
+ "Cannot downgrade while user write blocking is enabled.",
+ !isBlockingUserWrites);
+ }
+
FeatureCompatibilityVersion::updateFeatureCompatibilityVersionDocument(
opCtx,
actualVersion,
diff --git a/src/mongo/db/commands/set_user_write_block_mode_command.cpp b/src/mongo/db/commands/set_user_write_block_mode_command.cpp
index ab55073c714..b1d645dda4b 100644
--- a/src/mongo/db/commands/set_user_write_block_mode_command.cpp
+++ b/src/mongo/db/commands/set_user_write_block_mode_command.cpp
@@ -33,15 +33,17 @@
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/commands.h"
+#include "mongo/db/commands/feature_compatibility_version.h"
#include "mongo/db/commands/set_user_write_block_mode_gen.h"
#include "mongo/db/repl/repl_client_info.h"
#include "mongo/db/repl/replication_coordinator.h"
#include "mongo/db/s/user_writes_recoverable_critical_section_service.h"
+#include "mongo/db/server_feature_flags_gen.h"
#include "mongo/logv2/log.h"
-
namespace mongo {
namespace {
+
class SetUserWriteBlockModeCommand final : public TypedCommand<SetUserWriteBlockModeCommand> {
public:
using Request = SetUserWriteBlockMode;
@@ -73,6 +75,13 @@ public:
repl::ReplicationCoordinator::modeNone);
{
+ // TODO SERVER-65010 Remove FCV guard once 6.0 has branched out
+ FixedFCVRegion fixedFcvRegion(opCtx);
+ uassert(ErrorCodes::IllegalOperation,
+ "featureFlagUserWriteBlocking not enabled",
+ gFeatureFlagUserWriteBlocking.isEnabled(
+ serverGlobalParams.featureCompatibility));
+
if (request().getGlobal()) {
UserWritesRecoverableCriticalSectionService::get(opCtx)
->acquireRecoverableCriticalSectionBlockingUserWrites(
@@ -86,17 +95,17 @@ public:
UserWritesRecoverableCriticalSectionService::
kGlobalUserWritesNamespace);
}
-
- // Wait for the writes to the UserWritesRecoverableCriticalSection collection to be
- // majority commited.
- auto& replClient = repl::ReplClientInfo::forClient(opCtx->getClient());
- WriteConcernResult writeConcernResult;
- WriteConcernOptions majority(WriteConcernOptions::kMajority,
- WriteConcernOptions::SyncMode::UNSET,
- WriteConcernOptions::kWriteConcernTimeoutUserCommand);
- uassertStatusOK(waitForWriteConcern(
- opCtx, replClient.getLastOp(), majority, &writeConcernResult));
}
+
+ // Wait for the writes to the UserWritesRecoverableCriticalSection collection to be
+ // majority commited.
+ auto& replClient = repl::ReplClientInfo::forClient(opCtx->getClient());
+ WriteConcernResult writeConcernResult;
+ WriteConcernOptions majority(WriteConcernOptions::kMajority,
+ WriteConcernOptions::SyncMode::UNSET,
+ WriteConcernOptions::kWriteConcernTimeoutUserCommand);
+ uassertStatusOK(
+ waitForWriteConcern(opCtx, replClient.getLastOp(), majority, &writeConcernResult));
}
private:
diff --git a/src/mongo/db/s/config/configsvr_coordinator.h b/src/mongo/db/s/config/configsvr_coordinator.h
index 6c7facecb9a..ff6d6de23dc 100644
--- a/src/mongo/db/s/config/configsvr_coordinator.h
+++ b/src/mongo/db/s/config/configsvr_coordinator.h
@@ -56,6 +56,10 @@ public:
virtual bool hasSameOptions(const BSONObj&) const = 0;
+ ConfigsvrCoordinatorTypeEnum coordinatorType() const {
+ return _coordId.getCoordinatorType();
+ }
+
protected:
const ConfigsvrCoordinatorId _coordId;
diff --git a/src/mongo/db/s/config/configsvr_coordinator_service.cpp b/src/mongo/db/s/config/configsvr_coordinator_service.cpp
index 1a30da0a9b4..b3ba449ac0f 100644
--- a/src/mongo/db/s/config/configsvr_coordinator_service.cpp
+++ b/src/mongo/db/s/config/configsvr_coordinator_service.cpp
@@ -93,4 +93,20 @@ ConfigsvrCoordinatorService::constructInstance(BSONObj initialState) {
}
}
+bool ConfigsvrCoordinatorService::isAnyCoordinatorOfGivenTypeRunning(
+ OperationContext* opCtx, ConfigsvrCoordinatorTypeEnum coordinatorType) {
+
+ const auto instances = getAllInstances(opCtx);
+ for (const auto& instance : instances) {
+ auto typedInstance = checked_pointer_cast<ConfigsvrCoordinator>(instance);
+ if (typedInstance->coordinatorType() == coordinatorType) {
+ if (!typedInstance->getCompletionFuture().isReady()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
} // namespace mongo
diff --git a/src/mongo/db/s/config/configsvr_coordinator_service.h b/src/mongo/db/s/config/configsvr_coordinator_service.h
index 0d022edfef8..7b8f30a3ed3 100644
--- a/src/mongo/db/s/config/configsvr_coordinator_service.h
+++ b/src/mongo/db/s/config/configsvr_coordinator_service.h
@@ -67,6 +67,9 @@ public:
const std::vector<const PrimaryOnlyService::Instance*>& existingInstances) override{};
std::shared_ptr<Instance> constructInstance(BSONObj initialState) override;
+
+ bool isAnyCoordinatorOfGivenTypeRunning(OperationContext* opCtx,
+ ConfigsvrCoordinatorTypeEnum coordinatorType);
};
} // namespace mongo
diff --git a/src/mongo/db/s/config/configsvr_set_user_write_block_mode_command.cpp b/src/mongo/db/s/config/configsvr_set_user_write_block_mode_command.cpp
index de09fb92539..05414da45f9 100644
--- a/src/mongo/db/s/config/configsvr_set_user_write_block_mode_command.cpp
+++ b/src/mongo/db/s/config/configsvr_set_user_write_block_mode_command.cpp
@@ -33,6 +33,7 @@
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/commands.h"
+#include "mongo/db/commands/feature_compatibility_version.h"
#include "mongo/db/s/config/configsvr_coordinator_service.h"
#include "mongo/db/s/config/set_user_write_block_mode_coordinator.h"
#include "mongo/db/server_feature_flags_gen.h"
@@ -58,22 +59,29 @@ public:
serverGlobalParams.clusterRole == ClusterRole::ConfigServer);
CommandHelpers::uassertCommandRunWithMajority(Request::kCommandName,
opCtx->getWriteConcern());
- uassert(
- ErrorCodes::IllegalOperation,
- "featureFlagUserWriteBlocking not enabled",
- gFeatureFlagUserWriteBlocking.isEnabled(serverGlobalParams.featureCompatibility));
const auto startBlocking = request().getGlobal();
- SetUserWriteBlockModeCoordinatorDocument coordinatorDoc{startBlocking};
- coordinatorDoc.setConfigsvrCoordinatorMetadata(
- {ConfigsvrCoordinatorTypeEnum::kSetUserWriteBlockMode});
- const auto coordinatorDocBSON = coordinatorDoc.toBSON();
+ const auto coordinatorCompletionFuture = [&]() -> SharedSemiFuture<void> {
+ // TODO SERVER-65010 Remove FCV guard once 6.0 has branched out
+ FixedFCVRegion fixedFcvRegion(opCtx);
+ uassert(ErrorCodes::IllegalOperation,
+ "featureFlagUserWriteBlocking not enabled",
+ gFeatureFlagUserWriteBlocking.isEnabled(
+ serverGlobalParams.featureCompatibility));
- const auto service = ConfigsvrCoordinatorService::getService(opCtx);
- const auto instance = service->getOrCreateService(opCtx, coordinatorDoc.toBSON());
+ SetUserWriteBlockModeCoordinatorDocument coordinatorDoc{startBlocking};
+ coordinatorDoc.setConfigsvrCoordinatorMetadata(
+ {ConfigsvrCoordinatorTypeEnum::kSetUserWriteBlockMode});
+ const auto coordinatorDocBSON = coordinatorDoc.toBSON();
- instance->getCompletionFuture().get(opCtx);
+ const auto service = ConfigsvrCoordinatorService::getService(opCtx);
+ const auto instance = service->getOrCreateService(opCtx, coordinatorDoc.toBSON());
+
+ return instance->getCompletionFuture();
+ }();
+
+ coordinatorCompletionFuture.get(opCtx);
}
private: