github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/gossip/election/election_test.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package election 8 9 import ( 10 "fmt" 11 "sync" 12 "sync/atomic" 13 "testing" 14 "time" 15 16 "github.com/hechain20/hechain/gossip/util" 17 "github.com/stretchr/testify/mock" 18 "github.com/stretchr/testify/require" 19 ) 20 21 const ( 22 testTimeout = 5 * time.Second 23 testPollInterval = time.Millisecond * 300 24 testStartupGracePeriod = time.Millisecond * 500 25 testMembershipSampleInterval = time.Millisecond * 100 26 testLeaderAliveThreshold = time.Millisecond * 500 27 testLeaderElectionDuration = time.Millisecond * 500 28 testLeadershipDeclarationInterval = testLeaderAliveThreshold / 2 29 ) 30 31 func init() { 32 util.SetupTestLogging() 33 } 34 35 type msg struct { 36 sender string 37 proposal bool 38 } 39 40 func (m *msg) SenderID() peerID { 41 return peerID(m.sender) 42 } 43 44 func (m *msg) IsProposal() bool { 45 return m.proposal 46 } 47 48 func (m *msg) IsDeclaration() bool { 49 return !m.proposal 50 } 51 52 type peer struct { 53 mockedMethods map[string]struct{} 54 mock.Mock 55 id string 56 peers map[string]*peer 57 sharedLock *sync.RWMutex 58 msgChan chan Msg 59 leaderFromCallback bool 60 callbackInvoked bool 61 lock sync.RWMutex 62 LeaderElectionService 63 } 64 65 func (p *peer) On(methodName string, arguments ...interface{}) *mock.Call { 66 p.sharedLock.Lock() 67 defer p.sharedLock.Unlock() 68 p.mockedMethods[methodName] = struct{}{} 69 return p.Mock.On(methodName, arguments...) 70 } 71 72 func (p *peer) ID() peerID { 73 return peerID(p.id) 74 } 75 76 func (p *peer) Gossip(m Msg) { 77 p.sharedLock.RLock() 78 defer p.sharedLock.RUnlock() 79 80 if _, isMocked := p.mockedMethods["Gossip"]; isMocked { 81 p.Called(m) 82 return 83 } 84 85 for _, peer := range p.peers { 86 if peer.id == p.id { 87 continue 88 } 89 peer.msgChan <- m.(*msg) 90 } 91 } 92 93 func (p *peer) Accept() <-chan Msg { 94 p.sharedLock.RLock() 95 defer p.sharedLock.RUnlock() 96 97 if _, isMocked := p.mockedMethods["Accept"]; isMocked { 98 args := p.Called() 99 return args.Get(0).(<-chan Msg) 100 } 101 return (<-chan Msg)(p.msgChan) 102 } 103 104 func (p *peer) CreateMessage(isDeclaration bool) Msg { 105 return &msg{proposal: !isDeclaration, sender: p.id} 106 } 107 108 func (p *peer) Peers() []Peer { 109 p.sharedLock.RLock() 110 defer p.sharedLock.RUnlock() 111 112 if _, isMocked := p.mockedMethods["Peers"]; isMocked { 113 args := p.Called() 114 return args.Get(0).([]Peer) 115 } 116 117 var peers []Peer 118 for id := range p.peers { 119 peers = append(peers, &peer{id: id}) 120 } 121 return peers 122 } 123 124 func (p *peer) ReportMetrics(isLeader bool) { 125 p.Mock.Called(isLeader) 126 } 127 128 func (p *peer) leaderCallback(isLeader bool) { 129 p.lock.Lock() 130 defer p.lock.Unlock() 131 p.leaderFromCallback = isLeader 132 p.callbackInvoked = true 133 } 134 135 func (p *peer) isLeaderFromCallback() bool { 136 p.lock.RLock() 137 defer p.lock.RUnlock() 138 return p.leaderFromCallback 139 } 140 141 func (p *peer) isCallbackInvoked() bool { 142 p.lock.RLock() 143 defer p.lock.RUnlock() 144 return p.callbackInvoked 145 } 146 147 func createPeers(spawnInterval time.Duration, ids ...int) []*peer { 148 peers := make([]*peer, len(ids)) 149 peerMap := make(map[string]*peer) 150 l := &sync.RWMutex{} 151 for i, id := range ids { 152 p := createPeer(id, peerMap, l) 153 if spawnInterval != 0 { 154 time.Sleep(spawnInterval) 155 } 156 peers[i] = p 157 } 158 return peers 159 } 160 161 func createPeerWithCostumeMetrics(id int, peerMap map[string]*peer, l *sync.RWMutex, f func(mock.Arguments)) *peer { 162 idStr := fmt.Sprintf("p%d", id) 163 c := make(chan Msg, 100) 164 p := &peer{id: idStr, peers: peerMap, sharedLock: l, msgChan: c, mockedMethods: make(map[string]struct{}), leaderFromCallback: false, callbackInvoked: false} 165 p.On("ReportMetrics", mock.Anything).Run(f) 166 config := ElectionConfig{ 167 StartupGracePeriod: testStartupGracePeriod, 168 MembershipSampleInterval: testMembershipSampleInterval, 169 LeaderAliveThreshold: testLeaderAliveThreshold, 170 LeaderElectionDuration: testLeaderElectionDuration, 171 } 172 p.LeaderElectionService = NewLeaderElectionService(p, idStr, p.leaderCallback, config) 173 l.Lock() 174 peerMap[idStr] = p 175 l.Unlock() 176 return p 177 } 178 179 func createPeer(id int, peerMap map[string]*peer, l *sync.RWMutex) *peer { 180 return createPeerWithCostumeMetrics(id, peerMap, l, func(mock.Arguments) {}) 181 } 182 183 func waitForMultipleLeadersElection(t *testing.T, peers []*peer, leadersNum int) []string { 184 end := time.Now().Add(testTimeout) 185 for time.Now().Before(end) { 186 var leaders []string 187 for _, p := range peers { 188 if p.IsLeader() { 189 leaders = append(leaders, p.id) 190 } 191 } 192 if len(leaders) >= leadersNum { 193 return leaders 194 } 195 time.Sleep(testPollInterval) 196 } 197 t.Fatal("No leader detected") 198 return nil 199 } 200 201 func waitForLeaderElection(t *testing.T, peers []*peer) []string { 202 return waitForMultipleLeadersElection(t, peers, 1) 203 } 204 205 func TestMetrics(t *testing.T) { 206 // Scenario: spawn a single peer and ensure it reports being a leader after some time. 207 // Then, make it relinquish its leadership and then ensure it reports not being a leader. 208 var wgLeader sync.WaitGroup 209 var wgFollower sync.WaitGroup 210 wgLeader.Add(1) 211 wgFollower.Add(1) 212 var once sync.Once 213 var once2 sync.Once 214 f := func(args mock.Arguments) { 215 if args[0] == true { 216 once.Do(func() { 217 wgLeader.Done() 218 }) 219 } else { 220 once2.Do(func() { 221 wgFollower.Done() 222 }) 223 } 224 } 225 226 p := createPeerWithCostumeMetrics(0, make(map[string]*peer), &sync.RWMutex{}, f) 227 waitForLeaderElection(t, []*peer{p}) 228 229 // Ensure we sent a leadership declaration during the time of leadership acquisition 230 wgLeader.Wait() 231 p.AssertCalled(t, "ReportMetrics", true) 232 233 p.Yield() 234 require.False(t, p.IsLeader()) 235 236 // Ensure declaration for not being a leader was sent 237 wgFollower.Wait() 238 p.AssertCalled(t, "ReportMetrics", false) 239 240 waitForLeaderElection(t, []*peer{p}) 241 } 242 243 func TestInitPeersAtSameTime(t *testing.T) { 244 // Scenario: Peers are spawned at the same time 245 // expected outcome: the peer that has the lowest ID is the leader 246 peers := createPeers(0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 247 time.Sleep(testStartupGracePeriod + testLeaderElectionDuration) 248 leaders := waitForLeaderElection(t, peers) 249 isP0leader := peers[len(peers)-1].IsLeader() 250 require.True(t, isP0leader, "p0 isn't a leader. Leaders are: %v", leaders) 251 require.Len(t, leaders, 1, "More than 1 leader elected") 252 waitForBoolFunc(t, peers[len(peers)-1].isLeaderFromCallback, true, "Leadership callback result is wrong for ", peers[len(peers)-1].id) 253 } 254 255 func TestInitPeersStartAtIntervals(t *testing.T) { 256 // Scenario: Peers are spawned one by one in a slow rate 257 // expected outcome: the first peer is the leader although its ID is highest 258 peers := createPeers(testStartupGracePeriod+testLeadershipDeclarationInterval, 3, 2, 1, 0) 259 waitForLeaderElection(t, peers) 260 require.True(t, peers[0].IsLeader()) 261 } 262 263 func TestStop(t *testing.T) { 264 // Scenario: peers are spawned at the same time 265 // and then are stopped. We count the number of Gossip() invocations they invoke 266 // after they stop, and it should not increase after they are stopped 267 peers := createPeers(0, 3, 2, 1, 0) 268 var gossipCounter int32 269 for i, p := range peers { 270 p.On("Gossip", mock.Anything).Run(func(args mock.Arguments) { 271 msg := args.Get(0).(Msg) 272 atomic.AddInt32(&gossipCounter, int32(1)) 273 for j := range peers { 274 if i == j { 275 continue 276 } 277 peers[j].msgChan <- msg 278 } 279 }) 280 } 281 waitForLeaderElection(t, peers) 282 for _, p := range peers { 283 p.Stop() 284 } 285 time.Sleep(testLeaderAliveThreshold) 286 gossipCounterAfterStop := atomic.LoadInt32(&gossipCounter) 287 time.Sleep(testLeaderAliveThreshold * 5) 288 require.Equal(t, gossipCounterAfterStop, atomic.LoadInt32(&gossipCounter)) 289 } 290 291 func TestConvergence(t *testing.T) { 292 // Scenario: 2 peer group converge their views 293 // expected outcome: only 1 leader is left out of the 2 294 // and that leader is the leader with the lowest ID 295 peers1 := createPeers(0, 3, 2, 1, 0) 296 peers2 := createPeers(0, 4, 5, 6, 7) 297 leaders1 := waitForLeaderElection(t, peers1) 298 leaders2 := waitForLeaderElection(t, peers2) 299 require.Len(t, leaders1, 1, "Peer group 1 was suppose to have 1 leader exactly") 300 require.Len(t, leaders2, 1, "Peer group 2 was suppose to have 1 leader exactly") 301 combinedPeers := append(peers1, peers2...) 302 303 var allPeerIds []Peer 304 for _, p := range combinedPeers { 305 allPeerIds = append(allPeerIds, &peer{id: p.id}) 306 } 307 308 for i, p := range combinedPeers { 309 index := i 310 gossipFunc := func(args mock.Arguments) { 311 msg := args.Get(0).(Msg) 312 for j := range combinedPeers { 313 if index == j { 314 continue 315 } 316 combinedPeers[j].msgChan <- msg 317 } 318 } 319 p.On("Gossip", mock.Anything).Run(gossipFunc) 320 p.On("Peers").Return(allPeerIds) 321 } 322 323 time.Sleep(testLeaderAliveThreshold * 5) 324 finalLeaders := waitForLeaderElection(t, combinedPeers) 325 require.Len(t, finalLeaders, 1, "Combined peer group was suppose to have 1 leader exactly") 326 require.Equal(t, leaders1[0], finalLeaders[0], "Combined peer group has different leader than expected:") 327 328 for _, p := range combinedPeers { 329 if p.id == finalLeaders[0] { 330 waitForBoolFunc(t, p.isLeaderFromCallback, true, "Leadership callback result is wrong for ", p.id) 331 waitForBoolFunc(t, p.isCallbackInvoked, true, "Leadership callback wasn't invoked for ", p.id) 332 } else { 333 waitForBoolFunc(t, p.isLeaderFromCallback, false, "Leadership callback result is wrong for ", p.id) 334 if p.id == leaders2[0] { 335 waitForBoolFunc(t, p.isCallbackInvoked, true, "Leadership callback wasn't invoked for ", p.id) 336 } 337 } 338 } 339 } 340 341 func TestLeadershipTakeover(t *testing.T) { 342 // Scenario: Peers spawn one by one in descending order. 343 // After a while, the leader peer stops. 344 // expected outcome: the peer that takes over is the peer with lowest ID 345 peers := createPeers(testStartupGracePeriod+testLeadershipDeclarationInterval, 5, 4, 3, 2) 346 leaders := waitForLeaderElection(t, peers) 347 require.Len(t, leaders, 1, "Only 1 leader should have been elected") 348 require.Equal(t, "p5", leaders[0]) 349 peers[0].Stop() 350 time.Sleep(testLeadershipDeclarationInterval + testLeaderAliveThreshold*3) 351 leaders = waitForLeaderElection(t, peers[1:]) 352 require.Len(t, leaders, 1, "Only 1 leader should have been elected") 353 require.Equal(t, "p2", leaders[0]) 354 } 355 356 func TestYield(t *testing.T) { 357 // Scenario: Peers spawn and a leader is elected. 358 // After a while, the leader yields. 359 // (Call yield twice to ensure only one callback is called) 360 // Expected outcome: 361 // (1) A new leader is elected 362 // (2) The old leader doesn't take back its leadership 363 peers := createPeers(0, 0, 1, 2, 3, 4, 5) 364 leaders := waitForLeaderElection(t, peers) 365 require.Len(t, leaders, 1, "Only 1 leader should have been elected") 366 require.Equal(t, "p0", leaders[0]) 367 peers[0].Yield() 368 // Ensure the callback was called with 'false' 369 require.True(t, peers[0].isCallbackInvoked()) 370 require.False(t, peers[0].isLeaderFromCallback()) 371 // Clear the callback invoked flag 372 peers[0].lock.Lock() 373 peers[0].callbackInvoked = false 374 peers[0].lock.Unlock() 375 // Yield again and ensure it isn't called again 376 peers[0].Yield() 377 require.False(t, peers[0].isCallbackInvoked()) 378 379 ensureP0isNotAleader := func() bool { 380 leaders := waitForLeaderElection(t, peers) 381 return len(leaders) == 1 && leaders[0] != "p0" 382 } 383 // A new leader is elected, and it is not p0 384 waitForBoolFunc(t, ensureP0isNotAleader, true) 385 time.Sleep(testLeaderAliveThreshold * 2) 386 // After a while, p0 doesn't restore its leadership status 387 waitForBoolFunc(t, ensureP0isNotAleader, true) 388 } 389 390 func TestYieldSinglePeer(t *testing.T) { 391 // Scenario: spawn a single peer and have it yield. 392 // Ensure it recovers its leadership after a while. 393 peers := createPeers(0, 0) 394 waitForLeaderElection(t, peers) 395 peers[0].Yield() 396 require.False(t, peers[0].IsLeader()) 397 waitForLeaderElection(t, peers) 398 } 399 400 func TestYieldAllPeers(t *testing.T) { 401 // Scenario: spawn 2 peers and have them all yield after regaining leadership. 402 // Ensure the first peer is the leader in the end after both peers yield 403 peers := createPeers(0, 0, 1) 404 leaders := waitForLeaderElection(t, peers) 405 require.Len(t, leaders, 1, "Only 1 leader should have been elected") 406 require.Equal(t, "p0", leaders[0]) 407 peers[0].Yield() 408 leaders = waitForLeaderElection(t, peers) 409 require.Len(t, leaders, 1, "Only 1 leader should have been elected") 410 require.Equal(t, "p1", leaders[0]) 411 peers[1].Yield() 412 leaders = waitForLeaderElection(t, peers) 413 require.Len(t, leaders, 1, "Only 1 leader should have been elected") 414 require.Equal(t, "p0", leaders[0]) 415 } 416 417 func TestPartition(t *testing.T) { 418 // Scenario: peers spawn together, and then after a while a network partition occurs 419 // and no peer can communicate with another peer 420 // Expected outcome 1: each peer is a leader 421 // After this, we heal the partition to be a unified view again 422 // Expected outcome 2: p0 is the leader once again 423 peers := createPeers(0, 5, 4, 3, 2, 1, 0) 424 leaders := waitForLeaderElection(t, peers) 425 require.Len(t, leaders, 1, "Only 1 leader should have been elected") 426 require.Equal(t, "p0", leaders[0]) 427 waitForBoolFunc(t, peers[len(peers)-1].isLeaderFromCallback, true, "Leadership callback result is wrong for %s", peers[len(peers)-1].id) 428 429 for _, p := range peers { 430 p.On("Peers").Return([]Peer{}) 431 p.On("Gossip", mock.Anything) 432 } 433 time.Sleep(testLeadershipDeclarationInterval + testLeaderAliveThreshold*2) 434 leaders = waitForMultipleLeadersElection(t, peers, 6) 435 require.Len(t, leaders, 6) 436 for _, p := range peers { 437 waitForBoolFunc(t, p.isLeaderFromCallback, true, "Leadership callback result is wrong for %s", p.id) 438 } 439 440 for _, p := range peers { 441 p.sharedLock.Lock() 442 p.mockedMethods = make(map[string]struct{}) 443 p.callbackInvoked = false 444 p.sharedLock.Unlock() 445 } 446 time.Sleep(testLeadershipDeclarationInterval + testLeaderAliveThreshold*2) 447 leaders = waitForLeaderElection(t, peers) 448 require.Len(t, leaders, 1, "Only 1 leader should have been elected") 449 require.Equal(t, "p0", leaders[0]) 450 for _, p := range peers { 451 if p.id == leaders[0] { 452 waitForBoolFunc(t, p.isLeaderFromCallback, true, "Leadership callback result is wrong for %s", p.id) 453 } else { 454 waitForBoolFunc(t, p.isLeaderFromCallback, false, "Leadership callback result is wrong for %s", p.id) 455 waitForBoolFunc(t, p.isCallbackInvoked, true, "Leadership callback wasn't invoked for %s", p.id) 456 } 457 } 458 } 459 460 func Test_peerIDString(t *testing.T) { 461 tests := []struct { 462 input peerID 463 expected string 464 }{ 465 {nil, "<nil>"}, 466 {peerID{}, ""}, 467 {peerID{0, 1, 2, 3}, "00010203"}, 468 } 469 for _, tt := range tests { 470 require.Equal(t, tt.expected, tt.input.String()) 471 } 472 } 473 474 func waitForBoolFunc(t *testing.T, f func() bool, expectedValue bool, msgAndArgs ...interface{}) { 475 end := time.Now().Add(testTimeout) 476 for time.Now().Before(end) { 477 if f() == expectedValue { 478 return 479 } 480 time.Sleep(testPollInterval) 481 } 482 require.Fail(t, fmt.Sprintf("Should be %t", expectedValue), msgAndArgs...) 483 }