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 }