summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorMatthew Russotto <matthew.russotto@10gen.com>2020-04-10 13:26:16 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-04-10 17:48:42 +0000
commit0cfd1f9779dba8168913f5e5a00ab0974f7a957a (patch)
tree8b3115b6d63725247ad66acc6e781bf79358766b /src/mongo
parent7645ff98bb0f289f8eb1605cb743b414e5ded0fc (diff)
downloadmongo-0cfd1f9779dba8168913f5e5a00ab0974f7a957a.tar.gz
SERVER-47089 Create IDL for ReplSetConfig "settings" object.
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/repl/SConscript3
-rw-r--r--src/mongo/db/repl/repl_set_config.cpp275
-rw-r--r--src/mongo/db/repl/repl_set_config.h27
-rw-r--r--src/mongo/db/repl/repl_set_config.idl76
-rw-r--r--src/mongo/db/repl/repl_set_config_test.cpp42
-rw-r--r--src/mongo/db/repl/repl_set_config_validators.cpp48
-rw-r--r--src/mongo/db/repl/repl_set_config_validators.h40
-rw-r--r--src/mongo/db/repl/repl_set_tag.h3
-rw-r--r--src/mongo/db/repl/repl_set_write_concern_mode_definitions.cpp107
-rw-r--r--src/mongo/db/repl/repl_set_write_concern_mode_definitions.h82
-rw-r--r--src/mongo/db/repl/repl_set_write_concern_mode_definitions_test.cpp204
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