diff --git a/raft/raft_test.go b/raft/raft_test.go index 5f45ea7fe904..d87d53e6b6fa 100644 --- a/raft/raft_test.go +++ b/raft/raft_test.go @@ -420,6 +420,19 @@ func TestLearnerPromotion(t *testing.T) { } } +// TestLearnerCannotVote checks that a learner can't vote even it receives a valid Vote request. +func TestLearnerCannotVote(t *testing.T) { + n2 := newTestLearnerRaft(2, []uint64{1}, []uint64{2}, 10, 1, NewMemoryStorage()) + + n2.becomeFollower(1, None) + + n2.Step(pb.Message{From: 1, To: 2, Term: 2, Type: pb.MsgVote, LogTerm: 11, Index: 11}) + + if len(n2.msgs) != 0 { + t.Error("n2 is learner, can't vote") + } +} + func TestLeaderCycle(t *testing.T) { testLeaderCycle(t, false) } @@ -672,6 +685,48 @@ func TestLogReplication(t *testing.T) { } } +// TestLearnerLogReplication tests that a learner can receive entries from the leader. +func TestLearnerLogReplication(t *testing.T) { + n1 := newTestLearnerRaft(1, []uint64{1}, []uint64{2}, 10, 1, NewMemoryStorage()) + n2 := newTestLearnerRaft(2, []uint64{1}, []uint64{2}, 10, 1, NewMemoryStorage()) + + nt := newNetwork(n1, n2) + + n1.becomeFollower(1, None) + n2.becomeFollower(1, None) + + setRandomizedElectionTimeout(n1, n1.electionTimeout) + for i := 0; i < n1.electionTimeout; i++ { + n1.tick() + } + + nt.send(pb.Message{From: 1, To: 1, Type: pb.MsgBeat}) + + // n1 is leader and n2 is learner + if n1.state != StateLeader { + t.Errorf("peer 1 state: %s, want %s", n1.state, StateLeader) + } + + if !n2.isLearner { + t.Error("peer 2 is not learner, want yes") + } + + nextCommitted := n1.raftLog.committed + 1 + nt.send(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("somedata")}}}) + if n1.raftLog.committed != nextCommitted { + t.Errorf("peer 1 wants committed to %d, but still %d", nextCommitted, n1.raftLog.committed) + } + + if n1.raftLog.committed != n2.raftLog.committed { + t.Error("peer 2 must receive the entry from leader, but not") + } + + match := n1.getProgress(2).Match + if match != n2.raftLog.committed { + t.Errorf("progresss 2 of leader 1 wants match %d, but got %d", n2.raftLog.committed, match) + } +} + func TestSingleNodeCommit(t *testing.T) { tt := newNetwork(nil) tt.send(pb.Message{From: 1, To: 1, Type: pb.MsgHup}) @@ -2489,6 +2544,39 @@ func TestRestoreLearnerPromotion(t *testing.T) { } } +// TestLearnerReceiveSnapshot tests that a learner can receive a snpahost from leader +func TestLearnerReceiveSnapshot(t *testing.T) { + // restore the state machine from a snapshot so it has a compacted log and a snapshot + s := pb.Snapshot{ + Metadata: pb.SnapshotMetadata{ + Index: 11, // magic number + Term: 11, // magic number + ConfState: pb.ConfState{Nodes: []uint64{1}, Learners: []uint64{2}}, + }, + } + + n1 := newTestLearnerRaft(1, []uint64{1}, []uint64{2}, 10, 1, NewMemoryStorage()) + n2 := newTestLearnerRaft(2, []uint64{1}, []uint64{2}, 10, 1, NewMemoryStorage()) + + n1.restore(s) + + // Force set n1 appplied index. + n1.raftLog.appliedTo(n1.raftLog.committed) + + nt := newNetwork(n1, n2) + + setRandomizedElectionTimeout(n1, n1.electionTimeout) + for i := 0; i < n1.electionTimeout; i++ { + n1.tick() + } + + nt.send(pb.Message{From: 1, To: 1, Type: pb.MsgBeat}) + + if n2.raftLog.committed != n1.raftLog.committed { + t.Errorf("peer 2 must commit to %d, but %d", n1.raftLog.committed, n2.raftLog.committed) + } +} + func TestRestoreIgnoreSnapshot(t *testing.T) { previousEnts := []pb.Entry{{Term: 1, Index: 1}, {Term: 1, Index: 2}, {Term: 1, Index: 3}} commit := uint64(1) @@ -2732,6 +2820,7 @@ func TestAddNode(t *testing.T) { } } +// TestAddLearner tests that addLearner could update pendingConf and nodes correctly. func TestAddLearner(t *testing.T) { r := newTestRaft(1, []uint64{1}, 10, 1, NewMemoryStorage()) r.pendingConf = true @@ -2806,6 +2895,27 @@ func TestRemoveNode(t *testing.T) { } } +// TestRemoveLearner tests that removeNode could update pendingConf, nodes and +// and removed list correctly. +func TestRemoveLearner(t *testing.T) { + r := newTestLearnerRaft(1, []uint64{1}, []uint64{2}, 10, 1, NewMemoryStorage()) + r.pendingConf = true + r.removeNode(2) + if r.pendingConf { + t.Errorf("pendingConf = %v, want false", r.pendingConf) + } + w := []uint64{1} + if g := r.nodes(); !reflect.DeepEqual(g, w) { + t.Errorf("nodes = %v, want %v", g, w) + } + + // remove all nodes from cluster + r.removeNode(1) + w = []uint64{} + if g := r.nodes(); !reflect.DeepEqual(g, w) { + t.Errorf("nodes = %v, want %v", g, w) + } +} func TestPromotable(t *testing.T) { id := uint64(1) tests := []struct {