diff --git a/raft/raft.go b/raft/raft.go index 58bb021e04f3..d85003f8bb04 100644 --- a/raft/raft.go +++ b/raft/raft.go @@ -728,6 +728,9 @@ func (r *raft) Step(m pb.Message) error { // rejected our vote so we should become a follower at the new // term. default: + if m.Type == pb.MsgPreVoteResp { + lead = None + } r.logger.Infof("%x [term: %d] received a %s message with higher term from %x [term: %d]", r.id, r.Term, m.Type, m.From, m.Term) r.becomeFollower(m.Term, lead) diff --git a/raft/raft_test.go b/raft/raft_test.go index d170c8827af6..d947890f6266 100644 --- a/raft/raft_test.go +++ b/raft/raft_test.go @@ -3209,6 +3209,100 @@ func TestNodeWithSmallerTermCanCompleteElection(t *testing.T) { } } +// TestNodeWithSmallerTermCanCompleteElectionWithCheckQuorum ensures that when +// a pre-candidate recv PreVoteResp with reject, will checkQuorum correctly. +func TestNodeWithSmallerTermCanCompleteElectionWithCheckQuorum(t *testing.T) { + n1 := newTestRaft(1, []uint64{1, 2, 3}, 10, 1, NewMemoryStorage()) + n2 := newTestRaft(2, []uint64{1, 2, 3}, 10, 1, NewMemoryStorage()) + n3 := newTestRaft(3, []uint64{1, 2, 3}, 10, 1, NewMemoryStorage()) + + n1.becomeFollower(1, None) + n2.becomeFollower(1, None) + n3.becomeFollower(1, None) + + n1.preVote = true + n2.preVote = true + n3.preVote = true + + n1.checkQuorum = true + n2.checkQuorum = true + n3.checkQuorum = true + + // cause a network partition to isolate node 3 + nt := newNetwork(n1, n2, n3) + nt.cut(1, 3) + nt.cut(2, 3) + + nt.send(pb.Message{From: 1, To: 1, Type: pb.MsgHup}) + + sm := nt.peers[1].(*raft) + if sm.state != StateLeader { + t.Errorf("peer 1 state: %s, want %s", sm.state, StateLeader) + } + sm = nt.peers[2].(*raft) + if sm.state != StateFollower { + t.Errorf("peer 2 state: %s, want %s", sm.state, StateFollower) + } + + nt.send(pb.Message{From: 3, To: 3, Type: pb.MsgHup}) + sm = nt.peers[3].(*raft) + if sm.state != StatePreCandidate { + t.Errorf("peer 3 state: %s, want %s", sm.state, StatePreCandidate) + } + + // check whether the term values are expected + // n1.Term == 2 + // n2.Term == 2 + // n3.Term == 1 + sm = nt.peers[1].(*raft) + if sm.Term != 2 { + t.Errorf("peer 1 term: %d, want %d", sm.Term, 2) + } + sm = nt.peers[2].(*raft) + if sm.Term != 2 { + t.Errorf("peer 2 term: %d, want %d", sm.Term, 2) + } + sm = nt.peers[3].(*raft) + if sm.Term != 1 { + t.Errorf("peer 3 term: %d, want %d", sm.Term, 1) + } + + // check state + // n1 == leader + // n2 == follower + // n3 == pre-candidate + sm = nt.peers[1].(*raft) + if sm.state != StateLeader { + t.Errorf("peer 1 state: %s, want %s", sm.state, StateLeader) + } + sm = nt.peers[2].(*raft) + if sm.state != StateFollower { + t.Errorf("peer 2 state: %s, want %s", sm.state, StateFollower) + } + sm = nt.peers[3].(*raft) + if sm.state != StatePreCandidate { + t.Errorf("peer 3 state: %s, want %s", sm.state, StatePreCandidate) + } + + sm.logger.Infof("going to bring back peer 3 and kill peer 1") + // recover the network then immediately isolate node 1 which is currently + // the leader, this is to emulate the crash of node 1. + nt.recover() + nt.cut(1, 2) + nt.cut(1, 3) + + // call for election. node 3 should handle PreVoteResp (reject) correctly. + nt.send(pb.Message{From: 3, To: 3, Type: pb.MsgHup}) + nt.send(pb.Message{From: 2, To: 2, Type: pb.MsgHup}) + + // do we have a leader? + sm2 := nt.peers[2].(*raft) + sm3 := nt.peers[3].(*raft) + if sm2.state != StateLeader && sm3.state != StateLeader { + t.Errorf("no leader") + } +} + // TestPreVoteWithCheckQuorum ensures that after a node become pre-candidate, // it will checkQuorum correctly. func TestPreVoteWithCheckQuorum(t *testing.T) {