github.com/m3db/m3@v1.5.0/src/dbnode/storage/bootstrap/bootstrapper/peers/source_index_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 peers 22 23 import ( 24 "io/ioutil" 25 "os" 26 "sort" 27 "testing" 28 "time" 29 30 "github.com/m3db/m3/src/dbnode/client" 31 "github.com/m3db/m3/src/dbnode/namespace" 32 "github.com/m3db/m3/src/dbnode/persist/fs" 33 "github.com/m3db/m3/src/dbnode/retention" 34 "github.com/m3db/m3/src/dbnode/storage/block" 35 "github.com/m3db/m3/src/dbnode/storage/bootstrap" 36 "github.com/m3db/m3/src/dbnode/storage/bootstrap/result" 37 "github.com/m3db/m3/src/dbnode/storage/series" 38 "github.com/m3db/m3/src/dbnode/ts" 39 idxpersist "github.com/m3db/m3/src/m3ninx/persist" 40 "github.com/m3db/m3/src/x/checked" 41 "github.com/m3db/m3/src/x/ident" 42 xtime "github.com/m3db/m3/src/x/time" 43 44 "github.com/golang/mock/gomock" 45 "github.com/stretchr/testify/require" 46 ) 47 48 var ( 49 testShard = uint32(0) 50 testFileMode = os.FileMode(0666) 51 testDirMode = os.ModeDir | os.FileMode(0755) 52 testWriterBufferSize = 10 53 ) 54 55 func newTestFsOptions(filePathPrefix string) fs.Options { 56 return fs.NewOptions(). 57 SetFilePathPrefix(filePathPrefix). 58 SetWriterBufferSize(testWriterBufferSize). 59 SetNewFileMode(testFileMode). 60 SetNewDirectoryMode(testDirMode) 61 } 62 63 func createTempDir(t *testing.T) string { 64 dir, err := ioutil.TempDir("", "foo") 65 require.NoError(t, err) 66 return dir 67 } 68 69 type testSeriesMetadata struct { 70 id string 71 tags map[string]string 72 data []byte 73 } 74 75 func (s testSeriesMetadata) ID() ident.ID { 76 return ident.StringID(s.id) 77 } 78 79 func (s testSeriesMetadata) Tags() ident.Tags { 80 if s.tags == nil { 81 return ident.Tags{} 82 } 83 84 // Return in sorted order for deterministic order 85 var keys []string 86 for key := range s.tags { 87 keys = append(keys, key) 88 } 89 sort.Strings(keys) 90 91 var tags ident.Tags 92 for _, key := range keys { 93 tags.Append(ident.StringTag(key, s.tags[key])) 94 } 95 96 return tags 97 } 98 99 type testOptions struct { 100 name string 101 indexBlockStart xtime.UnixNano 102 expectedIndexBlocks int 103 retentionPeriod time.Duration 104 } 105 106 func TestBootstrapIndex(t *testing.T) { 107 tests := []testOptions{ 108 { 109 name: "now", 110 indexBlockStart: xtime.Now(), 111 expectedIndexBlocks: 12, 112 retentionPeriod: 48 * time.Hour, 113 }, 114 { 115 name: "now - 8h (out of retention)", 116 indexBlockStart: xtime.Now().Add(-8 * time.Hour), 117 expectedIndexBlocks: 0, 118 retentionPeriod: 4 * time.Hour, 119 }, 120 } 121 for _, test := range tests { 122 t.Run(test.name, func(t *testing.T) { 123 testBootstrapIndex(t, test) 124 }) 125 } 126 } 127 128 //nolint 129 func testBootstrapIndex(t *testing.T, test testOptions) { 130 ctrl := gomock.NewController(t) 131 defer ctrl.Finish() 132 133 opts := newTestDefaultOpts(t, ctrl) 134 opts = opts.SetResultOptions(result.NewOptions(). 135 SetSeriesCachePolicy(series.CacheLRU)) 136 pm, err := fs.NewPersistManager(opts.FilesystemOptions()) 137 require.NoError(t, err) 138 opts = opts.SetPersistManager(pm) 139 140 blockSize := 2 * time.Hour 141 indexBlockSize := 2 * blockSize 142 143 ropts := retention.NewOptions(). 144 SetBlockSize(blockSize). 145 SetRetentionPeriod(test.retentionPeriod) 146 147 nsMetadata := testNamespaceMetadata(t, func(opts namespace.Options) namespace.Options { 148 return opts. 149 SetRetentionOptions(ropts). 150 SetIndexOptions(opts.IndexOptions(). 151 SetEnabled(true). 152 SetBlockSize(indexBlockSize)) 153 }) 154 155 at := test.indexBlockStart 156 start := at.Add(-ropts.RetentionPeriod()).Truncate(blockSize) 157 indexStart := start.Truncate(indexBlockSize) 158 for !start.Equal(indexStart) { 159 // make sure data blocks overlap, test block size is 2h 160 // and test index block size is 4h 161 start = start.Add(blockSize) 162 indexStart = start.Truncate(indexBlockSize) 163 } 164 165 fooSeries := struct { 166 id string 167 tags map[string]string 168 }{ 169 "foo", 170 map[string]string{"aaa": "bbb", "ccc": "ddd"}, 171 } 172 dataBlocks := []struct { 173 blockStart xtime.UnixNano 174 series []testSeriesMetadata 175 }{ 176 { 177 blockStart: start, 178 series: []testSeriesMetadata{ 179 {fooSeries.id, fooSeries.tags, []byte{0x1}}, 180 {"bar", map[string]string{"eee": "fff", "ggg": "hhh"}, []byte{0x1}}, 181 {"baz", map[string]string{"iii": "jjj", "kkk": "lll"}, []byte{0x1}}, 182 }, 183 }, 184 { 185 blockStart: start.Add(blockSize), 186 series: []testSeriesMetadata{ 187 {fooSeries.id, fooSeries.tags, []byte{0x2}}, 188 {"qux", map[string]string{"mmm": "nnn", "ooo": "ppp"}, []byte{0x2}}, 189 {"qaz", map[string]string{"qqq": "rrr", "sss": "ttt"}, []byte{0x2}}, 190 }, 191 }, 192 { 193 blockStart: start.Add(2 * blockSize), 194 series: []testSeriesMetadata{ 195 {fooSeries.id, fooSeries.tags, []byte{0x3}}, 196 {"qan", map[string]string{"uuu": "vvv", "www": "xxx"}, []byte{0x3}}, 197 {"qam", map[string]string{"yyy": "zzz", "000": "111"}, []byte{0x3}}, 198 }, 199 }, 200 } 201 202 dir := createTempDir(t) 203 defer os.RemoveAll(dir) 204 opts = opts.SetFilesystemOptions(newTestFsOptions(dir)) 205 206 end := start.Add(ropts.RetentionPeriod()) 207 208 shardTimeRanges := result.NewShardTimeRanges().Set( 209 0, 210 xtime.NewRanges(xtime.Range{ 211 Start: start, 212 End: end, 213 }), 214 ) 215 216 // data block start at the edge of retention so return those first. 217 var dataBlocksIdx int 218 mockAdminSession := client.NewMockAdminSession(ctrl) 219 mockAdminSession.EXPECT(). 220 FetchBootstrapBlocksFromPeers(gomock.Any(), 221 gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( 222 func( 223 _ namespace.Metadata, 224 _ uint32, 225 blockStart xtime.UnixNano, 226 blockEnd xtime.UnixNano, 227 _ result.Options, 228 ) (result.ShardResult, error) { 229 goodID := ident.StringID("foo") 230 goodResult := result.NewShardResult(opts.ResultOptions()) 231 for ; blockStart.Before(blockEnd); blockStart = blockStart.Add(blockSize) { 232 if dataBlocksIdx < len(dataBlocks) { 233 dataBlock := dataBlocks[dataBlocksIdx] 234 for _, s := range dataBlock.series { 235 head := checked.NewBytes(s.data, nil) 236 head.IncRef() 237 block := block.NewDatabaseBlock(blockStart, ropts.BlockSize(), 238 ts.Segment{Head: head}, testBlockOpts, namespace.Context{}) 239 goodResult.AddBlock(s.ID(), s.Tags(), block) 240 } 241 dataBlocksIdx++ 242 continue 243 } 244 245 head := checked.NewBytes([]byte{0x1}, nil) 246 head.IncRef() 247 fooBlock := block.NewDatabaseBlock(blockStart, ropts.BlockSize(), 248 ts.Segment{Head: head}, testBlockOpts, namespace.Context{}) 249 goodResult.AddBlock(goodID, ident.NewTags( 250 ident.StringTag("aaa", "bbb"), 251 ident.StringTag("ccc", "ddd"), 252 ), fooBlock) 253 } 254 return goodResult, nil 255 }).AnyTimes() 256 257 mockAdminClient := client.NewMockAdminClient(ctrl) 258 mockAdminClient.EXPECT().DefaultAdminSession().Return(mockAdminSession, nil).AnyTimes() 259 opts = opts.SetAdminClient(mockAdminClient) 260 src, err := newPeersSource(opts) 261 require.NoError(t, err) 262 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, 263 testRunOptsWithPersist, shardTimeRanges, 264 opts.FilesystemOptions(), nsMetadata) 265 defer tester.Finish() 266 tester.TestReadWith(src) 267 268 tester.TestUnfulfilledForNamespaceIsEmpty(nsMetadata) 269 results := tester.ResultForNamespace(nsMetadata.ID()) 270 indexResults := results.IndexResult.IndexResults() 271 numIndexBlocks := 0 272 for _, indexBlockByVolumeType := range indexResults { 273 indexBlock, ok := indexBlockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 274 require.True(t, ok) 275 if len(indexBlock.Segments()) != 0 { 276 numIndexBlocks++ 277 } 278 } 279 require.Equal(t, test.expectedIndexBlocks, numIndexBlocks) 280 281 if numIndexBlocks > 0 { 282 for _, expected := range []*struct { 283 indexBlockStart xtime.UnixNano 284 series map[string]testSeriesMetadata 285 }{ 286 { 287 indexBlockStart: indexStart, 288 series: map[string]testSeriesMetadata{ 289 dataBlocks[0].series[0].id: dataBlocks[0].series[0], 290 dataBlocks[0].series[1].id: dataBlocks[0].series[1], 291 dataBlocks[0].series[2].id: dataBlocks[0].series[2], 292 dataBlocks[1].series[1].id: dataBlocks[1].series[1], 293 dataBlocks[1].series[2].id: dataBlocks[1].series[2], 294 }, 295 }, 296 { 297 indexBlockStart: indexStart.Add(indexBlockSize), 298 series: map[string]testSeriesMetadata{ 299 dataBlocks[2].series[0].id: dataBlocks[2].series[0], 300 dataBlocks[2].series[1].id: dataBlocks[2].series[1], 301 dataBlocks[2].series[2].id: dataBlocks[2].series[2], 302 }, 303 }, 304 } { 305 expectedAt := expected.indexBlockStart 306 indexBlockByVolumeType, ok := indexResults[expectedAt] 307 require.True(t, ok) 308 indexBlock, ok := indexBlockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 309 require.True(t, ok) 310 for _, seg := range indexBlock.Segments() { 311 reader, err := seg.Segment().Reader() 312 require.NoError(t, err) 313 314 docs, err := reader.AllDocs() 315 require.NoError(t, err) 316 317 matches := map[string]struct{}{} 318 for docs.Next() { 319 curr := docs.Current() 320 321 _, ok := matches[string(curr.ID)] 322 require.False(t, ok) 323 matches[string(curr.ID)] = struct{}{} 324 325 series, ok := expected.series[string(curr.ID)] 326 require.True(t, ok) 327 328 matchingTags := map[string]struct{}{} 329 for _, tag := range curr.Fields { 330 _, ok := matchingTags[string(tag.Name)] 331 require.False(t, ok) 332 matchingTags[string(tag.Name)] = struct{}{} 333 334 tagValue, ok := series.tags[string(tag.Name)] 335 require.True(t, ok) 336 337 require.Equal(t, tagValue, string(tag.Value)) 338 } 339 require.Equal(t, len(series.tags), len(matchingTags)) 340 } 341 require.NoError(t, docs.Err()) 342 require.NoError(t, docs.Close()) 343 344 require.Equal(t, len(expected.series), len(matches)) 345 } 346 } 347 348 t1 := indexStart 349 t2 := indexStart.Add(indexBlockSize) 350 t3 := t2.Add(indexBlockSize) 351 352 indexBlockByVolumeType, ok := indexResults[t1] 353 require.True(t, ok) 354 blk1, ok := indexBlockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 355 require.True(t, ok) 356 assertShardRangesEqual(t, result.NewShardTimeRangesFromRange(t1, t2, 0), blk1.Fulfilled()) 357 358 indexBlockByVolumeType, ok = indexResults[t2] 359 require.True(t, ok) 360 blk2, ok := indexBlockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 361 require.True(t, ok) 362 assertShardRangesEqual(t, result.NewShardTimeRangesFromRange(t2, t3, 0), blk2.Fulfilled()) 363 364 for _, indexBlockByVolumeType := range indexResults { 365 if indexBlockByVolumeType.BlockStart().Equal(t1) || indexBlockByVolumeType.BlockStart().Equal(t2) { 366 continue // already checked above 367 } 368 // rest should all be marked fulfilled despite no data, because we didn't see 369 // any errors in the response. 370 start := indexBlockByVolumeType.BlockStart() 371 end := start.Add(indexBlockSize) 372 blk, ok := indexBlockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 373 require.True(t, ok) 374 assertShardRangesEqual(t, result.NewShardTimeRangesFromRange(start, end, 0), blk.Fulfilled()) 375 } 376 } 377 tester.EnsureNoWrites() 378 } 379 380 func assertShardRangesEqual(t *testing.T, a, b result.ShardTimeRanges) { 381 ac := a.Copy() 382 ac.Subtract(b) 383 require.True(t, ac.IsEmpty()) 384 bc := b.Copy() 385 bc.Subtract(a) 386 require.True(t, bc.IsEmpty()) 387 }