diff options
author | Ali Mir <ali.mir@mongodb.com> | 2020-11-04 17:25:32 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-01-28 04:26:57 +0000 |
commit | 35a6a97338930d75a7f5cfd89671ae9af65aaf85 (patch) | |
tree | d0f026c6f06c0a38795fa4eaa56040d145034679 /src | |
parent | 5a5a75e4149faae8f1b6e95d60b84cc7f33d4e2b (diff) | |
download | mongo-35a6a97338930d75a7f5cfd89671ae9af65aaf85.tar.gz |
SERVER-50423 Change memberConfig's slaveDelay field to secondaryDelaySecs
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/commands/set_feature_compatibility_version_command.cpp | 73 | ||||
-rw-r--r-- | src/mongo/db/repl/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/repl/member_config.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/repl/member_config.h | 25 | ||||
-rw-r--r-- | src/mongo/db/repl/member_config.idl | 13 | ||||
-rw-r--r-- | src/mongo/db/repl/member_config_test.cpp | 128 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_server_parameters.idl | 6 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config.cpp | 42 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config.h | 21 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config_checks.cpp | 59 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config_test.cpp | 46 | ||||
-rw-r--r-- | src/mongo/db/repl/replication_coordinator_impl.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/repl/topology_coordinator.cpp | 19 | ||||
-rw-r--r-- | src/mongo/db/repl/topology_coordinator_v1_test.cpp | 15 | ||||
-rw-r--r-- | src/mongo/dbtests/mock/mock_replica_set.cpp | 6 |
15 files changed, 415 insertions, 51 deletions
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 7f73024c99d..cbb1a42b858 100644 --- a/src/mongo/db/commands/set_feature_compatibility_version_command.cpp +++ b/src/mongo/db/commands/set_feature_compatibility_version_command.cpp @@ -50,6 +50,8 @@ #include "mongo/db/ops/write_ops.h" #include "mongo/db/read_write_concern_defaults.h" #include "mongo/db/repl/repl_client_info.h" +#include "mongo/db/repl/repl_server_parameters_gen.h" +#include "mongo/db/repl/repl_set_config.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/s/active_migrations_registry.h" #include "mongo/db/s/config/sharding_catalog_manager.h" @@ -239,11 +241,47 @@ public: FeatureCompatibilityVersion::validateSetFeatureCompatibilityVersionRequest( actualVersion, requestedVersion, isFromConfigServer); + auto replCoord = repl::ReplicationCoordinator::get(opCtx); + const bool isReplSet = + replCoord->getReplicationMode() == repl::ReplicationCoordinator::modeReplSet; if (actualVersion < requestedVersion) { checkInitialSyncFinished(opCtx); FeatureCompatibilityVersion::updateFeatureCompatibilityVersionDocument( opCtx, actualVersion, requestedVersion, isFromConfigServer); + + // If the 'useSecondaryDelaySecs' feature flag is enabled in the upgraded FCV, issue a + // reconfig to change the 'slaveDelay' field to 'secondaryDelaySecs'. + if (isReplSet && repl::feature_flags::gUseSecondaryDelaySecs.isEnabledAndIgnoreFCV() && + requestedVersion == ServerGlobalParams::FeatureCompatibility::kLatest) { + auto getNewConfig = [&](const repl::ReplSetConfig& oldConfig, long long term) { + auto newConfig = oldConfig.getMutable(); + newConfig.setConfigVersion(newConfig.getConfigVersion() + 1); + for (auto mem = oldConfig.membersBegin(); mem != oldConfig.membersEnd(); + mem++) { + newConfig.useSecondaryDelaySecsFieldName(mem->getId()); + } + return repl::ReplSetConfig(std::move(newConfig)); + }; + auto status = replCoord->doReplSetReconfig(opCtx, getNewConfig, true /* force */); + uassertStatusOKWithContext(status, "Failed to upgrade the replica set config"); + + LOGV2(5042301, + "Waiting for the upgraded replica set config to propagate to a majority"); + // If a write concern is given, we'll use its wTimeout. It's kNoTimeout by default. + WriteConcernOptions writeConcern( + repl::ReplSetConfig::kConfigMajorityWriteConcernModeName, + WriteConcernOptions::SyncMode::NONE, + opCtx->getWriteConcern().wTimeout); + writeConcern.checkCondition = WriteConcernOptions::CheckCondition::Config; + repl::OpTime fakeOpTime(Timestamp(1, 1), replCoord->getTerm()); + uassertStatusOKWithContext( + replCoord->awaitReplication(opCtx, fakeOpTime, writeConcern).status, + "Failed to wait for the upgraded replica set config to propagate to a " + "majority"); + LOGV2(5042302, "The upgraded replica set config has been propagated to a majority"); + } + { // Take the global lock in S mode to create a barrier for operations taking the // global IX or X locks. This ensures that either @@ -322,6 +360,41 @@ public: FeatureCompatibilityVersion::updateFeatureCompatibilityVersionDocument( opCtx, actualVersion, requestedVersion, isFromConfigServer); + // If the 'useSecondaryDelaySecs' feature flag is disabled in the downgraded FCV, issue + // a reconfig to change the 'secondaryDelaySecs' field to 'slaveDelay'. + if (isReplSet && repl::feature_flags::gUseSecondaryDelaySecs.isEnabledAndIgnoreFCV() && + requestedVersion < repl::feature_flags::gUseSecondaryDelaySecs.getVersion()) { + auto getNewConfig = [&](const repl::ReplSetConfig& oldConfig, long long term) { + auto newConfig = oldConfig.getMutable(); + newConfig.setConfigVersion(newConfig.getConfigVersion() + 1); + for (auto mem = oldConfig.membersBegin(); mem != oldConfig.membersEnd(); + mem++) { + newConfig.useSlaveDelayFieldName(mem->getId()); + } + + return repl::ReplSetConfig(std::move(newConfig)); + }; + + auto status = replCoord->doReplSetReconfig(opCtx, getNewConfig, true /* force */); + uassertStatusOKWithContext(status, "Failed to downgrade the replica set config"); + + LOGV2(5042303, + "Waiting for the downgraded replica set config to propagate to a majority"); + // If a write concern is given, we'll use its wTimeout. It's kNoTimeout by default. + WriteConcernOptions writeConcern( + repl::ReplSetConfig::kConfigMajorityWriteConcernModeName, + WriteConcernOptions::SyncMode::NONE, + opCtx->getWriteConcern().wTimeout); + writeConcern.checkCondition = WriteConcernOptions::CheckCondition::Config; + repl::OpTime fakeOpTime(Timestamp(1, 1), replCoord->getTerm()); + uassertStatusOKWithContext( + replCoord->awaitReplication(opCtx, fakeOpTime, writeConcern).status, + "Failed to wait for the downgraded replica set config to propagate to a " + "majority"); + LOGV2(5042304, + "The downgraded replica set config has been propagated to a majority"); + } + { // Take the global lock in S mode to create a barrier for operations taking the // global IX or X locks. This ensures that either diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index f8d16936ad9..ce657d65aad 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -853,6 +853,7 @@ env.Library( '$BUILD_DIR/mongo/client/connection_string', '$BUILD_DIR/mongo/db/common', '$BUILD_DIR/mongo/db/server_options_core', + '$BUILD_DIR/mongo/idl/feature_flag', '$BUILD_DIR/mongo/rpc/command_status', '$BUILD_DIR/mongo/rpc/metadata', '$BUILD_DIR/mongo/transport/transport_layer_common', diff --git a/src/mongo/db/repl/member_config.cpp b/src/mongo/db/repl/member_config.cpp index 7e962d57267..6569a8f73b9 100644 --- a/src/mongo/db/repl/member_config.cpp +++ b/src/mongo/db/repl/member_config.cpp @@ -91,7 +91,7 @@ MemberConfig::MemberConfig(const BSONObj& mcfg) { if (!isVoter()) { uasserted(ErrorCodes::BadValue, "priority must be 0 when non-voting (votes:0)"); } - if (getSlaveDelay() > Seconds(0)) { + if (getSecondaryDelay() > Seconds(0)) { uasserted(ErrorCodes::BadValue, "priority must be 0 when slaveDelay is used"); } if (isHidden()) { @@ -211,7 +211,13 @@ BSONObj MemberConfig::toBSON(bool omitNewlyAddedField) const { _splitHorizon.toBSON(configBuilder); - configBuilder.append(kSlaveDelaySecsFieldName, getSlaveDelaySecs()); + if (getSecondaryDelaySecs()) { + configBuilder.append(kSecondaryDelaySecsFieldName, getSecondaryDelaySecs().get()); + } + if (getSlaveDelaySecs()) { + configBuilder.append(kSlaveDelaySecsFieldName, getSlaveDelaySecs().get()); + } + configBuilder.append(kVotesFieldName, MemberConfigBase::getVotes() ? 1 : 0); return configBuilder.obj(); } diff --git a/src/mongo/db/repl/member_config.h b/src/mongo/db/repl/member_config.h index d6bb75622f3..0122b1f05a0 100644 --- a/src/mongo/db/repl/member_config.h +++ b/src/mongo/db/repl/member_config.h @@ -63,6 +63,7 @@ public: using MemberConfigBase::kIdFieldName; using MemberConfigBase::kNewlyAddedFieldName; using MemberConfigBase::kPriorityFieldName; + using MemberConfigBase::kSecondaryDelaySecsFieldName; using MemberConfigBase::kSlaveDelaySecsFieldName; using MemberConfigBase::kTagsFieldName; using MemberConfigBase::kVotesFieldName; @@ -134,8 +135,14 @@ public: * Gets the amount of time behind the primary that this member will atempt to * remain. Zero seconds means stay as caught up as possible. */ - Seconds getSlaveDelay() const { - return Seconds(getSlaveDelaySecs()); + Seconds getSecondaryDelay() const { + if (getSecondaryDelaySecs()) { + return Seconds(getSecondaryDelaySecs().get()); + } + if (getSlaveDelaySecs()) { + return Seconds(getSlaveDelaySecs().get()); + } + return Seconds(0); } /** @@ -161,6 +168,20 @@ public: } /** + * Returns true if this member has the field 'slaveDelay'. + */ + bool hasSlaveDelay() const { + return getSlaveDelaySecs().has_value(); + } + + /** + * Returns true if this member has the field 'secondaryDelaySecs'. + */ + bool hasSecondaryDelaySecs() const { + return getSecondaryDelaySecs().has_value(); + } + + /** * Returns true if this member is newly added from reconfig. This indicates that this node * should be treated as non-voting. */ diff --git a/src/mongo/db/repl/member_config.idl b/src/mongo/db/repl/member_config.idl index 9f937c39eb8..4340d8957e9 100644 --- a/src/mongo/db/repl/member_config.idl +++ b/src/mongo/db/repl/member_config.idl @@ -95,10 +95,19 @@ structs: slaveDelay: cpp_name: slaveDelaySecs type: safeInt64 - default: 0 + optional: true + validator: { gte: 0, lte: 31622400 } + description: "The number of seconds 'behind' the primary that this replica set + member should 'lag'. This is a legacy field kept for backwards + compatibility, and 5.0+ nodes should use secondaryDelaySecs" + secondaryDelaySecs: + cpp_name: secondaryDelaySecs + type: safeInt64 + optional: true validator: { gte: 0, lte: 31622400 } description: "The number of seconds 'behind' the primary that this replica set - member should 'lag'" + member should 'lag'. Only 5.0+ nodes will use this field, + and older nodes will use slaveDelay" arbiterOnly: type: safeBool default: false diff --git a/src/mongo/db/repl/member_config_test.cpp b/src/mongo/db/repl/member_config_test.cpp index 00c9fb36765..a9cad26e77c 100644 --- a/src/mongo/db/repl/member_config_test.cpp +++ b/src/mongo/db/repl/member_config_test.cpp @@ -49,7 +49,7 @@ TEST(MemberConfig, ParseMinimalMemberConfigAndCheckDefaults) { ASSERT_EQUALS(MemberId(0), mc.getId()); ASSERT_EQUALS(HostAndPort("localhost", 12345), mc.getHostAndPort()); ASSERT_EQUALS(1.0, mc.getPriority()); - ASSERT_EQUALS(Seconds(0), mc.getSlaveDelay()); + ASSERT_EQUALS(Seconds(0), mc.getSecondaryDelay()); ASSERT_TRUE(mc.isVoter()); ASSERT_FALSE(mc.isHidden()); ASSERT_FALSE(mc.isArbiter()); @@ -675,7 +675,7 @@ TEST(MemberConfig, ParseSlaveDelay) { << "h" << "priority" << 0 << "slaveDelay" << 100), &tagConfig); - ASSERT_EQUALS(Seconds(100), mc.getSlaveDelay()); + ASSERT_EQUALS(Seconds(100), mc.getSecondaryDelay()); } { ASSERT_THROWS(MemberConfig(BSON("_id" << 0 << "host" @@ -715,6 +715,54 @@ TEST(MemberConfig, ParseSlaveDelay) { } } +TEST(MemberConfig, ParseSecondaryDelay) { + ReplSetTagConfig tagConfig; + { + MemberConfig mc(BSON("_id" << 0 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << 100), + &tagConfig); + ASSERT_EQUALS(Seconds(100), mc.getSecondaryDelay()); + } + { + ASSERT_THROWS(MemberConfig(BSON("_id" << 0 << "host" + << "h" + << "secondaryDelaySecs" << 100), + &tagConfig), + ExceptionFor<ErrorCodes::BadValue>); + } + { + MemberConfig mc(BSON("_id" << 0 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << 0), + &tagConfig); + } + { + MemberConfig mc(BSON("_id" << 0 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << 3600 * 10), + &tagConfig); + } + { + ASSERT_THROWS_CODE( + MemberConfig(BSON("_id" << 0 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << -1), + &tagConfig), + AssertionException, + 51024); + } + { + ASSERT_THROWS_CODE( + MemberConfig(BSON("_id" << 0 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << 3600 * 24 * 400), + &tagConfig), + AssertionException, + 51024); + } +} + TEST(MemberConfig, ParseAcceptsAnyNumberSlaveDelay) { ReplSetTagConfig tagConfig; { @@ -722,28 +770,28 @@ TEST(MemberConfig, ParseAcceptsAnyNumberSlaveDelay) { << "h" << "priority" << 0 << "slaveDelay" << 0), &tagConfig); - ASSERT_EQUALS(mc.getSlaveDelay(), Seconds(0)); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(0)); } { MemberConfig mc(BSON("_id" << 1 << "host" << "h" << "priority" << 0 << "slaveDelay" << 0.5), &tagConfig); - ASSERT_EQUALS(mc.getSlaveDelay(), Seconds(0)); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(0)); } { MemberConfig mc(BSON("_id" << 1 << "host" << "h" << "priority" << 0 << "slaveDelay" << -0.5), &tagConfig); - ASSERT_EQUALS(mc.getSlaveDelay(), Seconds(0)); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(0)); } { MemberConfig mc(BSON("_id" << 1 << "host" << "h" << "priority" << 0 << "slaveDelay" << 1), &tagConfig); - ASSERT_EQUALS(mc.getSlaveDelay(), Seconds(1)); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(1)); } { ASSERT_THROWS_CODE(MemberConfig(BSON("_id" << 1 << "host" @@ -758,14 +806,69 @@ TEST(MemberConfig, ParseAcceptsAnyNumberSlaveDelay) { << "h" << "priority" << 0 << "slaveDelay" << 1.6), &tagConfig); - ASSERT_EQUALS(mc.getSlaveDelay(), Seconds(1)); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(1)); } { MemberConfig mc(BSON("_id" << 1 << "host" << "h" << "priority" << 0 << "slaveDelay" << 4), &tagConfig); - ASSERT_EQUALS(mc.getSlaveDelay(), Seconds(4)); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(4)); + } +} + +TEST(MemberConfig, ParseAcceptsAnyNumberSecondaryDelay) { + ReplSetTagConfig tagConfig; + { + MemberConfig mc(BSON("_id" << 1 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << 0), + &tagConfig); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(0)); + } + { + MemberConfig mc(BSON("_id" << 1 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << 0.5), + &tagConfig); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(0)); + } + { + MemberConfig mc(BSON("_id" << 1 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << -0.5), + &tagConfig); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(0)); + } + { + MemberConfig mc(BSON("_id" << 1 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << 1), + &tagConfig); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(1)); + } + { + ASSERT_THROWS_CODE( + MemberConfig(BSON("_id" << 1 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << -1.5), + &tagConfig), + AssertionException, + 51024); + } + { + MemberConfig mc(BSON("_id" << 0 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << 1.6), + &tagConfig); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(1)); + } + { + MemberConfig mc(BSON("_id" << 1 << "host" + << "h" + << "priority" << 0 << "secondaryDelaySecs" << 4), + &tagConfig); + ASSERT_EQUALS(mc.getSecondaryDelay(), Seconds(4)); } } @@ -990,6 +1093,15 @@ TEST(MemberConfig, ValidatePriorityAndSlaveDelayRelationship) { ExceptionFor<ErrorCodes::BadValue>); } +TEST(MemberConfig, ValidatePriorityAndSecondaryDelayRelationship) { + ReplSetTagConfig tagConfig; + ASSERT_THROWS(MemberConfig(BSON("_id" << 0 << "host" + << "h" + << "priority" << 1 << "secondaryDelaySecs" << 60), + &tagConfig), + ExceptionFor<ErrorCodes::BadValue>); +} + TEST(MemberConfig, ValidatePriorityAndHiddenRelationship) { ReplSetTagConfig tagConfig; { diff --git a/src/mongo/db/repl/repl_server_parameters.idl b/src/mongo/db/repl/repl_server_parameters.idl index 0fcbefb7a60..a1c56d2c6f7 100644 --- a/src/mongo/db/repl/repl_server_parameters.idl +++ b/src/mongo/db/repl/repl_server_parameters.idl @@ -450,3 +450,9 @@ feature_flags: When enabled, tenant migration commands are supported. cpp_varname: feature_flags::gTenantMigrations default: false + + featureFlagUseSecondaryDelaySecs: + description: >- + If enabled, use field secondaryDelaySecs instead of slaveDelay in MemberConfig. + cpp_varname: feature_flags::gUseSecondaryDelaySecs + default: false
\ No newline at end of file diff --git a/src/mongo/db/repl/repl_set_config.cpp b/src/mongo/db/repl/repl_set_config.cpp index 9fb2c00aea6..c40b7428203 100644 --- a/src/mongo/db/repl/repl_set_config.cpp +++ b/src/mongo/db/repl/repl_set_config.cpp @@ -97,6 +97,18 @@ ReplSetConfig ReplSetConfig::parseForInitiate(const BSONObj& cfg, OID newReplica return result; } +void ReplSetConfig::setDefaultDelayFieldForMember(MemberConfig mem) { + // We should only be setting the default values if the config has neither delay field set. + if (!mem.hasSecondaryDelaySecs() && !mem.hasSlaveDelay()) { + if (feature_flags::gUseSecondaryDelaySecs.isEnabled( + serverGlobalParams.featureCompatibility)) { + useSecondaryDelaySecsFieldName(mem.getId()); + } else { + useSlaveDelayFieldName(mem.getId()); + } + } +} + void ReplSetConfig::_setRequiredFields() { // The three required fields need to be set to something valid to avoid a potential // invariant if the uninitialized object is ever used with toBSON(). @@ -144,6 +156,8 @@ Status ReplSetConfig::_initialize(bool forInitiate, // The const_cast is necessary because "non_const_getter" in the IDL doesn't work for // arrays. const_cast<MemberConfig&>(member).addTagInfo(&_tagConfig); + + setDefaultDelayFieldForMember(member); } // @@ -252,6 +266,12 @@ Status ReplSetConfig::validate() const { } } + if (memberI.hasSlaveDelay() && memberI.hasSecondaryDelaySecs()) { + return Status(ErrorCodes::BadValue, + "Cannot specify both secondaryDelaySecs and slaveDelay as fields in " + "member configuration."); + } + if (memberI.getHostAndPort().isLocalHost()) { ++localhostCount; } @@ -377,10 +397,10 @@ Status ReplSetConfig::validate() const { "Members in replica set configurations being used for config " "servers must build indexes"); } - if (mem->getSlaveDelay() != Seconds(0)) { + if (mem->getSecondaryDelay() != Seconds(0)) { return Status(ErrorCodes::BadValue, "Members in replica set configurations being used for config " - "servers cannot have a non-zero slaveDelay"); + "servers cannot have a non-zero secondaryDelaySecs"); } } if (serverGlobalParams.clusterRole != ClusterRole::ConfigServer && @@ -709,5 +729,23 @@ void MutableReplSetConfig::removeNewlyAddedFieldForMember(MemberId memberId) { _findMemberByID(memberId)->setNewlyAdded(boost::none); } +void MutableReplSetConfig::useSecondaryDelaySecsFieldName(MemberId memberId) { + auto mem = _findMemberByID(memberId); + if (mem->hasSecondaryDelaySecs()) { + return; + } + mem->setSecondaryDelaySecs(mem->hasSlaveDelay() ? mem->getSlaveDelaySecs() : 0LL); + mem->setSlaveDelaySecs(boost::none); +} + +void MutableReplSetConfig::useSlaveDelayFieldName(MemberId memberId) { + auto mem = _findMemberByID(memberId); + if (mem->hasSlaveDelay()) { + return; + } + mem->setSlaveDelaySecs(mem->hasSecondaryDelaySecs() ? mem->getSecondaryDelaySecs() : 0LL); + mem->setSecondaryDelaySecs(boost::none); +} + } // namespace repl } // namespace mongo diff --git a/src/mongo/db/repl/repl_set_config.h b/src/mongo/db/repl/repl_set_config.h index 8f2ae9d04be..5148d0fa8ea 100644 --- a/src/mongo/db/repl/repl_set_config.h +++ b/src/mongo/db/repl/repl_set_config.h @@ -131,6 +131,20 @@ public: */ void removeNewlyAddedFieldForMember(MemberId memberId); + /** + * Sets the member config's 'secondaryDelaySecs' to the value of 'slaveDelay' and removes the + * 'slaveDelay' field entirely. If 'slaveDelay' is not set, then sets 'secondaryDelaySecs' to + * the default value. + */ + void useSecondaryDelaySecsFieldName(MemberId memberId); + + /** + * Sets the member config's 'slaveDelay' to the value of 'secondaryDelaySecs' and removes the + * 'secondaryDelaySecs' field entirely. If 'secondaryDelaySecs' is not set, then sets + * 'slaveDelay' to the default value. + */ + void useSlaveDelayFieldName(MemberId memberId); + protected: MutableReplSetConfig() = default; @@ -218,6 +232,13 @@ public: static ReplSetConfig parseForInitiate(const BSONObj& cfg, OID newReplicaSetId); /** + * Sets the default delay field name for a member config based on feature compatibility version, + * but only if the member config has neither 'secondaryDelaySecs' nor 'slaveDelay' already set. + * This function is used when constructing 'ReplSetConfigs' for initiate and reconfig. + */ + void setDefaultDelayFieldForMember(MemberConfig mem); + + /** * Returns true if this object has been successfully initialized or copied from * an initialized object. */ diff --git a/src/mongo/db/repl/repl_set_config_checks.cpp b/src/mongo/db/repl/repl_set_config_checks.cpp index b768317f041..20ad4106e2f 100644 --- a/src/mongo/db/repl/repl_set_config_checks.cpp +++ b/src/mongo/db/repl/repl_set_config_checks.cpp @@ -34,6 +34,7 @@ #include <algorithm> #include <iterator> +#include "mongo/db/repl/repl_server_parameters_gen.h" #include "mongo/db/repl/repl_set_config.h" #include "mongo/db/repl/replication_coordinator_external_state.h" #include "mongo/db/service_context.h" @@ -98,6 +99,54 @@ Status ensureNoNewlyAddedMembers(const ReplSetConfig& config) { } /** + * Checks that the current feature compatibility version is compatible with + * each member config's delay field name. If the feature flag 'featureFlagUseSecondaryDelaySecs' is + * enabled, the nodes must use the 'secondaryDelaySecs' field instead of the 'slaveDelay' field. + */ +Status isFCVCompatible(const ReplSetConfig& config) { + auto version = serverGlobalParams.featureCompatibility.getVersion(); + // TODO (SERVER-53354) If we are currently upgrading, we check if the feature flag is enabled + // for the target version. We use the generic FCV references here to avoid having to update the + // FCV constants used after each continuous release. After release, we should make sure to + // remove these references while removing the feature flag. + // + //(Generic FCV reference): feature flag support + if (version == ServerGlobalParams::FeatureCompatibility::kUpgradingFromLastLTSToLatest || + version == ServerGlobalParams::FeatureCompatibility::kUpgradingFromLastContinuousToLatest) { + version = ServerGlobalParams::FeatureCompatibility::kLatest; + } + + ServerGlobalParams::FeatureCompatibility targetFCV; + targetFCV.setVersion(version); + bool isEnabled = feature_flags::gUseSecondaryDelaySecs.isEnabled(targetFCV); + // We must check that every member config has a valid delay field name. + for (auto iter = config.membersBegin(); iter != config.membersEnd(); ++iter) { + if ((isEnabled && iter->hasSlaveDelay()) || (!isEnabled && iter->hasSecondaryDelaySecs())) { + // TODO (SERVER-53354) If the feature flag is disabled, getVersion() will throw. In this + // case, the version should default to kLatest. We use the generic FCV references here + // to avoid having to update the FCV constants used after each continuous release. After + // release, we should make sure to remove these references while removing the feature + // flag. + // + //(Generic FCV reference): feature flag support + auto featureFlagVersion = isEnabled + ? FeatureCompatibilityVersionParser::toString( + feature_flags::gUseSecondaryDelaySecs.getVersion()) + : FeatureCompatibilityVersionParser::toString( + ServerGlobalParams::FeatureCompatibility::kLatest); + return Status(ErrorCodes::BadValue, + str::stream() << "If the node is in FCV " << featureFlagVersion + << ", we must use the secondaryDelaySecs field name " + "instead of slaveDelay. Only nodes below FCV " + << featureFlagVersion << " should use slaveDelay."); + } + } + + + return Status::OK(); +} + +/** * Compares two initialized and validated replica set configurations and checks to see if the * transition from 'oldConfig' to 'newConfig' adds or removes at most 1 voting node. * @@ -331,6 +380,11 @@ StatusWith<int> validateConfigForInitiate(ReplicationCoordinatorExternalState* e return StatusWith<int>(status); } + status = isFCVCompatible(newConfig); + if (!status.isOK()) { + return StatusWith<int>(status); + } + status = newConfig.checkIfWriteConcernCanBeSatisfied(newConfig.getDefaultWriteConcern()); if (!status.isOK()) { return status.withContext( @@ -370,6 +424,11 @@ Status validateConfigForReconfig(const ReplSetConfig& oldConfig, return status; } + status = isFCVCompatible(newConfig); + if (!status.isOK()) { + return status; + } + status = newConfig.checkIfWriteConcernCanBeSatisfied(newConfig.getDefaultWriteConcern()); if (!status.isOK()) { return status.withContext( diff --git a/src/mongo/db/repl/repl_set_config_test.cpp b/src/mongo/db/repl/repl_set_config_test.cpp index 16618f56cbf..2606bd06c42 100644 --- a/src/mongo/db/repl/repl_set_config_test.cpp +++ b/src/mongo/db/repl/repl_set_config_test.cpp @@ -820,6 +820,23 @@ TEST(ReplSetConfig, ValidateFailsWithDuplicateMemberId) { ASSERT_EQUALS(ErrorCodes::BadValue, status); } +TEST(ReplSetConfig, ValidateFailsWithBothDelaySecsFieldNames) { + ReplSetConfig config(ReplSetConfig::parse( + BSON("_id" + << "rs0" + << "protocolVersion" << 1 << "version" << 1 << "configsvr" << true << "members" + << BSON_ARRAY(BSON("_id" << 0 << "host" + << "localhost:12345") + << BSON("_id" << 1 << "host" + << "localhost:54321" + << "priority" << 0 << "secondaryDelaySecs" << 10 + << "slaveDelay" << 10))))); + Status status = config.validate(); + ASSERT_EQUALS(ErrorCodes::BadValue, status); + ASSERT_STRING_CONTAINS(status.reason(), + "Cannot specify both secondaryDelaySecs and slaveDelay"); +} + TEST(ReplSetConfig, InitializeFailsWithInvalidMember) { ReplSetConfig config; ASSERT_THROWS(ReplSetConfig::parse(BSON("_id" @@ -1255,7 +1272,7 @@ bool operator==(const MemberConfig& a, const MemberConfig& b) { } } return a.getId() == b.getId() && a.getHostAndPort() == b.getHostAndPort() && - a.getPriority() == b.getPriority() && a.getSlaveDelay() == b.getSlaveDelay() && + a.getPriority() == b.getPriority() && a.getSecondaryDelay() == b.getSecondaryDelay() && a.isVoter() == b.isVoter() && a.isArbiter() == b.isArbiter() && a.isNewlyAdded() == b.isNewlyAdded() && a.isHidden() == b.isHidden() && a.shouldBuildIndexes() == b.shouldBuildIndexes() && a.getNumTags() == b.getNumTags() && @@ -1365,8 +1382,8 @@ TEST(ReplSetConfig, toBSONRoundTripAbilityLarge) { << BSON("_id" << 3 << "host" << "localhost:3828" << "arbiterOnly" << false << "hidden" << true << "buildIndexes" - << false << "priority" << 0 << "slaveDelay" << 17 << "votes" - << 0 << "newlyAdded" << true << "tags" + << false << "priority" << 0 << "secondaryDelaySecs" << 17 + << "votes" << 0 << "newlyAdded" << true << "tags" << BSON("coast" << "east" << "ssd" @@ -1558,21 +1575,20 @@ TEST(ReplSetConfig, CheckConfigServerMustBuildIndexes) { ASSERT_STRING_CONTAINS(status.reason(), "must build indexes"); } -TEST(ReplSetConfig, CheckConfigServerCantHaveSlaveDelay) { +TEST(ReplSetConfig, CheckConfigServerCantHaveSecondaryDelaySecs) { ReplSetConfig configA; - configA = ReplSetConfig::parse(BSON("_id" - << "rs0" - << "protocolVersion" << 1 << "version" << 1 << "configsvr" - << true << "members" - << BSON_ARRAY(BSON("_id" << 0 << "host" - << "localhost:12345") - << BSON("_id" << 1 << "host" - << "localhost:54321" - << "priority" << 0 - << "slaveDelay" << 3)))); + configA = ReplSetConfig::parse( + BSON("_id" + << "rs0" + << "protocolVersion" << 1 << "version" << 1 << "configsvr" << true << "members" + << BSON_ARRAY(BSON("_id" << 0 << "host" + << "localhost:12345") + << BSON("_id" << 1 << "host" + << "localhost:54321" + << "priority" << 0 << "secondaryDelaySecs" << 3)))); Status status = configA.validate(); ASSERT_EQUALS(ErrorCodes::BadValue, status); - ASSERT_STRING_CONTAINS(status.reason(), "cannot have a non-zero slaveDelay"); + ASSERT_STRING_CONTAINS(status.reason(), "cannot have a non-zero secondaryDelaySecs"); } TEST(ReplSetConfig, CheckConfigServerMustHaveTrueForWriteConcernMajorityJournalDefault) { diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp index cde642c1fe9..fe58a8fc366 100644 --- a/src/mongo/db/repl/replication_coordinator_impl.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl.cpp @@ -1041,7 +1041,7 @@ Seconds ReplicationCoordinatorImpl::getSecondaryDelaySecs() const { // queue of work. return Seconds(0); } - return _rsConfig.getMemberAt(_selfIndex).getSlaveDelay(); + return _rsConfig.getMemberAt(_selfIndex).getSecondaryDelay(); } void ReplicationCoordinatorImpl::clearSyncSourceBlacklist() { diff --git a/src/mongo/db/repl/topology_coordinator.cpp b/src/mongo/db/repl/topology_coordinator.cpp index e98b19e20f8..e7bf9e67d2e 100644 --- a/src/mongo/db/repl/topology_coordinator.cpp +++ b/src/mongo/db/repl/topology_coordinator.cpp @@ -483,13 +483,14 @@ bool TopologyCoordinator::_isEligibleSyncSource(int candidateIndex, return false; } // Candidate must not have a configured delay larger than ours. - if (_selfConfig().getSlaveDelay() < memberConfig.getSlaveDelay()) { + if (_selfConfig().getSecondaryDelay() < memberConfig.getSecondaryDelay()) { LOGV2_DEBUG(3873111, 2, - "Cannot select sync source with larger slaveDelay than ours", + "Cannot select sync source with larger secondaryDelaySecs than ours", "syncSourceCandidate"_attr = syncSourceCandidate, - "syncSourceCandidateSlaveDelay"_attr = memberConfig.getSlaveDelay(), - "slaveDelay"_attr = _selfConfig().getSlaveDelay()); + "syncSourceCandidateSecondaryDelaySecs"_attr = + memberConfig.getSecondaryDelay(), + "secondaryDelaySecs"_attr = _selfConfig().getSecondaryDelay()); return false; } } @@ -2096,7 +2097,7 @@ void TopologyCoordinator::fillHelloForReplSet(std::shared_ptr<HelloResponse> res invariant(!_rsConfig.members().empty()); for (const auto& member : _rsConfig.members()) { - if (member.isHidden() || member.getSlaveDelay() > Seconds{0}) { + if (member.isHidden() || member.getSecondaryDelay() > Seconds{0}) { continue; } auto hostView = member.getHostAndPort(horizonString); @@ -2130,8 +2131,8 @@ void TopologyCoordinator::fillHelloForReplSet(std::shared_ptr<HelloResponse> res } else if (selfConfig.getPriority() == 0) { response->setIsPassive(true); } - if (selfConfig.getSlaveDelay() > Seconds(0)) { - response->setSecondaryDelaySecs(selfConfig.getSlaveDelay()); + if (selfConfig.getSecondaryDelay() > Seconds(0)) { + response->setSecondaryDelaySecs(selfConfig.getSecondaryDelay()); } if (selfConfig.isHidden()) { response->setIsHidden(true); @@ -3098,8 +3099,8 @@ bool TopologyCoordinator::shouldChangeSyncSourceDueToPingTime(const HostAndPort& return false; } - // If we are configured with slaveDelay, do not re-evaluate our sync source. - if (_selfIndex == -1 || _selfConfig().getSlaveDelay() > Seconds(0)) { + // If we are configured with secondaryDelaySecs, do not re-evaluate our sync source. + if (_selfIndex == -1 || _selfConfig().getSecondaryDelay() > Seconds(0)) { return false; } diff --git a/src/mongo/db/repl/topology_coordinator_v1_test.cpp b/src/mongo/db/repl/topology_coordinator_v1_test.cpp index 83d8af17643..bef6dc6238d 100644 --- a/src/mongo/db/repl/topology_coordinator_v1_test.cpp +++ b/src/mongo/db/repl/topology_coordinator_v1_test.cpp @@ -434,7 +434,7 @@ TEST_F(TopoCoordTest, NodeReturnsClosestValidSyncSourceAsSyncSource) { << "arbiterOnly" << true) << BSON("_id" << 50 << "host" << "h5" - << "slaveDelay" << 1 << "priority" << 0) + << "secondaryDelaySecs" << 1 << "priority" << 0) << BSON("_id" << 60 << "host" << "h6") << BSON("_id" << 70 << "host" @@ -4360,13 +4360,13 @@ TEST_F(TopoCoordTest, DontChangeDueToPingTimeWhenCandidatePingTimeIsMissing) { ReadPreference::Nearest)); } -TEST_F(ReevalSyncSourceTest, NoChangeWhenNodeConfiguredWithSlaveDelay) { +TEST_F(ReevalSyncSourceTest, NoChangeWhenNodeConfiguredWithSecondaryDelaySecs) { updateConfig(BSON("_id" << "rs0" << "version" << 5 << "term" << 1 << "members" << BSON_ARRAY(BSON("_id" << 0 << "host" << "host1:27017" - << "slaveDelay" << 1 << "priority" << 0) + << "secondaryDelaySecs" << 1 << "priority" << 0) << BSON("_id" << 1 << "host" << "host2:27017") << BSON("_id" << 2 << "host" @@ -4374,7 +4374,8 @@ TEST_F(ReevalSyncSourceTest, NoChangeWhenNodeConfiguredWithSlaveDelay) { << "protocolVersion" << 1), 0); - // Set up so that without slaveDelay, the node otherwise would have changed sync sources. + // Set up so that without secondaryDelaySecs, the node otherwise would have changed sync + // sources. getTopoCoord().setPing_forTest(HostAndPort("host2"), pingTime); getTopoCoord().setPing_forTest(HostAndPort("host3"), significantlyCloserPingTime); @@ -4391,7 +4392,7 @@ TEST_F(ReevalSyncSourceTest, NoChangeWhenNodeNotFoundInConfig) { << "version" << 5 << "term" << 1 << "members" << BSON_ARRAY(BSON("_id" << 0 << "host" << "host1:27017" - << "slaveDelay" << 1 << "priority" << 0) + << "secondaryDelaySecs" << 1 << "priority" << 0) << BSON("_id" << 1 << "host" << "host2:27017") << BSON("_id" << 2 << "host" @@ -4399,8 +4400,8 @@ TEST_F(ReevalSyncSourceTest, NoChangeWhenNodeNotFoundInConfig) { << "protocolVersion" << 1), -1); - // Set up so that without slaveDelay and not being in the config, the node otherwise would have - // changed sync sources. + // Set up so that without secondaryDelaySecs and not being in the config, the node otherwise + // would have changed sync sources. getTopoCoord().setPing_forTest(HostAndPort("host2"), pingTime); getTopoCoord().setPing_forTest(HostAndPort("host3"), significantlyCloserPingTime); diff --git a/src/mongo/dbtests/mock/mock_replica_set.cpp b/src/mongo/dbtests/mock/mock_replica_set.cpp index d7dfb75aec1..53fd2d0a8cd 100644 --- a/src/mongo/dbtests/mock/mock_replica_set.cpp +++ b/src/mongo/dbtests/mock/mock_replica_set.cpp @@ -248,9 +248,9 @@ void MockReplicaSet::mockIsMasterCmd() { builder.append("passive", true); } - if (member->getSlaveDelay().count()) { - builder.appendIntOrLL("slaveDelay", - durationCount<Seconds>(member->getSlaveDelay())); + if (member->getSecondaryDelay().count()) { + builder.appendIntOrLL("secondaryDelaySecs", + durationCount<Seconds>(member->getSecondaryDelay())); } if (member->isHidden()) { |