github.com/m3db/m3@v1.5.0/src/cluster/placement/storage/helper_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 storage 22 23 import ( 24 "testing" 25 26 "github.com/m3db/m3/src/cluster/generated/proto/placementpb" 27 "github.com/m3db/m3/src/cluster/kv/mem" 28 "github.com/m3db/m3/src/cluster/placement" 29 30 "github.com/gogo/protobuf/proto" 31 "github.com/stretchr/testify/require" 32 ) 33 34 func TestPlacementHelper(t *testing.T) { 35 protoShards := getProtoShards([]uint32{0, 1, 2}) 36 proto1 := &placementpb.Placement{ 37 Instances: map[string]*placementpb.Instance{ 38 "i1": newTestProtoInstance("i1", 0, protoShards), 39 "i2": newTestProtoInstance("i2", 1, protoShards), 40 }, 41 ReplicaFactor: 2, 42 NumShards: 3, 43 IsSharded: true, 44 CutoverTime: 1000, 45 MaxShardSetId: 1, 46 } 47 key := "key" 48 store := mem.NewStore() 49 _, err := store.Set(key, proto1) 50 require.NoError(t, err) 51 52 helper := newPlacementHelper(store, key) 53 54 p, v, err := helper.Placement() 55 require.NoError(t, err) 56 require.Equal(t, 1, v) 57 58 _, err = helper.PlacementForVersion(0) 59 require.Error(t, err) 60 61 _, err = helper.PlacementForVersion(2) 62 require.Error(t, err) 63 64 h, err := helper.PlacementForVersion(1) 65 require.NoError(t, err) 66 require.Equal(t, p, h) 67 68 m, err := helper.GenerateProto(p) 69 require.NoError(t, err) 70 71 newProto := m.(*placementpb.Placement) 72 require.Equal(t, proto1, newProto) 73 74 err = helper.ValidateProto(m) 75 require.NoError(t, err) 76 77 err = helper.ValidateProto(&placementpb.Instance{Id: "id"}) 78 require.Error(t, err) 79 require.Equal(t, errInvalidProtoForSinglePlacement, err) 80 } 81 82 func TestPlacementSnapshotsHelper(t *testing.T) { 83 protoShards := getProtoShards([]uint32{0, 1, 2}) 84 proto1 := &placementpb.Placement{ 85 Instances: map[string]*placementpb.Instance{ 86 "i1": newTestProtoInstance("i1", 0, protoShards), 87 "i2": newTestProtoInstance("i2", 1, protoShards), 88 }, 89 ReplicaFactor: 2, 90 NumShards: 3, 91 IsSharded: true, 92 CutoverTime: 1000, 93 } 94 proto2 := &placementpb.Placement{ 95 Instances: map[string]*placementpb.Instance{ 96 "i1": newTestProtoInstance("i1", 0, protoShards), 97 }, 98 ReplicaFactor: 1, 99 NumShards: 3, 100 IsSharded: true, 101 CutoverTime: 2000, 102 } 103 104 setupHelper := func(proto proto.Message) helper { 105 key := "key" 106 store := mem.NewStore() 107 compressFlag := false 108 _, err := store.Set(key, proto) 109 require.NoError(t, err) 110 111 return newStagedPlacementHelper(store, key, compressFlag) 112 } 113 114 t.Run("version_is_respected", func(t *testing.T) { 115 helper := setupHelper(&placementpb.PlacementSnapshots{ 116 Snapshots: []*placementpb.Placement{ 117 proto1, 118 proto2, 119 }, 120 }) 121 122 p, v, err := helper.Placement() 123 require.NoError(t, err) 124 require.Equal(t, 1, v) 125 126 _, err = helper.PlacementForVersion(0) 127 require.Error(t, err) 128 129 _, err = helper.PlacementForVersion(2) 130 require.Error(t, err) 131 132 h, err := helper.PlacementForVersion(1) 133 require.NoError(t, err) 134 require.Equal(t, p, h) 135 }) 136 137 t.Run("generates_snapshots_with_single_specified_placement", func(t *testing.T) { 138 helper := setupHelper(&placementpb.PlacementSnapshots{ 139 Snapshots: []*placementpb.Placement{ 140 proto1, 141 proto2, 142 }, 143 }) 144 145 p, v, err := helper.Placement() 146 require.NoError(t, err) 147 require.Equal(t, 1, v) 148 require.Equal(t, proto2.GetCutoverTime(), p.CutoverNanos()) 149 150 p1 := p.SetCutoverNanos(p.CutoverNanos() + 1) 151 m, err := helper.GenerateProto(p1) 152 require.NoError(t, err) 153 require.NoError(t, helper.ValidateProto(m)) 154 155 actualProto := m.(*placementpb.PlacementSnapshots) 156 require.Equal(t, 1, len(actualProto.Snapshots)) 157 158 proto2.CutoverTime = 0 159 expectedProto := &placementpb.PlacementSnapshots{ 160 Snapshots: []*placementpb.Placement{ 161 proto2, 162 }, 163 } 164 165 require.Equal(t, expectedProto, actualProto) 166 }) 167 168 t.Run("proto_of_wrong_type_is_invalid", func(t *testing.T) { 169 helper := setupHelper(proto1) 170 err := helper.ValidateProto(proto1) 171 require.Error(t, err) 172 require.Equal(t, errInvalidProtoForPlacementSnapshots, err) 173 }) 174 175 t.Run("empty_proto", func(t *testing.T) { 176 helper := setupHelper(&placementpb.PlacementSnapshots{}) 177 178 _, _, err := helper.Placement() 179 require.Error(t, err) 180 require.Contains(t, err.Error(), "placement snapshots is empty") 181 182 _, err = helper.PlacementForVersion(1) 183 require.Error(t, err) 184 require.Contains(t, err.Error(), "placement snapshots is empty") 185 }) 186 187 t.Run("generates_compressed_proto", func(t *testing.T) { 188 expected, err := placement.NewPlacementFromProto(proto1) 189 require.NoError(t, err) 190 191 key := "key" 192 store := mem.NewStore() 193 compressFlag := true 194 helper := newStagedPlacementHelper(store, key, compressFlag) 195 196 m, err := helper.GenerateProto(expected) 197 require.NoError(t, err) 198 require.NoError(t, helper.ValidateProto(m)) 199 200 proto := m.(*placementpb.PlacementSnapshots) 201 require.NotNil(t, proto) 202 203 require.Equal(t, placementpb.CompressMode_ZSTD, proto.CompressMode) 204 require.Equal(t, 0, len(proto.Snapshots)) 205 206 ps, err := placement.NewPlacementsFromProto(proto) 207 require.NoError(t, err) 208 actual := ps.Latest() 209 require.Equal(t, expected.String(), actual.String()) 210 }) 211 } 212 213 func newTestProtoInstance(id string, shardSetID uint32, shards []*placementpb.Shard) *placementpb.Instance { 214 return &placementpb.Instance{ 215 Id: id, 216 IsolationGroup: "g-" + id, 217 Zone: "z-" + id, 218 Endpoint: "e-" + id, 219 Weight: 1, 220 Shards: shards, 221 ShardSetId: shardSetID, 222 Metadata: &placementpb.InstanceMetadata{ 223 DebugPort: 1, 224 }, 225 } 226 } 227 228 func getProtoShards(ids []uint32) []*placementpb.Shard { 229 r := make([]*placementpb.Shard, len(ids)) 230 for i, id := range ids { 231 r[i] = &placementpb.Shard{ 232 Id: id, 233 State: placementpb.ShardState_AVAILABLE, 234 } 235 } 236 return r 237 }