diff options
author | Matthew Russotto <matthew.russotto@10gen.com> | 2020-04-10 13:26:16 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-04-10 17:48:42 +0000 |
commit | 0cfd1f9779dba8168913f5e5a00ab0974f7a957a (patch) | |
tree | 8b3115b6d63725247ad66acc6e781bf79358766b /src/mongo | |
parent | 7645ff98bb0f289f8eb1605cb743b414e5ded0fc (diff) | |
download | mongo-0cfd1f9779dba8168913f5e5a00ab0974f7a957a.tar.gz |
SERVER-47089 Create IDL for ReplSetConfig "settings" object.
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/repl/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config.cpp | 275 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config.h | 27 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config.idl | 76 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config_test.cpp | 42 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config_validators.cpp | 48 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_config_validators.h | 40 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_tag.h | 3 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_write_concern_mode_definitions.cpp | 107 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_write_concern_mode_definitions.h | 82 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_write_concern_mode_definitions_test.cpp | 204 |
11 files changed, 626 insertions, 281 deletions
diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index 8a9313e5fb0..b8316198382 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -795,10 +795,12 @@ env.Library('replica_set_messages', 'is_master_response.cpp', 'member_config.cpp', 'repl_set_config.cpp', + 'repl_set_config_validators.cpp', 'repl_set_heartbeat_args_v1.cpp', 'repl_set_heartbeat_response.cpp', 'repl_set_request_votes_args.cpp', 'repl_set_tag.cpp', + 'repl_set_write_concern_mode_definitions.cpp', 'update_position_args.cpp', 'last_vote.cpp', env.Idlc('repl_set_config.idl')[0], @@ -1245,6 +1247,7 @@ env.CppUnitTest( 'repl_set_config_test.cpp', 'repl_set_heartbeat_response_test.cpp', 'repl_set_tag_test.cpp', + 'repl_set_write_concern_mode_definitions_test.cpp', 'replication_consistency_markers_impl_test.cpp', 'replication_process_test.cpp', 'replication_recovery_test.cpp', diff --git a/src/mongo/db/repl/repl_set_config.cpp b/src/mongo/db/repl/repl_set_config.cpp index 91cf71f6f87..4e4ca5da708 100644 --- a/src/mongo/db/repl/repl_set_config.cpp +++ b/src/mongo/db/repl/repl_set_config.cpp @@ -81,17 +81,6 @@ const std::string kLegalConfigTopFieldNames[] = {kIdFieldName, kProtocolVersionFieldName, ReplSetConfig::kConfigServerFieldName, kWriteConcernMajorityJournalDefaultFieldName}; - -const std::string kChainingAllowedFieldName = "chainingAllowed"; -const std::string kElectionTimeoutFieldName = "electionTimeoutMillis"; -const std::string kGetLastErrorDefaultsFieldName = "getLastErrorDefaults"; -const std::string kGetLastErrorModesFieldName = "getLastErrorModes"; -const std::string kHeartbeatIntervalFieldName = "heartbeatIntervalMillis"; -const std::string kHeartbeatTimeoutFieldName = "heartbeatTimeoutSecs"; -const std::string kCatchUpTimeoutFieldName = "catchUpTimeoutMillis"; -const std::string kReplicaSetIdFieldName = "replicaSetId"; -const std::string kCatchUpTakeoverDelayFieldName = "catchUpTakeoverDelayMillis"; - } // namespace Status ReplSetConfig::initialize(const BSONObj& cfg, @@ -222,20 +211,20 @@ Status ReplSetConfig::_initialize(const BSONObj& cfg, // // Generate replica set ID if called from replSetInitiate. - // Otherwise, uses 'defaultReplicatSetId' as default if 'cfg' doesn't have an ID. + // Otherwise, uses 'defaultReplicaSetId' as default if 'cfg' doesn't have an ID. // if (forInitiate) { - if (_replicaSetId.isSet()) { + if (_settings.getReplicaSetId()) { return Status(ErrorCodes::InvalidReplicaSetConfig, str::stream() << "replica set configuration cannot contain '" - << kReplicaSetIdFieldName + << ReplSetConfigSettings::kReplicaSetIdFieldName << "' " "field when called from replSetInitiate: " << cfg); } - _replicaSetId = OID::gen(); - } else if (!_replicaSetId.isSet()) { - _replicaSetId = defaultReplicaSetId; + _settings.setReplicaSetId(OID::gen()); + } else if (!_settings.getReplicaSetId() && defaultReplicaSetId.isSet()) { + _settings.setReplicaSetId(defaultReplicaSetId); } _calculateMajorities(); @@ -246,182 +235,21 @@ Status ReplSetConfig::_initialize(const BSONObj& cfg, } Status ReplSetConfig::_parseSettingsSubdocument(const BSONObj& settings) { - // - // Parse heartbeatIntervalMillis - // - long long heartbeatIntervalMillis; - Status hbIntervalStatus = - bsonExtractIntegerFieldWithDefault(settings, - kHeartbeatIntervalFieldName, - durationCount<Milliseconds>(kDefaultHeartbeatInterval), - &heartbeatIntervalMillis); - if (!hbIntervalStatus.isOK()) { - return hbIntervalStatus; - } - _heartbeatInterval = Milliseconds(heartbeatIntervalMillis); - - // - // Parse electionTimeoutMillis - // - long long electionTimeoutMillis; - auto greaterThanZero = [](const auto& x) { return x > 0; }; - auto electionTimeoutStatus = bsonExtractIntegerFieldWithDefaultIf( - settings, - kElectionTimeoutFieldName, - durationCount<Milliseconds>(kDefaultElectionTimeoutPeriod), - greaterThanZero, - "election timeout must be greater than 0", - &electionTimeoutMillis); - if (!electionTimeoutStatus.isOK()) { - return electionTimeoutStatus; - } - _electionTimeoutPeriod = Milliseconds(electionTimeoutMillis); - - // - // Parse heartbeatTimeoutSecs - // - long long heartbeatTimeoutSecs; - Status heartbeatTimeoutStatus = - bsonExtractIntegerFieldWithDefaultIf(settings, - kHeartbeatTimeoutFieldName, - durationCount<Seconds>(kDefaultHeartbeatTimeoutPeriod), - greaterThanZero, - "heartbeat timeout must be greater than 0", - &heartbeatTimeoutSecs); - if (!heartbeatTimeoutStatus.isOK()) { - return heartbeatTimeoutStatus; - } - _heartbeatTimeoutPeriod = Seconds(heartbeatTimeoutSecs); - - // - // Parse catchUpTimeoutMillis - // - auto validCatchUpParameter = [](long long timeout) { - return timeout >= 0LL || timeout == -1LL; - }; - long long catchUpTimeoutMillis; - Status catchUpTimeoutStatus = bsonExtractIntegerFieldWithDefaultIf( - settings, - kCatchUpTimeoutFieldName, - durationCount<Milliseconds>(kDefaultCatchUpTimeoutPeriod), - validCatchUpParameter, - "catch-up timeout must be positive, 0 (no catch-up) or -1 (infinite catch-up).", - &catchUpTimeoutMillis); - if (!catchUpTimeoutStatus.isOK()) { - return catchUpTimeoutStatus; - } - _catchUpTimeoutPeriod = Milliseconds(catchUpTimeoutMillis); - - // - // Parse catchUpTakeoverDelayMillis - // - long long catchUpTakeoverDelayMillis; - Status catchUpTakeoverDelayStatus = bsonExtractIntegerFieldWithDefaultIf( - settings, - kCatchUpTakeoverDelayFieldName, - durationCount<Milliseconds>(kDefaultCatchUpTakeoverDelay), - validCatchUpParameter, - "catch-up takeover delay must be -1 (no catch-up takeover) or greater than or equal to 0.", - &catchUpTakeoverDelayMillis); - if (!catchUpTakeoverDelayStatus.isOK()) { - return catchUpTakeoverDelayStatus; - } - _catchUpTakeoverDelay = Milliseconds(catchUpTakeoverDelayMillis); - - // - // Parse chainingAllowed - // - Status status = bsonExtractBooleanFieldWithDefault( - settings, kChainingAllowedFieldName, kDefaultChainingAllowed, &_chainingAllowed); - if (!status.isOK()) - return status; - - // - // Parse getLastErrorDefaults - // - BSONElement gleDefaultsElement; - status = bsonExtractTypedField( - settings, kGetLastErrorDefaultsFieldName, Object, &gleDefaultsElement); - if (status.isOK()) { - auto sw = WriteConcernOptions::parse(gleDefaultsElement.Obj()); - if (!sw.isOK()) { - return sw.getStatus(); - } - _defaultWriteConcern = sw.getValue(); - } else if (status == ErrorCodes::NoSuchKey) { - // Default write concern is w: 1. - _defaultWriteConcern = WriteConcernOptions(); - } else { - return status; + try { + _settings = + ReplSetConfigSettings::parse(IDLParserErrorContext("ReplSetConfig.settings"), settings); + } catch (const DBException& e) { + return e.toStatus(); } // - // Parse getLastErrorModes + // Put getLastErrorModes into the tag configuration. // - BSONElement gleModesElement; - status = bsonExtractTypedField(settings, kGetLastErrorModesFieldName, Object, &gleModesElement); - BSONObj gleModes; - if (status.isOK()) { - gleModes = gleModesElement.Obj(); - } else if (status != ErrorCodes::NoSuchKey) { - return status; - } - - for (auto&& modeElement : gleModes) { - if (_customWriteConcernModes.find(modeElement.fieldNameStringData()) != - _customWriteConcernModes.end()) { - return Status(ErrorCodes::Error(51001), - str::stream() - << kSettingsFieldName << '.' << kGetLastErrorModesFieldName - << " contains multiple fields named " << modeElement.fieldName()); - } - if (modeElement.type() != Object) { - return Status(ErrorCodes::TypeMismatch, - str::stream() - << "Expected " << kSettingsFieldName << '.' - << kGetLastErrorModesFieldName << '.' << modeElement.fieldName() - << " to be an Object, not " << typeName(modeElement.type())); - } - ReplSetTagPattern pattern = _tagConfig.makePattern(); - for (auto&& constraintElement : modeElement.Obj()) { - if (!constraintElement.isNumber()) { - return Status(ErrorCodes::TypeMismatch, - str::stream() - << "Expected " << kSettingsFieldName << '.' - << kGetLastErrorModesFieldName << '.' << modeElement.fieldName() - << '.' << constraintElement.fieldName() << " to be a number, not " - << typeName(constraintElement.type())); - } - const int minCount = constraintElement.numberInt(); - if (minCount <= 0) { - return Status(ErrorCodes::BadValue, - str::stream() - << "Value of " << kSettingsFieldName << '.' - << kGetLastErrorModesFieldName << '.' << modeElement.fieldName() - << '.' << constraintElement.fieldName() - << " must be positive, but found " << minCount); - } - status = _tagConfig.addTagCountConstraintToPattern( - &pattern, constraintElement.fieldNameStringData(), minCount); - if (!status.isOK()) { - return status; - } - } - _customWriteConcernModes[modeElement.fieldNameStringData()] = pattern; - } - - // Parse replica set ID. - OID replicaSetId; - status = mongo::bsonExtractOIDField(settings, kReplicaSetIdFieldName, &replicaSetId); - if (status.isOK()) { - if (!replicaSetId.isSet()) { - return Status(ErrorCodes::BadValue, - str::stream() << kReplicaSetIdFieldName << " field value cannot be null"); - } - } else if (status != ErrorCodes::NoSuchKey) { - return status; + auto modesStatus = _settings.getGetLastErrorModes().convertToTagPatternMap(&_tagConfig); + if (!modesStatus.isOK()) { + return modesStatus.getStatus(); } - _replicaSetId = replicaSetId; + _customWriteConcernModes = std::move(modesStatus.getValue()); return Status::OK(); } @@ -442,13 +270,6 @@ Status ReplSetConfig::validate() const { str::stream() << "Replica set configuration must have non-empty " << kIdFieldName << " field"); } - if (_heartbeatInterval < Milliseconds(0)) { - return Status(ErrorCodes::BadValue, - str::stream() << kSettingsFieldName << '.' << kHeartbeatIntervalFieldName - << " field value must be non-negative, " - "but found " - << durationCount<Milliseconds>(_heartbeatInterval)); - } if (_members.size() > kMaxMembers || _members.empty()) { return Status(ErrorCodes::BadValue, str::stream() << "Replica set configuration contains " << _members.size() @@ -606,21 +427,17 @@ Status ReplSetConfig::validate() const { "one non-arbiter member with priority > 0"); } - if (_defaultWriteConcern.wMode.empty()) { - if (_defaultWriteConcern.wNumNodes == 0) { - return Status(ErrorCodes::BadValue, - "Default write concern mode must wait for at least 1 member"); - } - } else { - if (WriteConcernOptions::kMajority != _defaultWriteConcern.wMode && - !findCustomWriteMode(_defaultWriteConcern.wMode).isOK()) { - return Status(ErrorCodes::BadValue, - str::stream() << "Default write concern requires undefined write mode " - << _defaultWriteConcern.wMode); - } + // This validation must be done outside the IDL because we need to parse the settings object + // completely to get the custom write modes. + const auto& defaultWriteConcern = _settings.getDefaultWriteConcern(); + if (!defaultWriteConcern.wMode.empty() && + WriteConcernOptions::kMajority != defaultWriteConcern.wMode && + !findCustomWriteMode(defaultWriteConcern.wMode).isOK()) { + return Status(ErrorCodes::BadValue, + str::stream() << "Default write concern requires undefined write mode " + << defaultWriteConcern.wMode); } - if (_protocolVersion == 0) { return Status( ErrorCodes::BadValue, @@ -777,7 +594,7 @@ const MemberConfig* ReplSetConfig::findMemberByHostAndPort(const HostAndPort& ha } Milliseconds ReplSetConfig::getHeartbeatInterval() const { - auto heartbeatInterval = _heartbeatInterval; + auto heartbeatInterval = Milliseconds(_settings.getHeartbeatIntervalMillis()); forceHeartbeatIntervalMS.execute([&](const BSONObj& data) { auto intervalMS = data["intervalMS"].numberInt(); heartbeatInterval = Milliseconds(intervalMS); @@ -926,45 +743,9 @@ BSONObj ReplSetConfig::toBSON() const { members.done(); BSONObjBuilder settingsBuilder(configBuilder.subobjStart(kSettingsFieldName)); - settingsBuilder.append(kChainingAllowedFieldName, _chainingAllowed); - settingsBuilder.appendIntOrLL(kHeartbeatIntervalFieldName, - durationCount<Milliseconds>(_heartbeatInterval)); - settingsBuilder.appendIntOrLL(kHeartbeatTimeoutFieldName, - durationCount<Seconds>(_heartbeatTimeoutPeriod)); - settingsBuilder.appendIntOrLL(kElectionTimeoutFieldName, - durationCount<Milliseconds>(_electionTimeoutPeriod)); - settingsBuilder.appendIntOrLL(kCatchUpTimeoutFieldName, - durationCount<Milliseconds>(_catchUpTimeoutPeriod)); - settingsBuilder.appendIntOrLL(kCatchUpTakeoverDelayFieldName, - durationCount<Milliseconds>(_catchUpTakeoverDelay)); - - - BSONObjBuilder gleModes(settingsBuilder.subobjStart(kGetLastErrorModesFieldName)); - for (StringMap<ReplSetTagPattern>::const_iterator mode = _customWriteConcernModes.begin(); - mode != _customWriteConcernModes.end(); - ++mode) { - if (mode->first[0] == '$') { - // Filter out internal modes - continue; - } - BSONObjBuilder modeBuilder(gleModes.subobjStart(mode->first)); - for (ReplSetTagPattern::ConstraintIterator itr = mode->second.constraintsBegin(); - itr != mode->second.constraintsEnd(); - itr++) { - modeBuilder.append(_tagConfig.getTagKey(ReplSetTag(itr->getKeyIndex(), 0)), - itr->getMinCount()); - } - modeBuilder.done(); - } - gleModes.done(); - - settingsBuilder.append(kGetLastErrorDefaultsFieldName, _defaultWriteConcern.toBSON()); - - if (_replicaSetId.isSet()) { - settingsBuilder.append(kReplicaSetIdFieldName, _replicaSetId); - } - + _settings.serialize(&settingsBuilder); settingsBuilder.done(); + return configBuilder.obj(); } diff --git a/src/mongo/db/repl/repl_set_config.h b/src/mongo/db/repl/repl_set_config.h index 943f543cf59..bdee6cb1e03 100644 --- a/src/mongo/db/repl/repl_set_config.h +++ b/src/mongo/db/repl/repl_set_config.h @@ -308,7 +308,7 @@ public: * Gets the default write concern for the replica set described by this configuration. */ const WriteConcernOptions& getDefaultWriteConcern() const { - return _defaultWriteConcern; + return _settings.getDefaultWriteConcern(); } /** @@ -323,7 +323,7 @@ public: * run for election. */ Milliseconds getElectionTimeoutPeriod() const { - return _electionTimeoutPeriod; + return Milliseconds(_settings.getElectionTimeoutMillis()); } /** @@ -331,7 +331,7 @@ public: * nodes in the replica set. */ Seconds getHeartbeatTimeoutPeriod() const { - return _heartbeatTimeoutPeriod; + return Seconds(_settings.getHeartbeatTimeoutSecs()); } /** @@ -340,14 +340,14 @@ public: * Seconds object. */ Milliseconds getHeartbeatTimeoutPeriodMillis() const { - return _heartbeatTimeoutPeriod; + return duration_cast<Milliseconds>(getHeartbeatTimeoutPeriod()); } /** * Gets the timeout to wait for a primary to catch up its oplog. */ Milliseconds getCatchUpTimeoutPeriod() const { - return _catchUpTimeoutPeriod; + return Milliseconds(_settings.getCatchUpTimeoutMillis()); } /** @@ -368,7 +368,7 @@ public: * Returns true if automatic (not explicitly set) chaining is allowed. */ bool isChainingAllowed() const { - return _chainingAllowed; + return _settings.getChainingAllowed(); } /** @@ -455,14 +455,14 @@ public: * have the same replica set name (_id field) but meant for different replica set instances. */ bool hasReplicaSetId() const { - return _replicaSetId.isSet(); + return _settings.getReplicaSetId() != boost::none; } /** * Returns replica set ID. */ OID getReplicaSetId() const { - return _replicaSetId; + return _settings.getReplicaSetId() ? *_settings.getReplicaSetId() : OID(); } /** @@ -476,7 +476,7 @@ public: * sees that it is more caught up than the current primary. */ Milliseconds getCatchUpTakeoverDelay() const { - return _catchUpTakeoverDelay; + return Milliseconds(_settings.getCatchUpTakeoverDelayMillis()); } /** @@ -541,23 +541,16 @@ private: long long _term = OpTime::kUninitializedTerm; std::string _replSetName; std::vector<MemberConfig> _members; - WriteConcernOptions _defaultWriteConcern; - Milliseconds _electionTimeoutPeriod = kDefaultElectionTimeoutPeriod; - Milliseconds _heartbeatInterval = kDefaultHeartbeatInterval; - Seconds _heartbeatTimeoutPeriod = kDefaultHeartbeatTimeoutPeriod; - Milliseconds _catchUpTimeoutPeriod = kDefaultCatchUpTimeoutPeriod; - Milliseconds _catchUpTakeoverDelay = kDefaultCatchUpTakeoverDelay; - bool _chainingAllowed = kDefaultChainingAllowed; bool _writeConcernMajorityJournalDefault = false; int _majorityVoteCount = 0; int _writableVotingMembersCount = 0; int _writeMajority = 0; int _totalVotingMembers = 0; ReplSetTagConfig _tagConfig; + ReplSetConfigSettings _settings; StringMap<ReplSetTagPattern> _customWriteConcernModes; long long _protocolVersion = 1; bool _configServer = false; - OID _replicaSetId; ConnectionString _connectionString; }; diff --git a/src/mongo/db/repl/repl_set_config.idl b/src/mongo/db/repl/repl_set_config.idl index 387595fc88e..41c37c5b91f 100644 --- a/src/mongo/db/repl/repl_set_config.idl +++ b/src/mongo/db/repl/repl_set_config.idl @@ -30,13 +30,16 @@ global: cpp_namespace: "mongo::repl" cpp_includes: - "mongo/db/repl/repl_set_config_validators.h" + - "mongo/db/repl/repl_set_tag.h" + - "mongo/db/repl/repl_set_write_concern_mode_definitions.h" - "mongo/db/repl/member_id.h" - + - "mongo/util/string_map.h" imports: - "mongo/idl/basic_types.idl" - "mongo/util/net/hostandport.idl" - "mongo/db/repl/replication_types.idl" + - "mongo/db/write_concern_options.idl" types: memberId: @@ -45,7 +48,23 @@ types: cpp_type: "MemberId" serializer: MemberId::serializeToBSON deserializer: MemberId::parseFromBSON + + smallExactInt64: + bson_serialization_type: any + description: "Accepts any numerical type within int64 range. Serializes as NumberInt if it + fits in an int32, NumberLong otherwise. Rejects decimals and doubles that + are not integer values." + cpp_type: std::int64_t + serializer: ::smallExactInt64Append + deserializer: ::parseSmallExactInt64 + replSetWriteConcernModeMap: + bson_serialization_type: any + description: "A BSON object mapping write concern mode names to tag sets" + cpp_type: ReplSetWriteConcernModeDefinitions + serializer: ReplSetWriteConcernModeDefinitions::serializeToBSON + deserializer: ReplSetWriteConcernModeDefinitions::parseFromBSON + structs: MemberConfigBase: description: "The configuration for a given member inside of the replica set config" @@ -110,3 +129,58 @@ structs: type: object optional: true description: "The hostnames for the provided horizons" + + ReplSetConfigSettings: + description: "The configuration options that apply to the whole replica set" + strict: false + fields: + chainingAllowed: + type: safeBool + default: true + description: "When true, the replica set allows secondary members to replicate + from other secondary members. When false, secondaries can replicate + only from the primary." + heartbeatIntervalMillis: + type: smallExactInt64 + default: 2000 + description: "The frequency in milliseconds of the heartbeats" + validator: { gt: 0 } + heartbeatTimeoutSecs: + type: smallExactInt64 + default: 10 + description: "Number of seconds that the replica set members wait for a successful + heartbeat from each other" + validator: { gt: 0 } + electionTimeoutMillis: + type: smallExactInt64 + default: 10000 + description: "The time limit in milliseconds for detecting when a replica set’s + primary is unreachable" + validator: { gt: 0 } + catchUpTimeoutMillis: + type: smallExactInt64 + default: -1 + description: "Time limit in milliseconds for a newly elected primary to sync + (catch up) with the other replica set members that may have more + recent writes. If timeout is -1, infinite catchup time. If timeout + is 0, newly elected primaries will not attempt to catch up" + validator: { gte: -1 } + catchUpTakeoverDelayMillis: + type: smallExactInt64 + default: 30000 + description: "Time in milliseconds a node waits to initiate a catchup takeover + after determining it is ahead of the current primary. -1 disables + catchup takeover" + validator: { gte: -1 } + getLastErrorModes: + type: replSetWriteConcernModeMap + default: ReplSetWriteConcernModeDefinitions() + getLastErrorDefaults: + cpp_name: defaultWriteConcern + type: WriteConcern + default: WriteConcernOptions() + validator: { callback: "validateDefaultWriteConcernHasMember"} + replicaSetId: + type: objectid + optional: true + validator: { callback: "validateReplicaSetIdNotNull"} diff --git a/src/mongo/db/repl/repl_set_config_test.cpp b/src/mongo/db/repl/repl_set_config_test.cpp index e088108c723..7ae9d796956 100644 --- a/src/mongo/db/repl/repl_set_config_test.cpp +++ b/src/mongo/db/repl/repl_set_config_test.cpp @@ -1111,13 +1111,32 @@ TEST(ReplSetConfig, HeartbeatIntervalField) { ASSERT_OK(config.validate()); ASSERT_EQUALS(Seconds(5), config.getHeartbeatInterval()); + ASSERT_NOT_OK( + config.initialize(BSON("_id" + << "rs0" + << "version" << 1 << "protocolVersion" << 1 << "members" + << BSON_ARRAY(BSON("_id" << 0 << "host" + << "localhost:12345")) + << "settings" << BSON("heartbeatIntervalMillis" << -5000)))); +} + +// This test covers the "exact" behavior of all the smallExactInt fields. +TEST(ReplSetConfig, DecimalHeartbeatIntervalField) { + ReplSetConfig config; ASSERT_OK(config.initialize(BSON("_id" << "rs0" << "version" << 1 << "protocolVersion" << 1 << "members" << BSON_ARRAY(BSON("_id" << 0 << "host" << "localhost:12345")) - << "settings" << BSON("heartbeatIntervalMillis" << -5000)))); - ASSERT_EQUALS(ErrorCodes::BadValue, config.validate()); + << "settings" << BSON("heartbeatIntervalMillis" << 5000.0)))); + + ASSERT_NOT_OK( + config.initialize(BSON("_id" + << "rs0" + << "version" << 1 << "protocolVersion" << 1 << "members" + << BSON_ARRAY(BSON("_id" << 0 << "host" + << "localhost:12345")) + << "settings" << BSON("heartbeatIntervalMillis" << 5000.1)))); } TEST(ReplSetConfig, ElectionTimeoutField) { @@ -1137,8 +1156,7 @@ TEST(ReplSetConfig, ElectionTimeoutField) { << BSON_ARRAY(BSON("_id" << 0 << "host" << "localhost:12345")) << "settings" << BSON("electionTimeoutMillis" << -20))); - ASSERT_EQUALS(ErrorCodes::BadValue, status); - ASSERT_STRING_CONTAINS(status.reason(), "election timeout must be greater than 0"); + ASSERT_NOT_OK(status); } TEST(ReplSetConfig, HeartbeatTimeoutField) { @@ -1158,8 +1176,7 @@ TEST(ReplSetConfig, HeartbeatTimeoutField) { << BSON_ARRAY(BSON("_id" << 0 << "host" << "localhost:12345")) << "settings" << BSON("heartbeatTimeoutSecs" << -20))); - ASSERT_EQUALS(ErrorCodes::BadValue, status); - ASSERT_STRING_CONTAINS(status.reason(), "heartbeat timeout must be greater than 0"); + ASSERT_NOT_OK(status); } TEST(ReplSetConfig, GleDefaultField) { @@ -1185,14 +1202,14 @@ TEST(ReplSetConfig, GleDefaultField) { << "frim"))))); ASSERT_EQUALS(ErrorCodes::BadValue, config.validate()); - ASSERT_OK( + // Test that default write concern must have at least one member. + ASSERT_NOT_OK( config.initialize(BSON("_id" << "rs0" << "version" << 1 << "protocolVersion" << 1 << "members" << BSON_ARRAY(BSON("_id" << 0 << "host" << "localhost:12345")) << "settings" << BSON("getLastErrorDefaults" << BSON("w" << 0))))); - ASSERT_EQUALS(ErrorCodes::BadValue, config.validate()); ASSERT_OK( config.initialize(BSON("_id" @@ -1352,7 +1369,7 @@ TEST(ReplSetConfig, toBSONRoundTripAbilityLarge) { << "protocolVersion" << 1 << "settings" << BSON("heartbeatIntervalMillis" << 5000 << "heartbeatTimeoutSecs" << 20 - << "electionTimeoutMillis" << 4 << "chainingAllowd" + << "electionTimeoutMillis" << 4 << "chainingAllowed" << true << "getLastErrorDefaults" << BSON("w" << "majority") @@ -1638,10 +1655,7 @@ TEST(ReplSetConfig, GetCatchUpTakeoverDelay) { << BSON_ARRAY(BSON("_id" << 0 << "host" << "localhost:12345")) << "settings" << BSON("catchUpTakeoverDelayMillis" << -5000))); - ASSERT_EQUALS(ErrorCodes::BadValue, status); - ASSERT_STRING_CONTAINS( - status.reason(), - "catch-up takeover delay must be -1 (no catch-up takeover) or greater than or equal to 0"); + ASSERT_NOT_OK(status); } TEST(ReplSetConfig, GetCatchUpTakeoverDelayDefault) { @@ -1877,8 +1891,6 @@ TEST(ReplSetConfig, ReplSetId) { << "priority" << 1)) << "settings" << BSON("replicaSetId" << 12345))); ASSERT_EQUALS(ErrorCodes::TypeMismatch, status); - ASSERT_STRING_CONTAINS(status.reason(), - "\"replicaSetId\" had the wrong type. Expected objectId, found int"); } TEST(ReplSetConfig, ConfigVersionAndTermComparison) { diff --git a/src/mongo/db/repl/repl_set_config_validators.cpp b/src/mongo/db/repl/repl_set_config_validators.cpp new file mode 100644 index 00000000000..065c8274c77 --- /dev/null +++ b/src/mongo/db/repl/repl_set_config_validators.cpp @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/repl/repl_set_config_gen.h" +#include "mongo/db/repl/repl_set_config_validators.h" + +namespace mongo { +namespace repl { + +Status validateReplicaSetIdNotNull(OID replicaSetId) { + if (!replicaSetId.isSet()) { + return Status(ErrorCodes::BadValue, + str::stream() << ReplSetConfigSettings::kReplicaSetIdFieldName + << " field value cannot be null"); + } + return Status::OK(); +} + +} // namespace repl +} // namespace mongo diff --git a/src/mongo/db/repl/repl_set_config_validators.h b/src/mongo/db/repl/repl_set_config_validators.h index b02961aba78..061d0b80c86 100644 --- a/src/mongo/db/repl/repl_set_config_validators.h +++ b/src/mongo/db/repl/repl_set_config_validators.h @@ -27,7 +27,13 @@ * it in the license file. */ +#pragma once + #include "mongo/base/status.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/bson/oid.h" +#include "mongo/db/write_concern_options.h" +#include "mongo/util/str.h" namespace mongo { namespace repl { @@ -42,5 +48,37 @@ inline Status validateTrue(bool boolVal) { return Status::OK(); } +inline Status validateDefaultWriteConcernHasMember(const WriteConcernOptions& defaultWriteConcern) { + if (defaultWriteConcern.wMode.empty() && defaultWriteConcern.wNumNodes == 0) { + return Status(ErrorCodes::BadValue, + "Default write concern mode must wait for at least 1 member"); + } + return Status::OK(); +} + +Status validateReplicaSetIdNotNull(OID replicaSetId); + +/** + * For serialization and deserialization of certain values in the IDL. + */ +inline void smallExactInt64Append(std::int64_t value, StringData fieldName, BSONObjBuilder* bob) { + bob->appendIntOrLL(fieldName, value); +} + +inline std::int64_t parseSmallExactInt64(const BSONElement& element) { + uassert(ErrorCodes::TypeMismatch, + str::stream() << "Expected a number, got value of type " << typeName(element.type()) + << " for field " << element.fieldName(), + element.isNumber()); + std::int64_t result = element.safeNumberLong(); + uassert(4708900, + str::stream() << "Expected field \"" << element.fieldName() + << "\" to have a value " + "exactly representable as a 64-bit integer, but found " + << element, + result == element.numberDouble()); + return result; +} + } // namespace repl -} // namespace mongo
\ No newline at end of file +} // namespace mongo diff --git a/src/mongo/db/repl/repl_set_tag.h b/src/mongo/db/repl/repl_set_tag.h index 641b4283323..87dd68bd2ac 100644 --- a/src/mongo/db/repl/repl_set_tag.h +++ b/src/mongo/db/repl/repl_set_tag.h @@ -115,6 +115,9 @@ public: public: TagCountConstraint() {} TagCountConstraint(int32_t keyIndex, int32_t minCount); + bool operator==(const TagCountConstraint& other) const { + return _keyIndex == other._keyIndex && _minCount == other._minCount; + } int32_t getKeyIndex() const { return _keyIndex; } diff --git a/src/mongo/db/repl/repl_set_write_concern_mode_definitions.cpp b/src/mongo/db/repl/repl_set_write_concern_mode_definitions.cpp new file mode 100644 index 00000000000..648d447b1e9 --- /dev/null +++ b/src/mongo/db/repl/repl_set_write_concern_mode_definitions.cpp @@ -0,0 +1,107 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/repl/repl_set_write_concern_mode_definitions.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace repl { +void ReplSetWriteConcernModeDefinitions::serializeToBSON(StringData fieldName, + BSONObjBuilder* bob) const { + BSONObjBuilder mapBuilder(bob->subobjStart(fieldName)); + for (const auto& definitionItems : _definitions) { + BSONObjBuilder defBuilder(mapBuilder.subobjStart(definitionItems.first)); + for (const auto& constraint : definitionItems.second) { + defBuilder.append(constraint.first, constraint.second); + } + defBuilder.done(); + } + mapBuilder.done(); +} + +/* static */ +ReplSetWriteConcernModeDefinitions ReplSetWriteConcernModeDefinitions::parseFromBSON( + BSONElement patternMapElement) { + const auto& fieldName = patternMapElement.fieldName(); + uassert(ErrorCodes::TypeMismatch, + str::stream() << "Expected " << fieldName << " to be an Object, it actually had type " + << typeName(patternMapElement.type()), + patternMapElement.type() == Object); + Definitions definitions; + BSONObj modes = patternMapElement.Obj(); + for (auto&& modeElement : modes) { + uassert(51001, + str::stream() << fieldName << " contains multiple fields named " + << modeElement.fieldName(), + definitions.find(modeElement.fieldNameStringData()) == definitions.end()); + uassert(ErrorCodes::TypeMismatch, + str::stream() << "Expected " << fieldName << '.' << modeElement.fieldName() + << " to be an Object, not " << typeName(modeElement.type()), + modeElement.type() == Object); + Definition& definition = definitions[modeElement.fieldNameStringData()]; + for (auto&& constraintElement : modeElement.Obj()) { + uassert(ErrorCodes::TypeMismatch, + str::stream() << "Expected " << fieldName << '.' << modeElement.fieldName() + << '.' << constraintElement.fieldName() << " to be a number, not " + << typeName(constraintElement.type()), + constraintElement.isNumber()); + const int minCount = constraintElement.numberInt(); + uassert(ErrorCodes::BadValue, + str::stream() << "Value of " << fieldName << '.' << modeElement.fieldName() + << '.' << constraintElement.fieldName() + << " must be positive, but found " << minCount, + minCount > 0); + definition.emplace_back(constraintElement.fieldNameStringData(), minCount); + } + } + return ReplSetWriteConcernModeDefinitions(std::move(definitions)); +} + +StatusWith<StringMap<ReplSetTagPattern>> ReplSetWriteConcernModeDefinitions::convertToTagPatternMap( + ReplSetTagConfig* tagConfig) const { + StringMap<ReplSetTagPattern> result; + for (const auto& definitionItems : _definitions) { + ReplSetTagPattern& pattern = + result.insert({definitionItems.first, tagConfig->makePattern()}).first->second; + for (const auto& constraint : definitionItems.second) { + Status status = tagConfig->addTagCountConstraintToPattern( + &pattern, constraint.first, constraint.second); + if (!status.isOK()) { + return status; + } + } + } + return result; +} + +} // namespace repl +} // namespace mongo diff --git a/src/mongo/db/repl/repl_set_write_concern_mode_definitions.h b/src/mongo/db/repl/repl_set_write_concern_mode_definitions.h new file mode 100644 index 00000000000..b8fb35de3fc --- /dev/null +++ b/src/mongo/db/repl/repl_set_write_concern_mode_definitions.h @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <utility> +#include <vector> + +#include "mongo/base/status.h" +#include "mongo/db/repl/repl_set_tag.h" +#include "mongo/util/string_map.h" + +namespace mongo { +namespace repl { + +class ReplSetWriteConcernModeDefinitions { +public: + // Default constructor for use as an IDL default. + ReplSetWriteConcernModeDefinitions() {} + + /** + * A constraint maps a tag (defined in the "members" section of the ReplSetConfig) to an integer + * specifying the number of nodes matching that tag which need to accept the write before the + * constraint is satisfied. + */ + typedef std::pair<std::string, std::int32_t> Constraint; + + /** + * A write concern mode is defined by a list of constraints. + */ + typedef std::vector<Constraint> Definition; + + /** + * This maps a write concern mode name to the list of constraints which defines it. + */ + typedef StringMap<Definition> Definitions; + + void serializeToBSON(StringData fieldName, BSONObjBuilder* bob) const; + static ReplSetWriteConcernModeDefinitions parseFromBSON(BSONElement patternMapElement); + + /** + * Returns a StringMap of ReplSetTagPatterns, created from these definitions using the passed-in + * tagConfig. Will fail if the constraints don't correspond to existing tags in the tagConfig. + */ + StatusWith<StringMap<ReplSetTagPattern>> convertToTagPatternMap( + ReplSetTagConfig* tagConfig) const; + +private: + ReplSetWriteConcernModeDefinitions(Definitions&& definitions) + : _definitions(std::move(definitions)) {} + + Definitions _definitions; +}; + +} // namespace repl +} // namespace mongo diff --git a/src/mongo/db/repl/repl_set_write_concern_mode_definitions_test.cpp b/src/mongo/db/repl/repl_set_write_concern_mode_definitions_test.cpp new file mode 100644 index 00000000000..cdf0b5e10c2 --- /dev/null +++ b/src/mongo/db/repl/repl_set_write_concern_mode_definitions_test.cpp @@ -0,0 +1,204 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include <algorithm> + +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/bson/unordered_fields_bsonobj_comparator.h" +#include "mongo/db/repl/repl_set_write_concern_mode_definitions.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace repl { +TEST(ReplSetWriteConcernModeDefinitions, Default) { + auto definitions = ReplSetWriteConcernModeDefinitions(); + BSONObjBuilder bob; + definitions.serializeToBSON("someTag", &bob); + ASSERT_BSONOBJ_EQ(bob.obj(), BSON("someTag" << BSONObj())); + + ReplSetTagConfig tagConfig; + auto tagPatternMapStatus = definitions.convertToTagPatternMap(&tagConfig); + ASSERT_OK(tagPatternMapStatus.getStatus()); + auto tagPatternMap = tagPatternMapStatus.getValue(); + ASSERT(tagPatternMap.empty()); +} + +TEST(ReplSetWriteConcernModeDefinitions, Empty) { + auto definitions = ReplSetWriteConcernModeDefinitions::parseFromBSON( + BSON("someTag" << BSONObj()).firstElement()); + BSONObjBuilder bob; + definitions.serializeToBSON("someTag", &bob); + ASSERT_BSONOBJ_EQ(bob.obj(), BSON("someTag" << BSONObj())); + + ReplSetTagConfig tagConfig; + auto tagPatternMapStatus = definitions.convertToTagPatternMap(&tagConfig); + ASSERT_OK(tagPatternMapStatus.getStatus()); + auto tagPatternMap = tagPatternMapStatus.getValue(); + ASSERT(tagPatternMap.empty()); +} + +TEST(ReplSetWriteConcernModeDefinitions, HasCustomModes) { + const StringData fieldName("modes"_sd); + auto writeConcernModes = BSON( + fieldName << BSON("wc1" << BSON("tag1" << 1 << "tag2" << 2) << "wc2" << BSON("tag3" << 3))); + auto definitions = + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()); + BSONObjBuilder bob; + definitions.serializeToBSON(fieldName, &bob); + auto roundTrip = bob.obj(); + UnorderedFieldsBSONObjComparator comparator; + ASSERT_EQ(0, comparator.compare(roundTrip, writeConcernModes)) + << "Expected " << writeConcernModes << " == " << roundTrip; + + ReplSetTagConfig tagConfig; + auto tag1 = tagConfig.makeTag("tag1", "id1"); + auto tag2 = tagConfig.makeTag("tag2", "id2"); + auto tag3 = tagConfig.makeTag("tag3", "id3"); + auto tagPatternMapStatus = definitions.convertToTagPatternMap(&tagConfig); + ASSERT_OK(tagPatternMapStatus.getStatus()); + auto tagPatternMap = tagPatternMapStatus.getValue(); + ASSERT_EQ(2, tagPatternMap.size()); + ASSERT(tagPatternMap.find("wc1") != tagPatternMap.end()); + ASSERT(tagPatternMap.find("wc2") != tagPatternMap.end()); + ASSERT_EQ(2, + std::distance(tagPatternMap["wc1"].constraintsBegin(), + tagPatternMap["wc1"].constraintsEnd())); + ASSERT(std::find(tagPatternMap["wc1"].constraintsBegin(), + tagPatternMap["wc1"].constraintsEnd(), + ReplSetTagPattern::TagCountConstraint(tag1.getKeyIndex(), 1)) != + tagPatternMap["wc1"].constraintsEnd()); + ASSERT(std::find(tagPatternMap["wc1"].constraintsBegin(), + tagPatternMap["wc1"].constraintsEnd(), + ReplSetTagPattern::TagCountConstraint(tag2.getKeyIndex(), 2)) != + tagPatternMap["wc1"].constraintsEnd()); + + ASSERT_EQ(1, + std::distance(tagPatternMap["wc2"].constraintsBegin(), + tagPatternMap["wc2"].constraintsEnd())); + ASSERT(std::find(tagPatternMap["wc2"].constraintsBegin(), + tagPatternMap["wc2"].constraintsEnd(), + ReplSetTagPattern::TagCountConstraint(tag3.getKeyIndex(), 3)) != + tagPatternMap["wc2"].constraintsEnd()); +} + +TEST(ReplSetWriteConcernModeDefinitions, TagMissingFromTagConfig) { + const StringData fieldName("modes"_sd); + auto writeConcernModes = BSON( + fieldName << BSON("wc1" << BSON("tag1" << 1 << "tag2" << 2) << "wc2" << BSON("tag3" << 3))); + auto definitions = + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()); + BSONObjBuilder bob; + definitions.serializeToBSON(fieldName, &bob); + auto roundTrip = bob.obj(); + UnorderedFieldsBSONObjComparator comparator; + ASSERT_EQ(0, comparator.compare(roundTrip, writeConcernModes)) + << "Expected " << writeConcernModes << " == " << roundTrip; + + ReplSetTagConfig tagConfig; + tagConfig.makeTag("tag1", "id1"); + tagConfig.makeTag("tag3", "id3"); + + auto tagPatternMapStatus = definitions.convertToTagPatternMap(&tagConfig); + ASSERT_EQ(ErrorCodes::NoSuchKey, tagPatternMapStatus.getStatus()); + ASSERT_STRING_CONTAINS(tagPatternMapStatus.getStatus().reason(), "tag2"); +} + +TEST(ReplSetWriteConcernModeDefinitions, DuplicateModeNames) { + const StringData fieldName("modes"_sd); + auto writeConcernModes = BSON( + fieldName << BSON("wc1" << BSON("tag1" << 1 << "tag2" << 2) << "wc1" << BSON("tag3" << 3))); + ASSERT_THROWS( + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()), + AssertionException); +} + +TEST(ReplSetWriteConcernModeDefinitions, ZeroConstraint) { + const StringData fieldName("modes"_sd); + auto writeConcernModes = BSON( + fieldName << BSON("wc1" << BSON("tag1" << 0 << "tag2" << 2) << "wc2" << BSON("tag3" << 3))); + ASSERT_THROWS( + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()), + AssertionException); +} + +TEST(ReplSetWriteConcernModeDefinitions, NegativeConstraint) { + const StringData fieldName("modes"_sd); + auto writeConcernModes = BSON(fieldName << BSON("wc1" << BSON("tag1" << -1 << "tag2" << 2) + << "wc2" << BSON("tag3" << 3))); + ASSERT_THROWS( + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()), + AssertionException); +} + +TEST(ReplSetWriteConcernModeDefinitions, ModesMustBeObject) { + auto writeConcernModes = BSON("modes" + << "stringIsBad"); + ASSERT_THROWS( + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()), + ExceptionFor<ErrorCodes::TypeMismatch>); + + writeConcernModes = BSON("modes" << 99); + ASSERT_THROWS( + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()), + ExceptionFor<ErrorCodes::TypeMismatch>); + + writeConcernModes = BSON("modes" << BSON_ARRAY("a" + << "b" + << "c")); + ASSERT_THROWS( + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()), + ExceptionFor<ErrorCodes::TypeMismatch>); +} + +TEST(ReplSetWriteConcernModeDefinitions, ConstraintsMustBeNumbers) { + auto writeConcernModes = BSON("modes" << BSON("wc1" << BSON("tag1" + << "1" + << "tag2" << 2) + << "wc2" << BSON("tag3" << 3))); + ASSERT_THROWS( + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()), + ExceptionFor<ErrorCodes::TypeMismatch>); + + writeConcernModes = BSON("modes" << BSON("wc1" << BSON("tag1" << BSONObj() << "tag2" << 2) + << "wc2" << BSON("tag3" << 3))); + ASSERT_THROWS( + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()), + ExceptionFor<ErrorCodes::TypeMismatch>); + + writeConcernModes = BSON("modes" << BSON("wc1" << BSON("tag1" << BSON_ARRAY(1) << "tag2" << 2) + << "wc2" << BSON("tag3" << 3))); + ASSERT_THROWS( + ReplSetWriteConcernModeDefinitions::parseFromBSON(writeConcernModes.firstElement()), + ExceptionFor<ErrorCodes::TypeMismatch>); +} + +} // namespace repl +} // namespace mongo |