Skip to content

Commit 1c0ecbd

Browse files
FAB-1930 Subscription-like API to leader election
Peer leadership status change callback signature This callback invoked once peer become leader or give up on leadership And it passed as argument to NewLeaderElectionService, can be nil Change-Id: Ib0ef831f84428e3505cd4a69feb6135b57660e1d Signed-off-by: Gennady Laventman <[email protected]>
1 parent 36bbeb6 commit 1c0ecbd

File tree

2 files changed

+65
-11
lines changed

2 files changed

+65
-11
lines changed

gossip/election/election.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ type LeaderElectionAdapter interface {
102102
Peers() []Peer
103103
}
104104

105+
type leadershipCallback func(isLeader bool)
106+
105107
// LeaderElectionService is the object that runs the leader election algorithm
106108
type LeaderElectionService interface {
107109
// IsLeader returns whether this peer is a leader or not
@@ -127,8 +129,11 @@ type Msg interface {
127129
IsDeclaration() bool
128130
}
129131

132+
func noopCallback(_ bool) {
133+
}
134+
130135
// NewLeaderElectionService returns a new LeaderElectionService
131-
func NewLeaderElectionService(adapter LeaderElectionAdapter, id string) LeaderElectionService {
136+
func NewLeaderElectionService(adapter LeaderElectionAdapter, id string, callback leadershipCallback) LeaderElectionService {
132137
if len(id) == 0 {
133138
panic(fmt.Errorf("Empty id"))
134139
}
@@ -139,7 +144,13 @@ func NewLeaderElectionService(adapter LeaderElectionAdapter, id string) LeaderEl
139144
stopChan: make(chan struct{}, 1),
140145
interruptChan: make(chan struct{}, 1),
141146
logger: logging.MustGetLogger("LeaderElection"),
147+
callback: noopCallback,
142148
}
149+
150+
if callback != nil {
151+
le.callback = callback
152+
}
153+
143154
// TODO: This will be configured using the core.yaml when FAB-1217 (Integrate peer logging with gossip logging) is done
144155
logging.SetLevel(logging.WARNING, "LeaderElection")
145156
go le.start()
@@ -160,6 +171,7 @@ type leaderElectionSvcImpl struct {
160171
sleeping bool
161172
adapter LeaderElectionAdapter
162173
logger *logging.Logger
174+
callback leadershipCallback
163175
}
164176

165177
func (le *leaderElectionSvcImpl) start() {
@@ -357,11 +369,13 @@ func (le *leaderElectionSvcImpl) IsLeader() bool {
357369
func (le *leaderElectionSvcImpl) beLeader() {
358370
le.logger.Info(le.id, ": Becoming a leader")
359371
atomic.StoreInt32(&le.isLeader, int32(1))
372+
le.callback(true)
360373
}
361374

362375
func (le *leaderElectionSvcImpl) stopBeingLeader() {
363376
le.logger.Info(le.id, "Stopped being a leader")
364377
atomic.StoreInt32(&le.isLeader, int32(0))
378+
le.callback(false)
365379
}
366380

367381
func (le *leaderElectionSvcImpl) shouldStop() bool {

gossip/election/election_test.go

+50-10
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,12 @@ func (m *msg) IsDeclaration() bool {
6060
type peer struct {
6161
mockedMethods map[string]struct{}
6262
mock.Mock
63-
id string
64-
peers map[string]*peer
65-
sharedLock *sync.RWMutex
66-
msgChan chan Msg
63+
id string
64+
peers map[string]*peer
65+
sharedLock *sync.RWMutex
66+
msgChan chan Msg
67+
isLeaderFromCallback bool
68+
callbackInvoked bool
6769
LeaderElectionService
6870
}
6971

@@ -126,6 +128,11 @@ func (p *peer) Peers() []Peer {
126128
return peers
127129
}
128130

131+
func (p *peer) leaderCallback(isLeader bool) {
132+
p.isLeaderFromCallback = isLeader
133+
p.callbackInvoked = true
134+
}
135+
129136
func createPeers(spawnInterval time.Duration, ids ...int) []*peer {
130137
peers := make([]*peer, len(ids))
131138
peerMap := make(map[string]*peer)
@@ -143,16 +150,16 @@ func createPeers(spawnInterval time.Duration, ids ...int) []*peer {
143150
func createPeer(id int, peerMap map[string]*peer, l *sync.RWMutex) *peer {
144151
idStr := fmt.Sprintf("p%d", id)
145152
c := make(chan Msg, 100)
146-
p := &peer{id: idStr, peers: peerMap, sharedLock: l, msgChan: c, mockedMethods: make(map[string]struct{})}
147-
p.LeaderElectionService = NewLeaderElectionService(p, idStr)
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)
148155
l.Lock()
149156
peerMap[idStr] = p
150157
l.Unlock()
151158
return p
152159

153160
}
154161

155-
func waitForLeaderElection(t *testing.T, peers []*peer) []string {
162+
func waitForMultipleLeadersElection(t *testing.T, peers []*peer, leadersNum int) []string {
156163
end := time.Now().Add(testTimeout)
157164
for time.Now().Before(end) {
158165
var leaders []string
@@ -161,7 +168,7 @@ func waitForLeaderElection(t *testing.T, peers []*peer) []string {
161168
leaders = append(leaders, p.id)
162169
}
163170
}
164-
if len(leaders) > 0 {
171+
if len(leaders) >= leadersNum {
165172
return leaders
166173
}
167174
time.Sleep(testPollInterval)
@@ -170,6 +177,10 @@ func waitForLeaderElection(t *testing.T, peers []*peer) []string {
170177
return nil
171178
}
172179

180+
func waitForLeaderElection(t *testing.T, peers []*peer) []string {
181+
return waitForMultipleLeadersElection(t, peers, 1)
182+
}
183+
173184
func TestInitPeersAtSameTime(t *testing.T) {
174185
t.Parallel()
175186
// Scenario: Peers are spawned at the same time
@@ -179,6 +190,7 @@ func TestInitPeersAtSameTime(t *testing.T) {
179190
leaders := waitForLeaderElection(t, peers)
180191
isP0leader := peers[len(peers)-1].IsLeader()
181192
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")
182194
assert.Len(t, leaders, 1, "More than 1 leader elected")
183195
}
184196

@@ -257,6 +269,18 @@ func TestConvergence(t *testing.T) {
257269
finalLeaders := waitForLeaderElection(t, combinedPeers)
258270
assert.Len(t, finalLeaders, 1, "Combined peer group was suppose to have 1 leader exactly")
259271
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+
}
260284
}
261285

262286
func TestLeadershipTakeover(t *testing.T) {
@@ -286,20 +310,36 @@ func TestPartition(t *testing.T) {
286310
leaders := waitForLeaderElection(t, peers)
287311
assert.Len(t, leaders, 1, "Only 1 leader should have been elected")
288312
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+
289315
for _, p := range peers {
290316
p.On("Peers").Return([]Peer{})
291317
p.On("Gossip", mock.Anything)
292318
}
293319
time.Sleep(leadershipDeclarationInterval + leaderAliveThreshold*2)
294-
leaders = waitForLeaderElection(t, peers)
295-
assert.Len(t, leaders, len(leaders))
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+
296326
for _, p := range peers {
297327
p.sharedLock.Lock()
298328
p.mockedMethods = make(map[string]struct{})
329+
p.callbackInvoked = false
299330
p.sharedLock.Unlock()
300331
}
301332
time.Sleep(leadershipDeclarationInterval + leaderAliveThreshold*2)
302333
leaders = waitForLeaderElection(t, peers)
303334
assert.Len(t, leaders, 1, "Only 1 leader should have been elected")
304335
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 %", 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+
305345
}

0 commit comments

Comments
 (0)