func (n *node) ProposeConfChange(ctx context.Context, cc pb.ConfChange) error { data, err := cc.Marshal() if err != nil { return err } return n.Step(ctx, pb.Message{Type: pb.MsgProp, Entries: []pb.Entry{{Type: pb.EntryConfChange, Data: data}}}) }
// TestRawNodeProposeAndConfChange ensures that RawNode.Propose and RawNode.ProposeConfChange // send the given proposal and ConfChange to the underlying raft. func TestRawNodeProposeAndConfChange(t *testing.T) { s := NewMemoryStorage() var err error rawNode, err := NewRawNode(newTestConfig(1, nil, 10, 1, s), []Peer{{ID: 1}}) if err != nil { t.Fatal(err) } rd := rawNode.Ready() s.Append(rd.Entries) rawNode.Advance(rd) rawNode.Campaign() proposed := false var ( lastIndex uint64 ccdata []byte ) for { rd = rawNode.Ready() s.Append(rd.Entries) // Once we are the leader, propose a command and a ConfChange. if !proposed && rd.SoftState.Lead == rawNode.raft.ID { rawNode.Propose([]byte("somedata")) cc := raftpb.ConfChange{Type: raftpb.ConfChangeAddNode, NodeID: 1} ccdata, err = cc.Marshal() if err != nil { t.Fatal(err) } rawNode.ProposeConfChange(cc) proposed = true } rawNode.Advance(rd) // Exit when we have four entries: one ConfChange, one no-op for the election, // our proposed command and proposed ConfChange. lastIndex = s.LastIndex() if lastIndex >= 4 { break } } entries, err := s.Entries(lastIndex-1, lastIndex+1, noLimit) if err != nil { t.Fatal(err) } if len(entries) != 2 { t.Fatalf("len(entries) = %d, want %d", len(entries), 2) } if !bytes.Equal(entries[0].Data, []byte("somedata")) { t.Errorf("entries[0].Data = %v, want %v", entries[0].Data, []byte("somedata")) } if entries[1].Type != raftpb.EntryConfChange { t.Fatalf("type = %v, want %v", entries[1].Type, raftpb.EntryConfChange) } if !bytes.Equal(entries[1].Data, ccdata) { t.Errorf("data = %v, want %v", entries[1].Data, ccdata) } }
func tryRaftLogEntry(kv engine.MVCCKeyValue) (string, error) { var ent raftpb.Entry if err := maybeUnmarshalInline(kv.Value, &ent); err != nil { return "", err } if ent.Type == raftpb.EntryNormal { if len(ent.Data) > 0 { _, cmdData := storage.DecodeRaftCommand(ent.Data) var cmd storagebase.RaftCommand if err := cmd.Unmarshal(cmdData); err != nil { return "", err } ent.Data = nil return fmt.Sprintf("%s by %v\n%s\n%s\n", &ent, cmd.OriginReplica, cmd.BatchRequest, &cmd), nil } return fmt.Sprintf("%s: EMPTY\n", &ent), nil } else if ent.Type == raftpb.EntryConfChange { var cc raftpb.ConfChange if err := cc.Unmarshal(ent.Data); err != nil { return "", err } var ctx storage.ConfChangeContext if err := ctx.Unmarshal(cc.Context); err != nil { return "", err } var cmd storagebase.ReplicatedEvalResult if err := cmd.Unmarshal(ctx.Payload); err != nil { return "", err } ent.Data = nil return fmt.Sprintf("%s\n%s\n", &ent, &cmd), nil } return "", fmt.Errorf("unknown log entry type: %s", &ent) }
func (n *node) processCommitCh() { pending := make(chan struct{}, numPendingMutations) for e := range n.commitCh { if e.Data == nil { continue } if e.Type == raftpb.EntryConfChange { var cc raftpb.ConfChange cc.Unmarshal(e.Data) if len(cc.Context) > 0 { var rc task.RaftContext x.Check(rc.Unmarshal(cc.Context)) n.Connect(rc.Id, rc.Addr) } n.raft.ApplyConfChange(cc) } else { go n.process(e, pending) } } }
// getIDs returns an ordered set of IDs included in the given snapshot and // the entries. The given snapshot/entries can contain two kinds of // ID-related entry: // - ConfChangeAddNode, in which case the contained ID will be added into the set. // - ConfChangeRemoveNode, in which case the contained ID will be removed from the set. func getIDs(snap *raftpb.Snapshot, ents []raftpb.Entry) []uint64 { ids := make(map[uint64]bool) if snap != nil { for _, id := range snap.Metadata.ConfState.Nodes { ids[id] = true } } for _, e := range ents { if e.Type != raftpb.EntryConfChange { continue } if snap != nil && e.Index < snap.Metadata.Index { continue } var cc raftpb.ConfChange if err := cc.Unmarshal(e.Data); err != nil { log.L.WithError(err).Panic("unmarshal configuration change should never fail") } switch cc.Type { case raftpb.ConfChangeAddNode: ids[cc.NodeID] = true case raftpb.ConfChangeRemoveNode: delete(ids, cc.NodeID) case raftpb.ConfChangeUpdateNode: // do nothing default: log.L.Panic("ConfChange Type should be either ConfChangeAddNode or ConfChangeRemoveNode!") } } var sids []uint64 for id := range ids { sids = append(sids, id) } return sids }
func (n *node) run() { for { select { case <-n.ticker: n.raft.Tick() case rd := <-n.raft.Ready(): n.saveToStorage(rd.HardState, rd.Entries, rd.Snapshot) n.send(rd.Messages) if !raft.IsEmptySnap(rd.Snapshot) { n.processSnapshot(rd.Snapshot) } for _, entry := range rd.CommittedEntries { n.process(entry) if entry.Type == raftpb.EntryConfChange { var cc raftpb.ConfChange cc.Unmarshal(entry.Data) n.raft.ApplyConfChange(cc) } } n.raft.Advance() case <-n.done: return } } }
// StartNode returns a new Node given configuration and a list of raft peers. // It appends a ConfChangeAddNode entry for each given peer to the initial log. func StartNode(c *Config, peers []Peer) Node { r := newRaft(c) // become the follower at term 1 and apply initial configuration // entries of term 1 r.becomeFollower(1, None) for _, peer := range peers { cc := pb.ConfChange{Type: pb.ConfChangeAddNode, NodeID: peer.ID, Context: peer.Context} d, err := cc.Marshal() if err != nil { panic("unexpected marshal error") } e := pb.Entry{Type: pb.EntryConfChange, Term: 1, Index: r.raftLog.lastIndex() + 1, Data: d} r.raftLog.append(e) } // Mark these initial entries as committed. // TODO(bdarnell): These entries are still unstable; do we need to preserve // the invariant that committed < unstable? r.raftLog.committed = r.raftLog.lastIndex() // Now apply them, mainly so that the application can call Campaign // immediately after StartNode in tests. Note that these nodes will // be added to raft twice: here and when the application's Ready // loop calls ApplyConfChange. The calls to addNode must come after // all calls to raftLog.append so progress.next is set after these // bootstrapping entries (it is an error if we try to append these // entries since they have already been committed). // We do not set raftLog.applied so the application will be able // to observe all conf changes via Ready.CommittedEntries. for _, peer := range peers { r.addNode(peer.ID) } n := newNode() go n.run(r) return &n }
// Start is the main loop for a Raft node, it // goes along the state machine, acting on the // messages received from other Raft nodes in // the cluster func (n *Node) Start() { for { select { case <-n.ticker.C: n.Tick() case rd := <-n.Ready(): n.saveToStorage(rd.HardState, rd.Entries, rd.Snapshot) n.send(rd.Messages) if !raft.IsEmptySnap(rd.Snapshot) { n.processSnapshot(rd.Snapshot) } for _, entry := range rd.CommittedEntries { n.process(entry) if entry.Type == raftpb.EntryConfChange { var cc raftpb.ConfChange err := cc.Unmarshal(entry.Data) if err != nil { log.Fatal("raft: Can't unmarshal configuration change") } switch cc.Type { case raftpb.ConfChangeAddNode: n.applyAddNode(cc) case raftpb.ConfChangeRemoveNode: n.applyRemoveNode(cc) } n.ApplyConfChange(cc) } } n.Advance() case <-n.stopChan: n.Stop() n.Node = nil close(n.stopChan) return case pause := <-n.pauseChan: // FIXME lock hell n.SetPaused(pause) for n.pause { select { case pause = <-n.pauseChan: n.SetPaused(pause) } } n.pauseLock.Lock() // process pending messages for _, m := range n.rcvmsg { err := n.Step(n.Ctx, m) if err != nil { log.Fatal("Something went wrong when unpausing the node") } } n.rcvmsg = nil n.pauseLock.Unlock() } } }
func (c *RemoveCommand) Data5() ([]byte, error) { req5 := raftpb.ConfChange{ ID: 0, Type: raftpb.ConfChangeRemoveNode, NodeID: c.id, } return req5.Marshal() }
// TestRawNodeStart ensures that a node can be started correctly. The node should // start with correct configuration change entries, and can accept and commit // proposals. func TestRawNodeStart(t *testing.T) { cc := raftpb.ConfChange{Type: raftpb.ConfChangeAddNode, NodeID: 1} ccdata, err := cc.Marshal() if err != nil { t.Fatalf("unexpected marshal error: %v", err) } wants := []Ready{ { HardState: raftpb.HardState{Term: 1, Commit: 1, Vote: 0}, Entries: []raftpb.Entry{ {Type: raftpb.EntryConfChange, Term: 1, Index: 1, Data: ccdata}, }, CommittedEntries: []raftpb.Entry{ {Type: raftpb.EntryConfChange, Term: 1, Index: 1, Data: ccdata}, }, }, { HardState: raftpb.HardState{Term: 2, Commit: 3, Vote: 1}, Entries: []raftpb.Entry{{Term: 2, Index: 3, Data: []byte("foo")}}, CommittedEntries: []raftpb.Entry{{Term: 2, Index: 3, Data: []byte("foo")}}, }, } storage := NewMemoryStorage() rawNode, err := NewRawNode(newTestConfig(1, nil, 10, 1, storage), []Peer{{ID: 1}}) if err != nil { t.Fatal(err) } rd := rawNode.Ready() t.Logf("rd %v", rd) if !reflect.DeepEqual(rd, wants[0]) { t.Fatalf("#%d: g = %+v,\n w %+v", 1, rd, wants[0]) } else { storage.Append(rd.Entries) rawNode.Advance(rd) } storage.Append(rd.Entries) rawNode.Advance(rd) rawNode.Campaign() rd = rawNode.Ready() storage.Append(rd.Entries) rawNode.Advance(rd) rawNode.Propose([]byte("foo")) if rd = rawNode.Ready(); !reflect.DeepEqual(rd, wants[1]) { t.Errorf("#%d: g = %+v,\n w %+v", 2, rd, wants[1]) } else { storage.Append(rd.Entries) rawNode.Advance(rd) } if rawNode.HasReady() { t.Errorf("unexpected Ready: %+v", rawNode.Ready()) } }
func (n *nodeConfChangeCommitterRecorder) ProposeConfChange(ctx context.Context, conf raftpb.ConfChange) error { data, err := conf.Marshal() if err != nil { return err } n.index++ n.Record(testutil.Action{Name: "ProposeConfChange:" + conf.Type.String()}) n.readyc <- raft.Ready{CommittedEntries: []raftpb.Entry{{Index: n.index, Type: raftpb.EntryConfChange, Data: data}}} return nil }
// TestMultiNodeStart ensures that a node can be started correctly. The node should // start with correct configuration change entries, and can accept and commit // proposals. func TestMultiNodeStart(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() cc := raftpb.ConfChange{Type: raftpb.ConfChangeAddNode, NodeID: 1} ccdata, err := cc.Marshal() if err != nil { t.Fatalf("unexpected marshal error: %v", err) } wants := []Ready{ { SoftState: &SoftState{Lead: 1, RaftState: StateLeader}, HardState: raftpb.HardState{Term: 2, Commit: 2, Vote: 1}, Entries: []raftpb.Entry{ {Type: raftpb.EntryConfChange, Term: 1, Index: 1, Data: ccdata}, {Term: 2, Index: 2}, }, CommittedEntries: []raftpb.Entry{ {Type: raftpb.EntryConfChange, Term: 1, Index: 1, Data: ccdata}, {Term: 2, Index: 2}, }, }, { HardState: raftpb.HardState{Term: 2, Commit: 3, Vote: 1}, Entries: []raftpb.Entry{{Term: 2, Index: 3, Data: []byte("foo")}}, CommittedEntries: []raftpb.Entry{{Term: 2, Index: 3, Data: []byte("foo")}}, }, } mn := StartMultiNode(1) storage := NewMemoryStorage() mn.CreateGroup(1, newTestConfig(1, nil, 10, 1, storage), []Peer{{ID: 1}}) mn.Campaign(ctx, 1) gs := <-mn.Ready() g := gs[1] if !reflect.DeepEqual(g, wants[0]) { t.Fatalf("#%d: g = %+v,\n w %+v", 1, g, wants[0]) } else { storage.Append(g.Entries) mn.Advance(gs) } mn.Propose(ctx, 1, []byte("foo")) if gs2 := <-mn.Ready(); !reflect.DeepEqual(gs2[1], wants[1]) { t.Errorf("#%d: g = %+v,\n w %+v", 2, gs2[1], wants[1]) } else { storage.Append(gs2[1].Entries) mn.Advance(gs2) } select { case rd := <-mn.Ready(): t.Errorf("unexpected Ready: %+v", rd) case <-time.After(time.Millisecond): } }
// ProposeConfChange proposes a config change. func (rn *RawNode) ProposeConfChange(cc pb.ConfChange) error { data, err := cc.Marshal() if err != nil { return err } return rn.raft.Step(pb.Message{ Type: pb.MsgProp, Entries: []pb.Entry{ {Type: pb.EntryConfChange, Data: data}, }, }) }
func (mn *multiNode) ProposeConfChange(ctx context.Context, group uint64, cc pb.ConfChange) error { data, err := cc.Marshal() if err != nil { return err } return mn.Step(ctx, group, pb.Message{ Type: pb.MsgProp, Entries: []pb.Entry{ {Type: pb.EntryConfChange, Data: data}, }, }) }
// TestMultiNodeProposeConfig ensures that multiNode.ProposeConfChange // sends the given configuration proposal to the underlying raft. func TestMultiNodeProposeConfig(t *testing.T) { mn := newMultiNode(1) go mn.run() s := NewMemoryStorage() mn.CreateGroup(1, newTestConfig(1, nil, 10, 1, s), []Peer{{ID: 1}}) 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) } }
// makeWAL creates a WAL for the initial cluster func makeWAL(waldir string, cl *membership.RaftCluster) { if err := os.MkdirAll(waldir, 0755); err != nil { ExitWithError(ExitIO, err) } m := cl.MemberByName(restoreName) md := &etcdserverpb.Metadata{NodeID: uint64(m.ID), ClusterID: uint64(cl.ID())} metadata, merr := md.Marshal() if merr != nil { ExitWithError(ExitInvalidInput, merr) } w, walerr := wal.Create(waldir, metadata) if walerr != nil { ExitWithError(ExitIO, walerr) } defer w.Close() peers := make([]raft.Peer, len(cl.MemberIDs())) for i, id := range cl.MemberIDs() { ctx, err := json.Marshal((*cl).Member(id)) if err != nil { ExitWithError(ExitInvalidInput, err) } peers[i] = raft.Peer{ID: uint64(id), Context: ctx} } ents := make([]raftpb.Entry, len(peers)) for i, p := range peers { cc := raftpb.ConfChange{ Type: raftpb.ConfChangeAddNode, NodeID: p.ID, Context: p.Context} d, err := cc.Marshal() if err != nil { ExitWithError(ExitInvalidInput, err) } e := raftpb.Entry{ Type: raftpb.EntryConfChange, Term: 1, Index: uint64(i + 1), Data: d, } ents[i] = e } w.Save(raftpb.HardState{ Term: 1, Vote: peers[0].ID, Commit: uint64(len(ents))}, ents) }
// configure sends a configuration change through consensus and // then waits for it to be applied to the server. It will block // until the change is performed or there is an error. func (n *Node) configure(ctx context.Context, cc raftpb.ConfChange) error { cc.ID = n.reqIDGen.Next() ch := n.wait.register(cc.ID, nil) if err := n.ProposeConfChange(ctx, cc); err != nil { n.wait.trigger(cc.ID, nil) return err } select { case x := <-ch: if err, ok := x.(error); ok { return err } if x != nil { log.G(ctx).Panic("raft: configuration change error, return type should always be error") } return nil case <-ctx.Done(): n.wait.trigger(cc.ID, nil) return ctx.Err() case <-n.stopCh: return ErrStopped } }
// configure sends a configuration change through consensus and // then waits for it to be applied to the server. It // will block until the change is performed or there is an error. func (r *raftNode) configure(ctx context.Context, cc raftpb.ConfChange) error { cc.ID = r.idgen.Next() ch := r.w.Register(cc.ID) start := time.Now() if err := r.ProposeConfChange(ctx, cc); err != nil { // if err := r.node.ProposeConfChange(ctx, cc); err != nil { r.w.Trigger(cc.ID, nil) return err } select { case x := <-ch: if err, ok := x.(error); ok { return err } if x != nil { return fmt.Errorf("return type should always be error") } return nil case <-ctx.Done(): r.w.Trigger(cc.ID, nil) // GC wait return r.parseProposeCtxErr(ctx.Err(), start) case <-r.done: return raft.ErrStopped } }
// configure sends a configuration change through consensus and // then waits for it to be applied to the server. It will block // until the change is performed or there is an error. func (n *Node) configure(ctx context.Context, cc raftpb.ConfChange) error { cc.ID = n.reqIDGen.Next() ctx, cancel := context.WithCancel(ctx) ch := n.wait.register(cc.ID, nil, cancel) if err := n.raftNode.ProposeConfChange(ctx, cc); err != nil { n.wait.cancel(cc.ID) return err } select { case x := <-ch: if err, ok := x.(error); ok { return err } if x != nil { log.G(ctx).Panic("raft: configuration change error, return type should always be error") } return nil case <-ctx.Done(): n.wait.cancel(cc.ID) return ctx.Err() } }
// publishEntries writes committed log entries to commit channel and returns // whether all entries could be published. func (rc *raftNode) publishEntries(ents []raftpb.Entry) bool { for i := range ents { switch ents[i].Type { case raftpb.EntryNormal: if len(ents[i].Data) == 0 { // ignore empty messages break } s := string(ents[i].Data) select { case rc.commitC <- &s: case <-rc.stopc: return false } case raftpb.EntryConfChange: var cc raftpb.ConfChange cc.Unmarshal(ents[i].Data) rc.node.ApplyConfChange(cc) switch cc.Type { case raftpb.ConfChangeAddNode: if len(cc.Context) > 0 { rc.transport.AddPeer(types.ID(cc.NodeID), []string{string(cc.Context)}) } case raftpb.ConfChangeRemoveNode: if cc.NodeID == uint64(rc.id) { log.Println("I've been removed from the cluster! Shutting down.") return false } rc.transport.RemovePeer(types.ID(cc.NodeID)) } } // after commit, update appliedIndex rc.appliedIndex = ents[i].Index // special nil commit to signal replay has finished if ents[i].Index == rc.lastIndex { select { case rc.commitC <- nil: case <-rc.stopc: return false } } } return true }
func TestNode(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() cc := raftpb.ConfChange{Type: raftpb.ConfChangeAddNode, NodeID: 1} ccdata, err := cc.Marshal() if err != nil { t.Fatalf("unexpected marshal error: %v", err) } wants := []Ready{ { SoftState: &SoftState{Lead: 1, Nodes: []uint64{1}, RaftState: StateLeader}, HardState: raftpb.HardState{Term: 1, Commit: 2}, Entries: []raftpb.Entry{ {}, {Type: raftpb.EntryConfChange, Term: 1, Index: 1, Data: ccdata}, {Term: 1, Index: 2}, }, CommittedEntries: []raftpb.Entry{ {Type: raftpb.EntryConfChange, Term: 1, Index: 1, Data: ccdata}, {Term: 1, Index: 2}, }, }, { HardState: raftpb.HardState{Term: 1, Commit: 3}, Entries: []raftpb.Entry{{Term: 1, Index: 3, Data: []byte("foo")}}, CommittedEntries: []raftpb.Entry{{Term: 1, Index: 3, Data: []byte("foo")}}, }, } n := StartNode(1, []Peer{{ID: 1}}, 10, 1) n.ApplyConfChange(cc) n.Campaign(ctx) if g := <-n.Ready(); !reflect.DeepEqual(g, wants[0]) { t.Errorf("#%d: g = %+v,\n w %+v", 1, g, wants[0]) } n.Propose(ctx, []byte("foo")) if g := <-n.Ready(); !reflect.DeepEqual(g, wants[1]) { t.Errorf("#%d: g = %+v,\n w %+v", 2, g, wants[1]) } select { case rd := <-n.Ready(): t.Errorf("unexpected Ready: %+v", rd) default: } }
// NewRawNode returns a new RawNode given configuration and a list of raft peers. func NewRawNode(config *Config, peers []Peer) (*RawNode, error) { if config.ID == 0 { panic("config.ID must not be zero") } r := newRaft(config) rn := &RawNode{ raft: r, } lastIndex, err := config.Storage.LastIndex() if err != nil { panic(err) // TODO(bdarnell) } // If the log is empty, this is a new RawNode (like StartNode); otherwise it's // restoring an existing RawNode (like RestartNode). // TODO(bdarnell): rethink RawNode initialization and whether the application needs // to be able to tell us when it expects the RawNode to exist. if lastIndex == 0 { r.becomeFollower(1, None) ents := make([]pb.Entry, len(peers)) for i, peer := range peers { cc := pb.ConfChange{Type: pb.ConfChangeAddNode, NodeID: peer.ID, Context: peer.Context} data, err := cc.Marshal() if err != nil { panic("unexpected marshal error") } ents[i] = pb.Entry{Type: pb.EntryConfChange, Term: 1, Index: uint64(i + 1), Data: data} } r.raftLog.append(ents...) r.raftLog.committed = uint64(len(ents)) for _, peer := range peers { r.addNode(peer.ID) } } // Set the initial hard and soft states after performing all initialization. rn.prevSoftSt = r.softState() if lastIndex == 0 { rn.prevHardSt = emptyState } else { rn.prevHardSt = r.hardState() } return rn, nil }
// StartNode returns a new Node given a unique raft id, a list of raft peers, and // the election and heartbeat timeouts in units of ticks. // It also builds ConfChangeAddNode entry for each peer and puts them at the head of the log. func StartNode(id uint64, peers []Peer, election, heartbeat int) Node { n := newNode() r := newRaft(id, nil, election, heartbeat) for _, peer := range peers { cc := pb.ConfChange{Type: pb.ConfChangeAddNode, NodeID: peer.ID, Context: peer.Context} d, err := cc.Marshal() if err != nil { panic("unexpected marshal error") } e := pb.Entry{Type: pb.EntryConfChange, Term: 1, Index: r.raftLog.lastIndex() + 1, Data: d} r.raftLog.append(r.raftLog.lastIndex(), e) } r.raftLog.committed = r.raftLog.lastIndex() go n.run(r) return &n }
// TestProposeAfterRemoveLeader ensures that we gracefully handle // proposals that are attempted after a leader has been removed from // the active configuration, but before that leader has called // MultiNode.RemoveGroup. func TestProposeAfterRemoveLeader(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() mn := newMultiNode(1) go mn.run() defer mn.Stop() storage := NewMemoryStorage() if err := mn.CreateGroup(1, newTestConfig(1, nil, 10, 1, storage), []Peer{{ID: 1}}); err != nil { t.Fatal(err) } if err := mn.Campaign(ctx, 1); err != nil { t.Fatal(err) } if err := mn.ProposeConfChange(ctx, 1, raftpb.ConfChange{ Type: raftpb.ConfChangeRemoveNode, NodeID: 1, }); err != nil { t.Fatal(err) } gs := <-mn.Ready() g := gs[1] if err := storage.Append(g.Entries); err != nil { t.Fatal(err) } for _, e := range g.CommittedEntries { if e.Type == raftpb.EntryConfChange { var cc raftpb.ConfChange if err := cc.Unmarshal(e.Data); err != nil { t.Fatal(err) } mn.ApplyConfChange(1, cc) } } mn.Advance(gs) if err := mn.Propose(ctx, 1, []byte("somedata")); err != nil { t.Errorf("err = %v, want nil", err) } }
// StartNode returns a new Node given a unique raft id, a list of raft peers, and // the election and heartbeat timeouts in units of ticks. // It appends a ConfChangeAddNode entry for each given peer to the initial log. func StartNode(id uint64, peers []Peer, election, heartbeat int, storage Storage) Node { n := newNode() r := newRaft(id, nil, election, heartbeat, storage) for _, peer := range peers { cc := pb.ConfChange{Type: pb.ConfChangeAddNode, NodeID: peer.ID, Context: peer.Context} d, err := cc.Marshal() if err != nil { panic("unexpected marshal error") } e := pb.Entry{Type: pb.EntryConfChange, Term: 1, Index: r.raftLog.lastIndex() + 1, Data: d} r.raftLog.append(r.raftLog.lastIndex(), e) } // Mark these initial entries as committed. // TODO(bdarnell): These entries are still unstable; do we need to preserve // the invariant that committed < unstable? r.raftLog.committed = r.raftLog.lastIndex() go n.run(r) return &n }
func (n *node) process(entry raftpb.Entry) { fmt.Printf("node %v: processing entry", n.id) if entry.Data == nil { return } if entry.Type == raftpb.EntryConfChange { fmt.Printf("Configuration change\n") var cc raftpb.ConfChange cc.Unmarshal(entry.Data) n.raft.ApplyConfChange(cc) return } if entry.Type == raftpb.EntryNormal { parts := bytes.SplitN(entry.Data, []byte(":"), 2) k := string(parts[0]) v := string(parts[1]) n.data[k] = v fmt.Printf(" Key: %v Val: %v\n", k, v) } }
func (c *ctrl) readyApply(snapshot raftpb.Snapshot, committedEntries []raftpb.Entry) error { c.snapshotc <- snapshot for _, committedEntry := range committedEntries { c.entryc <- committedEntry if committedEntry.Type == raftpb.EntryConfChange { // See raftexample raftNode.publishEntries var cc raftpb.ConfChange if err := cc.Unmarshal(committedEntry.Data); err != nil { return fmt.Errorf("unmarshal ConfChange: %v", err) } c.node.ApplyConfChange(cc) if cc.Type == raftpb.ConfChangeRemoveNode && cc.NodeID == c.self.ID { return errors.New("got ConfChange that removed me from the cluster; terminating") } } } return nil }
// TestNodeProposeConfig ensures that node.ProposeConfChange sends the given configuration proposal // to the underlying raft. func TestNodeProposeConfig(t *testing.T) { msgs := []raftpb.Message{} appendStep := func(r *raft, m raftpb.Message) { msgs = append(msgs, m) } n := newNode() s := NewMemoryStorage() r := newTestRaft(1, []uint64{1}, 10, 1, s) go n.run(r) n.Campaign(context.TODO()) for { rd := <-n.Ready() s.Append(rd.Entries) // change the step function to appendStep until this raft becomes leader if rd.SoftState.Lead == r.id { r.step = appendStep n.Advance() break } n.Advance() } cc := raftpb.ConfChange{Type: raftpb.ConfChangeAddNode, NodeID: 1} ccdata, err := cc.Marshal() if err != nil { t.Fatal(err) } n.ProposeConfChange(context.TODO(), cc) n.Stop() if len(msgs) != 1 { t.Fatalf("len(msgs) = %d, want %d", len(msgs), 1) } if msgs[0].Type != raftpb.MsgProp { t.Errorf("msg type = %d, want %d", msgs[0].Type, raftpb.MsgProp) } if !bytes.Equal(msgs[0].Entries[0].Data, ccdata) { t.Errorf("data = %v, want %v", msgs[0].Entries[0].Data, ccdata) } }
// applyConfChange applies a ConfChange to the server. It is only // invoked with a ConfChange that has already passed through Raft func (s *EtcdServer) applyConfChange(cc raftpb.ConfChange, confState *raftpb.ConfState) (bool, error) { if err := s.Cluster.ValidateConfigurationChange(cc); err != nil { cc.NodeID = raft.None s.node.ApplyConfChange(cc) return false, err } *confState = *s.node.ApplyConfChange(cc) switch cc.Type { case raftpb.ConfChangeAddNode: m := new(Member) if err := json.Unmarshal(cc.Context, m); err != nil { log.Panicf("unmarshal member should never fail: %v", err) } if cc.NodeID != uint64(m.ID) { log.Panicf("nodeID should always be equal to member ID") } s.Cluster.AddMember(m) if m.ID == s.id { log.Printf("etcdserver: added local member %s %v to cluster %s", m.ID, m.PeerURLs, s.Cluster.ID()) } else { s.sendhub.Add(m) log.Printf("etcdserver: added member %s %v to cluster %s", m.ID, m.PeerURLs, s.Cluster.ID()) } case raftpb.ConfChangeRemoveNode: id := types.ID(cc.NodeID) s.Cluster.RemoveMember(id) if id == s.id { log.Printf("etcdserver: removed local member %s from cluster %s", id, s.Cluster.ID()) log.Println("etcdserver: the data-dir used by this member must be removed so that this host can be re-added with a new member ID") return true, nil } else { s.sendhub.Remove(id) log.Printf("etcdserver: removed member %s from cluster %s", id, s.Cluster.ID()) } case raftpb.ConfChangeUpdateNode: m := new(Member) if err := json.Unmarshal(cc.Context, m); err != nil { log.Panicf("unmarshal member should never fail: %v", err) } if cc.NodeID != uint64(m.ID) { log.Panicf("nodeID should always be equal to member ID") } s.Cluster.UpdateMember(m) if m.ID == s.id { log.Printf("etcdserver: update local member %s %v in cluster %s", m.ID, m.PeerURLs, s.Cluster.ID()) } else { s.sendhub.Update(m) log.Printf("etcdserver: update member %s %v in cluster %s", m.ID, m.PeerURLs, s.Cluster.ID()) } } return false, nil }
// applyConfChange applies a ConfChange to the server. It is only // invoked with a ConfChange that has already passed through Raft func (s *EtcdServer) applyConfChange(cc raftpb.ConfChange, confState *raftpb.ConfState) (bool, error) { if err := s.cluster.ValidateConfigurationChange(cc); err != nil { cc.NodeID = raft.None s.r.ApplyConfChange(cc) return false, err } *confState = *s.r.ApplyConfChange(cc) switch cc.Type { case raftpb.ConfChangeAddNode: m := new(membership.Member) if err := json.Unmarshal(cc.Context, m); err != nil { plog.Panicf("unmarshal member should never fail: %v", err) } if cc.NodeID != uint64(m.ID) { plog.Panicf("nodeID should always be equal to member ID") } s.cluster.AddMember(m) if m.ID == s.id { plog.Noticef("added local member %s %v to cluster %s", m.ID, m.PeerURLs, s.cluster.ID()) } else { s.r.transport.AddPeer(m.ID, m.PeerURLs) plog.Noticef("added member %s %v to cluster %s", m.ID, m.PeerURLs, s.cluster.ID()) } case raftpb.ConfChangeRemoveNode: id := types.ID(cc.NodeID) s.cluster.RemoveMember(id) if id == s.id { return true, nil } else { s.r.transport.RemovePeer(id) plog.Noticef("removed member %s from cluster %s", id, s.cluster.ID()) } case raftpb.ConfChangeUpdateNode: m := new(membership.Member) if err := json.Unmarshal(cc.Context, m); err != nil { plog.Panicf("unmarshal member should never fail: %v", err) } if cc.NodeID != uint64(m.ID) { plog.Panicf("nodeID should always be equal to member ID") } s.cluster.UpdateRaftAttributes(m.ID, m.RaftAttributes) if m.ID == s.id { plog.Noticef("update local member %s %v in cluster %s", m.ID, m.PeerURLs, s.cluster.ID()) } else { s.r.transport.UpdatePeer(m.ID, m.PeerURLs) plog.Noticef("update member %s %v in cluster %s", m.ID, m.PeerURLs, s.cluster.ID()) } } return false, nil }