github.com/m3db/m3@v1.5.0/src/cluster/placement/algo/placement_checker_test.go (about)

     1  // Copyright (c) 2021 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 algo
    22  
    23  import (
    24  	"fmt"
    25  	"testing"
    26  
    27  	"github.com/stretchr/testify/require"
    28  
    29  	"github.com/m3db/m3/src/cluster/placement"
    30  	"github.com/m3db/m3/src/cluster/shard"
    31  )
    32  
    33  type placementCheckerTest struct {
    34  	name     string
    35  	globalFn func(placement.Placement, []string, int64) bool
    36  	localFn  func(placement.Placement, []string, int64) bool
    37  	cases    []placementCheckerTestCase
    38  }
    39  
    40  type placementCheckerTestCase struct {
    41  	instances      []string
    42  	expectedGlobal bool
    43  	expectedLocal  bool
    44  }
    45  
    46  //nolint:dupl
    47  func TestPlacementChecker(t *testing.T) {
    48  	var nowNanos int64 = 10
    49  	pastNanos := nowNanos - 1
    50  	futureNanos := nowNanos + 1
    51  
    52  	i1 := newTestInstance("i1").SetShards(newTestShards(shard.Available, 1, 4, 0, pastNanos))
    53  	i2 := newTestInstance("i2").SetShards(newTestShards(shard.Available, 1, 4, 0, pastNanos))
    54  	i3 := newTestInstance("i3").SetShards(newTestShards(shard.Leaving, 5, 8, futureNanos, 0))
    55  	i4 := newTestInstance("i4").SetShards(newTestShards(shard.Leaving, 5, 8, futureNanos, 0))
    56  	i5 := newTestInstance("i5").SetShards(newTestShards(shard.Initializing, 9, 12, 0, futureNanos))
    57  	i6 := newTestInstance("i6").SetShards(newTestShards(shard.Initializing, 9, 12, 0, futureNanos))
    58  	p := placement.NewPlacement().SetInstances([]placement.Instance{i1, i2, i3, i4, i5, i6})
    59  
    60  	tests := []placementCheckerTest{
    61  		{
    62  			name:     "allAvailable",
    63  			globalFn: globalChecker.allAvailable,
    64  			localFn:  localChecker.allAvailable,
    65  			cases: []placementCheckerTestCase{
    66  				{
    67  					instances:      []string{"i1", "i2"},
    68  					expectedGlobal: true,
    69  					expectedLocal:  true,
    70  				},
    71  				{
    72  					instances:      []string{"i1"},
    73  					expectedGlobal: false,
    74  					expectedLocal:  true,
    75  				},
    76  				{
    77  					instances:      []string{"i2"},
    78  					expectedGlobal: false,
    79  					expectedLocal:  true,
    80  				},
    81  				{
    82  					instances:      []string{"i3"},
    83  					expectedGlobal: false,
    84  					expectedLocal:  false,
    85  				},
    86  				{
    87  					instances:      []string{"i1,i3"},
    88  					expectedGlobal: false,
    89  					expectedLocal:  false,
    90  				},
    91  				{
    92  					instances:      []string{"non-existent"},
    93  					expectedGlobal: false,
    94  					expectedLocal:  false,
    95  				},
    96  				{
    97  					instances:      []string{},
    98  					expectedGlobal: false,
    99  					expectedLocal:  false,
   100  				},
   101  			},
   102  		},
   103  		{
   104  			name:     "allLeaving",
   105  			globalFn: globalChecker.allLeavingByIDs,
   106  			localFn:  localChecker.allLeavingByIDs,
   107  			cases: []placementCheckerTestCase{
   108  				{
   109  					instances:      []string{"i3", "i4"},
   110  					expectedGlobal: true,
   111  					expectedLocal:  true,
   112  				},
   113  				{
   114  					instances:      []string{"i3"},
   115  					expectedGlobal: false,
   116  					expectedLocal:  true,
   117  				},
   118  				{
   119  					instances:      []string{"i4"},
   120  					expectedGlobal: false,
   121  					expectedLocal:  true,
   122  				},
   123  				{
   124  					instances:      []string{"i1"},
   125  					expectedGlobal: false,
   126  					expectedLocal:  false,
   127  				},
   128  				{
   129  					instances:      []string{"i3,i6"},
   130  					expectedGlobal: false,
   131  					expectedLocal:  false,
   132  				},
   133  				{
   134  					instances:      []string{"non-existent"},
   135  					expectedGlobal: false,
   136  					expectedLocal:  false,
   137  				},
   138  				{
   139  					instances:      []string{},
   140  					expectedGlobal: false,
   141  					expectedLocal:  false,
   142  				},
   143  			},
   144  		},
   145  		{
   146  			name:     "allInitializing",
   147  			globalFn: globalChecker.allInitializing,
   148  			localFn:  localChecker.allInitializing,
   149  			cases: []placementCheckerTestCase{
   150  				{
   151  					instances:      []string{"i5", "i6"},
   152  					expectedGlobal: true,
   153  					expectedLocal:  true,
   154  				},
   155  				{
   156  					instances:      []string{"i5"},
   157  					expectedGlobal: false,
   158  					expectedLocal:  true,
   159  				},
   160  				{
   161  					instances:      []string{"i6"},
   162  					expectedGlobal: false,
   163  					expectedLocal:  true,
   164  				},
   165  				{
   166  					instances:      []string{"i1"},
   167  					expectedGlobal: false,
   168  					expectedLocal:  false,
   169  				},
   170  				{
   171  					instances:      []string{"i5,i1"},
   172  					expectedGlobal: false,
   173  					expectedLocal:  false,
   174  				},
   175  				{
   176  					instances:      []string{"non-existent"},
   177  					expectedGlobal: false,
   178  					expectedLocal:  false,
   179  				},
   180  				{
   181  					instances:      []string{},
   182  					expectedGlobal: false,
   183  					expectedLocal:  false,
   184  				},
   185  			},
   186  		},
   187  	}
   188  
   189  	for _, test := range tests {
   190  		for _, tc := range test.cases {
   191  			testName := fmt.Sprintf("%s(%+v)", test.name, tc.instances)
   192  			//nolint:scopelint
   193  			t.Run(testName, func(t *testing.T) {
   194  				require.Equal(t, tc.expectedGlobal, test.globalFn(p, tc.instances, nowNanos))
   195  				require.Equal(t, tc.expectedLocal, test.localFn(p, tc.instances, nowNanos))
   196  			})
   197  		}
   198  	}
   199  }
   200  
   201  func newTestShards(s shard.State, minID, maxID uint32, cutoffNanos int64, cutoverNanos int64) shard.Shards {
   202  	var shards []shard.Shard
   203  	for id := minID; id <= maxID; id++ {
   204  		s := shard.NewShard(id).SetState(s).SetCutoffNanos(cutoffNanos).SetCutoverNanos(cutoverNanos)
   205  		shards = append(shards, s)
   206  	}
   207  
   208  	return shard.NewShards(shards)
   209  }