github.com/m3db/m3@v1.5.0/src/dbnode/storage/bootstrap/bootstrapper/uninitialized/source_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 uninitialized 22 23 import ( 24 "errors" 25 "testing" 26 "time" 27 28 "github.com/m3db/m3/src/cluster/shard" 29 "github.com/m3db/m3/src/dbnode/namespace" 30 "github.com/m3db/m3/src/dbnode/persist/fs" 31 "github.com/m3db/m3/src/dbnode/storage/bootstrap" 32 "github.com/m3db/m3/src/dbnode/storage/bootstrap/result" 33 "github.com/m3db/m3/src/dbnode/topology" 34 tu "github.com/m3db/m3/src/dbnode/topology/testutil" 35 "github.com/m3db/m3/src/x/ident" 36 "github.com/m3db/m3/src/x/instrument" 37 xtime "github.com/m3db/m3/src/x/time" 38 39 "github.com/stretchr/testify/require" 40 ) 41 42 var ( 43 testNamespaceID = ident.StringID("testnamespace") 44 testDefaultRunOpts = bootstrap.NewRunOptions() 45 notSelfID1 = "not-self-1" 46 notSelfID2 = "not-self-2" 47 notSelfID3 = "not-self-3" 48 ) 49 50 func TestUnitializedTopologySourceAvailableDataAndAvailableIndex(t *testing.T) { 51 var ( 52 blockSize = 2 * time.Hour 53 numShards = uint32(4) 54 blockStart = xtime.Now().Truncate(blockSize) 55 shardTimeRangesToBootstrap = result.NewShardTimeRanges() 56 bootstrapRanges = xtime.NewRanges(xtime.Range{ 57 Start: blockStart, 58 End: blockStart.Add(blockSize), 59 }) 60 cacheOptions = bootstrap.NewCacheOptions(). 61 SetFilesystemOptions(fs.NewOptions()). 62 SetInstrumentOptions(instrument.NewOptions()) 63 ) 64 nsOpts := namespace.NewOptions() 65 nsOpts = nsOpts.SetIndexOptions(nsOpts.IndexOptions().SetEnabled(true)) 66 nsMetadata, err := namespace.NewMetadata(testNamespaceID, nsOpts) 67 require.NoError(t, err) 68 69 for i := 0; i < int(numShards); i++ { 70 shardTimeRangesToBootstrap.Set(uint32(i), bootstrapRanges) 71 } 72 73 testCases := []struct { 74 title string 75 topoState *topology.StateSnapshot 76 shardsTimeRangesToBootstrap result.ShardTimeRanges 77 expectedAvailableShardsTimeRanges result.ShardTimeRanges 78 expectedErr error 79 }{ 80 // Snould return that it can bootstrap everything because 81 // it's a new namespace. 82 { 83 title: "Single node - Shard initializing", 84 topoState: tu.NewStateSnapshot(1, tu.HostShardStates{ 85 tu.SelfID: tu.ShardsRange(0, numShards, shard.Initializing), 86 }), 87 shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap, 88 expectedAvailableShardsTimeRanges: shardTimeRangesToBootstrap, 89 }, 90 // Snould return that it can't bootstrap anything because we don't 91 // know how to handle unknown shard states. 92 { 93 title: "Single node - Shard unknown", 94 topoState: tu.NewStateSnapshot(1, tu.HostShardStates{ 95 tu.SelfID: tu.ShardsRange(0, numShards, shard.Unknown), 96 }), 97 shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap, 98 expectedErr: errors.New("unknown shard state: Unknown"), 99 }, 100 // Snould return that it can't bootstrap anything because it's not 101 // a new namespace. 102 { 103 title: "Single node - Shard leaving", 104 topoState: tu.NewStateSnapshot(1, tu.HostShardStates{ 105 tu.SelfID: tu.ShardsRange(0, numShards, shard.Leaving), 106 }), 107 shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap, 108 expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(), 109 }, 110 // Snould return that it can't bootstrap anything because it's not 111 // a new namespace. 112 { 113 title: "Single node - Shard available", 114 topoState: tu.NewStateSnapshot(1, tu.HostShardStates{ 115 tu.SelfID: tu.ShardsRange(0, numShards, shard.Available), 116 }), 117 shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap, 118 expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(), 119 }, 120 // Snould return that it can bootstrap everything because 121 // it's a new namespace. 122 { 123 title: "Multi node - Brand new namespace (all nodes initializing)", 124 topoState: tu.NewStateSnapshot(2, tu.HostShardStates{ 125 tu.SelfID: tu.ShardsRange(0, numShards, shard.Initializing), 126 notSelfID1: tu.ShardsRange(0, numShards, shard.Initializing), 127 notSelfID2: tu.ShardsRange(0, numShards, shard.Initializing), 128 }), 129 shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap, 130 expectedAvailableShardsTimeRanges: shardTimeRangesToBootstrap, 131 }, 132 // Snould return that it can bootstrap everything because 133 // it's a new namespace (one of the nodes hasn't completed 134 // initializing yet.) 135 { 136 title: "Multi node - Recently created namespace (one node still initializing)", 137 topoState: tu.NewStateSnapshot(2, tu.HostShardStates{ 138 tu.SelfID: tu.ShardsRange(0, numShards, shard.Initializing), 139 notSelfID1: tu.ShardsRange(0, numShards, shard.Available), 140 notSelfID2: tu.ShardsRange(0, numShards, shard.Available), 141 }), 142 shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap, 143 expectedAvailableShardsTimeRanges: shardTimeRangesToBootstrap, 144 }, 145 // Snould return that it can't bootstrap anything because it's not 146 // a new namespace. 147 { 148 title: "Multi node - Initialized namespace (no nodes initializing)", 149 topoState: tu.NewStateSnapshot(2, tu.HostShardStates{ 150 tu.SelfID: tu.ShardsRange(0, numShards, shard.Available), 151 notSelfID1: tu.ShardsRange(0, numShards, shard.Available), 152 notSelfID2: tu.ShardsRange(0, numShards, shard.Available), 153 }), 154 shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap, 155 expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(), 156 }, 157 // Snould return that it can't bootstrap anything because it's not 158 // a new namespace, we're just doing a node replace. 159 { 160 title: "Multi node - Node replace (one node leaving, one initializing)", 161 topoState: tu.NewStateSnapshot(2, tu.HostShardStates{ 162 tu.SelfID: tu.ShardsRange(0, numShards, shard.Available), 163 notSelfID1: tu.ShardsRange(0, numShards, shard.Leaving), 164 notSelfID2: tu.ShardsRange(0, numShards, shard.Available), 165 notSelfID3: tu.ShardsRange(0, numShards, shard.Initializing), 166 }), 167 shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap, 168 expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(), 169 }, 170 // Snould return that it can't bootstrap anything because we don't 171 // know how to interpret the unknown host. 172 { 173 title: "Multi node - One node unknown", 174 topoState: tu.NewStateSnapshot(2, tu.HostShardStates{ 175 tu.SelfID: tu.ShardsRange(0, numShards, shard.Available), 176 notSelfID1: tu.ShardsRange(0, numShards, shard.Available), 177 notSelfID2: tu.ShardsRange(0, numShards, shard.Unknown), 178 }), 179 shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap, 180 expectedErr: errors.New("unknown shard state: Unknown"), 181 }, 182 } 183 184 for _, tc := range testCases { 185 t.Run(tc.title, func(t *testing.T) { 186 187 var ( 188 srcOpts = NewOptions().SetInstrumentOptions(instrument.NewOptions()) 189 src = newTopologyUninitializedSource(srcOpts) 190 runOpts = testDefaultRunOpts.SetInitialTopologyState(tc.topoState) 191 ) 192 var shards []uint32 193 for shard := range tc.shardsTimeRangesToBootstrap.Iter() { 194 shards = append(shards, shard) 195 } 196 cache, sErr := bootstrap.NewCache(cacheOptions. 197 SetNamespaceDetails([]bootstrap.NamespaceDetails{ 198 { 199 Namespace: nsMetadata, 200 Shards: shards, 201 }, 202 })) 203 require.NoError(t, sErr) 204 205 dataAvailabilityResult, dataErr := src.AvailableData(nsMetadata, tc.shardsTimeRangesToBootstrap, cache, runOpts) 206 indexAvailabilityResult, indexErr := src.AvailableIndex(nsMetadata, tc.shardsTimeRangesToBootstrap, cache, runOpts) 207 208 if tc.expectedErr != nil { 209 require.Equal(t, tc.expectedErr, dataErr) 210 require.Equal(t, tc.expectedErr, indexErr) 211 } else { 212 // Make sure AvailableData and AvailableIndex return the correct result 213 require.Equal(t, tc.expectedAvailableShardsTimeRanges, dataAvailabilityResult) 214 require.Equal(t, tc.expectedAvailableShardsTimeRanges, indexAvailabilityResult) 215 216 // Make sure Read marks anything that available ranges wouldn't return as unfulfilled 217 tester := bootstrap.BuildNamespacesTester(t, runOpts, tc.shardsTimeRangesToBootstrap, nsMetadata) 218 defer tester.Finish() 219 tester.TestReadWith(src) 220 221 expectedDataUnfulfilled := tc.shardsTimeRangesToBootstrap.Copy() 222 expectedDataUnfulfilled.Subtract(tc.expectedAvailableShardsTimeRanges) 223 expectedIndexUnfulfilled := tc.shardsTimeRangesToBootstrap.Copy() 224 expectedIndexUnfulfilled.Subtract(tc.expectedAvailableShardsTimeRanges) 225 tester.TestUnfulfilledForNamespace( 226 nsMetadata, 227 expectedDataUnfulfilled, 228 expectedIndexUnfulfilled, 229 ) 230 231 tester.EnsureNoLoadedBlocks() 232 tester.EnsureNoWrites() 233 } 234 }) 235 } 236 }