mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
raft: Separate test methods for vote and pre-vote tests
This commit is contained in:
parent
22aa710c1f
commit
8d5e969f12
@ -288,155 +288,173 @@ func TestProgressPaused(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLeaderElection(t *testing.T) {
|
||||
for i, preVote := range []bool{false, true} {
|
||||
var cfg func(*Config)
|
||||
if preVote {
|
||||
cfg = preVoteConfig
|
||||
testLeaderElection(t, false)
|
||||
}
|
||||
|
||||
func TestLeaderElectionPreVote(t *testing.T) {
|
||||
testLeaderElection(t, true)
|
||||
}
|
||||
|
||||
func testLeaderElection(t *testing.T, preVote bool) {
|
||||
var cfg func(*Config)
|
||||
if preVote {
|
||||
cfg = preVoteConfig
|
||||
}
|
||||
tests := []struct {
|
||||
*network
|
||||
state StateType
|
||||
expTerm uint64
|
||||
}{
|
||||
{newNetworkWithConfig(cfg, nil, nil, nil), StateLeader, 1},
|
||||
{newNetworkWithConfig(cfg, nil, nil, nopStepper), StateLeader, 1},
|
||||
{newNetworkWithConfig(cfg, nil, nopStepper, nopStepper), StateCandidate, 1},
|
||||
{newNetworkWithConfig(cfg, nil, nopStepper, nopStepper, nil), StateCandidate, 1},
|
||||
{newNetworkWithConfig(cfg, nil, nopStepper, nopStepper, nil, nil), StateLeader, 1},
|
||||
|
||||
// three logs further along than 0, but in the same term so rejections
|
||||
// are returned instead of the votes being ignored.
|
||||
{newNetworkWithConfig(cfg, nil, ents(1), ents(1), ents(1, 1), nil), StateFollower, 1},
|
||||
|
||||
// logs converge
|
||||
{newNetworkWithConfig(cfg, ents(1), nil, ents(2), ents(1), nil), StateLeader, 2},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
tt.send(pb.Message{From: 1, To: 1, Type: pb.MsgHup})
|
||||
sm := tt.network.peers[1].(*raft)
|
||||
var expState StateType
|
||||
var expTerm uint64
|
||||
if tt.state == StateCandidate && preVote {
|
||||
// In pre-vote mode, an election that fails to complete
|
||||
// leaves the node in pre-candidate state without advancing
|
||||
// the term.
|
||||
expState = StatePreCandidate
|
||||
expTerm = 0
|
||||
} else {
|
||||
expState = tt.state
|
||||
expTerm = tt.expTerm
|
||||
}
|
||||
tests := []struct {
|
||||
*network
|
||||
state StateType
|
||||
expTerm uint64
|
||||
}{
|
||||
{newNetworkWithConfig(cfg, nil, nil, nil), StateLeader, 1},
|
||||
{newNetworkWithConfig(cfg, nil, nil, nopStepper), StateLeader, 1},
|
||||
{newNetworkWithConfig(cfg, nil, nopStepper, nopStepper), StateCandidate, 1},
|
||||
{newNetworkWithConfig(cfg, nil, nopStepper, nopStepper, nil), StateCandidate, 1},
|
||||
{newNetworkWithConfig(cfg, nil, nopStepper, nopStepper, nil, nil), StateLeader, 1},
|
||||
|
||||
// three logs further along than 0, but in the same term so rejections
|
||||
// are returned instead of the votes being ignored.
|
||||
{newNetworkWithConfig(cfg, nil, ents(1), ents(1), ents(1, 1), nil), StateFollower, 1},
|
||||
|
||||
// logs converge
|
||||
{newNetworkWithConfig(cfg, ents(1), nil, ents(2), ents(1), nil), StateLeader, 2},
|
||||
if sm.state != expState {
|
||||
t.Errorf("#%d: state = %s, want %s", i, sm.state, expState)
|
||||
}
|
||||
|
||||
for j, tt := range tests {
|
||||
tt.send(pb.Message{From: 1, To: 1, Type: pb.MsgHup})
|
||||
sm := tt.network.peers[1].(*raft)
|
||||
var expState StateType
|
||||
var expTerm uint64
|
||||
if tt.state == StateCandidate && preVote {
|
||||
// In pre-vote mode, an election that fails to complete
|
||||
// leaves the node in pre-candidate state without advancing
|
||||
// the term.
|
||||
expState = StatePreCandidate
|
||||
expTerm = 0
|
||||
} else {
|
||||
expState = tt.state
|
||||
expTerm = tt.expTerm
|
||||
}
|
||||
if sm.state != expState {
|
||||
t.Errorf("#%d.%d: state = %s, want %s", i, j, sm.state, expState)
|
||||
}
|
||||
if g := sm.Term; g != expTerm {
|
||||
t.Errorf("#%d.%d: term = %d, want %d", i, j, g, expTerm)
|
||||
}
|
||||
if g := sm.Term; g != expTerm {
|
||||
t.Errorf("#%d: term = %d, want %d", i, g, expTerm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaderCycle verifies that each node in a cluster can campaign
|
||||
func TestLeaderCycle(t *testing.T) {
|
||||
testLeaderCycle(t, false)
|
||||
}
|
||||
|
||||
func TestLeaderCyclePreVote(t *testing.T) {
|
||||
testLeaderCycle(t, true)
|
||||
}
|
||||
|
||||
// testLeaderCycle verifies that each node in a cluster can campaign
|
||||
// and be elected in turn. This ensures that elections (including
|
||||
// pre-vote) work when not starting from a clean slate (as they do in
|
||||
// TestLeaderElection)
|
||||
func TestLeaderCycle(t *testing.T) {
|
||||
for _, preVote := range []bool{false, true} {
|
||||
var cfg func(*Config)
|
||||
if preVote {
|
||||
cfg = preVoteConfig
|
||||
}
|
||||
n := newNetworkWithConfig(cfg, nil, nil, nil)
|
||||
for campaignerID := uint64(1); campaignerID <= 3; campaignerID++ {
|
||||
n.send(pb.Message{From: campaignerID, To: campaignerID, Type: pb.MsgHup})
|
||||
func testLeaderCycle(t *testing.T, preVote bool) {
|
||||
var cfg func(*Config)
|
||||
if preVote {
|
||||
cfg = preVoteConfig
|
||||
}
|
||||
n := newNetworkWithConfig(cfg, nil, nil, nil)
|
||||
for campaignerID := uint64(1); campaignerID <= 3; campaignerID++ {
|
||||
n.send(pb.Message{From: campaignerID, To: campaignerID, Type: pb.MsgHup})
|
||||
|
||||
for _, peer := range n.peers {
|
||||
sm := peer.(*raft)
|
||||
if sm.id == campaignerID && sm.state != StateLeader {
|
||||
t.Errorf("preVote=%v: campaigning node %d state = %v, want StateLeader",
|
||||
preVote, sm.id, sm.state)
|
||||
} else if sm.id != campaignerID && sm.state != StateFollower {
|
||||
t.Errorf("preVote=%v: after campaign of node %d, "+
|
||||
"node %d had state = %v, want StateFollower",
|
||||
preVote, campaignerID, sm.id, sm.state)
|
||||
}
|
||||
for _, peer := range n.peers {
|
||||
sm := peer.(*raft)
|
||||
if sm.id == campaignerID && sm.state != StateLeader {
|
||||
t.Errorf("preVote=%v: campaigning node %d state = %v, want StateLeader",
|
||||
preVote, sm.id, sm.state)
|
||||
} else if sm.id != campaignerID && sm.state != StateFollower {
|
||||
t.Errorf("preVote=%v: after campaign of node %d, "+
|
||||
"node %d had state = %v, want StateFollower",
|
||||
preVote, campaignerID, sm.id, sm.state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoteFromAnyState(t *testing.T) {
|
||||
for _, vt := range []pb.MessageType{pb.MsgVote, pb.MsgPreVote} {
|
||||
for st := StateType(0); st < numStates; st++ {
|
||||
r := newTestRaft(1, []uint64{1, 2, 3}, 10, 1, NewMemoryStorage())
|
||||
r.Term = 1
|
||||
testVoteFromAnyState(t, pb.MsgVote)
|
||||
}
|
||||
|
||||
switch st {
|
||||
case StateFollower:
|
||||
r.becomeFollower(r.Term, 3)
|
||||
case StatePreCandidate:
|
||||
r.becomePreCandidate()
|
||||
case StateCandidate:
|
||||
r.becomeCandidate()
|
||||
case StateLeader:
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
}
|
||||
func TestPreVoteFromAnyState(t *testing.T) {
|
||||
testVoteFromAnyState(t, pb.MsgPreVote)
|
||||
}
|
||||
|
||||
// Note that setting our state above may have advanced r.Term
|
||||
// past its initial value.
|
||||
origTerm := r.Term
|
||||
newTerm := r.Term + 1
|
||||
func testVoteFromAnyState(t *testing.T, vt pb.MessageType) {
|
||||
for st := StateType(0); st < numStates; st++ {
|
||||
r := newTestRaft(1, []uint64{1, 2, 3}, 10, 1, NewMemoryStorage())
|
||||
r.Term = 1
|
||||
|
||||
msg := pb.Message{
|
||||
From: 2,
|
||||
To: 1,
|
||||
Type: vt,
|
||||
Term: newTerm,
|
||||
LogTerm: newTerm,
|
||||
Index: 42,
|
||||
}
|
||||
if err := r.Step(msg); err != nil {
|
||||
t.Errorf("%s,%s: Step failed: %s", vt, st, err)
|
||||
}
|
||||
if len(r.msgs) != 1 {
|
||||
t.Errorf("%s,%s: %d response messages, want 1: %+v", vt, st, len(r.msgs), r.msgs)
|
||||
} else {
|
||||
resp := r.msgs[0]
|
||||
if resp.Type != voteRespMsgType(vt) {
|
||||
t.Errorf("%s,%s: response message is %s, want %s",
|
||||
vt, st, resp.Type, voteRespMsgType(vt))
|
||||
}
|
||||
if resp.Reject {
|
||||
t.Errorf("%s,%s: unexpected rejection", vt, st)
|
||||
}
|
||||
}
|
||||
switch st {
|
||||
case StateFollower:
|
||||
r.becomeFollower(r.Term, 3)
|
||||
case StatePreCandidate:
|
||||
r.becomePreCandidate()
|
||||
case StateCandidate:
|
||||
r.becomeCandidate()
|
||||
case StateLeader:
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
}
|
||||
|
||||
// If this was a real vote, we reset our state and term.
|
||||
if vt == pb.MsgVote {
|
||||
if r.state != StateFollower {
|
||||
t.Errorf("%s,%s: state %s, want %s", vt, StateFollower, r.state, st)
|
||||
}
|
||||
if r.Term != newTerm {
|
||||
t.Errorf("%s,%s: term %d, want %d", vt, st, r.Term, newTerm)
|
||||
}
|
||||
if r.Vote != 2 {
|
||||
t.Errorf("%s,%s: vote %d, want 2", vt, st, r.Vote)
|
||||
}
|
||||
} else {
|
||||
// In a prevote, nothing changes.
|
||||
if r.state != st {
|
||||
t.Errorf("%s,%s: state %s, want %s", vt, st, r.state, st)
|
||||
}
|
||||
if r.Term != origTerm {
|
||||
t.Errorf("%s,%s: term %d, want %d", vt, st, r.Term, origTerm)
|
||||
}
|
||||
// if st == StateFollower or StatePreCandidate, r hasn't voted yet.
|
||||
// In StateCandidate or StateLeader, it's voted for itself.
|
||||
if r.Vote != None && r.Vote != 1 {
|
||||
t.Errorf("%s,%s: vote %d, want %d or 1", vt, st, r.Vote, None)
|
||||
}
|
||||
// Note that setting our state above may have advanced r.Term
|
||||
// past its initial value.
|
||||
origTerm := r.Term
|
||||
newTerm := r.Term + 1
|
||||
|
||||
msg := pb.Message{
|
||||
From: 2,
|
||||
To: 1,
|
||||
Type: vt,
|
||||
Term: newTerm,
|
||||
LogTerm: newTerm,
|
||||
Index: 42,
|
||||
}
|
||||
if err := r.Step(msg); err != nil {
|
||||
t.Errorf("%s,%s: Step failed: %s", vt, st, err)
|
||||
}
|
||||
if len(r.msgs) != 1 {
|
||||
t.Errorf("%s,%s: %d response messages, want 1: %+v", vt, st, len(r.msgs), r.msgs)
|
||||
} else {
|
||||
resp := r.msgs[0]
|
||||
if resp.Type != voteRespMsgType(vt) {
|
||||
t.Errorf("%s,%s: response message is %s, want %s",
|
||||
vt, st, resp.Type, voteRespMsgType(vt))
|
||||
}
|
||||
if resp.Reject {
|
||||
t.Errorf("%s,%s: unexpected rejection", vt, st)
|
||||
}
|
||||
}
|
||||
|
||||
// If this was a real vote, we reset our state and term.
|
||||
if vt == pb.MsgVote {
|
||||
if r.state != StateFollower {
|
||||
t.Errorf("%s,%s: state %s, want %s", vt, StateFollower, r.state, st)
|
||||
}
|
||||
if r.Term != newTerm {
|
||||
t.Errorf("%s,%s: term %d, want %d", vt, st, r.Term, newTerm)
|
||||
}
|
||||
if r.Vote != 2 {
|
||||
t.Errorf("%s,%s: vote %d, want 2", vt, st, r.Vote)
|
||||
}
|
||||
} else {
|
||||
// In a prevote, nothing changes.
|
||||
if r.state != st {
|
||||
t.Errorf("%s,%s: state %s, want %s", vt, st, r.state, st)
|
||||
}
|
||||
if r.Term != origTerm {
|
||||
t.Errorf("%s,%s: term %d, want %d", vt, st, r.Term, origTerm)
|
||||
}
|
||||
// if st == StateFollower or StatePreCandidate, r hasn't voted yet.
|
||||
// In StateCandidate or StateLeader, it's voted for itself.
|
||||
if r.Vote != None && r.Vote != 1 {
|
||||
t.Errorf("%s,%s: vote %d, want %d or 1", vt, st, r.Vote, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1225,71 +1243,77 @@ func TestMsgAppRespWaitReset(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecvMsgVote(t *testing.T) {
|
||||
for i, msgType := range []pb.MessageType{pb.MsgVote, pb.MsgPreVote} {
|
||||
tests := []struct {
|
||||
state StateType
|
||||
i, term uint64
|
||||
voteFor uint64
|
||||
wreject bool
|
||||
}{
|
||||
{StateFollower, 0, 0, None, true},
|
||||
{StateFollower, 0, 1, None, true},
|
||||
{StateFollower, 0, 2, None, true},
|
||||
{StateFollower, 0, 3, None, false},
|
||||
testRecvMsgVote(t, pb.MsgVote)
|
||||
}
|
||||
|
||||
{StateFollower, 1, 0, None, true},
|
||||
{StateFollower, 1, 1, None, true},
|
||||
{StateFollower, 1, 2, None, true},
|
||||
{StateFollower, 1, 3, None, false},
|
||||
func testRecvMsgPreVote(t *testing.T) {
|
||||
testRecvMsgVote(t, pb.MsgPreVote)
|
||||
}
|
||||
|
||||
{StateFollower, 2, 0, None, true},
|
||||
{StateFollower, 2, 1, None, true},
|
||||
{StateFollower, 2, 2, None, false},
|
||||
{StateFollower, 2, 3, None, false},
|
||||
func testRecvMsgVote(t *testing.T, msgType pb.MessageType) {
|
||||
tests := []struct {
|
||||
state StateType
|
||||
i, term uint64
|
||||
voteFor uint64
|
||||
wreject bool
|
||||
}{
|
||||
{StateFollower, 0, 0, None, true},
|
||||
{StateFollower, 0, 1, None, true},
|
||||
{StateFollower, 0, 2, None, true},
|
||||
{StateFollower, 0, 3, None, false},
|
||||
|
||||
{StateFollower, 3, 0, None, true},
|
||||
{StateFollower, 3, 1, None, true},
|
||||
{StateFollower, 3, 2, None, false},
|
||||
{StateFollower, 3, 3, None, false},
|
||||
{StateFollower, 1, 0, None, true},
|
||||
{StateFollower, 1, 1, None, true},
|
||||
{StateFollower, 1, 2, None, true},
|
||||
{StateFollower, 1, 3, None, false},
|
||||
|
||||
{StateFollower, 3, 2, 2, false},
|
||||
{StateFollower, 3, 2, 1, true},
|
||||
{StateFollower, 2, 0, None, true},
|
||||
{StateFollower, 2, 1, None, true},
|
||||
{StateFollower, 2, 2, None, false},
|
||||
{StateFollower, 2, 3, None, false},
|
||||
|
||||
{StateLeader, 3, 3, 1, true},
|
||||
{StatePreCandidate, 3, 3, 1, true},
|
||||
{StateCandidate, 3, 3, 1, true},
|
||||
{StateFollower, 3, 0, None, true},
|
||||
{StateFollower, 3, 1, None, true},
|
||||
{StateFollower, 3, 2, None, false},
|
||||
{StateFollower, 3, 3, None, false},
|
||||
|
||||
{StateFollower, 3, 2, 2, false},
|
||||
{StateFollower, 3, 2, 1, true},
|
||||
|
||||
{StateLeader, 3, 3, 1, true},
|
||||
{StatePreCandidate, 3, 3, 1, true},
|
||||
{StateCandidate, 3, 3, 1, true},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
sm := newTestRaft(1, []uint64{1}, 10, 1, NewMemoryStorage())
|
||||
sm.state = tt.state
|
||||
switch tt.state {
|
||||
case StateFollower:
|
||||
sm.step = stepFollower
|
||||
case StateCandidate, StatePreCandidate:
|
||||
sm.step = stepCandidate
|
||||
case StateLeader:
|
||||
sm.step = stepLeader
|
||||
}
|
||||
sm.Vote = tt.voteFor
|
||||
sm.raftLog = &raftLog{
|
||||
storage: &MemoryStorage{ents: []pb.Entry{{}, {Index: 1, Term: 2}, {Index: 2, Term: 2}}},
|
||||
unstable: unstable{offset: 3},
|
||||
}
|
||||
|
||||
for j, tt := range tests {
|
||||
sm := newTestRaft(1, []uint64{1}, 10, 1, NewMemoryStorage())
|
||||
sm.state = tt.state
|
||||
switch tt.state {
|
||||
case StateFollower:
|
||||
sm.step = stepFollower
|
||||
case StateCandidate, StatePreCandidate:
|
||||
sm.step = stepCandidate
|
||||
case StateLeader:
|
||||
sm.step = stepLeader
|
||||
}
|
||||
sm.Vote = tt.voteFor
|
||||
sm.raftLog = &raftLog{
|
||||
storage: &MemoryStorage{ents: []pb.Entry{{}, {Index: 1, Term: 2}, {Index: 2, Term: 2}}},
|
||||
unstable: unstable{offset: 3},
|
||||
}
|
||||
sm.Step(pb.Message{Type: msgType, From: 2, Index: tt.i, LogTerm: tt.term})
|
||||
|
||||
sm.Step(pb.Message{Type: msgType, From: 2, Index: tt.i, LogTerm: tt.term})
|
||||
|
||||
msgs := sm.readMessages()
|
||||
if g := len(msgs); g != 1 {
|
||||
t.Fatalf("#%d.%d: len(msgs) = %d, want 1", i, j, g)
|
||||
continue
|
||||
}
|
||||
if g := msgs[0].Type; g != voteRespMsgType(msgType) {
|
||||
t.Errorf("#%d.%d, m.Type = %v, want %v", i, j, g, voteRespMsgType(msgType))
|
||||
}
|
||||
if g := msgs[0].Reject; g != tt.wreject {
|
||||
t.Errorf("#%d.%d, m.Reject = %v, want %v", i, j, g, tt.wreject)
|
||||
}
|
||||
msgs := sm.readMessages()
|
||||
if g := len(msgs); g != 1 {
|
||||
t.Fatalf("#%d: len(msgs) = %d, want 1", i, g)
|
||||
continue
|
||||
}
|
||||
if g := msgs[0].Type; g != voteRespMsgType(msgType) {
|
||||
t.Errorf("#%d, m.Type = %v, want %v", i, g, voteRespMsgType(msgType))
|
||||
}
|
||||
if g := msgs[0].Reject; g != tt.wreject {
|
||||
t.Errorf("#%d, m.Reject = %v, want %v", i, g, tt.wreject)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2396,27 +2420,33 @@ func TestRaftNodes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCampaignWhileLeader(t *testing.T) {
|
||||
for i, preVote := range []bool{false, true} {
|
||||
cfg := newTestConfig(1, []uint64{1}, 5, 1, NewMemoryStorage())
|
||||
cfg.PreVote = preVote
|
||||
r := newRaft(cfg)
|
||||
if r.state != StateFollower {
|
||||
t.Errorf("#%d: expected new node to be follower but got %s", i, r.state)
|
||||
}
|
||||
// We don't call campaign() directly because it comes after the check
|
||||
// for our current state.
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgHup})
|
||||
if r.state != StateLeader {
|
||||
t.Errorf("#%d: expected single-node election to become leader but got %s", i, r.state)
|
||||
}
|
||||
term := r.Term
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgHup})
|
||||
if r.state != StateLeader {
|
||||
t.Errorf("#%d: expected to remain leader but got %s", i, r.state)
|
||||
}
|
||||
if r.Term != term {
|
||||
t.Errorf("#%d: expected to remain in term %v but got %v", i, term, r.Term)
|
||||
}
|
||||
testCampaignWhileLeader(t, false)
|
||||
}
|
||||
|
||||
func TestPreCampaignWhileLeader(t *testing.T) {
|
||||
testCampaignWhileLeader(t, true)
|
||||
}
|
||||
|
||||
func testCampaignWhileLeader(t *testing.T, preVote bool) {
|
||||
cfg := newTestConfig(1, []uint64{1}, 5, 1, NewMemoryStorage())
|
||||
cfg.PreVote = preVote
|
||||
r := newRaft(cfg)
|
||||
if r.state != StateFollower {
|
||||
t.Errorf("expected new node to be follower but got %s", r.state)
|
||||
}
|
||||
// We don't call campaign() directly because it comes after the check
|
||||
// for our current state.
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgHup})
|
||||
if r.state != StateLeader {
|
||||
t.Errorf("expected single-node election to become leader but got %s", r.state)
|
||||
}
|
||||
term := r.Term
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgHup})
|
||||
if r.state != StateLeader {
|
||||
t.Errorf("expected to remain leader but got %s", r.state)
|
||||
}
|
||||
if r.Term != term {
|
||||
t.Errorf("expected to remain in term %v but got %v", term, r.Term)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user