func TestTransportAdd(t *testing.T) { ls := stats.NewLeaderStats("") tr := &transport{ roundTripper: &roundTripperRecorder{}, leaderStats: ls, peers: make(map[types.ID]Peer), } tr.AddPeer(1, []string{"http://localhost:7001"}) defer tr.Stop() if _, ok := ls.Followers["1"]; !ok { t.Errorf("FollowerStats[1] is nil, want exists") } s, ok := tr.peers[types.ID(1)] if !ok { t.Fatalf("senders[1] is nil, want exists") } // duplicate AddPeer is ignored tr.AddPeer(1, []string{"http://localhost:7001"}) ns := tr.peers[types.ID(1)] if s != ns { t.Errorf("sender = %v, want %v", ns, s) } }
// TestStreamReaderDialResult tests the result of the dial func call meets the // HTTP response received. func TestStreamReaderDialResult(t *testing.T) { tests := []struct { code int err error wok bool }{ {0, errors.New("blah"), false}, {http.StatusOK, nil, true}, {http.StatusMethodNotAllowed, nil, false}, {http.StatusNotFound, nil, false}, {http.StatusPreconditionFailed, nil, false}, } for i, tt := range tests { tr := newRespRoundTripper(tt.code, tt.err) sr := &streamReader{ tr: tr, picker: mustNewURLPicker(t, []string{"http://localhost:7001"}), t: streamTypeMessage, from: types.ID(1), to: types.ID(2), cid: types.ID(1), } _, err := sr.dial() if ok := err == nil; ok != tt.wok { t.Errorf("#%d: ok = %v, want %v", i, ok, tt.wok) } } }
func TestPipelinePost(t *testing.T) { tr := &roundTripperRecorder{} picker := mustNewURLPicker(t, []string{"http://localhost:7001"}) p := newPipeline(tr, picker, types.ID(1), types.ID(1), nil, &fakeRaft{}, nil) if err := p.post([]byte("some data")); err != nil { t.Fatalf("unexpect post error: %v", err) } p.stop() if g := tr.Request().Method; g != "POST" { t.Errorf("method = %s, want %s", g, "POST") } if g := tr.Request().URL.String(); g != "http://localhost:7001/raft" { t.Errorf("url = %s, want %s", g, "http://localhost:7001/raft") } if g := tr.Request().Header.Get("Content-Type"); g != "application/protobuf" { t.Errorf("content type = %s, want %s", g, "application/protobuf") } if g := tr.Request().Header.Get("X-Etcd-Cluster-ID"); g != "1" { t.Errorf("cluster id = %s, want %s", g, "1") } b, err := ioutil.ReadAll(tr.Request().Body) if err != nil { t.Fatalf("unexpected ReadAll error: %v", err) } if string(b) != "some data" { t.Errorf("body = %s, want %s", b, "some data") } }
func (s *EtcdServer) Process(ctx context.Context, m raftpb.Message) error { if s.Cluster.IsIDRemoved(types.ID(m.From)) { log.Printf("etcdserver: reject message from removed member %s", types.ID(m.From).String()) return httptypes.NewHTTPError(http.StatusForbidden, "cannot process message from removed member") } if m.Type == raftpb.MsgApp { s.stats.RecvAppendReq(types.ID(m.From).String(), m.Size()) } return s.r.Step(ctx, m) }
func TestTransportUpdate(t *testing.T) { peer := newFakePeer() tr := &transport{ peers: map[types.ID]Peer{types.ID(1): peer}, } u := "http://localhost:7001" tr.UpdatePeer(types.ID(1), []string{u}) wurls := types.URLs(testutil.MustNewURLs(t, []string{"http://localhost:7001"})) if !reflect.DeepEqual(peer.urls, wurls) { t.Errorf("urls = %+v, want %+v", peer.urls, wurls) } }
func TestServeRaftStreamPrefix(t *testing.T) { tests := []struct { path string wtype streamType }{ { RaftStreamPrefix + "/message/1", streamTypeMessage, }, { RaftStreamPrefix + "/msgapp/1", streamTypeMsgApp, }, // backward compatibility { RaftStreamPrefix + "/1", streamTypeMsgApp, }, } for i, tt := range tests { req, err := http.NewRequest("GET", "http://localhost:7001"+tt.path, nil) if err != nil { t.Fatalf("#%d: could not create request: %#v", i, err) } req.Header.Set("X-Etcd-Cluster-ID", "1") req.Header.Set("X-Raft-To", "2") wterm := "1" req.Header.Set("X-Raft-Term", wterm) peer := newFakePeer() peerGetter := &fakePeerGetter{peers: map[types.ID]Peer{types.ID(1): peer}} h := newStreamHandler(peerGetter, types.ID(2), types.ID(1)) rw := httptest.NewRecorder() go h.ServeHTTP(rw, req) var conn *outgoingConn select { case conn = <-peer.connc: case <-time.After(time.Second): t.Fatalf("#%d: failed to attach outgoingConn", i) } if conn.t != tt.wtype { t.Errorf("$%d: type = %s, want %s", i, conn.t, tt.wtype) } if conn.termStr != wterm { t.Errorf("$%d: term = %s, want %s", i, conn.termStr, wterm) } conn.Close() } }
// TestPipelineSendFailed tests that when send func meets the post error, // it increases fail count in stats. func TestPipelineSendFailed(t *testing.T) { picker := mustNewURLPicker(t, []string{"http://localhost:7001"}) fs := &stats.FollowerStats{} p := newPipeline(newRespRoundTripper(0, errors.New("blah")), picker, types.ID(1), types.ID(1), fs, &fakeRaft{}, nil) p.msgc <- raftpb.Message{Type: raftpb.MsgApp} p.stop() fs.Lock() defer fs.Unlock() if fs.Counts.Fail != 1 { t.Errorf("fail = %d, want 1", fs.Counts.Fail) } }
func TestTransportRemove(t *testing.T) { tr := &transport{ roundTripper: &roundTripperRecorder{}, leaderStats: stats.NewLeaderStats(""), peers: make(map[types.ID]Peer), } tr.AddPeer(1, []string{"http://localhost:7001"}) tr.RemovePeer(types.ID(1)) defer tr.Stop() if _, ok := tr.peers[types.ID(1)]; ok { t.Fatalf("senders[1] exists, want removed") } }
func GuessNodeID(nodes map[string]uint64, snap4 *Snapshot4, cfg *Config4, name string) uint64 { var snapNodes map[string]uint64 if snap4 != nil { snapNodes = snap4.GetNodesFromStore() } // First, use the flag, if set. if name != "" { log.Printf("Using suggested name %s", name) if val, ok := nodes[name]; ok { log.Printf("Assigning %s the ID %s", name, types.ID(val)) return val } if snapNodes != nil { if val, ok := snapNodes[name]; ok { log.Printf("Assigning %s the ID %s", name, types.ID(val)) return val } } log.Printf("Name not found, autodetecting...") } // Next, look at the snapshot peers, if that exists. if snap4 != nil { //snapNodes := make(map[string]uint64) //for _, p := range snap4.Peers { //m := generateNodeMember(p.Name, p.ConnectionString, "") //snapNodes[p.Name] = uint64(m.ID) //} for _, p := range cfg.Peers { delete(snapNodes, p.Name) } if len(snapNodes) == 1 { for nodename, id := range nodes { log.Printf("Autodetected from snapshot: name %s", nodename) return id } } } // Then, try and deduce from the log. for _, p := range cfg.Peers { delete(nodes, p.Name) } if len(nodes) == 1 { for nodename, id := range nodes { log.Printf("Autodetected name %s", nodename) return id } } return 0 }
func readWAL(waldir string, snap walpb.Snapshot) (w *wal.WAL, id, cid types.ID, st raftpb.HardState, ents []raftpb.Entry) { var err error if w, err = wal.Open(waldir, snap); err != nil { log.Fatalf("etcdserver: open wal error: %v", err) } var wmetadata []byte if wmetadata, st, ents, err = w.ReadAll(); err != nil { log.Fatalf("etcdserver: read wal error: %v", err) } var metadata pb.Metadata pbutil.MustUnmarshal(&metadata, wmetadata) id = types.ID(metadata.NodeID) cid = types.ID(metadata.ClusterID) return }
func newTestMember(id uint64, peerURLs []string, name string, clientURLs []string) *Member { return &Member{ ID: types.ID(id), RaftAttributes: RaftAttributes{PeerURLs: peerURLs}, Attributes: Attributes{Name: name, ClientURLs: clientURLs}, } }
func reportSentDuration(channel string, m raftpb.Message, duration time.Duration) { typ := m.Type.String() if isLinkHeartbeatMessage(m) { typ = "MsgLinkHeartbeat" } msgSentDuration.WithLabelValues(channel, types.ID(m.To).String(), typ).Observe(float64(duration.Nanoseconds() / int64(time.Microsecond))) }
func reportSentFailure(channel string, m raftpb.Message) { typ := m.Type.String() if isLinkHeartbeatMessage(m) { typ = "MsgLinkHeartbeat" } msgSentFailed.WithLabelValues(channel, types.ID(m.To).String(), typ).Inc() }
func (s *EtcdServer) send(ms []raftpb.Message) { for _, m := range ms { if !s.Cluster.IsIDRemoved(types.ID(m.To)) { m.To = 0 } } s.r.transport.Send(ms) }
// TestPipelineSend tests that pipeline could send data using roundtripper // and increase success count in stats. func TestPipelineSend(t *testing.T) { tr := &roundTripperRecorder{} picker := mustNewURLPicker(t, []string{"http://localhost:7001"}) fs := &stats.FollowerStats{} p := newPipeline(tr, picker, types.ID(1), types.ID(1), fs, &fakeRaft{}, nil) p.msgc <- raftpb.Message{Type: raftpb.MsgApp} p.stop() if tr.Request() == nil { t.Errorf("sender fails to post the data") } fs.Lock() defer fs.Unlock() if fs.Counts.Success != 1 { t.Errorf("success = %d, want 1", fs.Counts.Success) } }
func (c *Cluster) genID() { mIDs := c.MemberIDs() b := make([]byte, 8*len(mIDs)) for i, id := range mIDs { binary.BigEndian.PutUint64(b[8*i:], uint64(id)) } hash := sha1.Sum(b) c.id = types.ID(binary.BigEndian.Uint64(hash[:8])) }
// ValidateConfigurationChange takes a proposed ConfChange and // ensures that it is still valid. func (c *Cluster) ValidateConfigurationChange(cc raftpb.ConfChange) error { members, removed := membersFromStore(c.store) id := types.ID(cc.NodeID) if removed[id] { return ErrIDRemoved } switch cc.Type { case raftpb.ConfChangeAddNode: if members[id] != nil { return ErrIDExists } urls := make(map[string]bool) for _, m := range members { for _, u := range m.PeerURLs { urls[u] = true } } m := new(Member) if err := json.Unmarshal(cc.Context, m); err != nil { log.Panicf("unmarshal member should never fail: %v", err) } for _, u := range m.PeerURLs { if urls[u] { return ErrPeerURLexists } } case raftpb.ConfChangeRemoveNode: if members[id] == nil { return ErrIDNotFound } case raftpb.ConfChangeUpdateNode: if members[id] == nil { return ErrIDNotFound } urls := make(map[string]bool) for _, m := range members { if m.ID == id { continue } for _, u := range m.PeerURLs { urls[u] = true } } m := new(Member) if err := json.Unmarshal(cc.Context, m); err != nil { log.Panicf("unmarshal member should never fail: %v", err) } for _, u := range m.PeerURLs { if urls[u] { return ErrPeerURLexists } } default: log.Panicf("ConfChange type should be either AddNode, RemoveNode or UpdateNode") } return nil }
func TestMsgApp(t *testing.T) { tests := []raftpb.Message{ { Type: raftpb.MsgApp, From: 1, To: 2, Term: 1, LogTerm: 1, Index: 3, Entries: []raftpb.Entry{{Term: 1, Index: 4}}, }, { Type: raftpb.MsgApp, From: 1, To: 2, Term: 1, LogTerm: 1, Index: 0, Entries: []raftpb.Entry{ {Term: 1, Index: 1, Data: []byte("some data")}, {Term: 1, Index: 2, Data: []byte("some data")}, {Term: 1, Index: 3, Data: []byte("some data")}, }, }, linkHeartbeatMessage, } for i, tt := range tests { b := &bytes.Buffer{} enc := &msgAppEncoder{w: b, fs: &stats.FollowerStats{}} if err := enc.encode(tt); err != nil { t.Errorf("#%d: unexpected encode message error: %v", i, err) continue } dec := &msgAppDecoder{r: b, local: types.ID(tt.To), remote: types.ID(tt.From), term: tt.Term} m, err := dec.decode() if err != nil { t.Errorf("#%d: unexpected decode message error: %v", i, err) continue } if !reflect.DeepEqual(m, tt) { t.Errorf("#%d: message = %+v, want %+v", i, m, tt) } } }
func TestStreamReaderDialRequest(t *testing.T) { for i, tt := range []streamType{streamTypeMsgApp, streamTypeMessage} { tr := &roundTripperRecorder{} sr := &streamReader{ tr: tr, picker: mustNewURLPicker(t, []string{"http://localhost:7001"}), t: tt, from: types.ID(1), to: types.ID(2), cid: types.ID(1), msgAppTerm: 1, } sr.dial() req := tr.Request() var wurl string switch tt { case streamTypeMsgApp: wurl = "http://localhost:7001/raft/stream/1" case streamTypeMessage: wurl = "http://localhost:7001/raft/stream/message/1" } if req.URL.String() != wurl { t.Errorf("#%d: url = %s, want %s", i, req.URL.String(), wurl) } if w := "GET"; req.Method != w { t.Errorf("#%d: method = %s, want %s", i, req.Method, w) } if g := req.Header.Get("X-Etcd-Cluster-ID"); g != "1" { t.Errorf("#%d: header X-Etcd-Cluster-ID = %s, want 1", i, g) } if g := req.Header.Get("X-Raft-To"); g != "2" { t.Errorf("#%d: header X-Raft-To = %s, want 2", i, g) } if g := req.Header.Get("X-Raft-Term"); tt == streamTypeMsgApp && g != "1" { t.Errorf("#%d: header X-Raft-Term = %s, want 1", i, g) } } }
func TestPipelinePostErrorc(t *testing.T) { tests := []struct { u string code int err error }{ {"http://localhost:7001", http.StatusForbidden, nil}, {"http://localhost:7001", http.StatusPreconditionFailed, nil}, } for i, tt := range tests { picker := mustNewURLPicker(t, []string{tt.u}) errorc := make(chan error, 1) p := newPipeline(newRespRoundTripper(tt.code, tt.err), picker, types.ID(1), types.ID(1), nil, &fakeRaft{}, errorc) p.post([]byte("some data")) p.stop() select { case <-errorc: default: t.Fatalf("#%d: cannot receive from errorc", i) } } }
func TestPipelineExceedMaximalServing(t *testing.T) { tr := newRoundTripperBlocker() picker := mustNewURLPicker(t, []string{"http://localhost:7001"}) fs := &stats.FollowerStats{} p := newPipeline(tr, picker, types.ID(1), types.ID(1), fs, &fakeRaft{}, nil) // keep the sender busy and make the buffer full // nothing can go out as we block the sender testutil.ForceGosched() for i := 0; i < connPerPipeline+pipelineBufSize; i++ { select { case p.msgc <- raftpb.Message{}: default: t.Errorf("failed to send out message") } // force the sender to grab data testutil.ForceGosched() } // try to send a data when we are sure the buffer is full select { case p.msgc <- raftpb.Message{}: t.Errorf("unexpected message sendout") default: } // unblock the senders and force them to send out the data tr.unblock() testutil.ForceGosched() // It could send new data after previous ones succeed select { case p.msgc <- raftpb.Message{}: default: t.Errorf("failed to send out message") } p.stop() }
func TestPipelinePostBad(t *testing.T) { tests := []struct { u string code int err error }{ // RoundTrip returns error {"http://localhost:7001", 0, errors.New("blah")}, // unexpected response status code {"http://localhost:7001", http.StatusOK, nil}, {"http://localhost:7001", http.StatusCreated, nil}, } for i, tt := range tests { picker := mustNewURLPicker(t, []string{tt.u}) p := newPipeline(newRespRoundTripper(tt.code, tt.err), picker, types.ID(1), types.ID(1), nil, &fakeRaft{}, make(chan error)) err := p.post([]byte("some data")) p.stop() if err == nil { t.Errorf("#%d: err = nil, want not nil", i) } } }
// 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(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.r.transport.AddPeer(m.ID, m.PeerURLs) 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 { return true, nil } else { s.r.transport.RemovePeer(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.UpdateRaftAttributes(m.ID, m.RaftAttributes) 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.r.transport.UpdatePeer(m.ID, m.PeerURLs) log.Printf("etcdserver: update member %s %v in cluster %s", m.ID, m.PeerURLs, s.Cluster.ID()) } } return false, nil }
// TestTransportSend tests that transport can send messages using correct // underlying peer, and drop local or unknown-target messages. func TestTransportSend(t *testing.T) { ss := &stats.ServerStats{} ss.Initialize() peer1 := newFakePeer() peer2 := newFakePeer() tr := &transport{ serverStats: ss, peers: map[types.ID]Peer{types.ID(1): peer1, types.ID(2): peer2}, } wmsgsIgnored := []raftpb.Message{ // bad local message {Type: raftpb.MsgBeat}, // bad remote message {Type: raftpb.MsgProp, To: 3}, } wmsgsTo1 := []raftpb.Message{ // good message {Type: raftpb.MsgProp, To: 1}, {Type: raftpb.MsgApp, To: 1}, } wmsgsTo2 := []raftpb.Message{ // good message {Type: raftpb.MsgProp, To: 2}, {Type: raftpb.MsgApp, To: 2}, } tr.Send(wmsgsIgnored) tr.Send(wmsgsTo1) tr.Send(wmsgsTo2) if !reflect.DeepEqual(peer1.msgs, wmsgsTo1) { t.Errorf("msgs to peer 1 = %+v, want %+v", peer1.msgs, wmsgsTo1) } if !reflect.DeepEqual(peer2.msgs, wmsgsTo2) { t.Errorf("msgs to peer 2 = %+v, want %+v", peer2.msgs, wmsgsTo2) } }
// TestStreamWriterAttachBadOutgoingConn tests that streamWriter with bad // outgoingConn will close the outgoingConn and fall back to non-working status. func TestStreamWriterAttachBadOutgoingConn(t *testing.T) { sw := startStreamWriter(types.ID(1), &stats.FollowerStats{}, &fakeRaft{}) defer sw.stop() wfc := &fakeWriteFlushCloser{err: errors.New("blah")} sw.attach(&outgoingConn{t: streamTypeMessage, Writer: wfc, Flusher: wfc, Closer: wfc}) sw.msgc <- raftpb.Message{} testutil.ForceGosched() // no longer working if _, ok := sw.writec(); ok != false { t.Errorf("working = %v, want false", ok) } if wfc.closed != true { t.Errorf("failed to close the underlying connection") } }
func main() { version := flag.Int("version", 5, "4 or 5") from := flag.String("data-dir", "", "") flag.Parse() if *from == "" { log.Fatal("Must provide -data-dir flag") } var ents []raftpb.Entry var err error switch *version { case 4: ents, err = dump4(*from) case 5: ents, err = dump5(*from) default: err = errors.New("value of -version flag must be 4 or 5") } if err != nil { log.Fatalf("Failed decoding log: %v", err) } for _, e := range ents { msg := fmt.Sprintf("%2d %5d: ", e.Term, e.Index) switch e.Type { case raftpb.EntryNormal: msg = fmt.Sprintf("%s norm", msg) var r etcdserverpb.Request if err := r.Unmarshal(e.Data); err != nil { msg = fmt.Sprintf("%s ???", msg) } else { msg = fmt.Sprintf("%s %s %s %s", msg, r.Method, r.Path, r.Val) } case raftpb.EntryConfChange: msg = fmt.Sprintf("%s conf", msg) var r raftpb.ConfChange if err := r.Unmarshal(e.Data); err != nil { msg = fmt.Sprintf("%s ???", msg) } else { msg = fmt.Sprintf("%s %s %s %s", msg, r.Type, types.ID(r.NodeID), r.Context) } } fmt.Println(msg) } }
// createConfigChangeEnts creates a series of Raft entries (i.e. // EntryConfChange) to remove the set of given IDs from the cluster. The ID // `self` is _not_ removed, even if present in the set. // If `self` is not inside the given ids, it creates a Raft entry to add a // default member with the given `self`. func createConfigChangeEnts(ids []uint64, self uint64, term, index uint64) []raftpb.Entry { ents := make([]raftpb.Entry, 0) next := index + 1 found := false for _, id := range ids { if id == self { found = true continue } cc := &raftpb.ConfChange{ Type: raftpb.ConfChangeRemoveNode, NodeID: id, } e := raftpb.Entry{ Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(cc), Term: term, Index: next, } ents = append(ents, e) next++ } if !found { m := Member{ ID: types.ID(self), RaftAttributes: RaftAttributes{PeerURLs: []string{"http://localhost:7001", "http://localhost:2380"}}, } ctx, err := json.Marshal(m) if err != nil { log.Panicf("marshal member should never fail: %v", err) } cc := &raftpb.ConfChange{ Type: raftpb.ConfChangeAddNode, NodeID: self, Context: ctx, } e := raftpb.Entry{ Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(cc), Term: term, Index: next, } ents = append(ents, e) } return ents }
func NewMember(name string, peerURLs types.URLs, clusterName string) *member { m := &member{ raftAttributes: raftAttributes{PeerURLs: peerURLs.StringSlice()}, attributes: attributes{Name: name}, } var b []byte sort.Strings(m.PeerURLs) for _, p := range m.PeerURLs { b = append(b, []byte(p)...) } b = append(b, []byte(clusterName)...) hash := sha1.Sum(b) m.ID = types.ID(binary.BigEndian.Uint64(hash[:8])) return m }
// TestStreamWriterAttachOutgoingConn tests that outgoingConn can be attached // to streamWriter. After that, streamWriter can use it to send messages // continuously, and closes it when stopped. func TestStreamWriterAttachOutgoingConn(t *testing.T) { sw := startStreamWriter(types.ID(1), &stats.FollowerStats{}, &fakeRaft{}) // the expected initial state of streamWrite is not working if _, ok := sw.writec(); ok != false { t.Errorf("initial working status = %v, want false", ok) } // repeatitive tests to ensure it can use latest connection var wfc *fakeWriteFlushCloser for i := 0; i < 3; i++ { prevwfc := wfc wfc = &fakeWriteFlushCloser{} sw.attach(&outgoingConn{t: streamTypeMessage, Writer: wfc, Flusher: wfc, Closer: wfc}) testutil.ForceGosched() // previous attached connection should be closed if prevwfc != nil && prevwfc.closed != true { t.Errorf("#%d: close of previous connection = %v, want true", i, prevwfc.closed) } // starts working if _, ok := sw.writec(); ok != true { t.Errorf("#%d: working status = %v, want true", i, ok) } sw.msgc <- raftpb.Message{} testutil.ForceGosched() // still working if _, ok := sw.writec(); ok != true { t.Errorf("#%d: working status = %v, want true", i, ok) } if wfc.written == 0 { t.Errorf("#%d: failed to write to the underlying connection", i) } } sw.stop() // no longer in working status now if _, ok := sw.writec(); ok != false { t.Errorf("working status after stop = %v, want false", ok) } if wfc.closed != true { t.Errorf("failed to close the underlying connection") } }
// NewMember creates a Member without an ID and generates one based on the // name, peer URLs. This is used for bootstrapping/adding new member. func NewMember(name string, peerURLs types.URLs, clusterName string, now *time.Time) *Member { m := &Member{ RaftAttributes: RaftAttributes{PeerURLs: peerURLs.StringSlice()}, Attributes: Attributes{Name: name}, } var b []byte sort.Strings(m.PeerURLs) for _, p := range m.PeerURLs { b = append(b, []byte(p)...) } b = append(b, []byte(clusterName)...) if now != nil { b = append(b, []byte(fmt.Sprintf("%d", now.Unix()))...) } hash := sha1.Sum(b) m.ID = types.ID(binary.BigEndian.Uint64(hash[:8])) return m }