func TestFollower(t *testing.T) { store, err := kv.NewStore("mock", []string{}, nil) assert.NoError(t, err) mockStore := store.(*kv.Mock) kvCh := make(chan *kv.KVPair) var mockKVCh <-chan *kv.KVPair = kvCh mockStore.On("Watch", "test_key", mock.Anything).Return(mockKVCh, nil) follower := NewFollower(store, "test_key") follower.FollowElection() leaderCh := follower.LeaderCh() // Simulate leader updates go func() { kvCh <- &kv.KVPair{Key: "test_key", Value: []byte("leader1")} kvCh <- &kv.KVPair{Key: "test_key", Value: []byte("leader1")} kvCh <- &kv.KVPair{Key: "test_key", Value: []byte("leader2")} kvCh <- &kv.KVPair{Key: "test_key", Value: []byte("leader1")} }() // We shouldn't see duplicate events. assert.Equal(t, <-leaderCh, "leader1") assert.Equal(t, <-leaderCh, "leader2") assert.Equal(t, <-leaderCh, "leader1") // Once stopped, iteration over the leader channel should stop. follower.Stop() close(kvCh) assert.Equal(t, "", <-leaderCh) mockStore.AssertExpectations(t) }
// Initialize is exported func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Duration) error { var ( parts = strings.SplitN(uris, "/", 2) addrs = strings.Split(parts[0], ",") prefix = "" err error ) // A custom prefix to the path can be optionally used. if len(parts) == 2 { prefix = parts[1] } s.heartbeat = heartbeat s.ttl = ttl s.path = path.Join(prefix, discoveryPath) // Creates a new store, will ignore options given // if not supported by the chosen store s.store, err = store.NewStore( s.backend, addrs, &store.Config{ EphemeralTTL: s.ttl, }, ) return err }
func TestCandidate(t *testing.T) { store, err := kv.NewStore("mock", []string{}, nil) assert.NoError(t, err) mockStore := store.(*kv.Mock) mockLock := &kv.MockLock{} mockStore.On("NewLock", "test_key", mock.Anything).Return(mockLock, nil) // Lock and unlock always succeeds. lostCh := make(chan struct{}) var mockLostCh <-chan struct{} = lostCh mockLock.On("Lock").Return(mockLostCh, nil) mockLock.On("Unlock").Return(nil) candidate := NewCandidate(store, "test_key", "test_node") candidate.RunForElection() electedCh := candidate.ElectedCh() // Should issue a false upon start, no matter what. assert.False(t, <-electedCh) // Since the lock always succeeeds, we should get elected. assert.True(t, <-electedCh) // Signaling a lost lock should get us de-elected... close(lostCh) assert.False(t, <-electedCh) // And we should attempt to get re-elected again. assert.True(t, <-electedCh) // When we resign, unlock will get called, we'll be notified of the // de-election and we'll try to get the lock again. go candidate.Resign() assert.False(t, <-electedCh) assert.True(t, <-electedCh) // After stopping the candidate, the ElectedCh should be closed. candidate.Stop() select { case <-electedCh: assert.True(t, false) // we should not get here. default: assert.True(t, true) } mockStore.AssertExpectations(t) }