github.com/ergo-services/ergo@v1.999.224/tests/raft_test.go (about)

     1  //go:build !manual
     2  
     3  package tests
     4  
     5  import (
     6  	"fmt"
     7  	"math/rand"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/ergo-services/ergo"
    12  	"github.com/ergo-services/ergo/etf"
    13  	"github.com/ergo-services/ergo/gen"
    14  	"github.com/ergo-services/ergo/node"
    15  )
    16  
    17  type testCaseRaft struct {
    18  	n     int
    19  	state gen.RaftQuorumState
    20  	name  string
    21  }
    22  
    23  var (
    24  	ql    string = "quorum of %2d members with 1 leader: "
    25  	qlf   string = "quorum of %2d members with 1 leader + %d follower(s): "
    26  	cases        = []testCaseRaft{
    27  		testCaseRaft{n: 2, name: "no quorum, no leader: "},
    28  		testCaseRaft{n: 3, name: ql, state: gen.RaftQuorumState3},
    29  		testCaseRaft{n: 4, name: qlf, state: gen.RaftQuorumState3},
    30  		testCaseRaft{n: 5, name: ql, state: gen.RaftQuorumState5},
    31  		testCaseRaft{n: 6, name: qlf, state: gen.RaftQuorumState5},
    32  		testCaseRaft{n: 7, name: ql, state: gen.RaftQuorumState7},
    33  
    34  		//
    35  		// cases below are work well, but quorum building takes too long some time.
    36  		//testCaseRaft{n: 8, name: qlf, state: gen.RaftQuorumState7},
    37  		//testCaseRaft{n: 9, name: ql, state: gen.RaftQuorumState9},
    38  		//testCaseRaft{n: 10, name: qlf, state: gen.RaftQuorumState9},
    39  		//testCaseRaft{n: 11, name: ql, state: gen.RaftQuorumState11},
    40  		//testCaseRaft{n: 12, name: qlf, state: gen.RaftQuorumState11},
    41  		//testCaseRaft{n: 15, name: qlf, state: gen.RaftQuorumState11},
    42  		//testCaseRaft{n: 25, name: qlf, state: gen.RaftQuorumState11},
    43  	}
    44  
    45  	data = map[string]dataValueSerial{
    46  		"key0": dataValueSerial{"value0", 0},
    47  		"key1": dataValueSerial{"value1", 1},
    48  		"key2": dataValueSerial{"value2", 2},
    49  		"key3": dataValueSerial{"value3", 3},
    50  		"key4": dataValueSerial{"value4", 4},
    51  		"key5": dataValueSerial{"value5", 5},
    52  		"key6": dataValueSerial{"value6", 6},
    53  		"key7": dataValueSerial{"value7", 7},
    54  		"key8": dataValueSerial{"value8", 8},
    55  		"key9": dataValueSerial{"value9", 9},
    56  	}
    57  	keySerials = []string{
    58  		"key0",
    59  		"key1",
    60  		"key2",
    61  		"key3",
    62  		"key4",
    63  		"key5",
    64  		"key6",
    65  		"key7",
    66  		"key8",
    67  		"key9",
    68  	}
    69  )
    70  
    71  type dataValueSerial struct {
    72  	value  string
    73  	serial uint64
    74  }
    75  
    76  type testRaft struct {
    77  	gen.Raft
    78  	n      int
    79  	qstate gen.RaftQuorumState
    80  	p      chan *gen.RaftProcess // Init
    81  	q      chan *gen.RaftQuorum  // HandleQuorum
    82  	l      chan *gen.RaftLeader  // HandleLeader
    83  	a      chan raftResult       // HandleAppend
    84  	s      chan raftResult       // HandleSerial
    85  }
    86  
    87  type raftResult struct {
    88  	process *gen.RaftProcess
    89  	ref     etf.Ref
    90  	serial  uint64
    91  	key     string
    92  	value   etf.Term
    93  }
    94  
    95  type raftArgs struct {
    96  	peers  []gen.ProcessID
    97  	serial uint64
    98  }
    99  type raftState struct {
   100  	data    map[string]dataValueSerial
   101  	serials []string
   102  }
   103  
   104  func (tr *testRaft) InitRaft(process *gen.RaftProcess, args ...etf.Term) (gen.RaftOptions, error) {
   105  	var options gen.RaftOptions
   106  	ra := args[0].(raftArgs)
   107  	options.Peers = ra.peers
   108  	options.Serial = ra.serial
   109  
   110  	state := &raftState{
   111  		data: make(map[string]dataValueSerial),
   112  	}
   113  	for i := 0; i < int(ra.serial)+1; i++ {
   114  		key := keySerials[i]
   115  		state.data[key] = data[key]
   116  		state.serials = append(state.serials, key)
   117  	}
   118  	process.State = state
   119  	tr.p <- process
   120  
   121  	return options, gen.RaftStatusOK
   122  }
   123  
   124  func (tr *testRaft) HandleQuorum(process *gen.RaftProcess, quorum *gen.RaftQuorum) gen.RaftStatus {
   125  	//fmt.Println(process.Self(), "QQQ", quorum)
   126  	if quorum != nil {
   127  		tr.q <- quorum
   128  	}
   129  	return gen.RaftStatusOK
   130  }
   131  
   132  func (tr *testRaft) HandleLeader(process *gen.RaftProcess, leader *gen.RaftLeader) gen.RaftStatus {
   133  	//fmt.Println(process.Self(), "LLL", leader)
   134  	// leader elected within a quorum
   135  	q := process.Quorum()
   136  	if q == nil {
   137  		return gen.RaftStatusOK
   138  	}
   139  	if leader != nil && q.State == tr.qstate {
   140  		tr.l <- leader
   141  	}
   142  
   143  	return gen.RaftStatusOK
   144  }
   145  
   146  func (tr *testRaft) HandleAppend(process *gen.RaftProcess, ref etf.Ref, serial uint64, key string, value etf.Term) gen.RaftStatus {
   147  	//fmt.Println(process.Self(), "HANDLE APPEND member:", process.Quorum().Member, "append", ref, serial, key, value)
   148  
   149  	result := raftResult{
   150  		process: process,
   151  		ref:     ref,
   152  		serial:  serial,
   153  		key:     key,
   154  		value:   value,
   155  	}
   156  	tr.a <- result
   157  	return gen.RaftStatusOK
   158  }
   159  
   160  func (tr *testRaft) HandleGet(process *gen.RaftProcess, serial uint64) (string, etf.Term, gen.RaftStatus) {
   161  	var key string
   162  	//fmt.Println(process.Self(), "HANDLE GET member:", process.Quorum().Member, "get", serial)
   163  
   164  	state := process.State.(*raftState)
   165  	if len(state.serials) < int(serial) {
   166  		//	fmt.Println(process.Self(), "NO DATA for", serial)
   167  		return key, nil, gen.RaftStatusOK
   168  	}
   169  	key = state.serials[int(serial)]
   170  	data := state.data[key]
   171  	return key, data.value, gen.RaftStatusOK
   172  }
   173  
   174  func (tr *testRaft) HandleSerial(process *gen.RaftProcess, ref etf.Ref, serial uint64, key string, value etf.Term) gen.RaftStatus {
   175  	//fmt.Println(process.Self(), "HANDLE SERIAL member:", process.Quorum().Member, "append", ref, serial, key, value)
   176  	result := raftResult{
   177  		process: process,
   178  		ref:     ref,
   179  		serial:  serial,
   180  		key:     key,
   181  		value:   value,
   182  	}
   183  	s := process.Serial()
   184  	if s != serial {
   185  		fmt.Println(process.Self(), "ERROR: disordered serial request")
   186  		tr.s <- raftResult{}
   187  		return gen.RaftStatusOK
   188  	}
   189  	state := process.State.(*raftState)
   190  	state.serials = append(state.serials, key)
   191  	state.data[key] = dataValueSerial{
   192  		value:  value.(string),
   193  		serial: serial,
   194  	}
   195  	tr.s <- result
   196  	return gen.RaftStatusOK
   197  }
   198  func (tr *testRaft) HandleCancel(process *gen.RaftProcess, ref etf.Ref, reason string) gen.RaftStatus {
   199  	return gen.RaftStatusOK
   200  }
   201  
   202  func (tr *testRaft) HandleRaftInfo(process *gen.RaftProcess, message etf.Term) gen.ServerStatus {
   203  	return gen.ServerStatusOK
   204  }
   205  
   206  func TestRaftLeader(t *testing.T) {
   207  	fmt.Printf("\n=== Test GenRaft - build quorum, leader election\n")
   208  	for _, c := range cases {
   209  		fmt.Printf("    cluster with %2d distributed raft processes. ", c.n)
   210  		if c.n == 2 {
   211  			fmt.Printf(c.name)
   212  		} else {
   213  			f := c.n - int(c.state)
   214  			if f == 0 {
   215  				fmt.Printf(c.name, c.state)
   216  			} else {
   217  				fmt.Printf(c.name, c.state, c.n-int(c.state))
   218  			}
   219  		}
   220  
   221  		server := &testRaft{
   222  			n:      c.n,
   223  			qstate: c.state,
   224  		}
   225  		// start distributed raft processes and wait until
   226  		// they build a quorum and elect their leader
   227  		nodes, rafts, leaderSerial := startRaftCluster("append", server)
   228  		ok := true
   229  		if c.n > 2 {
   230  			ok = false
   231  			for _, raft := range rafts {
   232  				q := raft.Quorum()
   233  				if q == nil {
   234  					continue
   235  				}
   236  				if q.Member == false {
   237  					continue
   238  				}
   239  
   240  				l := raft.Leader()
   241  				if l == nil {
   242  					continue
   243  				}
   244  				if l.Serial != leaderSerial {
   245  					t.Fatal("wrong leader serial")
   246  				}
   247  				ok = true
   248  				break
   249  			}
   250  		}
   251  		if ok == false {
   252  			t.Fatal("no quorum or leader found")
   253  		}
   254  		fmt.Println("OK")
   255  		// stop cluster
   256  		for _, node := range nodes {
   257  			node.Stop()
   258  		}
   259  	}
   260  
   261  }
   262  
   263  func startRaftCluster(name string, server *testRaft) ([]node.Node, []*gen.RaftProcess, uint64) {
   264  	nodes := make([]node.Node, server.n)
   265  	for i := range nodes {
   266  		name := fmt.Sprintf("nodeGenRaft-%s-cluster-%02dNode%02d@localhost", name, server.n, i)
   267  		node, err := ergo.StartNode(name, "cookies", node.Options{})
   268  		if err != nil {
   269  			panic(err)
   270  		}
   271  		nodes[i] = node
   272  	}
   273  
   274  	processes := make([]gen.Process, server.n)
   275  	server.p = make(chan *gen.RaftProcess, 1000)
   276  	server.q = make(chan *gen.RaftQuorum, 1000)
   277  	server.l = make(chan *gen.RaftLeader, 1000)
   278  	server.a = make(chan raftResult, 1000)
   279  	server.s = make(chan raftResult, 1000)
   280  	leaderSerial := uint64(0)
   281  	var peer gen.ProcessID
   282  	for i := range processes {
   283  		name := fmt.Sprintf("raft%02d", i+1)
   284  		args := raftArgs{
   285  			serial: uint64(rand.Intn(9)),
   286  		}
   287  		if args.serial > leaderSerial {
   288  			leaderSerial = args.serial
   289  		}
   290  		if i > 0 {
   291  			peer.Node = nodes[i-1].Name()
   292  			peer.Name = processes[i-1].Name()
   293  			args.peers = []gen.ProcessID{peer}
   294  		}
   295  		p, err := nodes[i].Spawn(name, gen.ProcessOptions{}, server, args)
   296  		if err != nil {
   297  			panic(err)
   298  		}
   299  		processes[i] = p
   300  	}
   301  
   302  	rafts := []*gen.RaftProcess{}
   303  	// how many results with 'leader' should be awaiting
   304  	resultsL := 0
   305  	// how many results with 'quorum' should be awaiting
   306  	resultsQ := 0
   307  	for {
   308  		select {
   309  		case p := <-server.p:
   310  			rafts = append(rafts, p)
   311  
   312  			if len(rafts) < server.n {
   313  				continue
   314  			}
   315  
   316  			if server.n == 2 {
   317  				// no leader, no quorum
   318  				return nodes, rafts, leaderSerial
   319  			}
   320  			continue
   321  
   322  		case q := <-server.q:
   323  
   324  			if q.State != server.qstate {
   325  				continue
   326  			}
   327  
   328  			resultsQ++
   329  			if resultsQ < int(server.qstate) {
   330  				continue
   331  			}
   332  
   333  			if resultsL < int(server.qstate) {
   334  				continue
   335  			}
   336  			// all quorum members are received leader election result
   337  			return nodes, rafts, leaderSerial
   338  
   339  		case l := <-server.l:
   340  			if l.State != server.qstate {
   341  				continue
   342  			}
   343  
   344  			resultsL++
   345  			if resultsL < int(server.qstate) {
   346  				continue
   347  			}
   348  
   349  			if resultsQ < server.n {
   350  				continue
   351  			}
   352  
   353  			// all quorum members are received leader election result
   354  			return nodes, rafts, leaderSerial
   355  
   356  		case <-time.After(30 * time.Second):
   357  			panic("can't start raft cluster")
   358  		}
   359  	}
   360  }