github.com/m3db/m3@v1.5.0/src/cluster/placementhandler/common_test.go (about) 1 // Copyright (c) 2018 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 placementhandler 22 23 import ( 24 "errors" 25 "testing" 26 "time" 27 28 "github.com/golang/mock/gomock" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 32 "github.com/m3db/m3/src/cluster/client" 33 "github.com/m3db/m3/src/cluster/generated/proto/placementpb" 34 "github.com/m3db/m3/src/cluster/placement" 35 "github.com/m3db/m3/src/cluster/placementhandler/handleroptions" 36 "github.com/m3db/m3/src/cluster/services" 37 "github.com/m3db/m3/src/cluster/shard" 38 ) 39 40 func TestPlacementService(t *testing.T) { 41 ctrl := gomock.NewController(t) 42 defer ctrl.Finish() 43 44 runForAllAllowedServices(func(serviceName string) { 45 mockClient := client.NewMockClient(ctrl) 46 require.NotNil(t, mockClient) 47 mockServices := services.NewMockServices(ctrl) 48 require.NotNil(t, mockServices) 49 mockPlacementService := placement.NewMockService(ctrl) 50 require.NotNil(t, mockPlacementService) 51 52 mockClient.EXPECT().Services(gomock.Not(nil)).Return(mockServices, nil) 53 mockServices.EXPECT().PlacementService(gomock.Not(nil), gomock.Not(nil)). 54 Return(mockPlacementService, nil) 55 56 svcDefaults := handleroptions.ServiceNameAndDefaults{ 57 ServiceName: serviceName, 58 } 59 60 placementService, algo, err := ServiceWithAlgo(mockClient, 61 handleroptions.NewServiceOptions(svcDefaults, nil, nil), 62 placement.Configuration{}, 63 time.Time{}, nil) 64 assert.NoError(t, err) 65 assert.NotNil(t, placementService) 66 assert.NotNil(t, algo) 67 68 // Test Services returns error 69 mockClient.EXPECT().Services(gomock.Not(nil)). 70 Return(nil, errors.New("dummy service error")) 71 placementService, err = Service(mockClient, 72 handleroptions.NewServiceOptions(svcDefaults, nil, nil), 73 placement.Configuration{}, 74 time.Time{}, nil) 75 assert.Nil(t, placementService) 76 assert.EqualError(t, err, "dummy service error") 77 78 // Test PlacementService returns error 79 mockClient.EXPECT().Services(gomock.Not(nil)).Return(mockServices, nil) 80 mockServices.EXPECT(). 81 PlacementService(gomock.Not(nil), gomock.Not(nil)). 82 Return(nil, errors.New("dummy placement error")) 83 placementService, err = Service(mockClient, 84 handleroptions.NewServiceOptions(svcDefaults, nil, nil), 85 placement.Configuration{}, 86 time.Time{}, nil) 87 assert.Nil(t, placementService) 88 assert.EqualError(t, err, "dummy placement error") 89 }) 90 } 91 92 func TestPlacementServiceWithClusterHeaders(t *testing.T) { 93 ctrl := gomock.NewController(t) 94 defer ctrl.Finish() 95 96 runForAllAllowedServices(func(serviceName string) { 97 mockClient := client.NewMockClient(ctrl) 98 require.NotNil(t, mockClient) 99 mockServices := services.NewMockServices(ctrl) 100 require.NotNil(t, mockServices) 101 mockPlacementService := placement.NewMockService(ctrl) 102 require.NotNil(t, mockPlacementService) 103 104 mockClient.EXPECT().Services(gomock.Not(nil)).Return(mockServices, nil) 105 106 var actual services.ServiceID 107 mockServices.EXPECT().PlacementService(gomock.Not(nil), gomock.Not(nil)). 108 DoAndReturn(func( 109 serviceID services.ServiceID, 110 _ placement.Options, 111 ) (placement.Service, error) { 112 actual = serviceID 113 return mockPlacementService, nil 114 }) 115 116 var ( 117 serviceValue = handleroptions.M3DBServiceName 118 svcDefaults = handleroptions.ServiceNameAndDefaults{ 119 ServiceName: handleroptions.M3DBServiceName, 120 } 121 environmentValue = "bar_env" 122 zoneValue = "baz_zone" 123 opts = handleroptions.NewServiceOptions(svcDefaults, nil, nil) 124 ) 125 126 opts.ServiceEnvironment = environmentValue 127 opts.ServiceZone = zoneValue 128 129 placementService, err := Service( 130 mockClient, 131 opts, 132 placement.Configuration{}, 133 time.Time{}, 134 nil, 135 ) 136 require.NoError(t, err) 137 require.NotNil(t, placementService) 138 139 require.NotNil(t, actual) 140 require.Equal(t, serviceValue, actual.Name()) 141 require.Equal(t, environmentValue, actual.Environment()) 142 require.Equal(t, zoneValue, actual.Zone()) 143 }) 144 } 145 146 func TestConvertInstancesProto(t *testing.T) { 147 runForAllAllowedServices(func(serviceName string) { 148 instances, err := ConvertInstancesProto([]*placementpb.Instance{}) 149 require.NoError(t, err) 150 require.Equal(t, 0, len(instances)) 151 152 instances, err = ConvertInstancesProto([]*placementpb.Instance{ 153 &placementpb.Instance{ 154 Id: "i1", 155 IsolationGroup: "r1", 156 Weight: 1, 157 Endpoint: "i1:1234", 158 Hostname: "i1", 159 Port: 1234, 160 Metadata: &placementpb.InstanceMetadata{ 161 DebugPort: 4231, 162 }, 163 }, 164 }) 165 require.NoError(t, err) 166 require.Equal(t, 1, len(instances)) 167 require.Equal(t, "Instance[ID=i1, IsolationGroup=r1, Zone=, Weight=1, Endpoint=i1:1234, Hostname=i1, Port=1234, ShardSetID=0, Shards=[Initializing=[], Available=[], Leaving=[]], Metadata={DebugPort:4231}]", instances[0].String()) 168 169 instances, err = ConvertInstancesProto([]*placementpb.Instance{ 170 &placementpb.Instance{ 171 Id: "i1", 172 IsolationGroup: "r1", 173 Weight: 1, 174 Endpoint: "i1:1234", 175 Hostname: "i1", 176 Port: 1234, 177 ShardSetId: 1, 178 Shards: []*placementpb.Shard{ 179 &placementpb.Shard{ 180 Id: 1, 181 State: placementpb.ShardState_AVAILABLE, 182 SourceId: "s1", 183 }, 184 &placementpb.Shard{ 185 Id: 2, 186 State: placementpb.ShardState_AVAILABLE, 187 SourceId: "s1", 188 }, 189 }, 190 Metadata: &placementpb.InstanceMetadata{ 191 DebugPort: 1, 192 }, 193 }, 194 &placementpb.Instance{ 195 Id: "i2", 196 IsolationGroup: "r1", 197 Weight: 1, 198 Endpoint: "i2:1234", 199 Hostname: "i2", 200 Port: 1234, 201 ShardSetId: 1, 202 Shards: []*placementpb.Shard{ 203 &placementpb.Shard{ 204 Id: 1, 205 State: placementpb.ShardState_AVAILABLE, 206 SourceId: "s2", 207 }, 208 &placementpb.Shard{ 209 Id: 1, 210 State: placementpb.ShardState_AVAILABLE, 211 SourceId: "s2", 212 }, 213 }, 214 Metadata: &placementpb.InstanceMetadata{ 215 DebugPort: 2, 216 }, 217 }, 218 &placementpb.Instance{ 219 Id: "i3", 220 IsolationGroup: "r2", 221 Weight: 2, 222 Endpoint: "i3:1234", 223 Hostname: "i3", 224 Port: 1234, 225 ShardSetId: 2, 226 Shards: []*placementpb.Shard{ 227 &placementpb.Shard{ 228 Id: 1, 229 State: placementpb.ShardState_INITIALIZING, 230 SourceId: "s1", 231 CutoverNanos: 2, 232 CutoffNanos: 3, 233 }, 234 }, 235 Metadata: &placementpb.InstanceMetadata{ 236 DebugPort: 3, 237 }, 238 }, 239 }) 240 require.NoError(t, err) 241 require.Equal(t, 3, len(instances)) 242 require.Equal(t, "Instance[ID=i1, IsolationGroup=r1, Zone=, Weight=1, Endpoint=i1:1234, Hostname=i1, Port=1234, ShardSetID=1, Shards=[Initializing=[], Available=[1 2], Leaving=[]], Metadata={DebugPort:1}]", instances[0].String()) 243 require.Equal(t, "Instance[ID=i2, IsolationGroup=r1, Zone=, Weight=1, Endpoint=i2:1234, Hostname=i2, Port=1234, ShardSetID=1, Shards=[Initializing=[], Available=[1], Leaving=[]], Metadata={DebugPort:2}]", instances[1].String()) 244 require.Equal(t, "Instance[ID=i3, IsolationGroup=r2, Zone=, Weight=2, Endpoint=i3:1234, Hostname=i3, Port=1234, ShardSetID=2, Shards=[Initializing=[1], Available=[], Leaving=[]], Metadata={DebugPort:3}]", instances[2].String()) 245 246 _, err = ConvertInstancesProto([]*placementpb.Instance{ 247 &placementpb.Instance{ 248 Id: "i1", 249 IsolationGroup: "r1", 250 Weight: 1, 251 Endpoint: "i1:1234", 252 Hostname: "i1", 253 Port: 1234, 254 ShardSetId: 1, 255 Shards: []*placementpb.Shard{ 256 &placementpb.Shard{ 257 Id: 1, 258 State: 9999, 259 SourceId: "s1", 260 }, 261 }, 262 }, 263 }) 264 require.EqualError(t, err, "invalid proto shard state") 265 }) 266 } 267 268 func newValidPlacement(state shard.State) placement.Placement { 269 shards := shard.NewShards([]shard.Shard{ 270 shard.NewShard(0).SetState(state), 271 }) 272 273 instA := placement.NewInstance().SetShards(shards).SetID("A").SetEndpoint("A") 274 instB := placement.NewInstance().SetShards(shards).SetID("B").SetEndpoint("B") 275 return placement.NewPlacement(). 276 SetInstances([]placement.Instance{instA, instB}). 277 SetIsSharded(true). 278 SetShards([]uint32{0}). 279 SetReplicaFactor(2) 280 } 281 282 func newValidInitPlacement() placement.Placement { 283 return newValidPlacement(shard.Initializing) 284 } 285 286 func newValidAvailPlacement() placement.Placement { 287 return newValidPlacement(shard.Available) 288 } 289 290 func newPlacement(state shard.State) placement.Placement { 291 shards := shard.NewShards([]shard.Shard{ 292 shard.NewShard(1).SetState(state), 293 }) 294 295 instA := placement.NewInstance().SetShards(shards).SetID("A") 296 instB := placement.NewInstance().SetShards(shards).SetID("B") 297 return placement.NewPlacement().SetInstances([]placement.Instance{instA, instB}) 298 } 299 300 func newInitPlacement() placement.Placement { 301 return newPlacement(shard.Initializing) 302 } 303 304 func newAvailPlacement() placement.Placement { 305 return newPlacement(shard.Available) 306 } 307 308 func TestValidateAllAvailable(t *testing.T) { 309 p := placement.NewPlacement() 310 assert.NoError(t, validateAllAvailable(p)) 311 312 p = newAvailPlacement() 313 assert.NoError(t, validateAllAvailable(p)) 314 315 p = newInitPlacement() 316 assert.Error(t, validateAllAvailable(p)) 317 } 318 319 func runForAllAllowedServices(f func(service string)) { 320 for _, service := range handleroptions.AllowedServices() { 321 f(service) 322 } 323 } 324 325 func TestIsStateless(t *testing.T) { 326 for _, s := range []string{ 327 handleroptions.M3CoordinatorServiceName, 328 } { 329 assert.True(t, isStateless(s)) 330 } 331 332 for _, s := range []string{ 333 handleroptions.M3AggregatorServiceName, 334 handleroptions.M3DBServiceName, 335 } { 336 assert.False(t, isStateless(s)) 337 } 338 }