diff options
author | Suganthi Mani <suganthi.mani@mongodb.com> | 2020-06-15 13:37:15 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-06-16 04:44:42 +0000 |
commit | 52db264df93fe1f5f75647136793bd025c0393a8 (patch) | |
tree | 8481ed5ae81cd9f088eff8e4909ce2cebca36af8 | |
parent | 608fa1638db44b8f1fb64653de8859a0256975fe (diff) | |
download | mongo-52db264df93fe1f5f75647136793bd025c0393a8.tar.gz |
SERVER-48411 Increase maximum allowed replica set memberId on disk from 255 to INT_MAX.
-rw-r--r-- | src/mongo/db/repl/member_config.h | 8 | ||||
-rw-r--r-- | src/mongo/db/repl/member_config_test.cpp | 17 | ||||
-rw-r--r-- | src/mongo/db/repl/member_id.h | 5 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config.h | 2 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config_checks.cpp | 39 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config_checks_test.cpp | 66 |
7 files changed, 130 insertions, 9 deletions
diff --git a/src/mongo/db/repl/member_config.h b/src/mongo/db/repl/member_config.h index 87077caf650..00d0b3a8e60 100644 --- a/src/mongo/db/repl/member_config.h +++ b/src/mongo/db/repl/member_config.h @@ -70,6 +70,14 @@ public: static const std::string kConfigVoterTagName; /** + * Inline `kMaxUserMemberId` to allow others use + * the constant without linking to `member_config.cpp`. + * + * Maximum allowed member id for new nodes added by user. + */ + inline static const int kMaxUserMemberId = 255; + + /** * Construct a MemberConfig from the contents of "mcfg". * * If "mcfg" describes any tags, builds ReplSetTags for this diff --git a/src/mongo/db/repl/member_config_test.cpp b/src/mongo/db/repl/member_config_test.cpp index 82f0790bf7d..61f18e2369b 100644 --- a/src/mongo/db/repl/member_config_test.cpp +++ b/src/mongo/db/repl/member_config_test.cpp @@ -73,19 +73,26 @@ TEST(MemberConfig, ParseFailsWithMissingIdField) { ExceptionFor<ErrorCodes::NoSuchKey>); } -TEST(MemberConfig, ParseFailsWithIdOutOfRange) { +TEST(MemberConfig, ParseMemberConfigId) { ReplSetTagConfig tagConfig; { + // Fail case ASSERT_THROWS(MemberConfig(BSON("_id" << -1 << "host" << "localhost:12345"), &tagConfig), ExceptionFor<ErrorCodes::BadValue>); } { - ASSERT_THROWS(MemberConfig(BSON("_id" << -1 << "host" - << "localhost:12345"), - &tagConfig), - ExceptionFor<ErrorCodes::BadValue>); + // Pass cases + MemberConfig(BSON("_id" << 0 << "host" + << "localhost:12345"), + &tagConfig); + MemberConfig(BSON("_id" << MemberConfig::kMaxUserMemberId << "host" + << "localhost:12345"), + &tagConfig); + MemberConfig(BSON("_id" << (MemberConfig::kMaxUserMemberId + 1) << "host" + << "localhost:12345"), + &tagConfig); } } diff --git a/src/mongo/db/repl/member_id.h b/src/mongo/db/repl/member_id.h index 4f2db2b342c..a9cdca09a11 100644 --- a/src/mongo/db/repl/member_id.h +++ b/src/mongo/db/repl/member_id.h @@ -45,9 +45,10 @@ public: MemberId() : _id(kUninitializedMemberId) {} explicit MemberId(int id) { - if (id < 0 || id > 255) { + if (id < 0) { uasserted(ErrorCodes::BadValue, - str::stream() << "_id field value of " << id << " is out of range."); + str::stream() + << "_id field value of " << id << " can't be a negative number"); } _id = id; } diff --git a/src/mongo/db/repl/repl_set_config.cpp b/src/mongo/db/repl/repl_set_config.cpp index 1f703cd1615..90b52b7818d 100644 --- a/src/mongo/db/repl/repl_set_config.cpp +++ b/src/mongo/db/repl/repl_set_config.cpp @@ -754,7 +754,7 @@ int ReplSetConfig::findMemberIndexByHostAndPort(const HostAndPort& hap) const { return -1; } -int ReplSetConfig::findMemberIndexByConfigId(long long configId) const { +int ReplSetConfig::findMemberIndexByConfigId(int configId) const { int x = 0; for (const auto& member : _members) { if (member.getId() == MemberId(configId)) { diff --git a/src/mongo/db/repl/repl_set_config.h b/src/mongo/db/repl/repl_set_config.h index ce93134f3e9..257fb9b11f2 100644 --- a/src/mongo/db/repl/repl_set_config.h +++ b/src/mongo/db/repl/repl_set_config.h @@ -302,7 +302,7 @@ public: * Returns a MemberConfig index position corresponding to the member with the given * _id in the config, or -1 if there is no member with that address. */ - int findMemberIndexByConfigId(long long configId) const; + int findMemberIndexByConfigId(int configId) const; /** * Gets the default write concern for the replica set described by this configuration. diff --git a/src/mongo/db/repl/repl_set_config_checks.cpp b/src/mongo/db/repl/repl_set_config_checks.cpp index b8927bc9d8d..0462c840fbc 100644 --- a/src/mongo/db/repl/repl_set_config_checks.cpp +++ b/src/mongo/db/repl/repl_set_config_checks.cpp @@ -124,6 +124,35 @@ Status validateSingleNodeChange(const ReplSetConfig& oldConfig, const ReplSetCon } /** + * Returns ErrorCodes::BadValue if the user tries to add new nodes with member id greater than + * MemberConfig::kMaxUserMemberId(255). + * + * validateMemberId() will be called only for replSetReconfig and replSetInitiate commands. + * + * TODO SERVER-48345: Remove this method on MongoDB v4.6 as we allow users to add new nodes + * with member id > MemberConfig::kMaxUserMemberId(255). + */ +Status validateMemberId(const ReplSetConfig& newConfig, + const ReplSetConfig& oldConfig = ReplSetConfig()) { + for (ReplSetConfig::MemberIterator mNew = newConfig.membersBegin(); + mNew != newConfig.membersEnd(); + ++mNew) { + auto mNewConfigId = mNew->getId().getData(); + // Existing members are allowed to have member id greater than kMaxUserMemberId. And, + // it can happen only if this node was previously running on MongoDB version greater + // than v4.4. + if (oldConfig.findMemberIndexByConfigId(mNewConfigId) == -1 && + mNewConfigId > MemberConfig::kMaxUserMemberId) { + return Status(ErrorCodes::BadValue, + str::stream() + << "Replica set configuration contains new members with " + << "member id greater than " << MemberConfig::kMaxUserMemberId); + } + } + return Status::OK(); +} + +/** * Compares two initialized and validated replica set configurations, and checks to * see if "newConfig" is a legal successor configuration to "oldConfig". * @@ -313,6 +342,11 @@ StatusWith<int> validateConfigForInitiate(ReplicationCoordinatorExternalState* e return StatusWith<int>(status); } + status = validateMemberId(newConfig); + if (!status.isOK()) { + return status; + } + status = newConfig.checkIfWriteConcernCanBeSatisfied(newConfig.getDefaultWriteConcern()); if (!status.isOK()) { return status.withContext( @@ -359,6 +393,11 @@ Status validateConfigForReconfig(const ReplSetConfig& oldConfig, return status; } + status = validateMemberId(newConfig, oldConfig); + if (!status.isOK()) { + return status; + } + // For non-force reconfigs, verify that the reconfig only adds or removes a single node. This // ensures that all quorums of the new config overlap with all quorums of the old config. if (!force && diff --git a/src/mongo/db/repl/repl_set_config_checks_test.cpp b/src/mongo/db/repl/repl_set_config_checks_test.cpp index f7ee995a638..dc90eb6d115 100644 --- a/src/mongo/db/repl/repl_set_config_checks_test.cpp +++ b/src/mongo/db/repl/repl_set_config_checks_test.cpp @@ -78,6 +78,23 @@ TEST_F(ServiceContextTest, ValidateConfigForInitiate_TermIsAlwaysInitialTerm) { ASSERT_EQUALS(config.getConfigTerm(), OpTime::kInitialTerm); } +TEST_F(ServiceContextTest, ValidateConfigForInitiate_memberId) { + ReplicationCoordinatorExternalStateMock rses; + rses.addSelf(HostAndPort("h1")); + + // Config with Member id > 255. + ReplSetConfig invalidConfig; + ASSERT_OK(invalidConfig.initializeForInitiate( + BSON("_id" + << "rs0" + << "version" << 1 << "protocolVersion" << 1 << "members" + << BSON_ARRAY(BSON("_id" << (MemberConfig::kMaxUserMemberId + 1) << "host" + << "h1"))))); + ASSERT_EQUALS( + ErrorCodes::BadValue, + validateConfigForInitiate(&rses, invalidConfig, getGlobalServiceContext()).getStatus()); +} + TEST_F(ServiceContextTest, ValidateConfigForInitiate_MustFindSelf) { ReplSetConfig config; ASSERT_OK( @@ -249,6 +266,55 @@ TEST_F(ServiceContextTest, ValidateConfigForReconfig_NewConfigVersionNumberMustB validateConfigForReconfig(newConfig, oldConfig, false)); } +TEST_F(ServiceContextTest, ValidateConfigForReconfig_memberId) { + ReplicationCoordinatorExternalStateMock externalState; + externalState.addSelf(HostAndPort("h1")); + + ReplSetConfig oldConfig; + ReplSetConfig newConfig; + + // Case 1: Add a new node with member id > 255. + ASSERT_OK(oldConfig.initialize(BSON("_id" + << "rs0" + << "version" << 1 << "protocolVersion" << 1 << "members" + << BSON_ARRAY(BSON("_id" << 1 << "host" + << "h1"))))); + ASSERT_OK(newConfig.initialize( + BSON("_id" + << "rs0" + << "version" << 2 << "protocolVersion" << 1 << "members" + << BSON_ARRAY(BSON("_id" << 1 << "host" + << "h1") + << BSON("_id" << (MemberConfig::kMaxUserMemberId + 1) << "host" + << "h2"))))); + ASSERT_OK(oldConfig.validate()); + ASSERT_OK(newConfig.validate()); + ASSERT_EQUALS(ErrorCodes::BadValue, validateConfigForReconfig(oldConfig, newConfig, false)); + + // Case 2: Change the member config setting for the existing member with member id > 255. + ASSERT_OK(oldConfig.initialize( + BSON("_id" + << "rs0" + << "version" << 1 << "protocolVersion" << 1 << "members" + << BSON_ARRAY(BSON("_id" << 1 << "host" + << "h1") + << BSON("_id" << (MemberConfig::kMaxUserMemberId + 1) << "host" + << "h2"))))); + ASSERT_OK(newConfig.initialize( + BSON("_id" + << "rs0" + << "version" << 2 << "protocolVersion" << 1 << "members" + << BSON_ARRAY(BSON("_id" << 1 << "host" + << "h1") + << BSON("_id" << (MemberConfig::kMaxUserMemberId + 1) << "host" + << "h2" + << "priority" << 0))))); + ASSERT_OK(oldConfig.validate()); + ASSERT_OK(newConfig.validate()); + ASSERT_OK(validateConfigForReconfig(oldConfig, newConfig, false)); +} + + TEST_F(ServiceContextTest, ValidateConfigForReconfig_NewConfigMustNotChangeSetName) { ReplicationCoordinatorExternalStateMock externalState; externalState.addSelf(HostAndPort("h1")); |