go.etcd.io/etcd@v3.3.27+incompatible/contrib/raftexample/raftexample_test.go (about) 1 // Copyright 2015 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "fmt" 19 "os" 20 "testing" 21 22 "github.com/coreos/etcd/raft/raftpb" 23 ) 24 25 type cluster struct { 26 peers []string 27 commitC []<-chan *string 28 errorC []<-chan error 29 proposeC []chan string 30 confChangeC []chan raftpb.ConfChange 31 } 32 33 // newCluster creates a cluster of n nodes 34 func newCluster(n int) *cluster { 35 peers := make([]string, n) 36 for i := range peers { 37 peers[i] = fmt.Sprintf("http://127.0.0.1:%d", 10000+i) 38 } 39 40 clus := &cluster{ 41 peers: peers, 42 commitC: make([]<-chan *string, len(peers)), 43 errorC: make([]<-chan error, len(peers)), 44 proposeC: make([]chan string, len(peers)), 45 confChangeC: make([]chan raftpb.ConfChange, len(peers)), 46 } 47 48 for i := range clus.peers { 49 os.RemoveAll(fmt.Sprintf("raftexample-%d", i+1)) 50 os.RemoveAll(fmt.Sprintf("raftexample-%d-snap", i+1)) 51 clus.proposeC[i] = make(chan string, 1) 52 clus.confChangeC[i] = make(chan raftpb.ConfChange, 1) 53 clus.commitC[i], clus.errorC[i], _ = newRaftNode(i+1, clus.peers, false, nil, clus.proposeC[i], clus.confChangeC[i]) 54 } 55 56 return clus 57 } 58 59 // sinkReplay reads all commits in each node's local log. 60 func (clus *cluster) sinkReplay() { 61 for i := range clus.peers { 62 for s := range clus.commitC[i] { 63 if s == nil { 64 break 65 } 66 } 67 } 68 } 69 70 // Close closes all cluster nodes and returns an error if any failed. 71 func (clus *cluster) Close() (err error) { 72 for i := range clus.peers { 73 close(clus.proposeC[i]) 74 for range clus.commitC[i] { 75 // drain pending commits 76 } 77 // wait for channel to close 78 if erri := <-clus.errorC[i]; erri != nil { 79 err = erri 80 } 81 // clean intermediates 82 os.RemoveAll(fmt.Sprintf("raftexample-%d", i+1)) 83 os.RemoveAll(fmt.Sprintf("raftexample-%d-snap", i+1)) 84 } 85 return err 86 } 87 88 func (clus *cluster) closeNoErrors(t *testing.T) { 89 if err := clus.Close(); err != nil { 90 t.Fatal(err) 91 } 92 } 93 94 // TestProposeOnCommit starts three nodes and feeds commits back into the proposal 95 // channel. The intent is to ensure blocking on a proposal won't block raft progress. 96 func TestProposeOnCommit(t *testing.T) { 97 clus := newCluster(3) 98 defer clus.closeNoErrors(t) 99 100 clus.sinkReplay() 101 102 donec := make(chan struct{}) 103 for i := range clus.peers { 104 // feedback for "n" committed entries, then update donec 105 go func(pC chan<- string, cC <-chan *string, eC <-chan error) { 106 for n := 0; n < 100; n++ { 107 s, ok := <-cC 108 if !ok { 109 pC = nil 110 } 111 select { 112 case pC <- *s: 113 continue 114 case err := <-eC: 115 t.Fatalf("eC message (%v)", err) 116 } 117 } 118 donec <- struct{}{} 119 for range cC { 120 // acknowledge the commits from other nodes so 121 // raft continues to make progress 122 } 123 }(clus.proposeC[i], clus.commitC[i], clus.errorC[i]) 124 125 // one message feedback per node 126 go func(i int) { clus.proposeC[i] <- "foo" }(i) 127 } 128 129 for range clus.peers { 130 <-donec 131 } 132 } 133 134 // TestCloseProposerBeforeReplay tests closing the producer before raft starts. 135 func TestCloseProposerBeforeReplay(t *testing.T) { 136 clus := newCluster(1) 137 // close before replay so raft never starts 138 defer clus.closeNoErrors(t) 139 } 140 141 // TestCloseProposerInflight tests closing the producer while 142 // committed messages are being published to the client. 143 func TestCloseProposerInflight(t *testing.T) { 144 clus := newCluster(1) 145 defer clus.closeNoErrors(t) 146 147 clus.sinkReplay() 148 149 // some inflight ops 150 go func() { 151 clus.proposeC[0] <- "foo" 152 clus.proposeC[0] <- "bar" 153 }() 154 155 // wait for one message 156 if c, ok := <-clus.commitC[0]; *c != "foo" || !ok { 157 t.Fatalf("Commit failed") 158 } 159 }