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  }