github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/m3em/cluster/cluster_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 cluster
    22  
    23  import (
    24  	"fmt"
    25  	"math/rand"
    26  	"testing"
    27  
    28  	"github.com/m3db/m3/src/cluster/placement"
    29  	"github.com/m3db/m3/src/cluster/shard"
    30  	"github.com/m3db/m3/src/m3em/build"
    31  	"github.com/m3db/m3/src/m3em/node"
    32  
    33  	"github.com/golang/mock/gomock"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  const (
    38  	defaultRandSeed         = 1234567890
    39  	defaultTestSessionToken = "someLongString"
    40  )
    41  
    42  var (
    43  	defaultRandomVar = rand.New(rand.NewSource(int64(defaultRandSeed)))
    44  )
    45  
    46  func newDefaultClusterTestOptions(ctrl *gomock.Controller, psvc placement.Service) Options {
    47  	mockBuild := build.NewMockServiceBuild(ctrl)
    48  	mockConf := build.NewMockServiceConfiguration(ctrl)
    49  	return NewOptions(psvc, nil).
    50  		SetNumShards(10).
    51  		SetReplication(10).
    52  		SetServiceBuild(mockBuild).
    53  		SetServiceConfig(mockConf).
    54  		SetSessionToken(defaultTestSessionToken).
    55  		SetPlacementService(psvc)
    56  }
    57  
    58  func newMockServiceNode(ctrl *gomock.Controller) *node.MockServiceNode {
    59  	r := defaultRandomVar
    60  	node := node.NewMockServiceNode(ctrl)
    61  	node.EXPECT().ID().AnyTimes().Return(fmt.Sprintf("%d", r.Int()))
    62  	node.EXPECT().IsolationGroup().AnyTimes().Return(fmt.Sprintf("%d", r.Int()))
    63  	node.EXPECT().Endpoint().AnyTimes().Return(fmt.Sprintf("%v:%v", r.Int(), r.Int()))
    64  	node.EXPECT().Zone().AnyTimes().Return(fmt.Sprintf("%d", r.Int()))
    65  	node.EXPECT().Weight().AnyTimes().Return(uint32(r.Int()))
    66  	node.EXPECT().Shards().AnyTimes().Return(nil)
    67  	return node
    68  }
    69  
    70  type expectNodeCallTypes struct {
    71  	expectSetup    bool
    72  	expectTeardown bool
    73  	expectStop     bool
    74  	expectStart    bool
    75  }
    76  
    77  // nolint: unparam
    78  func newMockServiceNodes(ctrl *gomock.Controller, numNodes int, calls expectNodeCallTypes) []node.ServiceNode {
    79  	nodes := make([]node.ServiceNode, 0, numNodes)
    80  	for i := 0; i < numNodes; i++ {
    81  		mNode := newMockServiceNode(ctrl)
    82  		if calls.expectSetup {
    83  			mNode.EXPECT().Setup(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
    84  		}
    85  		if calls.expectTeardown {
    86  			mNode.EXPECT().Teardown().Return(nil)
    87  		}
    88  		if calls.expectStop {
    89  			mNode.EXPECT().Stop().Return(nil)
    90  		}
    91  		if calls.expectStart {
    92  			mNode.EXPECT().Start().Return(nil)
    93  		}
    94  		nodes = append(nodes, mNode)
    95  	}
    96  	return nodes
    97  }
    98  
    99  func newMockPlacementService(ctrl *gomock.Controller) placement.Service {
   100  	return placement.NewMockService(ctrl)
   101  }
   102  
   103  func TestClusterErrorStatusTransitions(t *testing.T) {
   104  	ctrl := gomock.NewController(t)
   105  	defer ctrl.Finish()
   106  	mockPlacementService := newMockPlacementService(ctrl)
   107  	opts := newDefaultClusterTestOptions(ctrl, mockPlacementService)
   108  	nodes := newMockServiceNodes(ctrl, 5, expectNodeCallTypes{expectTeardown: true})
   109  	clusterIface, err := New(nodes, opts)
   110  	require.NoError(t, err)
   111  	cluster := clusterIface.(*svcCluster)
   112  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   113  	cluster.status = ClusterStatusError
   114  
   115  	// illegal transitions
   116  	_, err = cluster.Setup(1)
   117  	require.Error(t, err)
   118  	require.Error(t, cluster.Start())
   119  	require.Error(t, cluster.Stop())
   120  	_, err = cluster.AddNode()
   121  	require.Error(t, err)
   122  	err = cluster.RemoveNode(nil)
   123  	require.Error(t, err)
   124  	_, err = cluster.ReplaceNode(nil)
   125  	require.Error(t, err)
   126  
   127  	// teardown (legal)
   128  	require.NoError(t, cluster.Teardown())
   129  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   130  }
   131  
   132  func TestClusterUninitializedToSetupTransition(t *testing.T) {
   133  	ctrl := gomock.NewController(t)
   134  	defer ctrl.Finish()
   135  	var (
   136  		mockPlacementService = newMockPlacementService(ctrl)
   137  		mpsvc                = mockPlacementService.(*placement.MockService)
   138  		opts                 = newDefaultClusterTestOptions(ctrl, mockPlacementService)
   139  		nodes                = newMockServiceNodes(ctrl, 5, expectNodeCallTypes{expectSetup: true})
   140  		clusterIface, err    = New(nodes, opts)
   141  	)
   142  
   143  	require.NoError(t, err)
   144  	cluster := clusterIface.(*svcCluster)
   145  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   146  
   147  	// fake placement
   148  	pi, ok := nodes[0].(placement.Instance)
   149  	require.True(t, ok)
   150  	mockNode, ok := nodes[0].(*node.MockServiceNode)
   151  	require.True(t, ok)
   152  	mockNode.EXPECT().SetShards(gomock.Any())
   153  	mockPlacement := placement.NewMockPlacement(ctrl)
   154  	mockPlacement.EXPECT().Instances().Return([]placement.Instance{pi}).AnyTimes()
   155  
   156  	// setup (legal)
   157  	gomock.InOrder(
   158  		mpsvc.EXPECT().Placement().Return(nil, nil),
   159  		mpsvc.EXPECT().Delete().Return(nil),
   160  		mpsvc.EXPECT().
   161  			BuildInitialPlacement(gomock.Any(), gomock.Any(), gomock.Any()).
   162  			Return(mockPlacement, nil),
   163  	)
   164  
   165  	_, err = cluster.Setup(1)
   166  	require.NoError(t, err)
   167  	require.Equal(t, ClusterStatusSetup, cluster.Status())
   168  }
   169  
   170  func TestClusterUninitializedErrorTransitions(t *testing.T) {
   171  	ctrl := gomock.NewController(t)
   172  	defer ctrl.Finish()
   173  	var (
   174  		mockPlacementService = newMockPlacementService(ctrl)
   175  		opts                 = newDefaultClusterTestOptions(ctrl, mockPlacementService)
   176  		nodes                = newMockServiceNodes(ctrl, 5, expectNodeCallTypes{})
   177  		clusterIface, err    = New(nodes, opts)
   178  	)
   179  
   180  	require.NoError(t, err)
   181  	cluster := clusterIface.(*svcCluster)
   182  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   183  
   184  	// illegal transitions
   185  	require.Error(t, cluster.Start())
   186  	require.Error(t, cluster.Stop())
   187  	_, err = cluster.AddNode()
   188  	require.Error(t, err)
   189  	err = cluster.RemoveNode(nil)
   190  	require.Error(t, err)
   191  	_, err = cluster.ReplaceNode(nil)
   192  	require.Error(t, err)
   193  }
   194  
   195  func TestClusterSetupIllegalTransitions(t *testing.T) {
   196  	ctrl := gomock.NewController(t)
   197  	defer ctrl.Finish()
   198  	var (
   199  		mockPlacementService = newMockPlacementService(ctrl)
   200  		opts                 = newDefaultClusterTestOptions(ctrl, mockPlacementService)
   201  		nodes                = newMockServiceNodes(ctrl, 5, expectNodeCallTypes{})
   202  		clusterIface, err    = New(nodes, opts)
   203  	)
   204  	require.NoError(t, err)
   205  	cluster := clusterIface.(*svcCluster)
   206  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   207  
   208  	cluster.status = ClusterStatusSetup
   209  	require.Error(t, cluster.Stop())
   210  }
   211  
   212  func TestClusterSetupAddNodeTransition(t *testing.T) {
   213  	ctrl := gomock.NewController(t)
   214  	defer ctrl.Finish()
   215  	var (
   216  		mockPlacementService = newMockPlacementService(ctrl)
   217  		mpsvc                = mockPlacementService.(*placement.MockService)
   218  		opts                 = newDefaultClusterTestOptions(ctrl, mockPlacementService)
   219  		expectCalls          = expectNodeCallTypes{}
   220  		nodes                = newMockServiceNodes(ctrl, 5, expectCalls)
   221  		clusterIface, err    = New(nodes, opts)
   222  	)
   223  	require.NoError(t, err)
   224  	cluster := clusterIface.(*svcCluster)
   225  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   226  
   227  	// fake placement
   228  	pi, ok := nodes[0].(placement.Instance)
   229  	require.True(t, ok)
   230  	mockNode, ok := nodes[0].(*node.MockServiceNode)
   231  	require.True(t, ok)
   232  	mockNode.EXPECT().SetShards(gomock.Any())
   233  	mockPlacement := placement.NewMockPlacement(ctrl)
   234  	mockPlacement.EXPECT().Instances().Return([]placement.Instance{pi}).AnyTimes()
   235  	gomock.InOrder(
   236  		mpsvc.EXPECT().AddInstances(gomock.Any()).Return(nil, nil, fmt.Errorf("faking error to ensure retries")),
   237  		mpsvc.EXPECT().AddInstances(gomock.Any()).Return(mockPlacement, []placement.Instance{mockNode}, nil),
   238  	)
   239  
   240  	// ensure mockNode is in the spares
   241  	found := false
   242  	for _, inst := range cluster.SpareNodes() {
   243  		if inst.ID() == mockNode.ID() {
   244  			require.False(t, found)
   245  			found = true
   246  		}
   247  	}
   248  	require.True(t, found)
   249  
   250  	// now add the mockNode using the faked stuff above
   251  	cluster.status = ClusterStatusSetup
   252  	newNode, err := cluster.AddNode()
   253  	require.NoError(t, err)
   254  	require.Equal(t, mockNode.ID(), newNode.ID())
   255  
   256  	// ensure mockNode is not in spares
   257  	for _, inst := range cluster.SpareNodes() {
   258  		if inst.ID() == mockNode.ID() {
   259  			require.Fail(t, "found node with id: %s", mockNode.ID())
   260  		}
   261  	}
   262  }
   263  
   264  func TestClusterSetupToStart(t *testing.T) {
   265  	ctrl := gomock.NewController(t)
   266  	defer ctrl.Finish()
   267  	var (
   268  		mockPlacementService = newMockPlacementService(ctrl)
   269  		mpsvc                = mockPlacementService.(*placement.MockService)
   270  		opts                 = newDefaultClusterTestOptions(ctrl, mockPlacementService)
   271  		expectCalls          = expectNodeCallTypes{expectSetup: true}
   272  		nodes                = newMockServiceNodes(ctrl, 5, expectCalls)
   273  		clusterIface, err    = New(nodes, opts)
   274  	)
   275  	require.NoError(t, err)
   276  	cluster := clusterIface.(*svcCluster)
   277  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   278  
   279  	// fake placement
   280  	pi, ok := nodes[0].(placement.Instance)
   281  	require.True(t, ok)
   282  	mockNode, ok := nodes[0].(*node.MockServiceNode)
   283  	require.True(t, ok)
   284  	mockNode.EXPECT().SetShards(gomock.Any())
   285  	mockNode.EXPECT().Start().Return(nil)
   286  	mockPlacement := placement.NewMockPlacement(ctrl)
   287  	mockPlacement.EXPECT().Instances().Return([]placement.Instance{pi}).AnyTimes()
   288  
   289  	// setup (legal)
   290  	gomock.InOrder(
   291  		mpsvc.EXPECT().Placement().Return(nil, nil),
   292  		mpsvc.EXPECT().Delete().Return(nil),
   293  		mpsvc.EXPECT().
   294  			BuildInitialPlacement(gomock.Any(), gomock.Any(), gomock.Any()).
   295  			Return(mockPlacement, nil),
   296  	)
   297  
   298  	_, err = cluster.Setup(1)
   299  	require.NoError(t, err)
   300  	require.Equal(t, ClusterStatusSetup, cluster.Status())
   301  
   302  	// now ensure start is called
   303  	require.NoError(t, cluster.Start())
   304  	require.Equal(t, ClusterStatusRunning, cluster.Status())
   305  }
   306  
   307  func TestClusterSetupToRemoveNode(t *testing.T) {
   308  	ctrl := gomock.NewController(t)
   309  	defer ctrl.Finish()
   310  	var (
   311  		mockPlacementService = newMockPlacementService(ctrl)
   312  		mpsvc                = mockPlacementService.(*placement.MockService)
   313  		opts                 = newDefaultClusterTestOptions(ctrl, mockPlacementService)
   314  		expectCalls          = expectNodeCallTypes{expectSetup: true}
   315  		nodes                = newMockServiceNodes(ctrl, 5, expectCalls)
   316  		clusterIface, err    = New(nodes, opts)
   317  	)
   318  	require.NoError(t, err)
   319  	cluster := clusterIface.(*svcCluster)
   320  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   321  
   322  	// fake placement
   323  	pi, ok := nodes[0].(placement.Instance)
   324  	require.True(t, ok)
   325  	mockNode, ok := nodes[0].(*node.MockServiceNode)
   326  	require.True(t, ok)
   327  	mockNode.EXPECT().SetShards(gomock.Any())
   328  	mockPlacement := placement.NewMockPlacement(ctrl)
   329  	mockPlacement.EXPECT().Instances().Return([]placement.Instance{pi}).AnyTimes()
   330  
   331  	// setup (legal)
   332  	gomock.InOrder(
   333  		mpsvc.EXPECT().Placement().Return(nil, nil),
   334  		mpsvc.EXPECT().Delete().Return(nil),
   335  		mpsvc.EXPECT().
   336  			BuildInitialPlacement(gomock.Any(), gomock.Any(), gomock.Any()).
   337  			Return(mockPlacement, nil),
   338  	)
   339  
   340  	setupNodes, err := cluster.Setup(1)
   341  	require.NoError(t, err)
   342  	require.Equal(t, ClusterStatusSetup, cluster.Status())
   343  	require.Equal(t, 1, len(setupNodes))
   344  	require.Equal(t, mockNode.ID(), setupNodes[0].ID())
   345  
   346  	mockNode.EXPECT().SetShards(shard.NewShards(nil))
   347  	mockPlacement = placement.NewMockPlacement(ctrl)
   348  	mockPlacement.EXPECT().Instances().Return([]placement.Instance{}).AnyTimes()
   349  	gomock.InOrder(
   350  		mpsvc.EXPECT().RemoveInstances([]string{setupNodes[0].ID()}).Return(nil, fmt.Errorf("faking error to ensure retries")),
   351  		mpsvc.EXPECT().RemoveInstances([]string{setupNodes[0].ID()}).Return(mockPlacement, nil),
   352  	)
   353  
   354  	err = cluster.RemoveNode(setupNodes[0])
   355  	require.NoError(t, err)
   356  
   357  	// ensure node is in the spares list
   358  	found := false
   359  	for _, node := range cluster.SpareNodes() {
   360  		if node.ID() == mockNode.ID() {
   361  			require.False(t, found)
   362  			found = true
   363  		}
   364  	}
   365  	require.True(t, found)
   366  }
   367  
   368  func TestClusterSetupToReplaceNode(t *testing.T) {
   369  	ctrl := gomock.NewController(t)
   370  	defer ctrl.Finish()
   371  	var (
   372  		mockPlacementService = newMockPlacementService(ctrl)
   373  		mpsvc                = mockPlacementService.(*placement.MockService)
   374  		opts                 = newDefaultClusterTestOptions(ctrl, mockPlacementService)
   375  		expectCalls          = expectNodeCallTypes{expectSetup: true}
   376  		nodes                = newMockServiceNodes(ctrl, 5, expectCalls)
   377  		clusterIface, err    = New(nodes, opts)
   378  	)
   379  	require.NoError(t, err)
   380  	cluster := clusterIface.(*svcCluster)
   381  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   382  
   383  	// fake placement
   384  	pi, ok := nodes[0].(placement.Instance)
   385  	require.True(t, ok)
   386  	mockNode, ok := nodes[0].(*node.MockServiceNode)
   387  	require.True(t, ok)
   388  	mockNode.EXPECT().SetShards(gomock.Any())
   389  	mockPlacement := placement.NewMockPlacement(ctrl)
   390  	mockPlacement.EXPECT().Instances().Return([]placement.Instance{pi}).AnyTimes()
   391  
   392  	// setup (legal)
   393  	gomock.InOrder(
   394  		mpsvc.EXPECT().Placement().Return(nil, nil),
   395  		mpsvc.EXPECT().Delete().Return(nil),
   396  		mpsvc.EXPECT().
   397  			BuildInitialPlacement(gomock.Any(), gomock.Any(), gomock.Any()).
   398  			Return(mockPlacement, nil),
   399  	)
   400  
   401  	setupNodes, err := cluster.Setup(1)
   402  	require.NoError(t, err)
   403  	require.Equal(t, ClusterStatusSetup, cluster.Status())
   404  	require.Equal(t, 1, len(setupNodes))
   405  	require.Equal(t, mockNode.ID(), setupNodes[0].ID())
   406  
   407  	// create new mock placement for replace
   408  	mockNode.EXPECT().SetShards(shard.NewShards(nil))
   409  	mockPlacement = placement.NewMockPlacement(ctrl)
   410  	replacementInstances := []placement.Instance{
   411  		nodes[1].(placement.Instance),
   412  		nodes[2].(placement.Instance),
   413  	}
   414  	mockPlacement.EXPECT().Instances().Return(replacementInstances).AnyTimes()
   415  	mockNode1 := nodes[1].(*node.MockServiceNode)
   416  	mockNode2 := nodes[2].(*node.MockServiceNode)
   417  	mockNode1.EXPECT().SetShards(gomock.Any())
   418  	mockNode2.EXPECT().SetShards(gomock.Any())
   419  
   420  	gomock.InOrder(
   421  		mpsvc.EXPECT().
   422  			ReplaceInstances([]string{setupNodes[0].ID()}, gomock.Any()).
   423  			Return(nil, nil, fmt.Errorf("faking error to ensure retries")),
   424  		mpsvc.EXPECT().
   425  			ReplaceInstances([]string{setupNodes[0].ID()}, gomock.Any()).
   426  			Return(mockPlacement, replacementInstances, nil),
   427  	)
   428  
   429  	replacementNodes, err := cluster.ReplaceNode(setupNodes[0])
   430  	require.NoError(t, err)
   431  	require.Equal(t, 2, len(replacementNodes))
   432  }
   433  
   434  func TestClusterRunningIllegalTransitions(t *testing.T) {
   435  	ctrl := gomock.NewController(t)
   436  	defer ctrl.Finish()
   437  	var (
   438  		mockPlacementService = newMockPlacementService(ctrl)
   439  		opts                 = newDefaultClusterTestOptions(ctrl, mockPlacementService)
   440  		nodes                = newMockServiceNodes(ctrl, 5, expectNodeCallTypes{})
   441  		clusterIface, err    = New(nodes, opts)
   442  	)
   443  	require.NoError(t, err)
   444  	cluster := clusterIface.(*svcCluster)
   445  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   446  
   447  	cluster.status = ClusterStatusRunning
   448  	require.Error(t, cluster.Start())
   449  	_, err = cluster.Setup(1)
   450  	require.Error(t, err)
   451  }
   452  
   453  func TestClusterRunningToStop(t *testing.T) {
   454  	ctrl := gomock.NewController(t)
   455  	defer ctrl.Finish()
   456  	var (
   457  		mockPlacementService = newMockPlacementService(ctrl)
   458  		opts                 = newDefaultClusterTestOptions(ctrl, mockPlacementService)
   459  		nodes                = newMockServiceNodes(ctrl, 5, expectNodeCallTypes{})
   460  		clusterIface, err    = New(nodes, opts)
   461  	)
   462  	require.NoError(t, err)
   463  	cluster := clusterIface.(*svcCluster)
   464  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   465  
   466  	cluster.status = ClusterStatusRunning
   467  	mockNode, ok := nodes[0].(*node.MockServiceNode)
   468  	require.True(t, ok)
   469  	mockNode.EXPECT().Stop().Return(nil)
   470  	usedIDMap := map[string]node.ServiceNode{
   471  		mockNode.ID(): mockNode,
   472  	}
   473  	cluster.usedNodes = usedIDMap
   474  
   475  	require.NoError(t, cluster.Stop())
   476  }
   477  
   478  func TestClusterRunningToTeardown(t *testing.T) {
   479  	ctrl := gomock.NewController(t)
   480  	defer ctrl.Finish()
   481  	var (
   482  		mockPlacementService = newMockPlacementService(ctrl)
   483  		opts                 = newDefaultClusterTestOptions(ctrl, mockPlacementService)
   484  		nodes                = newMockServiceNodes(ctrl, 5, expectNodeCallTypes{expectTeardown: true})
   485  		clusterIface, err    = New(nodes, opts)
   486  	)
   487  	require.NoError(t, err)
   488  	cluster := clusterIface.(*svcCluster)
   489  	require.Equal(t, ClusterStatusUninitialized, cluster.Status())
   490  
   491  	cluster.status = ClusterStatusRunning
   492  	require.NoError(t, cluster.Teardown())
   493  }