github.com/adnan-c/fabric_e2e_couchdb@v0.6.1-preview.0.20170228180935-21ce6b23cf91/gossip/election/election_test.go (about) 1 /* 2 Copyright IBM Corp. 2017 All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package election 18 19 import ( 20 "fmt" 21 "sync" 22 "sync/atomic" 23 "testing" 24 "time" 25 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/mock" 28 ) 29 30 const ( 31 testTimeout = 5 * time.Second 32 testPollInterval = time.Millisecond * 300 33 ) 34 35 func init() { 36 startupGracePeriod = time.Millisecond * 500 37 membershipSampleInterval = time.Millisecond * 100 38 leaderAliveThreshold = time.Millisecond * 500 39 leadershipDeclarationInterval = leaderAliveThreshold / 2 40 leaderElectionDuration = time.Millisecond * 500 41 } 42 43 type msg struct { 44 sender string 45 proposal bool 46 } 47 48 func (m *msg) SenderID() string { 49 return m.sender 50 } 51 52 func (m *msg) IsProposal() bool { 53 return m.proposal 54 } 55 56 func (m *msg) IsDeclaration() bool { 57 return !m.proposal 58 } 59 60 type peer struct { 61 mockedMethods map[string]struct{} 62 mock.Mock 63 id string 64 peers map[string]*peer 65 sharedLock *sync.RWMutex 66 msgChan chan Msg 67 isLeaderFromCallback bool 68 callbackInvoked bool 69 LeaderElectionService 70 } 71 72 func (p *peer) On(methodName string, arguments ...interface{}) *mock.Call { 73 p.sharedLock.Lock() 74 defer p.sharedLock.Unlock() 75 p.mockedMethods[methodName] = struct{}{} 76 return p.Mock.On(methodName, arguments...) 77 } 78 79 func (p *peer) ID() string { 80 return p.id 81 } 82 83 func (p *peer) Gossip(m Msg) { 84 p.sharedLock.RLock() 85 defer p.sharedLock.RUnlock() 86 87 if _, isMocked := p.mockedMethods["Gossip"]; isMocked { 88 p.Called(m) 89 return 90 } 91 92 for _, peer := range p.peers { 93 if peer.id == p.id { 94 continue 95 } 96 peer.msgChan <- m.(*msg) 97 } 98 } 99 100 func (p *peer) Accept() <-chan Msg { 101 p.sharedLock.RLock() 102 defer p.sharedLock.RUnlock() 103 104 if _, isMocked := p.mockedMethods["Accept"]; isMocked { 105 args := p.Called() 106 return args.Get(0).(<-chan Msg) 107 } 108 return (<-chan Msg)(p.msgChan) 109 } 110 111 func (p *peer) CreateMessage(isDeclaration bool) Msg { 112 return &msg{proposal: !isDeclaration, sender: p.id} 113 } 114 115 func (p *peer) Peers() []Peer { 116 p.sharedLock.RLock() 117 defer p.sharedLock.RUnlock() 118 119 if _, isMocked := p.mockedMethods["Peers"]; isMocked { 120 args := p.Called() 121 return args.Get(0).([]Peer) 122 } 123 124 var peers []Peer 125 for id := range p.peers { 126 peers = append(peers, &peer{id: id}) 127 } 128 return peers 129 } 130 131 func (p *peer) leaderCallback(isLeader bool) { 132 p.isLeaderFromCallback = isLeader 133 p.callbackInvoked = true 134 } 135 136 func createPeers(spawnInterval time.Duration, ids ...int) []*peer { 137 peers := make([]*peer, len(ids)) 138 peerMap := make(map[string]*peer) 139 l := &sync.RWMutex{} 140 for i, id := range ids { 141 p := createPeer(id, peerMap, l) 142 if spawnInterval != 0 { 143 time.Sleep(spawnInterval) 144 } 145 peers[i] = p 146 } 147 return peers 148 } 149 150 func createPeer(id int, peerMap map[string]*peer, l *sync.RWMutex) *peer { 151 idStr := fmt.Sprintf("p%d", id) 152 c := make(chan Msg, 100) 153 p := &peer{id: idStr, peers: peerMap, sharedLock: l, msgChan: c, mockedMethods: make(map[string]struct{}), isLeaderFromCallback: false, callbackInvoked: false} 154 p.LeaderElectionService = NewLeaderElectionService(p, idStr, p.leaderCallback) 155 l.Lock() 156 peerMap[idStr] = p 157 l.Unlock() 158 return p 159 160 } 161 162 func waitForMultipleLeadersElection(t *testing.T, peers []*peer, leadersNum int) []string { 163 end := time.Now().Add(testTimeout) 164 for time.Now().Before(end) { 165 var leaders []string 166 for _, p := range peers { 167 if p.IsLeader() { 168 leaders = append(leaders, p.id) 169 } 170 } 171 if len(leaders) >= leadersNum { 172 return leaders 173 } 174 time.Sleep(testPollInterval) 175 } 176 t.Fatal("No leader detected") 177 return nil 178 } 179 180 func waitForLeaderElection(t *testing.T, peers []*peer) []string { 181 return waitForMultipleLeadersElection(t, peers, 1) 182 } 183 184 func TestInitPeersAtSameTime(t *testing.T) { 185 t.Parallel() 186 // Scenario: Peers are spawned at the same time 187 // expected outcome: the peer that has the lowest ID is the leader 188 peers := createPeers(0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 189 time.Sleep(startupGracePeriod + leaderElectionDuration) 190 leaders := waitForLeaderElection(t, peers) 191 isP0leader := peers[len(peers)-1].IsLeader() 192 assert.True(t, isP0leader, "p0 isn't a leader. Leaders are: %v", leaders) 193 assert.True(t, peers[len(peers)-1].isLeaderFromCallback, "p0 didn't got leaderhip change callback invoked") 194 assert.Len(t, leaders, 1, "More than 1 leader elected") 195 } 196 197 func TestInitPeersStartAtIntervals(t *testing.T) { 198 t.Parallel() 199 // Scenario: Peers are spawned one by one in a slow rate 200 // expected outcome: the first peer is the leader although its ID is lowest 201 peers := createPeers(startupGracePeriod+leadershipDeclarationInterval, 3, 2, 1, 0) 202 waitForLeaderElection(t, peers) 203 assert.True(t, peers[0].IsLeader()) 204 } 205 206 func TestStop(t *testing.T) { 207 t.Parallel() 208 // Scenario: peers are spawned at the same time 209 // and then are stopped. We count the number of Gossip() invocations they invoke 210 // after they stop, and it should not increase after they are stopped 211 peers := createPeers(0, 3, 2, 1, 0) 212 var gossipCounter int32 213 for i, p := range peers { 214 p.On("Gossip", mock.Anything).Run(func(args mock.Arguments) { 215 msg := args.Get(0).(Msg) 216 atomic.AddInt32(&gossipCounter, int32(1)) 217 for j := range peers { 218 if i == j { 219 continue 220 } 221 peers[j].msgChan <- msg 222 } 223 }) 224 } 225 waitForLeaderElection(t, peers) 226 for _, p := range peers { 227 p.Stop() 228 } 229 time.Sleep(leaderAliveThreshold) 230 gossipCounterAfterStop := atomic.LoadInt32(&gossipCounter) 231 time.Sleep(leaderAliveThreshold * 5) 232 assert.Equal(t, gossipCounterAfterStop, atomic.LoadInt32(&gossipCounter)) 233 } 234 235 func TestConvergence(t *testing.T) { 236 // Scenario: 2 peer group converge their views 237 // expected outcome: only 1 leader is left out of the 2 238 // and that leader is the leader with the lowest ID 239 t.Parallel() 240 peers1 := createPeers(0, 3, 2, 1, 0) 241 peers2 := createPeers(0, 4, 5, 6, 7) 242 leaders1 := waitForLeaderElection(t, peers1) 243 leaders2 := waitForLeaderElection(t, peers2) 244 assert.Len(t, leaders1, 1, "Peer group 1 was suppose to have 1 leader exactly") 245 assert.Len(t, leaders2, 1, "Peer group 2 was suppose to have 1 leader exactly") 246 combinedPeers := append(peers1, peers2...) 247 248 var allPeerIds []Peer 249 for _, p := range combinedPeers { 250 allPeerIds = append(allPeerIds, &peer{id: p.id}) 251 } 252 253 for i, p := range combinedPeers { 254 index := i 255 gossipFunc := func(args mock.Arguments) { 256 msg := args.Get(0).(Msg) 257 for j := range combinedPeers { 258 if index == j { 259 continue 260 } 261 combinedPeers[j].msgChan <- msg 262 } 263 } 264 p.On("Gossip", mock.Anything).Run(gossipFunc) 265 p.On("Peers").Return(allPeerIds) 266 } 267 268 time.Sleep(leaderAliveThreshold * 5) 269 finalLeaders := waitForLeaderElection(t, combinedPeers) 270 assert.Len(t, finalLeaders, 1, "Combined peer group was suppose to have 1 leader exactly") 271 assert.Equal(t, leaders1[0], finalLeaders[0], "Combined peer group has different leader than expected:") 272 273 for _, p := range combinedPeers { 274 if p.id == finalLeaders[0] { 275 assert.True(t, p.isLeaderFromCallback, "Leadership callback result is wrong for ", p.id) 276 assert.True(t, p.callbackInvoked, "Leadership callback wasn't invoked for ", p.id) 277 } else { 278 assert.False(t, p.isLeaderFromCallback, "Leadership callback result is wrong for ", p.id) 279 if p.id == leaders2[0] { 280 assert.True(t, p.callbackInvoked, "Leadership callback wasn't invoked for ", p.id) 281 } 282 } 283 } 284 } 285 286 func TestLeadershipTakeover(t *testing.T) { 287 t.Parallel() 288 // Scenario: Peers spawn one by one in descending order. 289 // After a while, the leader peer stops. 290 // expected outcome: the peer that takes over is the peer with lowest ID 291 peers := createPeers(startupGracePeriod+leadershipDeclarationInterval, 5, 4, 3, 2) 292 leaders := waitForLeaderElection(t, peers) 293 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 294 assert.Equal(t, "p5", leaders[0]) 295 peers[0].Stop() 296 time.Sleep(leadershipDeclarationInterval + leaderAliveThreshold*3) 297 leaders = waitForLeaderElection(t, peers[1:]) 298 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 299 assert.Equal(t, "p2", leaders[0]) 300 } 301 302 func TestPartition(t *testing.T) { 303 t.Parallel() 304 // Scenario: peers spawn together, and then after a while a network partition occurs 305 // and no peer can communicate with another peer 306 // Expected outcome 1: each peer is a leader 307 // After this, we heal the partition to be a unified view again 308 // Expected outcome 2: p0 is the leader once again 309 peers := createPeers(0, 5, 4, 3, 2, 1, 0) 310 leaders := waitForLeaderElection(t, peers) 311 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 312 assert.Equal(t, "p0", leaders[0]) 313 assert.True(t, peers[len(peers)-1].isLeaderFromCallback, "Leadership callback result is wrong for %s", peers[len(peers)-1].id) 314 315 for _, p := range peers { 316 p.On("Peers").Return([]Peer{}) 317 p.On("Gossip", mock.Anything) 318 } 319 time.Sleep(leadershipDeclarationInterval + leaderAliveThreshold*2) 320 leaders = waitForMultipleLeadersElection(t, peers, 6) 321 assert.Len(t, leaders, 6) 322 for _, p := range peers { 323 assert.True(t, p.isLeaderFromCallback, "Leadership callback result is wrong for %s", p.id) 324 } 325 326 for _, p := range peers { 327 p.sharedLock.Lock() 328 p.mockedMethods = make(map[string]struct{}) 329 p.callbackInvoked = false 330 p.sharedLock.Unlock() 331 } 332 time.Sleep(leadershipDeclarationInterval + leaderAliveThreshold*2) 333 leaders = waitForLeaderElection(t, peers) 334 assert.Len(t, leaders, 1, "Only 1 leader should have been elected") 335 assert.Equal(t, "p0", leaders[0]) 336 for _, p := range peers { 337 if p.id == leaders[0] { 338 assert.True(t, p.isLeaderFromCallback, "Leadership callback result is wrong for %s", p.id) 339 } else { 340 assert.False(t, p.isLeaderFromCallback, "Leadership callback result is wrong for %s", p.id) 341 assert.True(t, p.callbackInvoked, "Leadership callback wasn't invoked for %s", p.id) 342 } 343 } 344 345 }