github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/aggregator/tools/deploy/planner_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 deploy
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"sort"
    27  	"testing"
    28  
    29  	"github.com/m3db/m3/src/cluster/services"
    30  
    31  	"github.com/golang/mock/gomock"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  var (
    36  	testElectionKeyFmt    = "/shardset/%d"
    37  	testInstancesToDeploy = instanceMetadatas{
    38  		instanceMetadata{
    39  			PlacementInstanceID: "instance1",
    40  			ShardSetID:          0,
    41  		},
    42  		instanceMetadata{
    43  			PlacementInstanceID: "instance2",
    44  			ShardSetID:          0,
    45  		},
    46  		instanceMetadata{
    47  			PlacementInstanceID: "instance3",
    48  			ShardSetID:          1,
    49  		},
    50  	}
    51  	testAllInstances = instanceMetadatas{
    52  		instanceMetadata{
    53  			PlacementInstanceID: "instance1",
    54  			ShardSetID:          0,
    55  		},
    56  		instanceMetadata{
    57  			PlacementInstanceID: "instance2",
    58  			ShardSetID:          0,
    59  		},
    60  		instanceMetadata{
    61  			PlacementInstanceID: "instance3",
    62  			ShardSetID:          1,
    63  		},
    64  		instanceMetadata{
    65  			PlacementInstanceID: "instance4",
    66  			ShardSetID:          1,
    67  		},
    68  	}
    69  )
    70  
    71  func TestGeneratePlan(t *testing.T) {
    72  	ctrl := gomock.NewController(t)
    73  	defer ctrl.Finish()
    74  
    75  	var capturedInstance instanceMetadata
    76  	electionIDForShardset0 := fmt.Sprintf(testElectionKeyFmt, 0)
    77  	electionIDForShardset1 := fmt.Sprintf(testElectionKeyFmt, 1)
    78  	leaderService := services.NewMockLeaderService(ctrl)
    79  	leaderService.EXPECT().
    80  		Leader(gomock.Any()).
    81  		DoAndReturn(func(electionID string) (string, error) {
    82  			if electionID == electionIDForShardset0 {
    83  				return "instance1", nil
    84  			}
    85  			if electionID == electionIDForShardset1 {
    86  				return "instance3", nil
    87  			}
    88  			return "", errors.New("unrecognized election id")
    89  		}).
    90  		AnyTimes()
    91  	factory := NewMockvalidatorFactory(ctrl)
    92  	factory.EXPECT().
    93  		ValidatorFor(gomock.Any(), gomock.Any(), gomock.Any()).
    94  		DoAndReturn(func(
    95  			instance instanceMetadata,
    96  			_ *instanceGroup,
    97  			_ targetType,
    98  		) validator {
    99  			return func() error {
   100  				capturedInstance = instance
   101  				return nil
   102  			}
   103  		}).
   104  		AnyTimes()
   105  
   106  	opts := NewPlannerOptions().
   107  		SetLeaderService(leaderService).
   108  		SetElectionKeyFmt(testElectionKeyFmt)
   109  	planner := newPlanner(nil, opts).(deploymentPlanner)
   110  	planner.validatorFactory = factory
   111  	plan, err := planner.GeneratePlan(testInstancesToDeploy, testAllInstances)
   112  	require.NoError(t, err)
   113  
   114  	for _, step := range plan.Steps {
   115  		for _, target := range step.Targets {
   116  			require.NoError(t, target.Validator())
   117  			require.Equal(t, capturedInstance, target.Instance)
   118  		}
   119  	}
   120  
   121  	expected := deploymentPlan{
   122  		Steps: []deploymentStep{
   123  			{
   124  				Targets: []deploymentTarget{
   125  					{
   126  						Instance: instanceMetadata{
   127  							PlacementInstanceID: "instance2",
   128  							ShardSetID:          0,
   129  						},
   130  					},
   131  				},
   132  			},
   133  			{
   134  				Targets: []deploymentTarget{
   135  					{
   136  						Instance: instanceMetadata{
   137  							PlacementInstanceID: "instance1",
   138  							ShardSetID:          0,
   139  						},
   140  					},
   141  					{
   142  						Instance: instanceMetadata{
   143  							PlacementInstanceID: "instance3",
   144  							ShardSetID:          1,
   145  						},
   146  					},
   147  				},
   148  			},
   149  		},
   150  	}
   151  	validateDeploymentPlan(t, expected, plan)
   152  }
   153  
   154  func TestGeneratePlanWithStepSizeLimit(t *testing.T) {
   155  	ctrl := gomock.NewController(t)
   156  	defer ctrl.Finish()
   157  
   158  	var capturedInstance instanceMetadata
   159  	electionIDForShardset0 := fmt.Sprintf(testElectionKeyFmt, 0)
   160  	electionIDForShardset1 := fmt.Sprintf(testElectionKeyFmt, 1)
   161  	leaderService := services.NewMockLeaderService(ctrl)
   162  	leaderService.EXPECT().
   163  		Leader(gomock.Any()).
   164  		DoAndReturn(func(electionID string) (string, error) {
   165  			if electionID == electionIDForShardset0 {
   166  				return "instance1", nil
   167  			}
   168  			if electionID == electionIDForShardset1 {
   169  				return "instance3", nil
   170  			}
   171  			return "", errors.New("unrecognized election id")
   172  		}).
   173  		AnyTimes()
   174  	factory := NewMockvalidatorFactory(ctrl)
   175  	factory.EXPECT().
   176  		ValidatorFor(gomock.Any(), gomock.Any(), gomock.Any()).
   177  		DoAndReturn(func(
   178  			instance instanceMetadata,
   179  			_ *instanceGroup,
   180  			_ targetType,
   181  		) validator {
   182  			return func() error {
   183  				capturedInstance = instance
   184  				return nil
   185  			}
   186  		}).
   187  		AnyTimes()
   188  	opts := NewPlannerOptions().
   189  		SetLeaderService(leaderService).
   190  		SetElectionKeyFmt(testElectionKeyFmt).
   191  		SetMaxStepSize(1)
   192  	planner := newPlanner(nil, opts).(deploymentPlanner)
   193  	planner.validatorFactory = factory
   194  	plan, err := planner.GeneratePlan(testInstancesToDeploy, testAllInstances)
   195  	require.NoError(t, err)
   196  
   197  	for _, step := range plan.Steps {
   198  		for _, target := range step.Targets {
   199  			require.NoError(t, target.Validator())
   200  			require.Equal(t, capturedInstance, target.Instance)
   201  		}
   202  	}
   203  
   204  	step1 := deploymentStep{
   205  		Targets: []deploymentTarget{
   206  			{
   207  				Instance: instanceMetadata{
   208  					PlacementInstanceID: "instance2",
   209  					ShardSetID:          0,
   210  				},
   211  			},
   212  		},
   213  	}
   214  	step2 := deploymentStep{
   215  		Targets: []deploymentTarget{
   216  			{
   217  				Instance: instanceMetadata{
   218  					PlacementInstanceID: "instance1",
   219  					ShardSetID:          0,
   220  				},
   221  			},
   222  		},
   223  	}
   224  	step3 := deploymentStep{
   225  		Targets: []deploymentTarget{
   226  			{
   227  				Instance: instanceMetadata{
   228  					PlacementInstanceID: "instance3",
   229  					ShardSetID:          1,
   230  				},
   231  			},
   232  		},
   233  	}
   234  	require.Equal(t, 3, len(plan.Steps))
   235  	var expected deploymentPlan
   236  	if plan.Steps[1].Targets[0].Instance.PlacementInstanceID == "instance1" {
   237  		expected = deploymentPlan{Steps: []deploymentStep{step1, step2, step3}}
   238  	} else {
   239  		expected = deploymentPlan{Steps: []deploymentStep{step1, step3, step2}}
   240  	}
   241  	validateDeploymentPlan(t, expected, plan)
   242  }
   243  
   244  func TestGroupInstancesByShardSetID(t *testing.T) {
   245  	ctrl := gomock.NewController(t)
   246  	defer ctrl.Finish()
   247  
   248  	electionIDForShardset0 := fmt.Sprintf(testElectionKeyFmt, 0)
   249  	electionIDForShardset1 := fmt.Sprintf(testElectionKeyFmt, 1)
   250  	leaderService := services.NewMockLeaderService(ctrl)
   251  	leaderService.EXPECT().
   252  		Leader(gomock.Any()).
   253  		DoAndReturn(func(electionID string) (string, error) {
   254  			if electionID == electionIDForShardset0 {
   255  				return "instance1", nil
   256  			}
   257  			if electionID == electionIDForShardset1 {
   258  				return "instance3", nil
   259  			}
   260  			return "", errors.New("unrecognized election id")
   261  		}).
   262  		AnyTimes()
   263  
   264  	opts := NewPlannerOptions().
   265  		SetLeaderService(leaderService).
   266  		SetElectionKeyFmt(testElectionKeyFmt)
   267  	planner := newPlanner(nil, opts).(deploymentPlanner)
   268  	group, err := planner.groupInstancesByShardSetID(testInstancesToDeploy, testAllInstances)
   269  	require.NoError(t, err)
   270  
   271  	expectedGroup := map[uint32]*instanceGroup{
   272  		0: &instanceGroup{
   273  			LeaderID: "instance1",
   274  			ToDeploy: instanceMetadatas{
   275  				instanceMetadata{
   276  					PlacementInstanceID: "instance1",
   277  					ShardSetID:          0,
   278  				},
   279  				instanceMetadata{
   280  					PlacementInstanceID: "instance2",
   281  					ShardSetID:          0,
   282  				},
   283  			},
   284  			All: instanceMetadatas{
   285  				instanceMetadata{
   286  					PlacementInstanceID: "instance1",
   287  					ShardSetID:          0,
   288  				},
   289  				instanceMetadata{
   290  					PlacementInstanceID: "instance2",
   291  					ShardSetID:          0,
   292  				},
   293  			},
   294  		},
   295  		1: &instanceGroup{
   296  			LeaderID: "instance3",
   297  			ToDeploy: instanceMetadatas{
   298  				instanceMetadata{
   299  					PlacementInstanceID: "instance3",
   300  					ShardSetID:          1,
   301  				},
   302  			},
   303  			All: instanceMetadatas{
   304  				instanceMetadata{
   305  					PlacementInstanceID: "instance3",
   306  					ShardSetID:          1,
   307  				},
   308  				instanceMetadata{
   309  					PlacementInstanceID: "instance4",
   310  					ShardSetID:          1,
   311  				},
   312  			},
   313  		},
   314  	}
   315  	require.Equal(t, expectedGroup, group)
   316  }
   317  
   318  func TestGroupInstancesByShardSetIDLeaderError(t *testing.T) {
   319  	ctrl := gomock.NewController(t)
   320  	defer ctrl.Finish()
   321  
   322  	errLeader := errors.New("leader error")
   323  	leaderService := services.NewMockLeaderService(ctrl)
   324  	leaderService.EXPECT().Leader(gomock.Any()).Return("", errLeader).AnyTimes()
   325  	opts := NewPlannerOptions().
   326  		SetLeaderService(leaderService).
   327  		SetElectionKeyFmt(testElectionKeyFmt)
   328  	planner := newPlanner(nil, opts).(deploymentPlanner)
   329  	_, err := planner.groupInstancesByShardSetID(testInstancesToDeploy, testAllInstances)
   330  	require.Error(t, err)
   331  }
   332  
   333  func TestGroupInstancesByShardSetIDUnknownLeader(t *testing.T) {
   334  	ctrl := gomock.NewController(t)
   335  	defer ctrl.Finish()
   336  
   337  	leaderService := services.NewMockLeaderService(ctrl)
   338  	leaderService.EXPECT().Leader(gomock.Any()).Return("nonexistent", nil).AnyTimes()
   339  	opts := NewPlannerOptions().
   340  		SetLeaderService(leaderService).
   341  		SetElectionKeyFmt(testElectionKeyFmt)
   342  	planner := newPlanner(nil, opts).(deploymentPlanner)
   343  	_, err := planner.groupInstancesByShardSetID(testInstancesToDeploy, testAllInstances)
   344  	require.Error(t, err)
   345  }
   346  
   347  func TestRemoveInstanceToDeploy(t *testing.T) {
   348  	metadatas := instanceMetadatas{
   349  		instanceMetadata{PlacementInstanceID: "instance1"},
   350  		instanceMetadata{PlacementInstanceID: "instance2"},
   351  		instanceMetadata{PlacementInstanceID: "instance3"},
   352  	}
   353  	group := &instanceGroup{
   354  		ToDeploy: metadatas,
   355  	}
   356  	group.removeInstanceToDeploy(1)
   357  	expected := instanceMetadatas{
   358  		instanceMetadata{PlacementInstanceID: "instance1"},
   359  		instanceMetadata{PlacementInstanceID: "instance3"},
   360  	}
   361  	require.Equal(t, group.ToDeploy, expected)
   362  }
   363  
   364  func TestTargetsByInstanceIDAsc(t *testing.T) {
   365  	targets := []deploymentTarget{
   366  		{
   367  			Instance: instanceMetadata{
   368  				PlacementInstanceID: "instance3",
   369  			},
   370  		},
   371  		{
   372  			Instance: instanceMetadata{
   373  				PlacementInstanceID: "instance1",
   374  			},
   375  		},
   376  		{
   377  			Instance: instanceMetadata{
   378  				PlacementInstanceID: "instance2",
   379  			},
   380  		},
   381  		{
   382  			Instance: instanceMetadata{
   383  				PlacementInstanceID: "instance4",
   384  			},
   385  		},
   386  	}
   387  
   388  	expected := []deploymentTarget{targets[1], targets[2], targets[0], targets[3]}
   389  	sort.Sort(targetsByInstanceIDAsc(targets))
   390  	require.Equal(t, expected, targets)
   391  }
   392  
   393  func validateDeploymentPlan(
   394  	t *testing.T,
   395  	expected, actual deploymentPlan,
   396  ) {
   397  	require.Equal(t, len(expected.Steps), len(actual.Steps))
   398  	for i := 0; i < len(expected.Steps); i++ {
   399  		expectedTargets := expected.Steps[i].Targets
   400  		actualTargets := actual.Steps[i].Targets
   401  		require.Equal(t, len(expectedTargets), len(actualTargets))
   402  		for j := 0; j < len(expectedTargets); j++ {
   403  			expectedTarget := expectedTargets[j].Instance
   404  			actualTarget := actualTargets[j].Instance
   405  			require.Equal(t, expectedTarget, actualTarget)
   406  		}
   407  	}
   408  }