github.com/m3db/m3@v1.5.0/src/cluster/placement/placements_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 placement
    22  
    23  import (
    24  	"testing"
    25  
    26  	"github.com/stretchr/testify/require"
    27  
    28  	"github.com/m3db/m3/src/cluster/generated/proto/placementpb"
    29  )
    30  
    31  var (
    32  	testEarliestPlacementProto = &placementpb.Placement{
    33  		NumShards:   10,
    34  		CutoverTime: 0,
    35  		Instances: map[string]*placementpb.Instance{
    36  			"i1": testInstanceProto("i1", 1),
    37  			"i2": testInstanceProto("i2", 1),
    38  		},
    39  	}
    40  	testMiddlePlacementProto = &placementpb.Placement{
    41  		NumShards:   20,
    42  		CutoverTime: 456,
    43  		Instances: map[string]*placementpb.Instance{
    44  			"i1": testInstanceProto("i1", 1),
    45  			"i2": testInstanceProto("i2", 1),
    46  			"i3": testInstanceProto("i3", 2),
    47  			"i4": testInstanceProto("i4", 2),
    48  		},
    49  	}
    50  	testLatestPlacementProto = &placementpb.Placement{
    51  		NumShards:   30,
    52  		CutoverTime: 789,
    53  		Instances: map[string]*placementpb.Instance{
    54  			"i1": testInstanceProto("i1", 1),
    55  			"i2": testInstanceProto("i2", 1),
    56  			"i3": testInstanceProto("i3", 2),
    57  			"i4": testInstanceProto("i4", 2),
    58  			"i5": testInstanceProto("i5", 3),
    59  			"i6": testInstanceProto("i6", 3),
    60  		},
    61  	}
    62  	// Snapshots listed intentionally out of sorted order by CutoverTime
    63  	testPlacementsProto = []*placementpb.Placement{
    64  		testMiddlePlacementProto,
    65  		testLatestPlacementProto,
    66  		testEarliestPlacementProto,
    67  	}
    68  	testStagedPlacementProto = &placementpb.PlacementSnapshots{
    69  		Snapshots: testPlacementsProto,
    70  	}
    71  )
    72  
    73  func TestPlacements(t *testing.T) {
    74  	t.Run("new_fails_for_nil", func(t *testing.T) {
    75  		_, err := NewPlacementsFromProto(nil)
    76  		require.Equal(t, errNilPlacementSnapshotsProto, err)
    77  	})
    78  	t.Run("new_fails_for_empty", func(t *testing.T) {
    79  		emptyProto := &placementpb.PlacementSnapshots{}
    80  		_, err := NewPlacementsFromProto(emptyProto)
    81  		require.Equal(t, errEmptyPlacementSnapshots, err)
    82  	})
    83  	t.Run("new_falls_back_to_snapshots_field", func(t *testing.T) {
    84  		ps, err := NewPlacementsFromProto(testStagedPlacementProto)
    85  		require.NoError(t, err)
    86  
    87  		expected, err := NewPlacementFromProto(testLatestPlacementProto)
    88  		require.NoError(t, err)
    89  		require.Equal(t, expected, ps.Latest())
    90  	})
    91  	t.Run("proto_returns_latest_by_cutover_time", func(t *testing.T) {
    92  		ps, err := NewPlacementsFromProto(testStagedPlacementProto)
    93  		require.NoError(t, err)
    94  		require.NotNil(t, ps)
    95  
    96  		actual, err := ps.Proto()
    97  		require.NoError(t, err)
    98  		require.Equal(t, 1, len(actual.Snapshots))
    99  		require.Equal(t, testLatestPlacementProto, actual.Snapshots[0])
   100  	})
   101  	t.Run("backward_compatible_latest_returns_latest_by_cutover_time", func(t *testing.T) {
   102  		ps, err := NewPlacementsFromProto(testStagedPlacementProto)
   103  		require.NoError(t, err)
   104  
   105  		expected, err := NewPlacementFromProto(testLatestPlacementProto)
   106  		require.NoError(t, err)
   107  		require.Equal(t, expected, ps.Latest())
   108  	})
   109  	t.Run("compatible_with_single_snapshot_in_the_snapshots_field", func(t *testing.T) {
   110  		singleSnapshotProto := &placementpb.PlacementSnapshots{
   111  			Snapshots: []*placementpb.Placement{
   112  				testLatestPlacementProto,
   113  			},
   114  		}
   115  		ps, err := NewPlacementsFromProto(singleSnapshotProto)
   116  		require.NoError(t, err)
   117  
   118  		expected, err := NewPlacementFromProto(testLatestPlacementProto)
   119  		require.NoError(t, err)
   120  		require.Equal(t, expected, ps.Latest())
   121  	})
   122  	t.Run("new_decompresses_compressed_placement", func(t *testing.T) {
   123  		compressed, err := compressPlacementProto(testLatestPlacementProto)
   124  		require.NoError(t, err)
   125  		compressedProto := &placementpb.PlacementSnapshots{
   126  			CompressMode:        placementpb.CompressMode_ZSTD,
   127  			CompressedPlacement: compressed,
   128  		}
   129  
   130  		ps, err := NewPlacementsFromProto(compressedProto)
   131  		require.NoError(t, err)
   132  		require.NotNil(t, ps)
   133  		actual := ps.Latest()
   134  		require.NotNil(t, actual)
   135  
   136  		expected, err := NewPlacementFromProto(testLatestPlacementProto)
   137  		require.NoError(t, err)
   138  
   139  		require.Equal(t, expected, actual)
   140  	})
   141  	t.Run("new_fails_to_decompress_invalid_proto", func(t *testing.T) {
   142  		invalidProto := &placementpb.PlacementSnapshots{
   143  			CompressMode:        placementpb.CompressMode_ZSTD,
   144  			CompressedPlacement: nil,
   145  		}
   146  
   147  		_, err := NewPlacementsFromProto(invalidProto)
   148  		require.Equal(t, errNilValue, err)
   149  	})
   150  	t.Run("new_fails_to_decompress_corrupt_proto", func(t *testing.T) {
   151  		compressed, err := compressPlacementProto(testLatestPlacementProto)
   152  		require.NoError(t, err)
   153  
   154  		i := len(compressed) / 2
   155  		compressed[i] = (compressed[i] + 1) % 255 // corrupt
   156  		corruptProto := &placementpb.PlacementSnapshots{
   157  			CompressMode:        placementpb.CompressMode_ZSTD,
   158  			CompressedPlacement: compressed,
   159  		}
   160  
   161  		_, err = NewPlacementsFromProto(corruptProto)
   162  		require.Error(t, err)
   163  	})
   164  	t.Run("proto_compressed_compresses_placement", func(t *testing.T) {
   165  		ps, err := NewPlacementsFromProto(testStagedPlacementProto)
   166  		require.NoError(t, err)
   167  		require.NotNil(t, ps)
   168  
   169  		compressedProto, err := ps.ProtoCompressed()
   170  		require.NoError(t, err)
   171  		require.Equal(t, placementpb.CompressMode_ZSTD, compressedProto.CompressMode)
   172  		require.Equal(t, 0, len(compressedProto.Snapshots))
   173  
   174  		decompressedProto, err := decompressPlacementProto(compressedProto.CompressedPlacement)
   175  		require.NoError(t, err)
   176  
   177  		require.Equal(t, testLatestPlacementProto.String(), decompressedProto.String())
   178  	})
   179  }
   180  
   181  func testLatestPlacement(t *testing.T) Placement {
   182  	t.Helper()
   183  	p, err := NewPlacementFromProto(testLatestPlacementProto)
   184  	require.NoError(t, err)
   185  
   186  	return p
   187  }
   188  
   189  func testInstanceProto(id string, shardSetID uint32) *placementpb.Instance {
   190  	instance := NewInstance().
   191  		SetID(id).
   192  		SetShardSetID(shardSetID).
   193  		SetIsolationGroup("rack-" + id).
   194  		SetEndpoint("endpoint-" + id).
   195  		SetMetadata(InstanceMetadata{DebugPort: 80}).
   196  		SetWeight(1)
   197  
   198  	result, err := instance.Proto()
   199  	if err != nil {
   200  		panic(err)
   201  	}
   202  
   203  	return result
   204  }