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)
}
Beispiel #2
0
// 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)
}