// TestMultiNodeProposeConfig ensures that multiNode.ProposeConfChange // sends the given configuration proposal to the underlying raft. func TestMultiNodeProposeConfig(t *testing.T) { mn := newMultiNode(1, 10, 1) go mn.run() s := NewMemoryStorage() mn.CreateGroup(1, []Peer{{ID: 1}}, s) mn.Campaign(context.TODO(), 1) proposed := false var lastIndex uint64 var ccdata []byte for { rds := <-mn.Ready() rd := rds[1] s.Append(rd.Entries) // change the step function to appendStep until this raft becomes leader if !proposed && rd.SoftState.Lead == mn.id { cc := raftpb.ConfChange{Type: raftpb.ConfChangeAddNode, NodeID: 1} var err error ccdata, err = cc.Marshal() if err != nil { t.Fatal(err) } mn.ProposeConfChange(context.TODO(), 1, cc) proposed = true } mn.Advance(rds) var err error lastIndex, err = s.LastIndex() if err != nil { t.Fatal(err) } if lastIndex >= 3 { break } } mn.Stop() entries, err := s.Entries(lastIndex, lastIndex+1, noLimit) if err != nil { t.Fatal(err) } if len(entries) != 1 { t.Fatalf("len(entries) = %d, want %d", len(entries), 1) } if entries[0].Type != raftpb.EntryConfChange { t.Fatalf("type = %v, want %v", entries[0].Type, raftpb.EntryConfChange) } if !bytes.Equal(entries[0].Data, ccdata) { t.Errorf("data = %v, want %v", entries[0].Data, ccdata) } }
// TestMultiNodeStep ensures that multiNode.Step sends MsgProp to propc // chan and other kinds of messages to recvc chan. func TestMultiNodeStep(t *testing.T) { for i, msgn := range raftpb.MessageType_name { mn := &multiNode{ propc: make(chan multiMessage, 1), recvc: make(chan multiMessage, 1), } msgt := raftpb.MessageType(i) mn.Step(context.TODO(), 1, raftpb.Message{Type: msgt}) // Proposal goes to proc chan. Others go to recvc chan. if msgt == raftpb.MsgProp { select { case <-mn.propc: default: t.Errorf("%d: cannot receive %s on propc chan", msgt, msgn) } } else { if msgt == raftpb.MsgBeat || msgt == raftpb.MsgHup || msgt == raftpb.MsgUnreachable || msgt == raftpb.MsgSnapStatus { select { case <-mn.recvc: t.Errorf("%d: step should ignore %s", msgt, msgn) default: } } else { select { case <-mn.recvc: default: t.Errorf("%d: cannot receive %s on recvc chan", msgt, msgn) } } } } }
// TestMultiNodePropose ensures that node.Propose sends the given proposal to the underlying raft. func TestMultiNodePropose(t *testing.T) { mn := newMultiNode(1, 10, 1) go mn.run() s := NewMemoryStorage() mn.CreateGroup(1, []Peer{{ID: 1}}, s) mn.Campaign(context.TODO(), 1) proposed := false for { rds := <-mn.Ready() rd := rds[1] s.Append(rd.Entries) // Once we are the leader, propose a command. if !proposed && rd.SoftState.Lead == mn.id { mn.Propose(context.TODO(), 1, []byte("somedata")) proposed = true } mn.Advance(rds) // Exit when we have three entries: one ConfChange, one no-op for the election, // and our proposed command. lastIndex, err := s.LastIndex() if err != nil { t.Fatal(err) } if lastIndex >= 3 { break } } mn.Stop() lastIndex, err := s.LastIndex() if err != nil { t.Fatal(err) } entries, err := s.Entries(lastIndex, lastIndex+1, noLimit) if err != nil { t.Fatal(err) } if len(entries) != 1 { t.Fatalf("len(entries) = %d, want %d", len(entries), 1) } if !bytes.Equal(entries[0].Data, []byte("somedata")) { t.Errorf("entries[0].Data = %v, want %v", entries[0].Data, []byte("somedata")) } }
func (n *node) start() { n.stopc = make(chan struct{}) ticker := time.Tick(5 * time.Millisecond) go func() { for { select { case <-ticker: n.Tick() case rd := <-n.Ready(): if !raft.IsEmptyHardState(rd.HardState) { n.state = rd.HardState n.storage.SetHardState(n.state) } n.storage.Append(rd.Entries) // TODO: make send async, more like real world... for _, m := range rd.Messages { n.iface.send(m) } n.Advance() case m := <-n.iface.recv(): n.Step(context.TODO(), m) case <-n.stopc: n.Stop() log.Printf("raft.%d: stop", n.id) n.Node = nil close(n.stopc) return case p := <-n.pausec: recvms := make([]raftpb.Message, 0) for p { select { case m := <-n.iface.recv(): recvms = append(recvms, m) case p = <-n.pausec: } } // step all pending messages for _, m := range recvms { n.Step(context.TODO(), m) } } } }() }
func TestPause(t *testing.T) { peers := []raft.Peer{{1, nil}, {2, nil}, {3, nil}, {4, nil}, {5, nil}} nt := newRaftNetwork(1, 2, 3, 4, 5) nodes := make([]*node, 0) for i := 1; i <= 5; i++ { n := startNode(uint64(i), peers, nt.nodeNetwork(uint64(i))) nodes = append(nodes, n) } time.Sleep(50 * time.Millisecond) for i := 0; i < 300; i++ { nodes[0].Propose(context.TODO(), []byte("somedata")) } nodes[1].pause() for i := 0; i < 300; i++ { nodes[0].Propose(context.TODO(), []byte("somedata")) } nodes[2].pause() for i := 0; i < 300; i++ { nodes[0].Propose(context.TODO(), []byte("somedata")) } nodes[2].resume() for i := 0; i < 300; i++ { nodes[0].Propose(context.TODO(), []byte("somedata")) } nodes[1].resume() // give some time for nodes to catch up with the raft leader time.Sleep(300 * time.Millisecond) for _, n := range nodes { n.stop() if n.state.Commit != 1206 { t.Errorf("commit = %d, want = 1206", n.state.Commit) } } }
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { w.Header().Set("Allow", "POST") http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } wcid := h.cid.String() w.Header().Set("X-Etcd-Cluster-ID", wcid) gcid := r.Header.Get("X-Etcd-Cluster-ID") if gcid != wcid { log.Printf("rafthttp: request ignored due to cluster ID mismatch got %s want %s", gcid, wcid) http.Error(w, "clusterID mismatch", http.StatusPreconditionFailed) return } // Limit the data size that could be read from the request body, which ensures that read from // connection will not time out accidentally due to possible block in underlying implementation. limitedr := pioutil.NewLimitedBufferReader(r.Body, ConnReadLimitByte) b, err := ioutil.ReadAll(limitedr) if err != nil { log.Println("rafthttp: error reading raft message:", err) http.Error(w, "error reading raft message", http.StatusBadRequest) return } var m raftpb.Message if err := m.Unmarshal(b); err != nil { log.Println("rafthttp: error unmarshaling raft message:", err) http.Error(w, "error unmarshaling raft message", http.StatusBadRequest) return } if err := h.r.Process(context.TODO(), m); err != nil { switch v := err.(type) { case writerToResponse: v.WriteTo(w) default: log.Printf("rafthttp: error processing raft message: %v", err) http.Error(w, "error processing raft message", http.StatusInternalServerError) } return } // Write StatusNoContet header after the message has been processed by // raft, which faciliates the client to report MsgSnap status. w.WriteHeader(http.StatusNoContent) }
// Cancel and Stop should unblock Step() func TestMultiNodeStepUnblock(t *testing.T) { // a node without buffer to block step mn := &multiNode{ propc: make(chan multiMessage), done: make(chan struct{}), } ctx, cancel := context.WithCancel(context.Background()) stopFunc := func() { close(mn.done) } tests := []struct { unblock func() werr error }{ {stopFunc, ErrStopped}, {cancel, context.Canceled}, } for i, tt := range tests { errc := make(chan error, 1) go func() { err := mn.Step(ctx, 1, raftpb.Message{Type: raftpb.MsgProp}) errc <- err }() tt.unblock() select { case err := <-errc: if err != tt.werr { t.Errorf("#%d: err = %v, want %v", i, err, tt.werr) } //clean up side-effect if ctx.Err() != nil { ctx = context.TODO() } select { case <-mn.done: mn.done = make(chan struct{}) default: } case <-time.After(time.Millisecond * 100): t.Errorf("#%d: failed to unblock step", i) } } }
func TestBasicProgress(t *testing.T) { peers := []raft.Peer{{1, nil}, {2, nil}, {3, nil}, {4, nil}, {5, nil}} nt := newRaftNetwork(1, 2, 3, 4, 5) nodes := make([]*node, 0) for i := 1; i <= 5; i++ { n := startNode(uint64(i), peers, nt.nodeNetwork(uint64(i))) nodes = append(nodes, n) } time.Sleep(50 * time.Millisecond) for i := 0; i < 1000; i++ { nodes[0].Propose(context.TODO(), []byte("somedata")) } time.Sleep(100 * time.Millisecond) for _, n := range nodes { n.stop() if n.state.Commit != 1006 { t.Errorf("commit = %d, want = 1006", n.state.Commit) } } }
func startPeer(tr http.RoundTripper, urls types.URLs, local, to, cid types.ID, r Raft, fs *stats.FollowerStats, errorc chan error) *peer { picker := newURLPicker(urls) p := &peer{ id: to, r: r, msgAppWriter: startStreamWriter(to, fs, r), writer: startStreamWriter(to, fs, r), pipeline: newPipeline(tr, picker, to, cid, fs, r, errorc), sendc: make(chan raftpb.Message), recvc: make(chan raftpb.Message, recvBufSize), propc: make(chan raftpb.Message, maxPendingProposals), newURLsC: make(chan types.URLs), pausec: make(chan struct{}), resumec: make(chan struct{}), stopc: make(chan struct{}), done: make(chan struct{}), } // Use go-routine for process of MsgProp because it is // blocking when there is no leader. ctx, cancel := context.WithCancel(context.Background()) go func() { for { select { case mm := <-p.propc: if err := r.Process(ctx, mm); err != nil { log.Printf("peer: process raft message error: %v", err) } case <-p.stopc: return } } }() go func() { var paused bool msgAppReader := startStreamReader(tr, picker, streamTypeMsgApp, local, to, cid, p.recvc, p.propc) reader := startStreamReader(tr, picker, streamTypeMessage, local, to, cid, p.recvc, p.propc) for { select { case m := <-p.sendc: if paused { continue } writec, name := p.pick(m) select { case writec <- m: default: p.r.ReportUnreachable(m.To) if isMsgSnap(m) { p.r.ReportSnapshot(m.To, raft.SnapshotFailure) } log.Printf("peer: dropping %s to %s since %s with %d-size buffer is blocked", m.Type, p.id, name, bufSizeMap[name]) } case mm := <-p.recvc: if mm.Type == raftpb.MsgApp { msgAppReader.updateMsgAppTerm(mm.Term) } if err := r.Process(context.TODO(), mm); err != nil { log.Printf("peer: process raft message error: %v", err) } case urls := <-p.newURLsC: picker.update(urls) case <-p.pausec: paused = true case <-p.resumec: paused = false case <-p.stopc: cancel() p.msgAppWriter.stop() p.writer.stop() p.pipeline.stop() msgAppReader.stop() reader.stop() close(p.done) return } } }() return p }