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 }