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 }