diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/read_write_concern_defaults.cpp | 17 | ||||
-rw-r--r-- | src/mongo/db/read_write_concern_defaults.h | 5 | ||||
-rw-r--r-- | src/mongo/db/read_write_concern_defaults_test.cpp | 39 | ||||
-rw-r--r-- | src/mongo/db/repl/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/repl/replication_coordinator_impl.cpp | 23 | ||||
-rw-r--r-- | src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/repl/replication_coordinator_test_fixture.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/repl/replication_coordinator_test_fixture.h | 4 |
8 files changed, 91 insertions, 10 deletions
diff --git a/src/mongo/db/read_write_concern_defaults.cpp b/src/mongo/db/read_write_concern_defaults.cpp index 183805cf64d..c982ef78bda 100644 --- a/src/mongo/db/read_write_concern_defaults.cpp +++ b/src/mongo/db/read_write_concern_defaults.cpp @@ -161,6 +161,23 @@ RWConcernDefault ReadWriteConcernDefaults::generateNewCWRWCToBeSavedOnDisk( return rwc; } +bool ReadWriteConcernDefaults::isCWWCSet(OperationContext* opCtx) { + // TODO (SERVER-57042): Call getDefault() instead and return writeConcernSource == KGlobal. + auto cached = _getDefaultCWRWCFromDisk(opCtx); + if (!cached) { + return false; + } + + auto wc = cached.get().getDefaultWriteConcern(); + if (!wc) { + return false; + } + + // CWWC should have "usedDefault" flag set to false, as this flag only set to true if the + // default constructed WC is used. + return !wc.get().usedDefault; +} + void ReadWriteConcernDefaults::observeDirectWriteToConfigSettings(OperationContext* opCtx, BSONElement idElem, boost::optional<BSONObj> newDoc) { diff --git a/src/mongo/db/read_write_concern_defaults.h b/src/mongo/db/read_write_concern_defaults.h index 2108cb8c189..1ce216c2b80 100644 --- a/src/mongo/db/read_write_concern_defaults.h +++ b/src/mongo/db/read_write_concern_defaults.h @@ -130,6 +130,11 @@ public: const boost::optional<WriteConcern>& wc); /** + * Returns true if cluster-wide write concern is set and false otherwise. + */ + bool isCWWCSet(OperationContext* opCtx); + + /** * Invalidates the cached RWC defaults, causing them to be refreshed. * * After this call returns, the read methods below (getDefault, getDefaultReadConcern, diff --git a/src/mongo/db/read_write_concern_defaults_test.cpp b/src/mongo/db/read_write_concern_defaults_test.cpp index 1d558ff2ed4..73009587628 100644 --- a/src/mongo/db/read_write_concern_defaults_test.cpp +++ b/src/mongo/db/read_write_concern_defaults_test.cpp @@ -54,6 +54,10 @@ protected: return ReadWriteConcernDefaults::get(getServiceContext()).getDefault(_opCtx); } + bool isCWWCSet() { + return ReadWriteConcernDefaults::get(getServiceContext()).isCWWCSet(_opCtx); + } + ReadWriteConcernDefaultsLookupMock _lookupMock; bool _isDefaultWCMajorityEnabled{ @@ -72,6 +76,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithAbsentCWRWCWithImplicitWC auto defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); ASSERT(!defaults.getDefaultWriteConcern()); + ASSERT(!isCWWCSet()); ASSERT(!defaults.getUpdateOpTime()); ASSERT(!defaults.getUpdateWallClockTime()); ASSERT_EQ(Date_t(), defaults.localUpdateWallClockTime()); @@ -89,6 +94,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithAbsentCWRWCWithImplicitWC // By not calling _lookupMock.setLookupCallReturnValue(), tests _defaults.lookup() returning // boost::none. + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); ASSERT(defaults.getDefaultWriteConcern()); @@ -105,6 +111,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithCWRWCNeverSetWithImplicit // _defaults.lookup() returning default constructed RWConcern not boost::none. _lookupMock.setLookupCallReturnValue({}); + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); ASSERT(!defaults.getDefaultWriteConcern()); @@ -124,6 +131,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithCWRWCNeverSetWithImplicit createDefaults(true /* isImplicitWCMajority */); // _defaults.lookup() returning default constructed RWConcern not boost::none. + ASSERT(!isCWWCSet()); _lookupMock.setLookupCallReturnValue({}); auto defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); @@ -143,6 +151,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithUnsetCWRWCWithImplicitWCW newDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(1234)); _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); ASSERT(!defaults.getDefaultWriteConcern()); @@ -166,6 +175,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithUnsetCWRWCWithImplicitWCM newDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(1234)); _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); ASSERT(defaults.getDefaultWriteConcern()); @@ -184,6 +194,8 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithSetCWRWCWithImplicitWCW1) repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern)); WriteConcernOptions wc; wc.wNumNodes = 4; + wc.usedDefault = false; + wc.usedDefaultW = false; newDefaults.setDefaultWriteConcern(wc); newDefaults.setUpdateOpTime(Timestamp(1, 2)); newDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(1234)); @@ -192,6 +204,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithSetCWRWCWithImplicitWCW1) } _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); + ASSERT(isCWWCSet()); auto defaults = getDefault(); ASSERT(defaults.getDefaultReadConcern()->getLevel() == repl::ReadConcernLevel::kLocalReadConcern); @@ -226,6 +239,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithSetCWRWCWithImplicitWCMaj } _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); + ASSERT(isCWWCSet()); auto defaults = getDefault(); ASSERT(defaults.getDefaultReadConcern()->getLevel() == repl::ReadConcernLevel::kLocalReadConcern); @@ -246,17 +260,14 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWriteConcernSourceImplicitWit createDefaults(true /* isImplicitWCMajority */); RWConcernDefault newDefaults; - WriteConcernOptions wc; - wc.wNumNodes = 4; - wc.usedDefault = true; - wc.usedDefaultW = false; - newDefaults.setDefaultWriteConcern(wc); + newDefaults.setDefaultWriteConcern(WriteConcernOptions()); newDefaults.setUpdateOpTime(Timestamp(1, 2)); newDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(1234)); newDefaults.setDefaultWriteConcernSource(DefaultWriteConcernSourceEnum::kGlobal); _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); + ASSERT(!isCWWCSet()); // The default write concern source should be set to implicit if wc.usedDefault is true auto defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); @@ -279,6 +290,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithSetAndUnSetCWRCWithImplic newDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(1234)); _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT(defaults.getDefaultReadConcern()->getLevel() == repl::ReadConcernLevel::kLocalReadConcern); @@ -298,6 +310,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithSetAndUnSetCWRCWithImplic ReadWriteConcernDefaults::get(getServiceContext()).refreshIfNecessary(_opCtx); + ASSERT(!isCWWCSet()); defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); ASSERT(!defaults.getDefaultWriteConcern()); @@ -324,7 +337,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithSetAndUnSetCWRCWithImplic newDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(1234)); _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); - + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT(defaults.getDefaultReadConcern()->getLevel() == repl::ReadConcernLevel::kLocalReadConcern); @@ -343,6 +356,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithSetAndUnSetCWRCWithImplic ReadWriteConcernDefaults::get(getServiceContext()).refreshIfNecessary(_opCtx); + ASSERT(!isCWWCSet()); defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); ASSERT(defaults.getDefaultWriteConcern()); @@ -358,6 +372,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultLookupFailure) { createDefaults(true /* isImplicitWCMajority */); _lookupMock.setLookupCallFailure(Status{ErrorCodes::Error(1234), "foobar"}); ASSERT_THROWS_CODE_AND_WHAT(getDefault(), AssertionException, 1234, "foobar"); + ASSERT_THROWS_CODE_AND_WHAT(isCWWCSet(), AssertionException, 1234, "foobar"); } TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithoutInvalidateDoesNotCallLookup) { @@ -368,6 +383,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithoutInvalidateDoesNotCallL newDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(1234)); _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); ASSERT(!defaults.getDefaultWriteConcern()); @@ -383,6 +399,8 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithoutInvalidateDoesNotCallL repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern)); WriteConcernOptions wc; wc.wNumNodes = 4; + wc.usedDefault = false; + wc.usedDefaultW = false; newDefaults2.setDefaultWriteConcern(wc); newDefaults2.setUpdateOpTime(Timestamp(3, 4)); newDefaults2.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(5678)); @@ -391,6 +409,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestGetDefaultWithoutInvalidateDoesNotCallL } _lookupMock.setLookupCallReturnValue(std::move(newDefaults2)); + ASSERT(!isCWWCSet()); auto defaults2 = getDefault(); ASSERT(!defaults2.getDefaultReadConcern()); ASSERT(!defaults2.getDefaultWriteConcern()); @@ -411,6 +430,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestInvalidate) { newDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(1234)); _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); ASSERT(!defaults.getDefaultWriteConcern()); @@ -426,6 +446,8 @@ TEST_F(ReadWriteConcernDefaultsTest, TestInvalidate) { repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern)); WriteConcernOptions wc; wc.wNumNodes = 4; + wc.usedDefault = false; + wc.usedDefaultW = false; newDefaults2.setDefaultWriteConcern(wc); newDefaults2.setUpdateOpTime(Timestamp(3, 4)); newDefaults2.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(5678)); @@ -435,6 +457,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestInvalidate) { _lookupMock.setLookupCallReturnValue(std::move(newDefaults2)); ReadWriteConcernDefaults::get(getServiceContext()).invalidate(); + ASSERT(isCWWCSet()); auto defaults2 = getDefault(); ASSERT(defaults2.getDefaultReadConcern()->getLevel() == repl::ReadConcernLevel::kLocalReadConcern); @@ -451,6 +474,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestRefreshDefaultsWithEmptyCacheAndAbsentD createDefaults(false /* isImplicitWCMajority */); ReadWriteConcernDefaults::get(getServiceContext()).refreshIfNecessary(_opCtx); + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT(!defaults.getDefaultReadConcern()); ASSERT(!defaults.getDefaultWriteConcern()); @@ -472,6 +496,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestRefreshDefaultsWithEmptyCacheAndSetDefa ReadWriteConcernDefaults::get(getServiceContext()).refreshIfNecessary(_opCtx); + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT_EQ(Timestamp(1, 2), *defaults.getUpdateOpTime()); ASSERT_EQ(1234, defaults.getUpdateWallClockTime()->toMillisSinceEpoch()); @@ -489,6 +514,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestRefreshDefaultsWithHigherEpoch) { newDefaults.setUpdateWallClockTime(Date_t::fromMillisSinceEpoch(1234)); _lookupMock.setLookupCallReturnValue(std::move(newDefaults)); + ASSERT(!isCWWCSet()); auto defaults = getDefault(); ASSERT_EQ(Timestamp(1, 2), *defaults.getUpdateOpTime()); ASSERT_EQ(1234, defaults.getUpdateWallClockTime()->toMillisSinceEpoch()); @@ -503,6 +529,7 @@ TEST_F(ReadWriteConcernDefaultsTest, TestRefreshDefaultsWithHigherEpoch) { ReadWriteConcernDefaults::get(getServiceContext()).refreshIfNecessary(_opCtx); + ASSERT(!isCWWCSet()); auto defaults2 = getDefault(); ASSERT_EQ(Timestamp(3, 4), *defaults2.getUpdateOpTime()); ASSERT_EQ(5678, defaults2.getUpdateWallClockTime()->toMillisSinceEpoch()); diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index 67b2c9f9580..eb8ce074e9e 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -1686,6 +1686,7 @@ env.CppUnitTest( 'topology_coordinator_v1_test.cpp', ], LIBDEPS=[ + '$BUILD_DIR/mongo/db/read_write_concern_defaults_mock', 'isself', 'repl_coordinator_impl', 'repl_coordinator_test_fixture', diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp index 0fb1400aafe..80b7a39f614 100644 --- a/src/mongo/db/repl/replication_coordinator_impl.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl.cpp @@ -3451,6 +3451,29 @@ Status ReplicationCoordinatorImpl::_doReplSetReconfig(OperationContext* opCtx, return status; ReplSetConfig newConfig = newConfigStatus.getValue(); + // If the new config changes the replica set's implicit default write concern, we fail the + // reconfig command. This includes force reconfigs, but excludes reconfigs that bump the config + // term during step-up. The user should set a cluster-wide write concern and attempt the + // reconfig command again. We also need to exclude shard servers from this validation, as shard + // servers don't store the cluster-wide write concern. + if (!skipSafetyChecks /* skipping step-up reconfig */ && + repl::feature_flags::gDefaultWCMajority.isEnabled( + serverGlobalParams.featureCompatibility) && + serverGlobalParams.clusterRole != ClusterRole::ShardServer && + !repl::enableDefaultWriteConcernUpdatesForInitiate.load()) { + bool currIDWC = oldConfig.isImplicitDefaultWriteConcernMajority(); + bool newIDWC = newConfig.isImplicitDefaultWriteConcernMajority(); + bool isCWWCSet = ReadWriteConcernDefaults::get(opCtx).isCWWCSet(opCtx); + if (!isCWWCSet && currIDWC != newIDWC) { + return Status( + ErrorCodes::NewReplicaSetConfigurationIncompatible, + str::stream() + << "Reconfig attempted to install a config that would change the " + "implicit default write concern. Use the setDefaultRWConcern command to " + "set a cluster-wide write concern and try the reconfig again."); + } + } + BSONObj oldConfigObj = oldConfig.toBSON(); BSONObj newConfigObj = newConfig.toBSON(); audit::logReplSetReconfig(opCtx->getClient(), &oldConfigObj, &newConfigObj); diff --git a/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp b/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp index b3fb19795f7..4fdb2403540 100644 --- a/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp @@ -32,6 +32,7 @@ #include "mongo/platform/basic.h" #include "mongo/db/jsobj.h" +#include "mongo/db/read_write_concern_defaults_cache_lookup_mock.h" #include "mongo/db/repl/repl_server_parameters_gen.h" #include "mongo/db/repl/repl_set_config.h" #include "mongo/db/repl/repl_set_heartbeat_args_v1.h" @@ -1953,6 +1954,14 @@ TEST_F(ReplCoordReconfigTest, NodesWithNewlyAddedFieldSetHavePriorityZero) { } TEST_F(ReplCoordReconfigTest, ArbiterNodesShouldNeverHaveNewlyAddedField) { + RWConcernDefault newDefaults; + WriteConcernOptions wc; + wc.wMode = "majority"; + wc.usedDefault = false; + wc.usedDefaultW = false; + newDefaults.setDefaultWriteConcern(wc); + lookupMock.setLookupCallReturnValue(std::move(newDefaults)); + setUpNewlyAddedFieldTest(); auto opCtx = makeOperationContext(); diff --git a/src/mongo/db/repl/replication_coordinator_test_fixture.cpp b/src/mongo/db/repl/replication_coordinator_test_fixture.cpp index 802b4e921fb..2c6a45f4d0a 100644 --- a/src/mongo/db/repl/replication_coordinator_test_fixture.cpp +++ b/src/mongo/db/repl/replication_coordinator_test_fixture.cpp @@ -144,8 +144,7 @@ void ReplCoordTest::init() { // The ReadWriteConcernDefaults decoration on the service context won't always be created, // so we should manually instantiate it to ensure it exists in our tests. - ReadWriteConcernDefaults::create(service, _lookupMock.getFetchDefaultsFn()); - enableDefaultWriteConcernUpdatesForInitiate.store(true); + ReadWriteConcernDefaults::create(service, lookupMock.getFetchDefaultsFn()); TopologyCoordinator::Options settings; auto topo = std::make_unique<TopologyCoordinator>(settings); diff --git a/src/mongo/db/repl/replication_coordinator_test_fixture.h b/src/mongo/db/repl/replication_coordinator_test_fixture.h index 46a81edaff0..4589c82aca5 100644 --- a/src/mongo/db/repl/replication_coordinator_test_fixture.h +++ b/src/mongo/db/repl/replication_coordinator_test_fixture.h @@ -320,6 +320,8 @@ protected: */ void simulateCatchUpAbort(); + ReadWriteConcernDefaultsLookupMock lookupMock; + private: std::unique_ptr<ReplicationCoordinatorImpl> _repl; // Owned by ReplicationCoordinatorImpl @@ -335,8 +337,6 @@ private: ReplSettings _settings; bool _callShutdown = false; - - ReadWriteConcernDefaultsLookupMock _lookupMock; }; } // namespace repl |