github.com/defanghe/fabric@v2.1.1+incompatible/gossip/election/election_test.go (about) 1 /* 2 Copyright IBM Corp. 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/hyperledger/fabric/gossip/util" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/mock" 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 180 func createPeer(id int, peerMap map[string]*peer, l *sync.RWMutex) *peer { 181 return createPeerWithCostumeMetrics(id, peerMap, l, func(mock.Arguments) {}) 182 } 183 184 func waitForMultipleLeadersElection(t *testing.T, peers []*peer, leadersNum int) []string { 185 end := time.Now().Add(testTimeout) 186 for time.Now().Before(end) { 187 var leaders []string 188 for _, p := range peers { 189 if p.IsLeader() { 190 leaders = append(leaders, p.id) 191 } 192 } 193 if len(leaders) >= leadersNum { 194 return leaders 195 } 196 time.Sleep(testPollInterval) 197 } 198 t.Fatal("No leader detected") 199 return nil 200 } 201 202 func waitForLeaderElection(t *testing.T, peers []*peer) []string { 203 return waitForMultipleLeadersElection(t, peers, 1) 204 } 205 206 func TestMetrics(t *testing.T) { 207 t.Parallel() 208 // Scenario: spawn a single peer and ensure it reports being a leader after some time. 209 // Then, make it relinquish its leadership and then ensure it reports not being a leader. 210 var wgLeader sync.WaitGroup 211 var wgFollower sync.WaitGroup 212 wgLeader.Add(1) 213 wgFollower.Add(1) 214 var once sync.Once 215 var once2 sync.Once 216 f := func(args mock.Arguments) { 217 if args[0] == true { 218 once.Do(func() { 219 wgLeader.Done() 220 }) 221 } else { 222 once2.Do(func() { 223 wgFollower.Done() 224 }) 225 } 226 } 227 228 p := createPeerWithCostumeMetrics(0, make(map[string]*peer), &sync.RWMutex{}, f) 229 waitForLeaderElection(t, []*peer{p}) 230 231 // Ensure we sent a leadership declaration during the time of leadership acquisition 232 wgLeader.Wait() 233 p.AssertCalled(t, "ReportMetrics", true) 234 235 p.Yield() 236 assert.False(t, p.IsLeader()) 237 238 // Ensure declaration for not being a leader was sent 239 wgFollower.Wait() 240 p.AssertCalled(t, "ReportMetrics", false) 241 242 waitForLeaderElection(t, []*peer{p}) 243 } 244 245 func TestInitPeersAtSameTime(t *testing.T) { 246 t.Parallel() 247 // Scenario: Peers are spawned at the same time 248 // expected outcome: the peer that has the lowest ID is the leader 249 peers := createPeers(0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 250 time.Sleep(testStartupGracePeriod + testLeaderElectionDuration) 251 leaders := waitForLeaderElection(t, peers) 252 isP0leader := peers[len(peers)-1].IsLeader() 253 assert.True(t, isP0leader, "p0 isn't a leader. Leaders are: %v", leaders) 254 assert.Len(t, leaders, 1, "More than 1 leader elected") 255 waitForBoolFunc(t, peers[len(peers)-1].isLeaderFromCallback, true, "Leadership callback result is wrong for ", peers[len(peers)-1].id) 256 } 257 258 func TestInitPeersStartAtIntervals(t *testing.T) { 259 t.Parallel() 260 // Scenario: Peers are spawned one by one in a slow rate 261 // expected outcome: the first peer is the leader although its ID is highest 262 peers := createPeers(testStartupGracePeriod+testLeadershipDeclarationInterval, 3, 2, 1, 0) 263 waitForLeaderElection(t, peers) 264 assert.True(t, peers[0].IsLeader()) 265 } 266 267 func TestStop(t *testing.T) { 268 t.Parallel() 269 // Scenario: peers are spawned at the same time 270 // and then are stopped. We count the number of Gossip() invocations they invoke 271 // after they stop, and it should not increase after they are stopped 272 peers := createPeers(0, 3, 2, 1, 0) 273 var gossipCounter int32 274 for i, p := range peers { 275 p.On("Gossip", mock.Anything).Run(func(args mock.Arguments) { 276 msg := args.Get(0).(Msg) 277 atomic.AddInt32(&gossipCounter, int32(1)) 278 for j := range peers { 279 if i == j { 280 continue 281 } 282 peers[j].msgChan <- msg 283 } 284 }) 285 } 286 waitForLeaderElection(t, peers) 287 for _, p := range peers { 288 p.Stop() 289 } 290 time.Sleep(testLeaderAliveThreshold) 291 gossipCounterAfterStop := atomic.LoadInt32(&gossipCounter) 292 time.Sleep(testLeaderAliveThreshold * 5) 293 assert.Equal(t, gossipCounterAfterStop, atomic.LoadInt32(&gossipCounter)) 294 } 295 296 func TestConvergence(t *testing.T) { 297 // Scenario: 2 peer group converge their views 298 // expected outcome: only 1 leader is left out of the 2 299 // and that leader is the leader with the lowest ID 300 t.Parallel() 301 peers1 := createPeers(0, 3, 2, 1, 0) 302 peers2 := createPeers(0, 4, 5, 6, 7) 303 leaders1 := waitForLeaderElection(t, peers1) 304 leaders2 := waitForLeaderElection(t, peers2) 305 assert.Len(t, leaders1, 1, "Peer group 1 was suppose to have 1 leader exactly") 306 assert.Len(t, leaders2, 1, "Peer group 2 was suppose to have 1 leader exactly") 307 combinedPeers := append(peers1, peers2...) 308 309 var allPeerIds []Peer 310 for _, p := range combinedPeers { 311 allPeerIds = append(allPeerIds, &peer{id: p.id}) 312 } 313 314 for i, p := range combinedPeers { 315 index := i 316 gossipFunc := func(args mock.Arguments) { 317 msg := args.Get(0).(Msg) 318 for j := range combinedPeers { 319 if index == j { 320 continue 321 } 322 combinedPeers[j].msgChan <- msg 323 } 324 } 325 p.On("Gossip", mock.Anything).Run(gossipFunc) 326 p.On("Peers").Return(allPeerIds) 327 } 328 329 time.Sleep(testLeaderAliveThreshold * 5) 330 finalLeaders := waitForLeaderElection(t, combinedPeers) 331 assert.Len(t, finalLeaders, 1, "Combined peer group was suppose to have 1 leader exactly") 332 assert.Equal(t, leaders1[0], finalLeaders[0], "Combined peer group has different leader than expected:") 333 334 for _, p := range combinedPeers { 335 if p.id == finalLeaders[0] { 336 waitForBoolFunc(t, p.isLeaderFromCallback, true, "Leadership callback result is wrong for ", p.id) 337 waitForBoolFunc(t, p.isCallbackInvoked, true, "Leadership callback wasn't invoked for ", p.id) 338 } else { 339 waitForBoolFunc(t, p.isLeaderFromCallback, false, "Leadership callback result is wrong for ", p.id) 340 if p.id == leaders2[0] { 341 waitForBoolFunc(t, p.isCallbackInvoked, true, "Leadership callback wasn't invoked for ", p.id) 342 } 343 } 344 } 345 } 346 347 func TestLeadershipTakeover(t *testing.T) { 348 t.Parallel() 349 // Scenario: Peers spawn one by one in descending order. 350 // After a while, the leader peer stops. 351 // expected outcome: the peer that takes over is the peer with lowest ID 352 peers := createPeers(testStartupGracePeriod+testLeadershipDeclarationInterval, 5, 4, 3, 2) 353 leaders := waitForLeaderElection(t, peers) 354 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 355 assert.Equal(t, "p5", leaders[0]) 356 peers[0].Stop() 357 time.Sleep(testLeadershipDeclarationInterval + testLeaderAliveThreshold*3) 358 leaders = waitForLeaderElection(t, peers[1:]) 359 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 360 assert.Equal(t, "p2", leaders[0]) 361 } 362 363 func TestYield(t *testing.T) { 364 t.Parallel() 365 // Scenario: Peers spawn and a leader is elected. 366 // After a while, the leader yields. 367 // (Call yield twice to ensure only one callback is called) 368 // Expected outcome: 369 // (1) A new leader is elected 370 // (2) The old leader doesn't take back its leadership 371 peers := createPeers(0, 0, 1, 2, 3, 4, 5) 372 leaders := waitForLeaderElection(t, peers) 373 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 374 assert.Equal(t, "p0", leaders[0]) 375 peers[0].Yield() 376 // Ensure the callback was called with 'false' 377 assert.True(t, peers[0].isCallbackInvoked()) 378 assert.False(t, peers[0].isLeaderFromCallback()) 379 // Clear the callback invoked flag 380 peers[0].lock.Lock() 381 peers[0].callbackInvoked = false 382 peers[0].lock.Unlock() 383 // Yield again and ensure it isn't called again 384 peers[0].Yield() 385 assert.False(t, peers[0].isCallbackInvoked()) 386 387 ensureP0isNotAleader := func() bool { 388 leaders := waitForLeaderElection(t, peers) 389 return len(leaders) == 1 && leaders[0] != "p0" 390 } 391 // A new leader is elected, and it is not p0 392 waitForBoolFunc(t, ensureP0isNotAleader, true) 393 time.Sleep(testLeaderAliveThreshold * 2) 394 // After a while, p0 doesn't restore its leadership status 395 waitForBoolFunc(t, ensureP0isNotAleader, true) 396 } 397 398 func TestYieldSinglePeer(t *testing.T) { 399 t.Parallel() 400 // Scenario: spawn a single peer and have it yield. 401 // Ensure it recovers its leadership after a while. 402 peers := createPeers(0, 0) 403 waitForLeaderElection(t, peers) 404 peers[0].Yield() 405 assert.False(t, peers[0].IsLeader()) 406 waitForLeaderElection(t, peers) 407 } 408 409 func TestYieldAllPeers(t *testing.T) { 410 t.Parallel() 411 // Scenario: spawn 2 peers and have them all yield after regaining leadership. 412 // Ensure the first peer is the leader in the end after both peers yield 413 peers := createPeers(0, 0, 1) 414 leaders := waitForLeaderElection(t, peers) 415 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 416 assert.Equal(t, "p0", leaders[0]) 417 peers[0].Yield() 418 leaders = waitForLeaderElection(t, peers) 419 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 420 assert.Equal(t, "p1", leaders[0]) 421 peers[1].Yield() 422 leaders = waitForLeaderElection(t, peers) 423 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 424 assert.Equal(t, "p0", leaders[0]) 425 } 426 427 func TestPartition(t *testing.T) { 428 t.Parallel() 429 // Scenario: peers spawn together, and then after a while a network partition occurs 430 // and no peer can communicate with another peer 431 // Expected outcome 1: each peer is a leader 432 // After this, we heal the partition to be a unified view again 433 // Expected outcome 2: p0 is the leader once again 434 peers := createPeers(0, 5, 4, 3, 2, 1, 0) 435 leaders := waitForLeaderElection(t, peers) 436 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 437 assert.Equal(t, "p0", leaders[0]) 438 waitForBoolFunc(t, peers[len(peers)-1].isLeaderFromCallback, true, "Leadership callback result is wrong for %s", peers[len(peers)-1].id) 439 440 for _, p := range peers { 441 p.On("Peers").Return([]Peer{}) 442 p.On("Gossip", mock.Anything) 443 } 444 time.Sleep(testLeadershipDeclarationInterval + testLeaderAliveThreshold*2) 445 leaders = waitForMultipleLeadersElection(t, peers, 6) 446 assert.Len(t, leaders, 6) 447 for _, p := range peers { 448 waitForBoolFunc(t, p.isLeaderFromCallback, true, "Leadership callback result is wrong for %s", p.id) 449 } 450 451 for _, p := range peers { 452 p.sharedLock.Lock() 453 p.mockedMethods = make(map[string]struct{}) 454 p.callbackInvoked = false 455 p.sharedLock.Unlock() 456 } 457 time.Sleep(testLeadershipDeclarationInterval + testLeaderAliveThreshold*2) 458 leaders = waitForLeaderElection(t, peers) 459 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 460 assert.Equal(t, "p0", leaders[0]) 461 for _, p := range peers { 462 if p.id == leaders[0] { 463 waitForBoolFunc(t, p.isLeaderFromCallback, true, "Leadership callback result is wrong for %s", p.id) 464 } else { 465 waitForBoolFunc(t, p.isLeaderFromCallback, false, "Leadership callback result is wrong for %s", p.id) 466 waitForBoolFunc(t, p.isCallbackInvoked, true, "Leadership callback wasn't invoked for %s", p.id) 467 } 468 } 469 } 470 471 func Test_peerIDString(t *testing.T) { 472 tests := []struct { 473 input peerID 474 expected string 475 }{ 476 {nil, "<nil>"}, 477 {peerID{}, ""}, 478 {peerID{0, 1, 2, 3}, "00010203"}, 479 } 480 for _, tt := range tests { 481 assert.Equal(t, tt.expected, tt.input.String()) 482 } 483 } 484 485 func waitForBoolFunc(t *testing.T, f func() bool, expectedValue bool, msgAndArgs ...interface{}) { 486 end := time.Now().Add(testTimeout) 487 for time.Now().Before(end) { 488 if f() == expectedValue { 489 return 490 } 491 time.Sleep(testPollInterval) 492 } 493 assert.Fail(t, fmt.Sprintf("Should be %t", expectedValue), msgAndArgs...) 494 }