github.com/m3db/m3@v1.5.0/src/cluster/services/leader/election/client_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package election
    22  
    23  import (
    24  	"context"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	clientv3 "go.etcd.io/etcd/client/v3"
    31  	"go.etcd.io/etcd/client/v3/concurrency"
    32  	"go.etcd.io/etcd/tests/v3/framework/integration"
    33  )
    34  
    35  type testCluster struct {
    36  	t       *testing.T
    37  	cluster *integration.Cluster
    38  }
    39  
    40  func newTestCluster(t *testing.T) *testCluster {
    41  	integration.BeforeTestExternal(t)
    42  	return &testCluster{
    43  		t: t,
    44  		cluster: integration.NewCluster(t, &integration.ClusterConfig{
    45  			Size: 1,
    46  		}),
    47  	}
    48  }
    49  
    50  func (tc *testCluster) close() {
    51  	tc.cluster.Terminate(tc.t)
    52  }
    53  
    54  func (tc *testCluster) etcdClient() *clientv3.Client {
    55  	return tc.cluster.RandClient()
    56  }
    57  
    58  func (tc *testCluster) client(prefix string, options ...ClientOption) *Client {
    59  	options = append([]ClientOption{WithSessionOptions(concurrency.WithTTL(5))}, options...)
    60  	cl, err := NewClient(tc.etcdClient(), prefix, options...)
    61  	require.NoError(tc.t, err)
    62  
    63  	return cl
    64  }
    65  
    66  func TestNewClient(t *testing.T) {
    67  	tc := newTestCluster(t)
    68  
    69  	cl, err := NewClient(tc.etcdClient(), "foo")
    70  	assert.NoError(t, err)
    71  	assert.NotNil(t, cl)
    72  
    73  	tc.close()
    74  
    75  	_, err = NewClient(tc.etcdClient(), "foo")
    76  	assert.Error(t, err)
    77  }
    78  
    79  func TestCampaign(t *testing.T) {
    80  	tc := newTestCluster(t)
    81  	defer tc.close()
    82  
    83  	cl := tc.client("")
    84  	_, err := cl.Campaign(context.Background(), "foo")
    85  	assert.NoError(t, err)
    86  
    87  	ld, err := cl.Leader(context.Background())
    88  	assert.NoError(t, err)
    89  	assert.Equal(t, "foo", ld)
    90  }
    91  
    92  func TestCampaign_Multi(t *testing.T) {
    93  	tc := newTestCluster(t)
    94  	defer tc.close()
    95  
    96  	cl1 := tc.client("foo")
    97  	_, err := cl1.Campaign(context.Background(), "1")
    98  	assert.NoError(t, err)
    99  
   100  	ld, _ := cl1.Leader(context.Background())
   101  	assert.Equal(t, "1", ld)
   102  
   103  	cl2 := tc.client("foo")
   104  	ch := make(chan struct{})
   105  
   106  	go func() {
   107  		_, err := cl2.Campaign(context.Background(), "2")
   108  		assert.NoError(t, err)
   109  		close(ch)
   110  	}()
   111  
   112  	err = cl1.Resign(context.Background())
   113  	assert.NoError(t, err)
   114  
   115  	<-ch
   116  	ld, _ = cl1.Leader(context.Background())
   117  	assert.Equal(t, "2", ld)
   118  
   119  	ld, _ = cl2.Leader(context.Background())
   120  	assert.Equal(t, "2", ld)
   121  }
   122  
   123  func TestCampaign_DeadSession(t *testing.T) {
   124  	tc := newTestCluster(t)
   125  	defer tc.close()
   126  
   127  	cl1 := tc.client("")
   128  	cl2 := tc.client("")
   129  
   130  	_, err := cl1.Campaign(context.Background(), "1")
   131  	assert.NoError(t, err)
   132  
   133  	errC := make(chan error)
   134  	go func() {
   135  		_, err := cl2.Campaign(context.Background(), "2")
   136  		errC <- err
   137  	}()
   138  
   139  	time.Sleep(time.Second)
   140  
   141  	cl2.session.Close()
   142  
   143  	select {
   144  	case err := <-errC:
   145  		assert.Equal(t, context.Canceled, err)
   146  	case <-time.After(30 * time.Second):
   147  		t.Error("should receive err from client2 after orphaning session")
   148  	}
   149  
   150  	// Expect that even though cl2 has a dead session it can still lookup leader
   151  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   152  	defer cancel()
   153  	ld, err := cl2.Leader(ctx)
   154  	assert.Equal(t, "1", ld)
   155  	assert.NoError(t, err)
   156  }
   157  
   158  func TestCampaign_DeadSession_Background(t *testing.T) {
   159  	tc := newTestCluster(t)
   160  	defer tc.close()
   161  
   162  	cl := tc.client("")
   163  
   164  	ch, err := cl.Campaign(context.Background(), "1")
   165  	assert.NoError(t, err)
   166  
   167  	cl.session.Close()
   168  
   169  	sent := false
   170  	select {
   171  	case <-ch:
   172  		sent = true
   173  	case <-time.After(10 * time.Second):
   174  	}
   175  
   176  	assert.True(t, sent, "should have received session dead signal")
   177  
   178  	// new campaign should reset session
   179  	_, err = cl.Campaign(context.Background(), "1")
   180  	assert.NoError(t, err)
   181  
   182  	ld, err := cl.Leader(context.Background())
   183  	assert.NoError(t, err)
   184  	assert.Equal(t, "1", ld)
   185  }
   186  
   187  func TestResign(t *testing.T) {
   188  	tc := newTestCluster(t)
   189  	defer tc.close()
   190  
   191  	cl := tc.client("")
   192  	_, err := cl.Campaign(context.Background(), "foo")
   193  	assert.NoError(t, err)
   194  
   195  	err = cl.Resign(context.Background())
   196  	assert.NoError(t, err)
   197  }
   198  
   199  func TestCampaign_ResignActive(t *testing.T) {
   200  	tc := newTestCluster(t)
   201  	defer tc.close()
   202  
   203  	cl1 := tc.client("foo")
   204  	_, err := cl1.Campaign(context.Background(), "1")
   205  	assert.NoError(t, err)
   206  
   207  	ld, _ := cl1.Leader(context.Background())
   208  	assert.Equal(t, "1", ld)
   209  
   210  	cl2 := tc.client("foo")
   211  	ch := make(chan struct{})
   212  	ch2 := make(chan struct{})
   213  
   214  	ctx, cancel := context.WithCancel(context.Background())
   215  
   216  	go func() {
   217  		close(ch2)
   218  		_, err := cl2.Campaign(ctx, "2")
   219  		assert.Equal(t, context.Canceled, err)
   220  		close(ch)
   221  	}()
   222  
   223  	<-ch2
   224  	time.Sleep(200 * time.Millisecond)
   225  	cancel()
   226  	err = cl2.Resign(context.Background())
   227  	assert.NoError(t, err)
   228  
   229  	<-ch
   230  	ld, _ = cl1.Leader(context.Background())
   231  	assert.Equal(t, "1", ld)
   232  
   233  	ld, _ = cl2.Leader(context.Background())
   234  	assert.Equal(t, "1", ld)
   235  }
   236  
   237  func TestObserve(t *testing.T) {
   238  	tc := newTestCluster(t)
   239  	defer tc.close()
   240  
   241  	cl1 := tc.client("a")
   242  
   243  	ctx1, cancel1 := context.WithCancel(context.Background())
   244  	defer cancel1()
   245  
   246  	obsC, err := cl1.Observe(ctx1)
   247  	assert.NoError(t, err)
   248  
   249  	bufC := make(chan string, 100)
   250  	go func() {
   251  		for v := range obsC {
   252  			bufC <- v
   253  		}
   254  	}()
   255  
   256  	_, err = cl1.Campaign(context.Background(), "1")
   257  	assert.NoError(t, err)
   258  
   259  	select {
   260  	case <-time.After(time.Second):
   261  		t.Error("expected to receive leader update within 1s")
   262  	case v := <-bufC:
   263  		assert.Equal(t, "1", v)
   264  	}
   265  
   266  	cl2 := tc.client("a")
   267  
   268  	el2 := make(chan struct{})
   269  	go func() {
   270  		_, err := cl2.Campaign(context.Background(), "2")
   271  		assert.NoError(t, err)
   272  		close(el2)
   273  	}()
   274  
   275  	err = cl1.Resign(context.Background())
   276  	assert.NoError(t, err)
   277  
   278  	select {
   279  	case <-time.After(5 * time.Second):
   280  		t.Error("expected to be leader within 5s")
   281  	case <-el2:
   282  	}
   283  
   284  	select {
   285  	case <-time.After(time.Second):
   286  		t.Error("expected to receive new leader within 1s")
   287  	case v := <-bufC:
   288  		assert.Equal(t, "2", v)
   289  	}
   290  
   291  	cancel1()
   292  	select {
   293  	case <-time.After(time.Second):
   294  		t.Error("expected update channel to be closed within 1s")
   295  	case _, ok := <-obsC:
   296  		assert.False(t, ok)
   297  	}
   298  }
   299  
   300  func TestClose(t *testing.T) {
   301  	tc := newTestCluster(t)
   302  	defer tc.close()
   303  
   304  	cl := tc.client("")
   305  
   306  	ch, err := cl.Campaign(context.Background(), "1")
   307  	assert.NoError(t, err)
   308  
   309  	ld, err := cl.Leader(context.Background())
   310  	assert.Equal(t, "1", ld)
   311  	assert.NoError(t, err)
   312  
   313  	err = cl.Close()
   314  	assert.NoError(t, err)
   315  
   316  	select {
   317  	case <-ch:
   318  	case <-time.After(30 * time.Second):
   319  		t.Error("should have received close on campaign ch")
   320  	}
   321  
   322  	_, err = cl.Campaign(context.Background(), "foo")
   323  	assert.Equal(t, ErrClientClosed, err)
   324  
   325  	err = cl.Resign(context.Background())
   326  	assert.Equal(t, ErrClientClosed, err)
   327  
   328  	_, err = cl.Leader(context.Background())
   329  	assert.Equal(t, ErrClientClosed, err)
   330  }