summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorScott Hernandez <scotthernandez@gmail.com>2015-08-05 10:21:10 -0400
committerScott Hernandez <scotthernandez@gmail.com>2015-08-07 09:48:57 -0400
commit8f1fb72cb7d67cb22b1345d945ce00eaefbad52c (patch)
treecf784952132ca9f822326c08a80f6e328193e2f6 /src/mongo/db
parent44c3f7a4bdf641060dbf17187ed69da956c3e425 (diff)
downloadmongo-8f1fb72cb7d67cb22b1345d945ce00eaefbad52c.tar.gz
SERVER-18363: voters cannot sync from non-voters
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/repl/topology_coordinator_impl.cpp14
-rw-r--r--src/mongo/db/repl/topology_coordinator_impl_test.cpp72
2 files changed, 79 insertions, 7 deletions
diff --git a/src/mongo/db/repl/topology_coordinator_impl.cpp b/src/mongo/db/repl/topology_coordinator_impl.cpp
index a8648b00e05..1de17e301fc 100644
--- a/src/mongo/db/repl/topology_coordinator_impl.cpp
+++ b/src/mongo/db/repl/topology_coordinator_impl.cpp
@@ -249,6 +249,11 @@ HostAndPort TopologyCoordinatorImpl::chooseNewSyncSource(Date_t now,
const MemberConfig& itMemberConfig(_rsConfig.getMemberAt(itIndex));
+ // Candidate must be a voter if we are a voter
+ if (_selfConfig().isVoter() && !itMemberConfig.isVoter()) {
+ continue;
+ }
+
// Candidate must build indexes if we build indexes, to be considered.
if (_selfConfig().shouldBuildIndexes()) {
if (!itMemberConfig.shouldBuildIndexes()) {
@@ -400,6 +405,13 @@ void TopologyCoordinatorImpl::prepareSyncFromResponse(const ReplicationExecutor:
return;
}
+ if (selfConfig.isVoter() && !targetConfig->isVoter()) {
+ *result = Status(ErrorCodes::InvalidOptions,
+ str::stream() << "Cannot sync from \"" << target.toString()
+ << "\" because it is not a voter");
+ return;
+ }
+
const MemberHeartbeatData& hbdata = _hbdata[targetIndex];
if (hbdata.hasAuthIssue()) {
*result =
@@ -2116,7 +2128,7 @@ bool TopologyCoordinatorImpl::shouldChangeSyncSource(const HostAndPort& currentS
++it) {
const int itIndex = indexOfIterator(_hbdata, it);
const MemberConfig& candidateConfig = _rsConfig.getMemberAt(itIndex);
- if (it->up() &&
+ if (it->up() && (candidateConfig.isVoter() || !_selfConfig().isVoter()) &&
(candidateConfig.shouldBuildIndexes() || !_selfConfig().shouldBuildIndexes()) &&
it->getState().readable() && !_memberIsBlacklisted(candidateConfig, now) &&
goalSecs < it->getOpTime().getSecs()) {
diff --git a/src/mongo/db/repl/topology_coordinator_impl_test.cpp b/src/mongo/db/repl/topology_coordinator_impl_test.cpp
index 80c57e9bc51..9188fb2c0b2 100644
--- a/src/mongo/db/repl/topology_coordinator_impl_test.cpp
+++ b/src/mongo/db/repl/topology_coordinator_impl_test.cpp
@@ -30,6 +30,7 @@
#include <iostream>
+#include "mongo/bson/json.h"
#include "mongo/db/repl/heartbeat_response_action.h"
#include "mongo/db/repl/member_heartbeat_data.h"
#include "mongo/db/repl/repl_set_heartbeat_args.h"
@@ -107,6 +108,11 @@ protected:
int getCurrentPrimaryIndex() {
return getTopoCoord().getCurrentPrimaryIndex();
}
+
+ HostAndPort getCurrentPrimaryHost() {
+ return _currentConfig.getMemberAt(getTopoCoord().getCurrentPrimaryIndex()).getHostAndPort();
+ }
+
// Update config and set selfIndex
// If "now" is passed in, set _now to now+1
void updateConfig(BSONObj cfg,
@@ -127,6 +133,8 @@ protected:
getTopoCoord().updateConfig(config, selfIndex, now, lastOp);
_now = now + Milliseconds(1);
}
+
+ _currentConfig = config;
}
HeartbeatResponseAction receiveUpHeartbeat(const HostAndPort& member,
@@ -206,6 +214,7 @@ private:
private:
unique_ptr<TopologyCoordinatorImpl> _topo;
unique_ptr<ReplicationExecutor::CallbackArgs> _cbData;
+ ReplicaSetConfig _currentConfig;
Date_t _now;
int _selfIndex;
};
@@ -418,14 +427,9 @@ TEST_F(TopoCoordTest, ChooseSyncSourceCandidates) {
getTopoCoord().chooseNewSyncSource(now()++, lastOpTimeWeApplied);
ASSERT_EQUALS(HostAndPort("h5"), getTopoCoord().getSyncSourceAddress());
- // h5 goes down; should choose h3
+ // h5 goes down; should not choose h3 since it can't vote
receiveDownHeartbeat(HostAndPort("h5"), "rs0", OpTime());
getTopoCoord().chooseNewSyncSource(now()++, lastOpTimeWeApplied);
- ASSERT_EQUALS(HostAndPort("h3"), getTopoCoord().getSyncSourceAddress());
-
- // h3 goes down; no sync source candidates remain
- receiveDownHeartbeat(HostAndPort("h3"), "rs0", OpTime());
- getTopoCoord().chooseNewSyncSource(now()++, lastOpTimeWeApplied);
ASSERT(getTopoCoord().getSyncSourceAddress().empty());
}
@@ -484,6 +488,41 @@ TEST_F(TopoCoordTest, ChooseSyncSourceChainingNotAllowed) {
ASSERT_EQUALS(HostAndPort("h3"), getTopoCoord().getSyncSourceAddress());
}
+TEST_F(TopoCoordTest, ChooseSyncSourceWrtVoters) {
+ updateConfig(fromjson(
+ "{_id:'rs0', version:1, members:["
+ "{_id:10, host:'hself'}, "
+ "{_id:20, host:'h2', votes:0}, "
+ "{_id:30, host:'h3'} "
+ "]}"),
+ 0);
+
+ setSelfMemberState(MemberState::RS_SECONDARY);
+
+ HostAndPort h2("h2"), h3("h3");
+ Timestamp t1(1, 0), t5(5, 0), t10(10, 0), t15(15, 0);
+ OpTime ot1(t1, 0), ot5(t5, 0), ot10(t10, 0), ot15(t15, 0);
+ Milliseconds hbRTT100(100), hbRTT300(300);
+
+ heartbeatFromMember(h2, "rs0", MemberState::RS_SECONDARY, ot5, hbRTT100);
+ heartbeatFromMember(h2, "rs0", MemberState::RS_SECONDARY, ot5, hbRTT100);
+ heartbeatFromMember(h3, "rs0", MemberState::RS_SECONDARY, ot1, hbRTT300);
+ heartbeatFromMember(h3, "rs0", MemberState::RS_SECONDARY, ot1, hbRTT300);
+
+ // Should choose h3 as it is a voter
+ auto newSource = getTopoCoord().chooseNewSyncSource(now()++, Timestamp());
+ ASSERT_EQUALS(h3, newSource);
+
+ // Can't choose h2 as it is not a voter
+ newSource = getTopoCoord().chooseNewSyncSource(now()++, t10);
+ ASSERT_EQUALS(HostAndPort(), newSource);
+
+ // Should choose h3 as it is a voter, and ahead
+ heartbeatFromMember(h3, "rs0", MemberState::RS_SECONDARY, ot5, hbRTT300);
+ newSource = getTopoCoord().chooseNewSyncSource(now()++, t1);
+ ASSERT_EQUALS(h3, newSource);
+}
+
TEST_F(TopoCoordTest, EmptySyncSourceOnPrimary) {
updateConfig(BSON("_id"
<< "rs0"
@@ -921,6 +960,27 @@ TEST_F(TopoCoordTest, PrepareSyncFromResponse) {
ASSERT_EQUALS(HostAndPort("h6"), syncSource);
}
+TEST_F(TopoCoordTest, PrepareSyncFromResponseVoters) {
+ OpTime staleOpTime(Timestamp(1, 1), 0);
+ OpTime ourOpTime(Timestamp(staleOpTime.getSecs() + 11, 1), 0);
+
+ Status result = Status::OK();
+ BSONObjBuilder response;
+
+ // Test trying to sync from another node
+ updateConfig(fromjson(
+ "{_id:'rs0', version:1, members:["
+ "{_id:0, host:'self'},"
+ "{_id:1, host:'h1'},"
+ "{_id:2, host:'h2', votes:0}"
+ "]}"),
+ 0);
+
+ getTopoCoord().prepareSyncFromResponse(
+ cbData(), HostAndPort("h2"), ourOpTime, &response, &result);
+ ASSERT_EQUALS(ErrorCodes::InvalidOptions, result);
+ ASSERT_EQUALS("Cannot sync from \"h2:27017\" because it is not a voter", result.reason());
+}
TEST_F(TopoCoordTest, ReplSetGetStatus) {
// This test starts by configuring a TopologyCoordinator as a member of a 4 node replica
// set, with each node in a different state.