/**
* Copyright 2014 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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 GNU Affero General 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/json.h"
#include "mongo/bson/mutable/document.h"
#include "mongo/bson/mutable/element.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/repl/repl_set_config.h"
#include "mongo/db/server_options.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/scopeguard.h"
namespace mongo {
namespace repl {
namespace {
// Creates a bson document reprsenting a replica set config doc with the given members, and votes
BSONObj createConfigDoc(int members, int voters = ReplSetConfig::kMaxVotingMembers) {
str::stream configJson;
configJson << "{_id:'rs0', version:1, members:[";
for (int i = 0; i < members; ++i) {
configJson << "{_id:" << i << ", host:'node" << i << "'";
if (i >= voters) {
configJson << ", votes:0, priority:0";
}
configJson << "}";
if (i != (members - 1))
configJson << ",";
}
configJson << "]}";
return fromjson(configJson);
}
TEST(ReplSetConfig, ParseMinimalConfigAndCheckDefaults) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_OK(config.validate());
ASSERT_EQUALS("rs0", config.getReplSetName());
ASSERT_EQUALS(1, config.getConfigVersion());
ASSERT_EQUALS(1, config.getNumMembers());
ASSERT_EQUALS(0, config.membersBegin()->getId());
ASSERT_EQUALS(1, config.getDefaultWriteConcern().wNumNodes);
ASSERT_EQUALS("", config.getDefaultWriteConcern().wMode);
ASSERT_EQUALS(ReplSetConfig::kDefaultHeartbeatInterval, config.getHeartbeatInterval());
ASSERT_EQUALS(ReplSetConfig::kDefaultHeartbeatTimeoutPeriod,
config.getHeartbeatTimeoutPeriod());
ASSERT_EQUALS(ReplSetConfig::kDefaultElectionTimeoutPeriod, config.getElectionTimeoutPeriod());
ASSERT_TRUE(config.isChainingAllowed());
ASSERT_FALSE(config.getWriteConcernMajorityShouldJournal());
ASSERT_FALSE(config.isConfigServer());
ASSERT_EQUALS(0, config.getProtocolVersion());
ASSERT_EQUALS(
ConnectionString::forReplicaSet("rs0", {HostAndPort{"localhost:12345"}}).toString(),
config.getConnectionString().toString());
}
TEST(ReplSetConfig, ParseLargeConfigAndCheckAccessors) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1234
<< "members"
<< BSON_ARRAY(BSON("_id" << 234 << "host"
<< "localhost:12345"
<< "tags"
<< BSON("NYC"
<< "NY")))
<< "protocolVersion"
<< 1
<< "settings"
<< BSON("getLastErrorDefaults"
<< BSON("w"
<< "majority")
<< "getLastErrorModes"
<< BSON("eastCoast" << BSON("NYC" << 1))
<< "chainingAllowed"
<< false
<< "heartbeatIntervalMillis"
<< 5000
<< "heartbeatTimeoutSecs"
<< 120
<< "electionTimeoutMillis"
<< 10))));
ASSERT_OK(config.validate());
ASSERT_EQUALS("rs0", config.getReplSetName());
ASSERT_EQUALS(1234, config.getConfigVersion());
ASSERT_EQUALS(1, config.getNumMembers());
ASSERT_EQUALS(234, config.membersBegin()->getId());
ASSERT_EQUALS(0, config.getDefaultWriteConcern().wNumNodes);
ASSERT_EQUALS("majority", config.getDefaultWriteConcern().wMode);
ASSERT_FALSE(config.isChainingAllowed());
ASSERT_TRUE(config.getWriteConcernMajorityShouldJournal());
ASSERT_FALSE(config.isConfigServer());
ASSERT_EQUALS(Seconds(5), config.getHeartbeatInterval());
ASSERT_EQUALS(Seconds(120), config.getHeartbeatTimeoutPeriod());
ASSERT_EQUALS(Milliseconds(10), config.getElectionTimeoutPeriod());
ASSERT_EQUALS(1, config.getProtocolVersion());
ASSERT_EQUALS(
ConnectionString::forReplicaSet("rs0", {HostAndPort{"localhost:12345"}}).toString(),
config.getConnectionString().toString());
}
TEST(ReplSetConfig, GetConnectionStringFiltersHiddenNodes) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:11111")
<< BSON("_id" << 1 << "host"
<< "localhost:22222"
<< "arbiterOnly"
<< true)
<< BSON("_id" << 2 << "host"
<< "localhost:33333"
<< "hidden"
<< true
<< "priority"
<< 0)
<< BSON("_id" << 3 << "host"
<< "localhost:44444")))));
ASSERT_OK(config.validate());
ASSERT_EQUALS(ConnectionString::forReplicaSet(
"rs0", {HostAndPort{"localhost:11111"}, HostAndPort{"localhost:44444"}})
.toString(),
config.getConnectionString().toString());
}
TEST(ReplSetConfig, MajorityCalculationThreeVotersNoArbiters) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 2
<< "members"
<< BSON_ARRAY(BSON("_id" << 1 << "host"
<< "h1:1")
<< BSON("_id" << 2 << "host"
<< "h2:1")
<< BSON("_id" << 3 << "host"
<< "h3:1")
<< BSON("_id" << 4 << "host"
<< "h4:1"
<< "votes"
<< 0
<< "priority"
<< 0)
<< BSON("_id" << 5 << "host"
<< "h5:1"
<< "votes"
<< 0
<< "priority"
<< 0)))));
ASSERT_OK(config.validate());
ASSERT_EQUALS(2, config.getWriteMajority());
}
TEST(ReplSetConfig, MajorityCalculationNearlyHalfArbiters) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "mySet"
<< "version"
<< 2
<< "members"
<< BSON_ARRAY(BSON("host"
<< "node1:12345"
<< "_id"
<< 0)
<< BSON("host"
<< "node2:12345"
<< "_id"
<< 1)
<< BSON("host"
<< "node3:12345"
<< "_id"
<< 2)
<< BSON("host"
<< "node4:12345"
<< "_id"
<< 3
<< "arbiterOnly"
<< true)
<< BSON("host"
<< "node5:12345"
<< "_id"
<< 4
<< "arbiterOnly"
<< true)))));
ASSERT_OK(config.validate());
ASSERT_EQUALS(3, config.getWriteMajority());
}
TEST(ReplSetConfig, MajorityCalculationEvenNumberOfMembers) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "mySet"
<< "version"
<< 2
<< "members"
<< BSON_ARRAY(BSON("host"
<< "node1:12345"
<< "_id"
<< 0)
<< BSON("host"
<< "node2:12345"
<< "_id"
<< 1)
<< BSON("host"
<< "node3:12345"
<< "_id"
<< 2)
<< BSON("host"
<< "node4:12345"
<< "_id"
<< 3)))));
ASSERT_OK(config.validate());
ASSERT_EQUALS(3, config.getWriteMajority());
}
TEST(ReplSetConfig, MajorityCalculationNearlyHalfSecondariesNoVotes) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "mySet"
<< "version"
<< 2
<< "members"
<< BSON_ARRAY(BSON("host"
<< "node1:12345"
<< "_id"
<< 0)
<< BSON("host"
<< "node2:12345"
<< "_id"
<< 1
<< "votes"
<< 0
<< "priority"
<< 0)
<< BSON("host"
<< "node3:12345"
<< "_id"
<< 2
<< "votes"
<< 0
<< "priority"
<< 0)
<< BSON("host"
<< "node4:12345"
<< "_id"
<< 3)
<< BSON("host"
<< "node5:12345"
<< "_id"
<< 4)))));
ASSERT_OK(config.validate());
ASSERT_EQUALS(2, config.getWriteMajority());
}
TEST(ReplSetConfig, ParseFailsWithBadOrMissingIdField) {
ReplSetConfig config;
// Replica set name must be a string.
ASSERT_EQUALS(ErrorCodes::TypeMismatch,
config.initialize(BSON("_id" << 1 << "version" << 1 << "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
// Replica set name must be present.
ASSERT_EQUALS(
ErrorCodes::NoSuchKey,
config.initialize(
BSON("version" << 1 << "members" << BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
// Empty repl set name parses, but does not validate.
ASSERT_OK(config.initialize(BSON("_id"
<< ""
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
}
TEST(ReplSetConfig, ParseFailsWithBadOrMissingVersionField) {
ReplSetConfig config;
// Config version field must be present.
ASSERT_EQUALS(ErrorCodes::NoSuchKey,
config.initialize(BSON("_id"
<< "rs0"
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_EQUALS(ErrorCodes::TypeMismatch,
config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< "1"
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1.0
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_OK(config.validate());
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 0.0
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< static_cast(std::numeric_limits::max()) + 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
}
TEST(ReplSetConfig, ParseFailsWithBadMembers) {
ReplSetConfig config;
ASSERT_EQUALS(ErrorCodes::TypeMismatch,
config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")
<< "localhost:23456"))));
ASSERT_NOT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("host"
<< "localhost:12345")))));
}
TEST(ReplSetConfig, ParseFailsWithLocalNonLocalHostMix) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost")
<< BSON("_id" << 1 << "host"
<< "otherhost")))));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
}
TEST(ReplSetConfig, ParseFailsWithNoElectableNodes) {
ReplSetConfig config;
const BSONObj configBsonNoElectableNodes = BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:1"
<< "priority"
<< 0)
<< BSON("_id" << 1 << "host"
<< "localhost:2"
<< "priority"
<< 0)));
ASSERT_OK(config.initialize(configBsonNoElectableNodes));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
const BSONObj configBsonNoElectableNodesOneArbiter = BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(
BSON("_id" << 0 << "host"
<< "localhost:1"
<< "arbiterOnly"
<< 1)
<< BSON("_id" << 1 << "host"
<< "localhost:2"
<< "priority"
<< 0)));
ASSERT_OK(config.initialize(configBsonNoElectableNodesOneArbiter));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
const BSONObj configBsonNoElectableNodesTwoArbiters = BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(
BSON("_id" << 0 << "host"
<< "localhost:1"
<< "arbiterOnly"
<< 1)
<< BSON("_id" << 1 << "host"
<< "localhost:2"
<< "arbiterOnly"
<< 1)));
ASSERT_OK(config.initialize(configBsonNoElectableNodesOneArbiter));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
const BSONObj configBsonOneElectableNode = BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:1"
<< "priority"
<< 0)
<< BSON("_id" << 1 << "host"
<< "localhost:2"
<< "priority"
<< 1)));
ASSERT_OK(config.initialize(configBsonOneElectableNode));
ASSERT_OK(config.validate());
}
TEST(ReplSetConfig, ParseFailsWithTooFewVoters) {
ReplSetConfig config;
const BSONObj configBsonNoVoters = BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:1"
<< "votes"
<< 0
<< "priority"
<< 0)
<< BSON("_id" << 1 << "host"
<< "localhost:2"
<< "votes"
<< 0
<< "priority"
<< 0)));
ASSERT_OK(config.initialize(configBsonNoVoters));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
const BSONObj configBsonOneVoter = BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:1"
<< "votes"
<< 0
<< "priority"
<< 0)
<< BSON("_id" << 1 << "host"
<< "localhost:2"
<< "votes"
<< 1)));
ASSERT_OK(config.initialize(configBsonOneVoter));
ASSERT_OK(config.validate());
}
TEST(ReplSetConfig, ParseFailsWithTooManyVoters) {
ReplSetConfig config;
ASSERT_OK(config.initialize(createConfigDoc(8, ReplSetConfig::kMaxVotingMembers)));
ASSERT_OK(config.validate());
ASSERT_OK(config.initialize(createConfigDoc(8, ReplSetConfig::kMaxVotingMembers + 1)));
ASSERT_NOT_OK(config.validate());
}
TEST(ReplSetConfig, ParseFailsWithDuplicateHost) {
ReplSetConfig config;
const BSONObj configBson = BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:1")
<< BSON("_id" << 1 << "host"
<< "localhost:1")));
ASSERT_OK(config.initialize(configBson));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
}
TEST(ReplSetConfig, ParseFailsWithTooManyNodes) {
ReplSetConfig config;
namespace mmb = mutablebson;
mmb::Document configDoc;
mmb::Element configDocRoot = configDoc.root();
ASSERT_OK(configDocRoot.appendString("_id", "rs0"));
ASSERT_OK(configDocRoot.appendInt("version", 1));
mmb::Element membersArray = configDoc.makeElementArray("members");
ASSERT_OK(configDocRoot.pushBack(membersArray));
for (size_t i = 0; i < ReplSetConfig::kMaxMembers; ++i) {
mmb::Element memberElement = configDoc.makeElementObject("");
ASSERT_OK(membersArray.pushBack(memberElement));
ASSERT_OK(memberElement.appendInt("_id", i));
ASSERT_OK(
memberElement.appendString("host", std::string(str::stream() << "localhost" << i + 1)));
if (i >= ReplSetConfig::kMaxVotingMembers) {
ASSERT_OK(memberElement.appendInt("votes", 0));
ASSERT_OK(memberElement.appendInt("priority", 0));
}
}
const BSONObj configBsonMaxNodes = configDoc.getObject();
mmb::Element memberElement = configDoc.makeElementObject("");
ASSERT_OK(membersArray.pushBack(memberElement));
ASSERT_OK(memberElement.appendInt("_id", ReplSetConfig::kMaxMembers));
ASSERT_OK(memberElement.appendString(
"host", std::string(str::stream() << "localhost" << ReplSetConfig::kMaxMembers + 1)));
ASSERT_OK(memberElement.appendInt("votes", 0));
ASSERT_OK(memberElement.appendInt("priority", 0));
const BSONObj configBsonTooManyNodes = configDoc.getObject();
ASSERT_OK(config.initialize(configBsonMaxNodes));
ASSERT_OK(config.validate());
ASSERT_OK(config.initialize(configBsonTooManyNodes));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
}
TEST(ReplSetConfig, ParseFailsWithUnexpectedField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "unexpectedfield"
<< "value"));
ASSERT_EQUALS(ErrorCodes::BadValue, status);
}
TEST(ReplSetConfig, ParseFailsWithNonArrayMembersField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< "value"));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
}
TEST(ReplSetConfig, ParseFailsWithNonNumericHeartbeatIntervalMillisField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("heartbeatIntervalMillis"
<< "no")));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
ASSERT_FALSE(config.isInitialized());
// Uninitialized configuration should return default heartbeat interval.
ASSERT_EQUALS(ReplSetConfig::kDefaultHeartbeatInterval, config.getHeartbeatInterval());
}
TEST(ReplSetConfig, ParseFailsWithNonNumericElectionTimeoutMillisField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("electionTimeoutMillis"
<< "no")));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
}
TEST(ReplSetConfig, ParseFailsWithNonNumericHeartbeatTimeoutSecsField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("heartbeatTimeoutSecs"
<< "no")));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
}
TEST(ReplSetConfig, ParseFailsWithNonBoolChainingAllowedField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("chainingAllowed"
<< "no")));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
}
TEST(ReplSetConfig, ParseFailsWithNonBoolConfigServerField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "configsvr"
<< "no"));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
}
TEST(ReplSetConfig, ParseFailsWithNonObjectSettingsField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< "none"));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
}
TEST(ReplSetConfig, ParseFailsWithGetLastErrorDefaultsFieldUnparseable) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("getLastErrorDefaults" << BSON("fsync"
<< "seven"))));
ASSERT_EQUALS(ErrorCodes::FailedToParse, status);
}
TEST(ReplSetConfig, ParseFailsWithNonObjectGetLastErrorDefaultsField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("getLastErrorDefaults"
<< "no")));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
}
TEST(ReplSetConfig, ParseFailsWithNonObjectGetLastErrorModesField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("getLastErrorModes"
<< "no")));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
}
TEST(ReplSetConfig, ParseFailsWithDuplicateGetLastErrorModesField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "tags"
<< BSON("tag"
<< "yes")))
<< "settings"
<< BSON("getLastErrorModes"
<< BSON("one" << BSON("tag" << 1) << "one"
<< BSON("tag" << 1)))));
ASSERT_EQUALS(ErrorCodes::DuplicateKey, status);
}
TEST(ReplSetConfig, ParseFailsWithNonObjectGetLastErrorModesEntryField) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "tags"
<< BSON("tag"
<< "yes")))
<< "settings"
<< BSON("getLastErrorModes" << BSON("one" << 1))));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
}
TEST(ReplSetConfig, ParseFailsWithNonNumericGetLastErrorModesConstraintValue) {
ReplSetConfig config;
Status status =
config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "tags"
<< BSON("tag"
<< "yes")))
<< "settings"
<< BSON("getLastErrorModes" << BSON("one" << BSON("tag"
<< "no")))));
ASSERT_EQUALS(ErrorCodes::TypeMismatch, status);
}
TEST(ReplSetConfig, ParseFailsWithNegativeGetLastErrorModesConstraintValue) {
ReplSetConfig config;
Status status =
config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "tags"
<< BSON("tag"
<< "yes")))
<< "settings"
<< BSON("getLastErrorModes" << BSON("one" << BSON("tag" << -1)))));
ASSERT_EQUALS(ErrorCodes::BadValue, status);
}
TEST(ReplSetConfig, ParseFailsWithNonExistentGetLastErrorModesConstraintTag) {
ReplSetConfig config;
Status status =
config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "tags"
<< BSON("tag"
<< "yes")))
<< "settings"
<< BSON("getLastErrorModes" << BSON("one" << BSON("tag2" << 1)))));
ASSERT_EQUALS(ErrorCodes::NoSuchKey, status);
}
TEST(ReplSetConfig, ValidateFailsWithBadProtocolVersion) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 3
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")
<< BSON("_id" << 1 << "host"
<< "localhost:54321"))));
ASSERT_OK(status);
status = config.validate();
ASSERT_EQUALS(ErrorCodes::BadValue, status);
}
TEST(ReplSetConfig, ValidateFailsWithDuplicateMemberId) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")
<< BSON("_id" << 0 << "host"
<< "someoneelse:12345"))));
ASSERT_OK(status);
status = config.validate();
ASSERT_EQUALS(ErrorCodes::BadValue, status);
}
TEST(ReplSetConfig, ValidateFailsWithInvalidMember) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "hidden"
<< true))));
ASSERT_OK(status);
status = config.validate();
ASSERT_EQUALS(ErrorCodes::BadValue, status);
}
TEST(ReplSetConfig, ChainingAllowedField) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("chainingAllowed" << true))));
ASSERT_OK(config.validate());
ASSERT_TRUE(config.isChainingAllowed());
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("chainingAllowed" << false))));
ASSERT_OK(config.validate());
ASSERT_FALSE(config.isChainingAllowed());
}
TEST(ReplSetConfig, ConfigServerField) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "configsvr"
<< true
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_TRUE(config.isConfigServer());
ReplSetConfig config2;
ASSERT_OK(config2.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "configsvr"
<< false
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_FALSE(config2.isConfigServer());
// Configs in which configsvr is not the same as the --configsvr flag are invalid.
serverGlobalParams.clusterRole = ClusterRole::ConfigServer;
ON_BLOCK_EXIT([&] { serverGlobalParams.clusterRole = ClusterRole::None; });
ASSERT_OK(config.validate());
ASSERT_EQUALS(ErrorCodes::BadValue, config2.validate());
serverGlobalParams.clusterRole = ClusterRole::None;
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
ASSERT_OK(config2.validate());
}
TEST(ReplSetConfig, ConfigServerFieldDefaults) {
serverGlobalParams.clusterRole = ClusterRole::None;
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_FALSE(config.isConfigServer());
ReplSetConfig config2;
ASSERT_OK(config2.initializeForInitiate(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_FALSE(config2.isConfigServer());
serverGlobalParams.clusterRole = ClusterRole::ConfigServer;
ON_BLOCK_EXIT([&] { serverGlobalParams.clusterRole = ClusterRole::None; });
ReplSetConfig config3;
ASSERT_OK(config3.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_FALSE(config3.isConfigServer());
ReplSetConfig config4;
ASSERT_OK(config4.initializeForInitiate(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_TRUE(config4.isConfigServer());
}
TEST(ReplSetConfig, HeartbeatIntervalField) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("heartbeatIntervalMillis" << 5000))));
ASSERT_OK(config.validate());
ASSERT_EQUALS(Seconds(5), config.getHeartbeatInterval());
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("heartbeatIntervalMillis" << -5000))));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
}
TEST(ReplSetConfig, ElectionTimeoutField) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("electionTimeoutMillis" << 20))));
ASSERT_OK(config.validate());
ASSERT_EQUALS(Milliseconds(20), config.getElectionTimeoutPeriod());
auto status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< 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");
}
TEST(ReplSetConfig, HeartbeatTimeoutField) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("heartbeatTimeoutSecs" << 20))));
ASSERT_OK(config.validate());
ASSERT_EQUALS(Seconds(20), config.getHeartbeatTimeoutPeriod());
auto status = config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< 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");
}
TEST(ReplSetConfig, GleDefaultField) {
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("getLastErrorDefaults" << BSON("w"
<< "majority")))));
ASSERT_OK(config.validate());
ASSERT_EQUALS("majority", config.getDefaultWriteConcern().wMode);
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("getLastErrorDefaults" << BSON("w"
<< "frim")))));
ASSERT_EQUALS(ErrorCodes::BadValue, config.validate());
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 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"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "tags"
<< BSON("a"
<< "v")))
<< "settings"
<< BSON("getLastErrorDefaults" << BSON("w"
<< "frim")
<< "getLastErrorModes"
<< BSON("frim" << BSON("a" << 1))))));
ASSERT_OK(config.validate());
ASSERT_EQUALS("frim", config.getDefaultWriteConcern().wMode);
ASSERT_OK(config.findCustomWriteMode("frim").getStatus());
}
bool operator==(const MemberConfig& a, const MemberConfig& b) {
// do tag comparisons
for (MemberConfig::TagIterator itrA = a.tagsBegin(); itrA != a.tagsEnd(); ++itrA) {
if (std::find(b.tagsBegin(), b.tagsEnd(), *itrA) == b.tagsEnd()) {
return false;
}
}
return a.getId() == b.getId() && a.getHostAndPort() == b.getHostAndPort() &&
a.getPriority() == b.getPriority() && a.getSlaveDelay() == b.getSlaveDelay() &&
a.isVoter() == b.isVoter() && a.isArbiter() == b.isArbiter() &&
a.isHidden() == b.isHidden() && a.shouldBuildIndexes() == b.shouldBuildIndexes() &&
a.getNumTags() == b.getNumTags();
}
bool operator==(const ReplSetConfig& a, const ReplSetConfig& b) {
// compare WriteConcernModes
std::vector modeNames = a.getWriteConcernNames();
for (std::vector::iterator it = modeNames.begin(); it != modeNames.end(); it++) {
ReplSetTagPattern patternA = a.findCustomWriteMode(*it).getValue();
ReplSetTagPattern patternB = b.findCustomWriteMode(*it).getValue();
for (ReplSetTagPattern::ConstraintIterator itrA = patternA.constraintsBegin();
itrA != patternA.constraintsEnd();
itrA++) {
bool same = false;
for (ReplSetTagPattern::ConstraintIterator itrB = patternB.constraintsBegin();
itrB != patternB.constraintsEnd();
itrB++) {
if (itrA->getKeyIndex() == itrB->getKeyIndex() &&
itrA->getMinCount() == itrB->getMinCount()) {
same = true;
break;
}
}
if (!same) {
return false;
}
}
}
// compare the members
for (ReplSetConfig::MemberIterator memA = a.membersBegin(); memA != a.membersEnd(); memA++) {
bool same = false;
for (ReplSetConfig::MemberIterator memB = b.membersBegin(); memB != b.membersEnd();
memB++) {
if (*memA == *memB) {
same = true;
break;
}
}
if (!same) {
return false;
}
}
// simple comparisons
return a.getReplSetName() == b.getReplSetName() &&
a.getConfigVersion() == b.getConfigVersion() && a.getNumMembers() == b.getNumMembers() &&
a.getHeartbeatInterval() == b.getHeartbeatInterval() &&
a.getHeartbeatTimeoutPeriod() == b.getHeartbeatTimeoutPeriod() &&
a.getElectionTimeoutPeriod() == b.getElectionTimeoutPeriod() &&
a.isChainingAllowed() == b.isChainingAllowed() &&
a.isConfigServer() == b.isConfigServer() &&
a.getDefaultWriteConcern().wNumNodes == b.getDefaultWriteConcern().wNumNodes &&
a.getDefaultWriteConcern().wMode == b.getDefaultWriteConcern().wMode &&
a.getProtocolVersion() == b.getProtocolVersion() &&
a.getReplicaSetId() == b.getReplicaSetId();
}
TEST(ReplSetConfig, toBSONRoundTripAbility) {
ReplSetConfig configA;
ReplSetConfig configB;
ASSERT_OK(configA.initialize(BSON(
"_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "settings"
<< BSON("heartbeatIntervalMillis" << 5000 << "heartbeatTimeoutSecs" << 20 << "replicaSetId"
<< OID::gen()))));
ASSERT_OK(configB.initialize(configA.toBSON()));
ASSERT_TRUE(configA == configB);
}
TEST(ReplSetConfig, toBSONRoundTripAbilityLarge) {
ReplSetConfig configA;
ReplSetConfig configB;
ASSERT_OK(configA.initialize(
BSON("_id"
<< "asdf"
<< "version"
<< 9
<< "writeConcernMajorityJournalDefault"
<< true
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "arbiterOnly"
<< true
<< "votes"
<< 1)
<< BSON("_id" << 3 << "host"
<< "localhost:3828"
<< "arbiterOnly"
<< false
<< "hidden"
<< true
<< "buildIndexes"
<< false
<< "priority"
<< 0
<< "slaveDelay"
<< 17
<< "votes"
<< 0
<< "tags"
<< BSON("coast"
<< "east"
<< "ssd"
<< "true"))
<< BSON("_id" << 2 << "host"
<< "foo.com:3828"
<< "votes"
<< 0
<< "priority"
<< 0
<< "tags"
<< BSON("coast"
<< "west"
<< "hdd"
<< "true")))
<< "protocolVersion"
<< 0
<< "settings"
<< BSON("heartbeatIntervalMillis" << 5000 << "heartbeatTimeoutSecs" << 20
<< "electionTimeoutMillis"
<< 4
<< "chainingAllowd"
<< true
<< "getLastErrorDefaults"
<< BSON("w"
<< "majority")
<< "getLastErrorModes"
<< BSON("disks" << BSON("ssd" << 1 << "hdd" << 1)
<< "coasts"
<< BSON("coast" << 2))))));
BSONObj configObjA = configA.toBSON();
// Ensure a protocolVersion does not show up if it is 0 to maintain cross version compatibility.
ASSERT_FALSE(configObjA.hasField("protocolVersion"));
ASSERT_OK(configB.initialize(configObjA));
ASSERT_TRUE(configA == configB);
}
TEST(ReplSetConfig, toBSONRoundTripAbilityInvalid) {
ReplSetConfig configA;
ReplSetConfig configB;
ASSERT_OK(
configA.initialize(BSON("_id"
<< ""
<< "version"
<< -3
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "arbiterOnly"
<< true
<< "votes"
<< 0
<< "priority"
<< 0)
<< BSON("_id" << 0 << "host"
<< "localhost:3828"
<< "arbiterOnly"
<< false
<< "buildIndexes"
<< false
<< "priority"
<< 2)
<< BSON("_id" << 2 << "host"
<< "localhost:3828"
<< "votes"
<< 0
<< "priority"
<< 0))
<< "settings"
<< BSON("heartbeatIntervalMillis" << -5000 << "heartbeatTimeoutSecs"
<< 20
<< "electionTimeoutMillis"
<< 2))));
ASSERT_OK(configB.initialize(configA.toBSON()));
ASSERT_NOT_OK(configA.validate());
ASSERT_NOT_OK(configB.validate());
ASSERT_TRUE(configA == configB);
}
TEST(ReplSetConfig, CheckIfWriteConcernCanBeSatisfied) {
ReplSetConfig configA;
ASSERT_OK(configA.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "node0"
<< "tags"
<< BSON("dc"
<< "NA"
<< "rack"
<< "rackNA1"))
<< BSON("_id" << 1 << "host"
<< "node1"
<< "tags"
<< BSON("dc"
<< "NA"
<< "rack"
<< "rackNA2"))
<< BSON("_id" << 2 << "host"
<< "node2"
<< "tags"
<< BSON("dc"
<< "NA"
<< "rack"
<< "rackNA3"))
<< BSON("_id" << 3 << "host"
<< "node3"
<< "tags"
<< BSON("dc"
<< "EU"
<< "rack"
<< "rackEU1"))
<< BSON("_id" << 4 << "host"
<< "node4"
<< "tags"
<< BSON("dc"
<< "EU"
<< "rack"
<< "rackEU2"))
<< BSON("_id" << 5 << "host"
<< "node5"
<< "arbiterOnly"
<< true))
<< "settings"
<< BSON("getLastErrorModes"
<< BSON("valid" << BSON("dc" << 2 << "rack" << 3)
<< "invalidNotEnoughValues"
<< BSON("dc" << 3)
<< "invalidNotEnoughNodes"
<< BSON("rack" << 6))))));
WriteConcernOptions validNumberWC;
validNumberWC.wNumNodes = 5;
ASSERT_OK(configA.checkIfWriteConcernCanBeSatisfied(validNumberWC));
WriteConcernOptions invalidNumberWC;
invalidNumberWC.wNumNodes = 6;
ASSERT_EQUALS(ErrorCodes::CannotSatisfyWriteConcern,
configA.checkIfWriteConcernCanBeSatisfied(invalidNumberWC));
WriteConcernOptions majorityWC;
majorityWC.wMode = "majority";
ASSERT_OK(configA.checkIfWriteConcernCanBeSatisfied(majorityWC));
WriteConcernOptions validModeWC;
validModeWC.wMode = "valid";
ASSERT_OK(configA.checkIfWriteConcernCanBeSatisfied(validModeWC));
WriteConcernOptions fakeModeWC;
fakeModeWC.wMode = "fake";
ASSERT_EQUALS(ErrorCodes::UnknownReplWriteConcern,
configA.checkIfWriteConcernCanBeSatisfied(fakeModeWC));
WriteConcernOptions invalidModeNotEnoughValuesWC;
invalidModeNotEnoughValuesWC.wMode = "invalidNotEnoughValues";
ASSERT_EQUALS(ErrorCodes::CannotSatisfyWriteConcern,
configA.checkIfWriteConcernCanBeSatisfied(invalidModeNotEnoughValuesWC));
WriteConcernOptions invalidModeNotEnoughNodesWC;
invalidModeNotEnoughNodesWC.wMode = "invalidNotEnoughNodes";
ASSERT_EQUALS(ErrorCodes::CannotSatisfyWriteConcern,
configA.checkIfWriteConcernCanBeSatisfied(invalidModeNotEnoughNodesWC));
}
TEST(ReplSetConfig, CheckMaximumNodesOkay) {
ReplSetConfig configA;
ReplSetConfig configB;
const int memberCount = 50;
ASSERT_OK(configA.initialize(createConfigDoc(memberCount)));
ASSERT_OK(configB.initialize(configA.toBSON()));
ASSERT_OK(configA.validate());
ASSERT_OK(configB.validate());
ASSERT_TRUE(configA == configB);
}
TEST(ReplSetConfig, CheckBeyondMaximumNodesFailsValidate) {
ReplSetConfig configA;
ReplSetConfig configB;
const int memberCount = 51;
ASSERT_OK(configA.initialize(createConfigDoc(memberCount)));
ASSERT_OK(configB.initialize(configA.toBSON()));
ASSERT_NOT_OK(configA.validate());
ASSERT_NOT_OK(configB.validate());
ASSERT_TRUE(configA == configB);
}
TEST(ReplSetConfig, CheckConfigServerCantBeProtocolVersion0) {
ReplSetConfig configA;
ASSERT_OK(configA.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 0
<< "version"
<< 1
<< "configsvr"
<< true
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")
<< BSON("_id" << 1 << "host"
<< "localhost:54321"
<< "arbiterOnly"
<< true)))));
Status status = configA.validate();
ASSERT_EQUALS(ErrorCodes::BadValue, status);
ASSERT_STRING_CONTAINS(status.reason(), "cannot run in protocolVersion 0");
}
TEST(ReplSetConfig, CheckConfigServerCantHaveArbiters) {
ReplSetConfig configA;
ASSERT_OK(configA.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "configsvr"
<< true
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")
<< BSON("_id" << 1 << "host"
<< "localhost:54321"
<< "arbiterOnly"
<< true)))));
Status status = configA.validate();
ASSERT_EQUALS(ErrorCodes::BadValue, status);
ASSERT_STRING_CONTAINS(status.reason(), "Arbiters are not allowed");
}
TEST(ReplSetConfig, CheckConfigServerMustBuildIndexes) {
ReplSetConfig configA;
ASSERT_OK(configA.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "configsvr"
<< true
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")
<< BSON("_id" << 1 << "host"
<< "localhost:54321"
<< "priority"
<< 0
<< "buildIndexes"
<< false)))));
Status status = configA.validate();
ASSERT_EQUALS(ErrorCodes::BadValue, status);
ASSERT_STRING_CONTAINS(status.reason(), "must build indexes");
}
TEST(ReplSetConfig, CheckConfigServerCantHaveSlaveDelay) {
ReplSetConfig configA;
ASSERT_OK(configA.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "configsvr"
<< true
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")
<< BSON("_id" << 1 << "host"
<< "localhost:54321"
<< "priority"
<< 0
<< "slaveDelay"
<< 3)))));
Status status = configA.validate();
ASSERT_EQUALS(ErrorCodes::BadValue, status);
ASSERT_STRING_CONTAINS(status.reason(), "cannot have a non-zero slaveDelay");
}
TEST(ReplSetConfig, CheckConfigServerMustHaveTrueForWriteConcernMajorityJournalDefault) {
serverGlobalParams.clusterRole = ClusterRole::ConfigServer;
ON_BLOCK_EXIT([&] { serverGlobalParams.clusterRole = ClusterRole::None; });
ReplSetConfig configA;
ASSERT_OK(configA.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "configsvr"
<< true
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")
<< BSON("_id" << 1 << "host"
<< "localhost:54321"))
<< "writeConcernMajorityJournalDefault"
<< false)));
Status status = configA.validate();
ASSERT_EQUALS(ErrorCodes::BadValue, status);
ASSERT_STRING_CONTAINS(status.reason(), " must be true in replica set configurations being ");
}
TEST(ReplSetConfig, GetPriorityTakeoverDelay) {
ReplSetConfig configA;
ASSERT_OK(configA.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "priority"
<< 1)
<< BSON("_id" << 1 << "host"
<< "localhost:54321"
<< "priority"
<< 2)
<< BSON("_id" << 2 << "host"
<< "localhost:5321"
<< "priority"
<< 3)
<< BSON("_id" << 3 << "host"
<< "localhost:5421"
<< "priority"
<< 4)
<< BSON("_id" << 4 << "host"
<< "localhost:5431"
<< "priority"
<< 5))
<< "settings"
<< BSON("electionTimeoutMillis" << 1000))));
ASSERT_OK(configA.validate());
ASSERT_EQUALS(Milliseconds(5000), configA.getPriorityTakeoverDelay(0));
ASSERT_EQUALS(Milliseconds(4000), configA.getPriorityTakeoverDelay(1));
ASSERT_EQUALS(Milliseconds(3000), configA.getPriorityTakeoverDelay(2));
ASSERT_EQUALS(Milliseconds(2000), configA.getPriorityTakeoverDelay(3));
ASSERT_EQUALS(Milliseconds(1000), configA.getPriorityTakeoverDelay(4));
ReplSetConfig configB;
ASSERT_OK(configB.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "priority"
<< 1)
<< BSON("_id" << 1 << "host"
<< "localhost:54321"
<< "priority"
<< 2)
<< BSON("_id" << 2 << "host"
<< "localhost:5321"
<< "priority"
<< 2)
<< BSON("_id" << 3 << "host"
<< "localhost:5421"
<< "priority"
<< 3)
<< BSON("_id" << 4 << "host"
<< "localhost:5431"
<< "priority"
<< 3))
<< "settings"
<< BSON("electionTimeoutMillis" << 1000))));
ASSERT_OK(configB.validate());
ASSERT_EQUALS(Milliseconds(5000), configB.getPriorityTakeoverDelay(0));
ASSERT_EQUALS(Milliseconds(3000), configB.getPriorityTakeoverDelay(1));
ASSERT_EQUALS(Milliseconds(3000), configB.getPriorityTakeoverDelay(2));
ASSERT_EQUALS(Milliseconds(1000), configB.getPriorityTakeoverDelay(3));
ASSERT_EQUALS(Milliseconds(1000), configB.getPriorityTakeoverDelay(4));
}
TEST(ReplSetConfig, GetCatchupTakeoverDelay) {
ReplSetConfig configA;
ASSERT_OK(configA.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "priority"
<< 1)
<< BSON("_id" << 1 << "host"
<< "localhost:54321"
<< "priority"
<< 2)
<< BSON("_id" << 2 << "host"
<< "localhost:5321"
<< "priority"
<< 3))
<< "settings"
<< BSON("electionTimeoutMillis" << 1000))));
ASSERT_OK(configA.validate());
ASSERT_EQUALS(Milliseconds(30000), configA.getCatchupTakeoverDelay());
}
TEST(ReplSetConfig, ConfirmDefaultValuesOfAndAbilityToSetWriteConcernMajorityJournalDefault) {
// PV0, should default to false.
ReplSetConfig config;
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_OK(config.validate());
ASSERT_FALSE(config.getWriteConcernMajorityShouldJournal());
ASSERT_FALSE(config.toBSON().hasField("writeConcernMajorityJournalDefault"));
// Should be able to set it true in PV0.
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "writeConcernMajorityJournalDefault"
<< true)));
ASSERT_OK(config.validate());
ASSERT_TRUE(config.getWriteConcernMajorityShouldJournal());
ASSERT_TRUE(config.toBSON().hasField("writeConcernMajorityJournalDefault"));
// PV1, should default to true.
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345")))));
ASSERT_OK(config.validate());
ASSERT_TRUE(config.getWriteConcernMajorityShouldJournal());
ASSERT_FALSE(config.toBSON().hasField("writeConcernMajorityJournalDefault"));
// Should be able to set it false in PV1.
ASSERT_OK(config.initialize(BSON("_id"
<< "rs0"
<< "protocolVersion"
<< 1
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"))
<< "writeConcernMajorityJournalDefault"
<< false)));
ASSERT_OK(config.validate());
ASSERT_FALSE(config.getWriteConcernMajorityShouldJournal());
ASSERT_TRUE(config.toBSON().hasField("writeConcernMajorityJournalDefault"));
}
TEST(ReplSetConfig, ReplSetId) {
// Uninitialized configuration has no ID.
ASSERT_FALSE(ReplSetConfig().hasReplicaSetId());
// Cannot provide replica set ID in configuration document when initialized from
// replSetInitiate.
auto status =
ReplSetConfig().initializeForInitiate(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "priority"
<< 1))
<< "settings"
<< BSON("replicaSetId" << OID::gen())));
ASSERT_EQUALS(ErrorCodes::InvalidReplicaSetConfig, status);
ASSERT_STRING_CONTAINS(status.reason(),
"replica set configuration cannot contain 'replicaSetId' field when "
"called from replSetInitiate");
// Configuration created by replSetInitiate should generate replica set ID.
ReplSetConfig configInitiate;
ASSERT_OK(
configInitiate.initializeForInitiate(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "priority"
<< 1)))));
ASSERT_OK(configInitiate.validate());
ASSERT_TRUE(configInitiate.hasReplicaSetId());
OID replicaSetId = configInitiate.getReplicaSetId();
// Configuration initialized from local database can contain ID.
ReplSetConfig configLocal;
ASSERT_OK(configLocal.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "priority"
<< 1))
<< "settings"
<< BSON("replicaSetId" << replicaSetId))));
ASSERT_OK(configLocal.validate());
ASSERT_TRUE(configLocal.hasReplicaSetId());
ASSERT_EQUALS(replicaSetId, configLocal.getReplicaSetId());
// When reconfiguring, we can provide an default ID if the configuration does not contain one.
OID defaultReplicaSetId = OID::gen();
ASSERT_OK(configLocal.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "priority"
<< 1))),
true,
defaultReplicaSetId));
ASSERT_OK(configLocal.validate());
ASSERT_TRUE(configLocal.hasReplicaSetId());
ASSERT_EQUALS(defaultReplicaSetId, configLocal.getReplicaSetId());
// 'replicaSetId' field cannot be null.
status = configLocal.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "priority"
<< 1))
<< "settings"
<< BSON("replicaSetId" << OID())));
ASSERT_EQUALS(ErrorCodes::BadValue, status);
ASSERT_STRING_CONTAINS(status.reason(), "replicaSetId field value cannot be null");
// 'replicaSetId' field must be an OID.
status = configLocal.initialize(BSON("_id"
<< "rs0"
<< "version"
<< 1
<< "members"
<< BSON_ARRAY(BSON("_id" << 0 << "host"
<< "localhost:12345"
<< "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");
}
} // namespace
} // namespace repl
} // namespace mongo