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  }