github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/cluster/services/leader/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 leader
    22  
    23  import (
    24  	"fmt"
    25  	"reflect"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/cluster/services"
    30  	"github.com/m3db/m3/src/cluster/services/leader/campaign"
    31  	"github.com/m3db/m3/src/cluster/services/leader/election"
    32  
    33  	integration "github.com/m3db/m3/src/integration/resources/docker/dockerexternal/etcdintegration"
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  	clientv3 "go.etcd.io/etcd/client/v3"
    37  	"golang.org/x/net/context"
    38  )
    39  
    40  var (
    41  	newStatus = campaign.NewStatus
    42  	newErr    = campaign.NewErrorStatus
    43  	followerS = newStatus(campaign.Follower)
    44  	leaderS   = newStatus(campaign.Leader)
    45  )
    46  
    47  func waitForStates(ch <-chan campaign.Status, early bool, states ...campaign.Status) error {
    48  	var seen []campaign.Status
    49  	for s := range ch {
    50  		seen = append(seen, s)
    51  		// terminate early (before channel closes)
    52  		if early && reflect.DeepEqual(seen, states) {
    53  			return nil
    54  		}
    55  	}
    56  
    57  	if !reflect.DeepEqual(seen, states) {
    58  		return fmt.Errorf("states did not match: %v != %v", seen, states)
    59  	}
    60  
    61  	return nil
    62  }
    63  
    64  type testCluster struct {
    65  	t       *testing.T
    66  	cluster *integration.Cluster
    67  }
    68  
    69  func newTestCluster(t *testing.T) *testCluster {
    70  	integration.BeforeTestExternal(t)
    71  	return &testCluster{
    72  		t: t,
    73  		cluster: integration.NewCluster(t, &integration.ClusterConfig{
    74  			Size: 1,
    75  		}),
    76  	}
    77  }
    78  
    79  func (tc *testCluster) close() {
    80  	tc.cluster.Terminate(tc.t)
    81  }
    82  
    83  func (tc *testCluster) etcdClient() *clientv3.Client {
    84  	return tc.cluster.RandClient()
    85  }
    86  
    87  func (tc *testCluster) options() Options {
    88  	sid := services.NewServiceID().
    89  		SetEnvironment("e1").
    90  		SetName("s1").
    91  		SetZone("z1")
    92  
    93  	eopts := services.NewElectionOptions().
    94  		SetTTLSecs(5)
    95  
    96  	return NewOptions().
    97  		SetServiceID(sid).
    98  		SetElectionOpts(eopts)
    99  }
   100  
   101  func (tc *testCluster) client() *client {
   102  	svc, err := newClient(tc.etcdClient(), tc.options(), "")
   103  	require.NoError(tc.t, err)
   104  
   105  	return svc
   106  }
   107  
   108  func (tc *testCluster) service() services.LeaderService {
   109  	svc, err := NewService(tc.etcdClient(), tc.options())
   110  	require.NoError(tc.t, err)
   111  
   112  	return svc
   113  }
   114  
   115  func (tc *testCluster) opts(val string) services.CampaignOptions {
   116  	opts, err := services.NewCampaignOptions()
   117  	require.NoError(tc.t, err)
   118  	return opts.SetLeaderValue(val)
   119  }
   120  
   121  func TestNewClient(t *testing.T) {
   122  	tc := newTestCluster(t)
   123  	defer tc.close()
   124  
   125  	svc, err := newClient(tc.etcdClient(), tc.options(), "")
   126  	assert.NoError(t, err)
   127  	assert.NotNil(t, svc)
   128  }
   129  
   130  // TODO: this test most likely wasn't testing what we thought it was. While using etcd/testing/framework/integration,
   131  // the client gets closed
   132  func TestNewClient_BadCluster(t *testing.T) {
   133  	t.Skip("This test only works with the etcd/testing/framework/integration package, " +
   134  		"and doesn't provide much signal on correctness of our code.")
   135  	tc := newTestCluster(t)
   136  	cl := tc.etcdClient()
   137  	tc.close()
   138  	require.NoError(t, cl.Close())
   139  	_, err := newClient(cl, tc.options(), "")
   140  	assert.Error(t, err)
   141  }
   142  
   143  func TestCampaign(t *testing.T) {
   144  	tc := newTestCluster(t)
   145  	defer tc.close()
   146  
   147  	svc := tc.client()
   148  
   149  	sc, err := svc.campaign(tc.opts("foo"))
   150  	assert.NoError(t, err)
   151  
   152  	waitForStates(sc, true, followerS, leaderS)
   153  
   154  	_, err = svc.campaign(tc.opts("foo2"))
   155  	assert.Equal(t, ErrCampaignInProgress, err)
   156  
   157  	err = svc.resign()
   158  	assert.NoError(t, err)
   159  
   160  	errC := make(chan error)
   161  	go func() {
   162  		errC <- waitForStates(sc, false, followerS)
   163  	}()
   164  
   165  	err = <-errC
   166  	assert.NoError(t, err)
   167  
   168  	sc, err = svc.campaign(tc.opts("foo3"))
   169  	assert.NoError(t, err)
   170  
   171  	waitForStates(sc, true, followerS, leaderS)
   172  
   173  	err = svc.resign()
   174  	assert.NoError(t, err)
   175  	assert.NoError(t, waitForStates(sc, false, followerS))
   176  }
   177  
   178  func TestCampaign_Override(t *testing.T) {
   179  	tc := newTestCluster(t)
   180  	defer tc.close()
   181  
   182  	svc := tc.client()
   183  
   184  	sc, err := svc.campaign(tc.opts("foo"))
   185  	assert.NoError(t, err)
   186  	assert.NoError(t, waitForStates(sc, true, followerS, leaderS))
   187  
   188  	ld, err := svc.leader()
   189  	assert.NoError(t, err)
   190  	assert.Equal(t, "foo", ld)
   191  }
   192  
   193  func TestCampaign_Renew(t *testing.T) {
   194  	tc := newTestCluster(t)
   195  	defer tc.close()
   196  
   197  	svc := tc.client()
   198  	sc, err := svc.campaign(tc.opts(""))
   199  	assert.NoError(t, err)
   200  	assert.NoError(t, waitForStates(sc, true, followerS, leaderS))
   201  
   202  	err = svc.resign()
   203  	assert.NoError(t, err)
   204  	assert.NoError(t, waitForStates(sc, false, followerS))
   205  
   206  	_, err = svc.leader()
   207  	assert.Equal(t, ErrNoLeader, err)
   208  
   209  	sc2, err := svc.campaign(tc.opts(""))
   210  	assert.NoError(t, err)
   211  	assert.NoError(t, waitForStates(sc2, true, followerS, leaderS))
   212  }
   213  
   214  func TestResign(t *testing.T) {
   215  	tc := newTestCluster(t)
   216  	defer tc.close()
   217  
   218  	svc := tc.client()
   219  
   220  	sc, err := svc.campaign(tc.opts("i1"))
   221  	assert.NoError(t, err)
   222  
   223  	assert.NoError(t, waitForStates(sc, true, followerS, leaderS))
   224  
   225  	ld, err := svc.leader()
   226  	assert.NoError(t, err)
   227  	assert.Equal(t, "i1", ld)
   228  
   229  	err = svc.resign()
   230  	assert.NoError(t, err)
   231  
   232  	assert.NoError(t, waitForStates(sc, false, followerS))
   233  
   234  	ld, err = svc.leader()
   235  	assert.Equal(t, ErrNoLeader, err)
   236  	assert.Equal(t, "", ld)
   237  }
   238  
   239  func TestResign_BlockingCampaign(t *testing.T) {
   240  	tc := newTestCluster(t)
   241  	defer tc.close()
   242  
   243  	svc1, svc2 := tc.client(), tc.client()
   244  
   245  	sc1, err := svc1.campaign(tc.opts("i1"))
   246  	assert.NoError(t, err)
   247  	assert.NoError(t, waitForStates(sc1, true, followerS, leaderS))
   248  
   249  	sc2, err := svc2.campaign(tc.opts("i2"))
   250  	assert.NoError(t, err)
   251  	assert.NoError(t, waitForStates(sc2, true, followerS))
   252  
   253  	err = svc2.resign()
   254  	assert.NoError(t, err)
   255  	assert.NoError(t, waitForStates(sc2, false, newErr(context.Canceled)))
   256  }
   257  
   258  func TestResign_Early(t *testing.T) {
   259  	tc := newTestCluster(t)
   260  	defer tc.close()
   261  
   262  	svc := tc.client()
   263  
   264  	err := svc.resign()
   265  	assert.NoError(t, err)
   266  }
   267  
   268  func TestObserve(t *testing.T) {
   269  	tc := newTestCluster(t)
   270  	defer tc.close()
   271  
   272  	svc1 := tc.client()
   273  
   274  	obsC, err := svc1.observe()
   275  	assert.NoError(t, err)
   276  
   277  	sc1, err := svc1.campaign(tc.opts("i1"))
   278  	assert.NoError(t, err)
   279  	assert.NoError(t, waitForStates(sc1, true, followerS, leaderS))
   280  
   281  	select {
   282  	case <-time.After(time.Second):
   283  		t.Error("expected to receive leader update")
   284  	case v := <-obsC:
   285  		assert.Equal(t, "i1", v)
   286  	}
   287  
   288  	assert.NoError(t, svc1.close())
   289  	select {
   290  	case <-time.After(5 * time.Second):
   291  		t.Error("expected client channel to be closed")
   292  	case _, ok := <-obsC:
   293  		assert.False(t, ok)
   294  	}
   295  
   296  	_, err = svc1.observe()
   297  	assert.Equal(t, errClientClosed, err)
   298  }
   299  
   300  func testHandoff(t *testing.T, resign bool) {
   301  	tc := newTestCluster(t)
   302  	defer tc.close()
   303  
   304  	svc1, svc2 := tc.client(), tc.client()
   305  
   306  	sc1, err := svc1.campaign(tc.opts("i1"))
   307  	assert.NoError(t, err)
   308  
   309  	assert.NoError(t, waitForStates(sc1, true, followerS, leaderS))
   310  
   311  	sc2, err := svc2.campaign(tc.opts("i2"))
   312  	assert.NoError(t, err)
   313  
   314  	assert.NoError(t, waitForStates(sc2, true, followerS))
   315  
   316  	ld, err := svc1.leader()
   317  	assert.NoError(t, err)
   318  	assert.Equal(t, ld, "i1")
   319  
   320  	if resign {
   321  		err = svc1.resign()
   322  		assert.NoError(t, waitForStates(sc1, false, followerS))
   323  	} else {
   324  		err = svc1.close()
   325  		assert.NoError(t, waitForStates(sc1, false, newErr(election.ErrSessionExpired)))
   326  	}
   327  	assert.NoError(t, err)
   328  
   329  	assert.NoError(t, waitForStates(sc2, true, leaderS))
   330  
   331  	ld, err = svc2.leader()
   332  	assert.NoError(t, err)
   333  	assert.Equal(t, ld, "i2")
   334  }
   335  
   336  func TestCampaign_Cancel_Resign(t *testing.T) {
   337  	testHandoff(t, true)
   338  }
   339  
   340  func TestCampaign_Cancel_Close(t *testing.T) {
   341  	testHandoff(t, false)
   342  }
   343  
   344  func TestCampaign_Close_NonLeader(t *testing.T) {
   345  	tc := newTestCluster(t)
   346  	defer tc.close()
   347  
   348  	svc1, svc2 := tc.client(), tc.client()
   349  
   350  	sc1, err := svc1.campaign(tc.opts("i1"))
   351  	assert.NoError(t, err)
   352  	assert.NoError(t, waitForStates(sc1, true, followerS, leaderS))
   353  
   354  	sc2, err := svc2.campaign(tc.opts("i2"))
   355  	assert.NoError(t, err)
   356  	assert.NoError(t, waitForStates(sc2, true, followerS))
   357  
   358  	ld, err := svc1.leader()
   359  	assert.NoError(t, err)
   360  	assert.Equal(t, ld, "i1")
   361  
   362  	err = svc2.close()
   363  	assert.NoError(t, err)
   364  	assert.NoError(t, waitForStates(sc2, false, newErr(context.Canceled)))
   365  
   366  	err = svc1.resign()
   367  	assert.NoError(t, err)
   368  	assert.NoError(t, waitForStates(sc1, false, followerS))
   369  
   370  	_, err = svc2.leader()
   371  	assert.Equal(t, errClientClosed, err)
   372  }
   373  
   374  func TestClose(t *testing.T) {
   375  	tc := newTestCluster(t)
   376  	defer tc.close()
   377  
   378  	svc := tc.client()
   379  
   380  	sc, err := svc.campaign(tc.opts("i1"))
   381  	assert.NoError(t, err)
   382  	assert.NoError(t, waitForStates(sc, true, followerS, leaderS))
   383  
   384  	ld, err := svc.leader()
   385  	assert.NoError(t, err)
   386  	assert.Equal(t, "i1", ld)
   387  
   388  	err = svc.close()
   389  	assert.NoError(t, err)
   390  	assert.True(t, svc.isClosed())
   391  	assert.NoError(t, waitForStates(sc, false, newErr(election.ErrSessionExpired)))
   392  
   393  	err = svc.resign()
   394  	assert.Equal(t, errClientClosed, err)
   395  
   396  	_, err = svc.campaign(tc.opts(""))
   397  	assert.Equal(t, errClientClosed, err)
   398  }
   399  
   400  func TestLeader(t *testing.T) {
   401  	tc := newTestCluster(t)
   402  	defer tc.close()
   403  
   404  	svc1, svc2 := tc.client(), tc.client()
   405  	sc, err := svc1.campaign(tc.opts("i1"))
   406  	assert.NoError(t, err)
   407  	assert.NoError(t, waitForStates(sc, true, followerS, leaderS))
   408  
   409  	ld, err := svc2.leader()
   410  	assert.NoError(t, err)
   411  
   412  	assert.Equal(t, "i1", ld)
   413  }
   414  
   415  func TestElectionPrefix(t *testing.T) {
   416  	for args, exp := range map[*struct {
   417  		env, name, eid string
   418  	}]string{
   419  		{"", "svc", ""}:       "_ld/svc/default",
   420  		{"env", "svc", ""}:    "_ld/env/svc/default",
   421  		{"", "svc", "foo"}:    "_ld/svc/foo",
   422  		{"env", "svc", "foo"}: "_ld/env/svc/foo",
   423  	} {
   424  		sid := services.NewServiceID().
   425  			SetEnvironment(args.env).
   426  			SetName(args.name)
   427  
   428  		pfx := electionPrefix(sid, args.eid)
   429  
   430  		assert.Equal(t, exp, pfx)
   431  	}
   432  }