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  }