From c96f8dacc4c71b4774c932a07be4fac71b6db628 Mon Sep 17 00:00:00 2001 From: Spencer Jackson Date: Tue, 5 Apr 2022 17:05:03 +0000 Subject: SERVER-63725 Block FCV downgrade on User Write Block Mode --- jstests/noPassthrough/libs/user_write_blocking.js | 219 +++++++++++++++++++++ jstests/noPassthrough/set_user_write_block_mode.js | 194 ++---------------- jstests/noPassthrough/user_write_block_fcv.js | 83 ++++++++ src/mongo/db/commands/SConscript | 1 + .../set_feature_compatibility_version_command.cpp | 24 +++ .../commands/set_user_write_block_mode_command.cpp | 31 +-- src/mongo/db/s/config/configsvr_coordinator.h | 4 + .../db/s/config/configsvr_coordinator_service.cpp | 16 ++ .../db/s/config/configsvr_coordinator_service.h | 3 + ...configsvr_set_user_write_block_mode_command.cpp | 30 +-- 10 files changed, 400 insertions(+), 205 deletions(-) create mode 100644 jstests/noPassthrough/libs/user_write_blocking.js create mode 100644 jstests/noPassthrough/user_write_block_fcv.js 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 { 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(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& existingInstances) override{}; std::shared_ptr 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 { + // 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: -- cgit v1.2.1