github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/bootstrap/bootstrapper/fs/source_data_test.go (about) 1 // Copyright (c) 2016 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 fs 22 23 import ( 24 "errors" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "os" 29 "path" 30 "sort" 31 "testing" 32 "time" 33 34 "github.com/m3db/m3/src/dbnode/digest" 35 "github.com/m3db/m3/src/dbnode/namespace" 36 "github.com/m3db/m3/src/dbnode/persist" 37 "github.com/m3db/m3/src/dbnode/persist/fs" 38 "github.com/m3db/m3/src/dbnode/persist/fs/migration" 39 "github.com/m3db/m3/src/dbnode/persist/fs/msgpack" 40 "github.com/m3db/m3/src/dbnode/retention" 41 "github.com/m3db/m3/src/dbnode/storage" 42 "github.com/m3db/m3/src/dbnode/storage/block" 43 "github.com/m3db/m3/src/dbnode/storage/bootstrap" 44 "github.com/m3db/m3/src/dbnode/storage/bootstrap/result" 45 "github.com/m3db/m3/src/dbnode/storage/index" 46 "github.com/m3db/m3/src/dbnode/storage/index/compaction" 47 "github.com/m3db/m3/src/dbnode/storage/series" 48 "github.com/m3db/m3/src/dbnode/x/xio" 49 "github.com/m3db/m3/src/m3ninx/index/segment/fst" 50 "github.com/m3db/m3/src/x/checked" 51 "github.com/m3db/m3/src/x/context" 52 "github.com/m3db/m3/src/x/ident" 53 "github.com/m3db/m3/src/x/instrument" 54 "github.com/m3db/m3/src/x/pool" 55 xtest "github.com/m3db/m3/src/x/test" 56 xtime "github.com/m3db/m3/src/x/time" 57 58 "github.com/stretchr/testify/assert" 59 "github.com/stretchr/testify/require" 60 ) 61 62 var ( 63 testShard = uint32(0) 64 testNs1ID = ident.StringID("test_namespace") 65 testBlockSize = 2 * time.Hour 66 testIndexBlockSize = 4 * time.Hour 67 testStart = xtime.Now().Truncate(testBlockSize) 68 testFileMode = os.FileMode(0666) 69 testDirMode = os.ModeDir | os.FileMode(0755) 70 testWriterBufferSize = 10 71 testNamespaceIndexOptions = namespace.NewIndexOptions() 72 testNamespaceOptions = namespace.NewOptions() 73 testRetentionOptions = retention.NewOptions() 74 testDefaultFsOpts = fs.NewOptions() 75 76 testDefaultRunOpts = bootstrap.NewRunOptions().SetPersistConfig(bootstrap.PersistConfig{}) 77 testDefaultResultOpts = result.NewOptions().SetSeriesCachePolicy(series.CacheAll) 78 testDefaultOpts = NewOptions().SetResultOptions(testDefaultResultOpts) 79 testShardRanges = testShardTimeRanges() 80 ) 81 82 func newTestOptions(t require.TestingT, filePathPrefix string) Options { 83 idxOpts := index.NewOptions() 84 compactor, err := compaction.NewCompactor(idxOpts.MetadataArrayPool(), 85 index.MetadataArrayPoolCapacity, 86 idxOpts.SegmentBuilderOptions(), 87 idxOpts.FSTSegmentOptions(), 88 compaction.CompactorOptions{ 89 FSTWriterOptions: &fst.WriterOptions{ 90 // DisableRegistry is set to true to trade a larger FST size 91 // for a faster FST compaction since we want to reduce the end 92 // to end latency for time to first index a metric. 93 DisableRegistry: true, 94 }, 95 }) 96 require.NoError(t, err) 97 fsOpts := newTestFsOptions(filePathPrefix) 98 pm, err := fs.NewPersistManager(fsOpts) 99 require.NoError(t, err) 100 101 // Allow multiple index claim managers since need to create one 102 // for each file path prefix (fs options changes between tests). 103 fs.ResetIndexClaimsManagersUnsafe() 104 105 icm, err := fs.NewIndexClaimsManager(fsOpts) 106 require.NoError(t, err) 107 return testDefaultOpts. 108 SetCompactor(compactor). 109 SetIndexOptions(idxOpts). 110 SetFilesystemOptions(fsOpts). 111 SetPersistManager(pm). 112 SetIndexClaimsManager(icm) 113 } 114 115 func newTestOptionsWithPersistManager(t require.TestingT, filePathPrefix string) Options { 116 opts := newTestOptions(t, filePathPrefix) 117 pm, err := fs.NewPersistManager(opts.FilesystemOptions()) 118 require.NoError(t, err) 119 return opts.SetPersistManager(pm) 120 } 121 122 func newTestFsOptions(filePathPrefix string) fs.Options { 123 return testDefaultFsOpts. 124 SetFilePathPrefix(filePathPrefix). 125 SetWriterBufferSize(testWriterBufferSize). 126 SetNewFileMode(testFileMode). 127 SetNewDirectoryMode(testDirMode) 128 } 129 130 func testNsMetadata(t require.TestingT) namespace.Metadata { 131 return testNsMetadataWithIndex(t, true) 132 } 133 134 func testNsMetadataWithIndex(t require.TestingT, indexOn bool) namespace.Metadata { 135 ropts := testRetentionOptions.SetBlockSize(testBlockSize) 136 md, err := namespace.NewMetadata(testNs1ID, testNamespaceOptions. 137 SetRetentionOptions(ropts). 138 SetIndexOptions(testNamespaceIndexOptions. 139 SetEnabled(indexOn). 140 SetBlockSize(testIndexBlockSize))) 141 require.NoError(t, err) 142 return md 143 } 144 145 func testCache(t *testing.T, md namespace.Metadata, ranges result.ShardTimeRanges, fsOpts fs.Options) bootstrap.Cache { 146 var shards []uint32 147 for shard := range ranges.Iter() { 148 shards = append(shards, shard) 149 } 150 cache, err := bootstrap.NewCache(bootstrap.NewCacheOptions(). 151 SetFilesystemOptions(fsOpts). 152 SetInstrumentOptions(fsOpts.InstrumentOptions()). 153 SetNamespaceDetails([]bootstrap.NamespaceDetails{ 154 { 155 Namespace: md, 156 Shards: shards, 157 }, 158 })) 159 require.NoError(t, err) 160 161 return cache 162 } 163 164 func createTempDir(t *testing.T) string { 165 dir, err := ioutil.TempDir("", "foo") 166 require.NoError(t, err) 167 return dir 168 } 169 170 func writeInfoFile(t *testing.T, prefix string, namespace ident.ID, 171 shard uint32, start xtime.UnixNano, data []byte) { 172 shardDir := fs.ShardDataDirPath(prefix, namespace, shard) 173 filePath := path.Join(shardDir, 174 fmt.Sprintf("fileset-%d-0-info.db", start)) 175 writeFile(t, filePath, data) 176 } 177 178 func writeDataFile(t *testing.T, prefix string, namespace ident.ID, 179 shard uint32, start xtime.UnixNano, data []byte) { 180 shardDir := fs.ShardDataDirPath(prefix, namespace, shard) 181 filePath := path.Join(shardDir, 182 fmt.Sprintf("fileset-%d-0-data.db", start)) 183 writeFile(t, filePath, data) 184 } 185 186 func writeDigestFile(t *testing.T, prefix string, namespace ident.ID, 187 shard uint32, start xtime.UnixNano, data []byte) { 188 shardDir := fs.ShardDataDirPath(prefix, namespace, shard) 189 filePath := path.Join(shardDir, 190 fmt.Sprintf("fileset-%d-0-digest.db", start)) 191 writeFile(t, filePath, data) 192 } 193 194 func writeFile(t *testing.T, filePath string, data []byte) { 195 fd, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, testFileMode) 196 require.NoError(t, err) 197 if data != nil { 198 _, err = fd.Write(data) 199 require.NoError(t, err) 200 } 201 require.NoError(t, fd.Close()) 202 } 203 204 func testTimeRanges() xtime.Ranges { 205 return xtime.NewRanges(xtime.Range{Start: testStart, End: testStart.Add(11 * time.Hour)}) 206 } 207 208 func testShardTimeRanges() result.ShardTimeRanges { 209 return result.NewShardTimeRanges().Set(testShard, testTimeRanges()) 210 } 211 212 func testBootstrappingIndexShardTimeRanges() result.ShardTimeRanges { 213 // NB: since index files are not corrupted on this run, it's expected that 214 // `testBlockSize` values should be fulfilled in the index block. This is 215 // `testBlockSize` rather than `testIndexSize` since the files generated 216 // by this test use 2 hour (which is `testBlockSize`) reader blocks. 217 return result.NewShardTimeRanges().Set( 218 testShard, 219 xtime.NewRanges(xtime.Range{ 220 Start: testStart.Add(testBlockSize), 221 End: testStart.Add(11 * time.Hour), 222 }), 223 ) 224 } 225 226 func writeGoodFiles(t *testing.T, dir string, namespace ident.ID, shard uint32) { 227 writeGoodFilesWithFsOpts(t, namespace, shard, newTestFsOptions(dir)) 228 } 229 230 func writeGoodFilesWithFsOpts(t *testing.T, namespace ident.ID, shard uint32, fsOpts fs.Options) { 231 inputs := []struct { 232 start xtime.UnixNano 233 id string 234 tags map[string]string 235 data []byte 236 }{ 237 {testStart, "foo", map[string]string{"n": "0"}, []byte{1, 2, 3}}, 238 {testStart.Add(10 * time.Hour), "bar", map[string]string{"n": "1"}, []byte{4, 5, 6}}, 239 {testStart.Add(20 * time.Hour), "baz", nil, []byte{7, 8, 9}}, 240 } 241 242 for _, input := range inputs { 243 writeTSDBFilesWithFsOpts(t, namespace, shard, input.start, 244 []testSeries{{input.id, input.tags, input.data}}, fsOpts) 245 } 246 } 247 248 type testSeries struct { 249 id string 250 tags map[string]string 251 data []byte 252 } 253 254 func (s testSeries) ID() ident.ID { 255 return ident.StringID(s.id) 256 } 257 258 func (s testSeries) Tags() ident.Tags { 259 if s.tags == nil { 260 return ident.Tags{} 261 } 262 263 // Return in sorted order for deterministic order 264 var keys []string 265 for key := range s.tags { 266 keys = append(keys, key) 267 } 268 sort.Strings(keys) 269 270 var tags ident.Tags 271 for _, key := range keys { 272 tags.Append(ident.StringTag(key, s.tags[key])) 273 } 274 275 return tags 276 } 277 278 func writeTSDBFiles( 279 t require.TestingT, 280 dir string, 281 namespace ident.ID, 282 shard uint32, 283 start xtime.UnixNano, 284 series []testSeries, 285 ) { 286 writeTSDBFilesWithFsOpts(t, namespace, shard, start, series, newTestFsOptions(dir)) 287 } 288 289 func writeTSDBFilesWithFsOpts( 290 t require.TestingT, 291 namespace ident.ID, 292 shard uint32, 293 start xtime.UnixNano, 294 series []testSeries, 295 opts fs.Options, 296 ) { 297 w, err := fs.NewWriter(opts) 298 require.NoError(t, err) 299 writerOpts := fs.DataWriterOpenOptions{ 300 Identifier: fs.FileSetFileIdentifier{ 301 Namespace: namespace, 302 Shard: shard, 303 BlockStart: start, 304 }, 305 BlockSize: testBlockSize, 306 } 307 require.NoError(t, w.Open(writerOpts)) 308 309 for _, v := range series { 310 bytes := checked.NewBytes(v.data, nil) 311 bytes.IncRef() 312 metadata := persist.NewMetadataFromIDAndTags(ident.StringID(v.id), sortedTagsFromTagsMap(v.tags), 313 persist.MetadataOptions{}) 314 require.NoError(t, w.Write(metadata, bytes, digest.Checksum(bytes.Bytes()))) 315 bytes.DecRef() 316 } 317 318 require.NoError(t, w.Close()) 319 } 320 321 func sortedTagsFromTagsMap(tags map[string]string) ident.Tags { 322 var ( 323 seriesTags ident.Tags 324 tagNames []string 325 ) 326 for name := range tags { 327 tagNames = append(tagNames, name) 328 } 329 sort.Strings(tagNames) 330 for _, name := range tagNames { 331 seriesTags.Append(ident.StringTag(name, tags[name])) 332 } 333 return seriesTags 334 } 335 336 func validateTimeRanges(t *testing.T, tr xtime.Ranges, expected xtime.Ranges) { 337 // Make range eclipses expected 338 expectedWithRemovedRanges := expected.Clone() 339 expectedWithRemovedRanges.RemoveRanges(tr) 340 require.True(t, expectedWithRemovedRanges.IsEmpty()) 341 342 // Now make sure no ranges outside of expected 343 expectedWithAddedRanges := expected.Clone() 344 expectedWithAddedRanges.AddRanges(tr) 345 346 require.Equal(t, expected.Len(), expectedWithAddedRanges.Len()) 347 iter := expected.Iter() 348 withAddedRangesIter := expectedWithAddedRanges.Iter() 349 for iter.Next() && withAddedRangesIter.Next() { 350 require.True(t, iter.Value().Equal(withAddedRangesIter.Value())) 351 } 352 } 353 354 func TestAvailableEmptyRangeError(t *testing.T) { 355 opts := newTestOptions(t, "foo") 356 src, err := newFileSystemSource(opts) 357 require.NoError(t, err) 358 md := testNsMetadata(t) 359 shardRanges := result.NewShardTimeRanges().Set(0, xtime.NewRanges()) 360 cache := testCache(t, md, shardRanges, opts.FilesystemOptions()) 361 res, err := src.AvailableData( 362 md, 363 shardRanges, 364 cache, 365 testDefaultRunOpts, 366 ) 367 require.NoError(t, err) 368 require.NotNil(t, res) 369 require.True(t, res.IsEmpty()) 370 } 371 372 func TestAvailablePatternError(t *testing.T) { 373 opts := newTestOptions(t, "[[") 374 src, err := newFileSystemSource(opts) 375 require.NoError(t, err) 376 md := testNsMetadata(t) 377 res, err := src.AvailableData( 378 md, 379 testShardRanges, 380 testCache(t, md, testShardRanges, opts.FilesystemOptions()), 381 testDefaultRunOpts, 382 ) 383 require.NoError(t, err) 384 require.NotNil(t, res) 385 require.True(t, res.IsEmpty()) 386 } 387 388 func TestAvailableReadInfoError(t *testing.T) { 389 dir := createTempDir(t) 390 defer os.RemoveAll(dir) 391 392 shard := uint32(0) 393 writeTSDBFiles(t, dir, testNs1ID, shard, testStart, []testSeries{ 394 {"foo", nil, []byte{0x1}}, 395 }) 396 // Intentionally corrupt the info file 397 writeInfoFile(t, dir, testNs1ID, shard, testStart, []byte{0x1, 0x2}) 398 399 opts := newTestOptions(t, dir) 400 src, err := newFileSystemSource(opts) 401 require.NoError(t, err) 402 md := testNsMetadata(t) 403 res, err := src.AvailableData( 404 md, 405 testShardRanges, 406 testCache(t, md, testShardRanges, opts.FilesystemOptions()), 407 testDefaultRunOpts, 408 ) 409 require.NoError(t, err) 410 require.NotNil(t, res) 411 require.True(t, res.IsEmpty()) 412 } 413 414 func TestAvailableDigestOfDigestMismatch(t *testing.T) { 415 dir := createTempDir(t) 416 defer os.RemoveAll(dir) 417 418 shard := uint32(0) 419 writeTSDBFiles(t, dir, testNs1ID, shard, testStart, []testSeries{ 420 {"foo", nil, []byte{0x1}}, 421 }) 422 // Intentionally corrupt the digest file 423 writeDigestFile(t, dir, testNs1ID, shard, testStart, nil) 424 425 opts := newTestOptions(t, dir) 426 src, err := newFileSystemSource(opts) 427 require.NoError(t, err) 428 md := testNsMetadata(t) 429 res, err := src.AvailableData( 430 md, 431 testShardRanges, 432 testCache(t, md, testShardRanges, opts.FilesystemOptions()), 433 testDefaultRunOpts, 434 ) 435 require.NoError(t, err) 436 require.NotNil(t, res) 437 require.True(t, res.IsEmpty()) 438 } 439 440 func TestAvailableTimeRangeFilter(t *testing.T) { 441 dir := createTempDir(t) 442 defer os.RemoveAll(dir) 443 444 shard := uint32(0) 445 writeGoodFiles(t, dir, testNs1ID, shard) 446 447 opts := newTestOptions(t, dir) 448 src, err := newFileSystemSource(opts) 449 require.NoError(t, err) 450 md := testNsMetadata(t) 451 res, err := src.AvailableData( 452 md, 453 testShardRanges, 454 testCache(t, md, testShardRanges, opts.FilesystemOptions()), 455 testDefaultRunOpts, 456 ) 457 require.NoError(t, err) 458 require.NotNil(t, res) 459 require.Equal(t, 1, res.Len()) 460 _, ok := res.Get(testShard) 461 require.True(t, ok) 462 463 expected := xtime.NewRanges( 464 xtime.Range{Start: testStart, End: testStart.Add(2 * time.Hour)}, 465 xtime.Range{Start: testStart.Add(10 * time.Hour), End: testStart.Add(12 * time.Hour)}) 466 tr, ok := res.Get(testShard) 467 require.True(t, ok) 468 validateTimeRanges(t, tr, expected) 469 } 470 471 func TestAvailableTimeRangePartialError(t *testing.T) { 472 dir := createTempDir(t) 473 defer os.RemoveAll(dir) 474 475 shard := uint32(0) 476 writeGoodFiles(t, dir, testNs1ID, shard) 477 // Intentionally write a corrupted info file 478 writeInfoFile(t, dir, testNs1ID, shard, testStart.Add(4*time.Hour), []byte{0x1, 0x2}) 479 480 opts := newTestOptions(t, dir) 481 src, err := newFileSystemSource(opts) 482 require.NoError(t, err) 483 md := testNsMetadata(t) 484 res, err := src.AvailableData( 485 md, 486 testShardRanges, 487 testCache(t, md, testShardRanges, opts.FilesystemOptions()), 488 testDefaultRunOpts, 489 ) 490 require.NoError(t, err) 491 require.NotNil(t, res) 492 require.Equal(t, 1, res.Len()) 493 _, ok := res.Get(testShard) 494 require.True(t, ok) 495 496 expected := xtime.NewRanges( 497 xtime.Range{Start: testStart, End: testStart.Add(2 * time.Hour)}, 498 xtime.Range{Start: testStart.Add(10 * time.Hour), End: testStart.Add(12 * time.Hour)}) 499 tr, ok := res.Get(testShard) 500 require.True(t, ok) 501 validateTimeRanges(t, tr, expected) 502 } 503 504 // NB: too real :'( 505 func unfulfilledAndEmpty(t *testing.T, src bootstrap.Source, 506 md namespace.Metadata, tester bootstrap.NamespacesTester) { 507 tester.TestReadWith(src) 508 tester.TestUnfulfilledForNamespaceIsEmpty(md) 509 510 tester.EnsureNoWrites() 511 tester.EnsureNoLoadedBlocks() 512 } 513 514 func TestReadEmptyRangeErr(t *testing.T) { 515 src, err := newFileSystemSource(newTestOptions(t, "foo")) 516 require.NoError(t, err) 517 nsMD := testNsMetadata(t) 518 tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, result.NewShardTimeRanges(), nsMD) 519 defer tester.Finish() 520 unfulfilledAndEmpty(t, src, nsMD, tester) 521 } 522 523 func TestReadPatternError(t *testing.T) { 524 src, err := newFileSystemSource(newTestOptions(t, "[[")) 525 require.NoError(t, err) 526 timeRanges := result.NewShardTimeRanges().Set(testShard, xtime.NewRanges()) 527 nsMD := testNsMetadata(t) 528 tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, 529 timeRanges, nsMD) 530 defer tester.Finish() 531 unfulfilledAndEmpty(t, src, nsMD, tester) 532 } 533 534 func validateReadResults( 535 t *testing.T, 536 src bootstrap.Source, 537 dir string, 538 strs result.ShardTimeRanges, 539 ) { 540 nsMD := testNsMetadata(t) 541 fsOpts := newTestOptions(t, dir).FilesystemOptions() 542 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, testDefaultRunOpts, strs, fsOpts, nsMD) 543 defer tester.Finish() 544 545 tester.TestReadWith(src) 546 readers := tester.EnsureDumpReadersForNamespace(nsMD) 547 require.Equal(t, 2, len(readers)) 548 ids := []string{"foo", "bar"} 549 data := [][]byte{ 550 {1, 2, 3}, 551 {4, 5, 6}, 552 } 553 554 times := []xtime.UnixNano{testStart, testStart.Add(10 * time.Hour)} 555 for i, id := range ids { 556 seriesReaders, ok := readers[id] 557 require.True(t, ok) 558 require.Equal(t, 1, len(seriesReaders)) 559 readerAtTime := seriesReaders[0] 560 assert.Equal(t, times[i], readerAtTime.Start) 561 ctx := context.NewBackground() 562 bytes, err := xio.ToBytes(readerAtTime.Reader) 563 ctx.Close() 564 require.Equal(t, io.EOF, err) 565 require.Equal(t, data[i], bytes) 566 } 567 568 tester.EnsureNoWrites() 569 } 570 571 func TestReadNilTimeRanges(t *testing.T) { 572 dir := createTempDir(t) 573 defer os.RemoveAll(dir) 574 575 shard := uint32(0) 576 writeGoodFiles(t, dir, testNs1ID, shard) 577 578 src, err := newFileSystemSource(newTestOptions(t, dir)) 579 require.NoError(t, err) 580 timeRanges := result.NewShardTimeRanges().Set( 581 testShard, 582 testTimeRanges(), 583 ).Set( 584 555, 585 xtime.NewRanges(), 586 ) 587 588 validateReadResults(t, src, dir, timeRanges) 589 } 590 591 func TestReadOpenFileError(t *testing.T) { 592 dir := createTempDir(t) 593 defer os.RemoveAll(dir) 594 595 shard := uint32(0) 596 writeTSDBFiles(t, dir, testNs1ID, shard, testStart, []testSeries{ 597 {"foo", nil, []byte{0x1}}, 598 }) 599 600 // Intentionally truncate the info file 601 writeInfoFile(t, dir, testNs1ID, shard, testStart, nil) 602 603 src, err := newFileSystemSource(newTestOptions(t, dir)) 604 require.NoError(t, err) 605 nsMD := testNsMetadata(t) 606 ranges := testShardTimeRanges() 607 tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, 608 ranges, nsMD) 609 defer tester.Finish() 610 611 tester.TestReadWith(src) 612 tester.TestUnfulfilledForNamespace(nsMD, ranges, ranges) 613 614 tester.EnsureNoLoadedBlocks() 615 tester.EnsureNoWrites() 616 } 617 618 func TestReadDataCorruptionErrorNoIndex(t *testing.T) { 619 testReadDataCorruptionErrorWithIndexEnabled(t, false, testShardTimeRanges()) 620 } 621 622 func TestReadDataCorruptionErrorWithIndex(t *testing.T) { 623 expectedIndex := testBootstrappingIndexShardTimeRanges() 624 testReadDataCorruptionErrorWithIndexEnabled(t, true, expectedIndex) 625 } 626 627 func testReadDataCorruptionErrorWithIndexEnabled( 628 t *testing.T, 629 withIndex bool, 630 expectedIndexUnfulfilled result.ShardTimeRanges, 631 ) { 632 dir := createTempDir(t) 633 defer os.RemoveAll(dir) 634 635 shard := uint32(0) 636 writeTSDBFiles(t, dir, testNs1ID, shard, testStart, []testSeries{ 637 {"foo", nil, []byte{0x1}}, 638 }) 639 // Intentionally corrupt the data file 640 writeDataFile(t, dir, testNs1ID, shard, testStart, []byte{0x2}) 641 642 testOpts := newTestOptions(t, dir) 643 src, err := newFileSystemSource(testOpts) 644 require.NoError(t, err) 645 646 strs := testShardTimeRanges() 647 648 nsMD := testNsMetadataWithIndex(t, withIndex) 649 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, testDefaultRunOpts, strs, testOpts.FilesystemOptions(), nsMD) 650 defer tester.Finish() 651 652 tester.TestReadWith(src) 653 tester.TestUnfulfilledForNamespace(nsMD, strs, expectedIndexUnfulfilled) 654 tester.EnsureNoWrites() 655 } 656 657 func TestReadTimeFilter(t *testing.T) { 658 dir := createTempDir(t) 659 defer os.RemoveAll(dir) 660 661 writeGoodFiles(t, dir, testNs1ID, testShard) 662 663 src, err := newFileSystemSource(newTestOptions(t, dir)) 664 require.NoError(t, err) 665 666 validateReadResults(t, src, dir, testShardTimeRanges()) 667 } 668 669 func TestReadPartialError(t *testing.T) { 670 dir := createTempDir(t) 671 defer os.RemoveAll(dir) 672 673 writeGoodFiles(t, dir, testNs1ID, testShard) 674 // Intentionally corrupt the data file 675 writeDataFile(t, dir, testNs1ID, testShard, testStart.Add(4*time.Hour), []byte{0x1}) 676 677 src, err := newFileSystemSource(newTestOptions(t, dir)) 678 require.NoError(t, err) 679 680 validateReadResults(t, src, dir, testShardTimeRanges()) 681 } 682 683 func TestReadValidateErrorNoIndex(t *testing.T) { 684 testReadValidateErrorWithIndexEnabled(t, false, testShardTimeRanges()) 685 } 686 687 func TestReadValidateErrorWithIndex(t *testing.T) { 688 expectedIndex := testBootstrappingIndexShardTimeRanges() 689 testReadValidateErrorWithIndexEnabled(t, true, expectedIndex) 690 } 691 692 func testReadValidateErrorWithIndexEnabled( 693 t *testing.T, 694 enabled bool, 695 expectedIndexUnfulfilled result.ShardTimeRanges, 696 ) { 697 ctrl := xtest.NewController(t) 698 defer ctrl.Finish() 699 700 dir := createTempDir(t) 701 defer os.RemoveAll(dir) 702 703 reader := fs.NewMockDataFileSetReader(ctrl) 704 705 testOpts := newTestOptions(t, dir) 706 fsSrc, err := newFileSystemSource(testOpts) 707 require.NoError(t, err) 708 709 src, ok := fsSrc.(*fileSystemSource) 710 require.True(t, ok) 711 712 first := true 713 src.newReaderFn = func( 714 b pool.CheckedBytesPool, 715 opts fs.Options, 716 ) (fs.DataFileSetReader, error) { 717 if first { 718 first = false 719 return reader, nil 720 } 721 return fs.NewReader(b, opts) 722 } 723 src.newReaderPoolOpts.DisableReuse = true 724 725 shard := uint32(0) 726 writeTSDBFiles(t, dir, testNs1ID, shard, testStart, []testSeries{ 727 {"foo", nil, []byte{0x1}}, 728 }) 729 rOpenOpts := fs.ReaderOpenOptionsMatcher{ 730 ID: fs.FileSetFileIdentifier{ 731 Namespace: testNs1ID, 732 Shard: shard, 733 BlockStart: testStart, 734 }, 735 } 736 reader.EXPECT(). 737 Open(rOpenOpts). 738 Return(nil) 739 reader.EXPECT(). 740 Range(). 741 Return(xtime.Range{ 742 Start: testStart, 743 End: testStart.Add(2 * time.Hour), 744 }) 745 reader.EXPECT().Entries().Return(0).AnyTimes() 746 reader.EXPECT().Validate().Return(errors.New("foo")) 747 reader.EXPECT().Close().Return(nil) 748 749 nsMD := testNsMetadataWithIndex(t, enabled) 750 ranges := testShardTimeRanges() 751 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, testDefaultRunOpts, 752 ranges, testOpts.FilesystemOptions(), nsMD) 753 defer tester.Finish() 754 755 tester.TestReadWith(src) 756 tester.TestUnfulfilledForNamespace(nsMD, ranges, expectedIndexUnfulfilled) 757 tester.EnsureNoLoadedBlocks() 758 tester.EnsureNoWrites() 759 } 760 761 func TestReadOpenErrorNoIndex(t *testing.T) { 762 testReadOpenError(t, false, testShardTimeRanges()) 763 } 764 765 func TestReadOpenErrorWithIndex(t *testing.T) { 766 expectedIndex := testBootstrappingIndexShardTimeRanges() 767 testReadOpenError(t, true, expectedIndex) 768 } 769 770 func testReadOpenError( 771 t *testing.T, 772 enabled bool, 773 expectedIndexUnfulfilled result.ShardTimeRanges, 774 ) { 775 ctrl := xtest.NewController(t) 776 defer ctrl.Finish() 777 778 dir := createTempDir(t) 779 defer os.RemoveAll(dir) 780 781 reader := fs.NewMockDataFileSetReader(ctrl) 782 783 testOpts := newTestOptions(t, dir) 784 fsSrc, err := newFileSystemSource(testOpts) 785 require.NoError(t, err) 786 787 src, ok := fsSrc.(*fileSystemSource) 788 require.True(t, ok) 789 790 first := true 791 src.newReaderFn = func( 792 b pool.CheckedBytesPool, 793 opts fs.Options, 794 ) (fs.DataFileSetReader, error) { 795 if first { 796 first = false 797 return reader, nil 798 } 799 return fs.NewReader(b, opts) 800 } 801 802 shard := uint32(0) 803 writeTSDBFiles(t, dir, testNs1ID, shard, testStart, []testSeries{ 804 {"foo", nil, []byte{0x1}}, 805 }) 806 rOpts := fs.ReaderOpenOptionsMatcher{ 807 ID: fs.FileSetFileIdentifier{ 808 Namespace: testNs1ID, 809 Shard: shard, 810 BlockStart: testStart, 811 }, 812 } 813 reader.EXPECT(). 814 Open(rOpts). 815 Return(errors.New("error")) 816 817 nsMD := testNsMetadataWithIndex(t, enabled) 818 ranges := testShardTimeRanges() 819 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, testDefaultRunOpts, 820 ranges, testOpts.FilesystemOptions(), nsMD) 821 defer tester.Finish() 822 823 tester.TestReadWith(src) 824 tester.TestUnfulfilledForNamespace(nsMD, ranges, expectedIndexUnfulfilled) 825 tester.EnsureNoLoadedBlocks() 826 tester.EnsureNoWrites() 827 } 828 829 func TestReadDeleteOnError(t *testing.T) { 830 ctrl := xtest.NewController(t) 831 defer ctrl.Finish() 832 833 dir := createTempDir(t) 834 defer os.RemoveAll(dir) 835 836 reader := fs.NewMockDataFileSetReader(ctrl) 837 838 testOpts := newTestOptions(t, dir) 839 fsSrc, err := newFileSystemSource(testOpts) 840 require.NoError(t, err) 841 842 src, ok := fsSrc.(*fileSystemSource) 843 require.True(t, ok) 844 845 src.newReaderFn = func( 846 b pool.CheckedBytesPool, 847 opts fs.Options, 848 ) (fs.DataFileSetReader, error) { 849 return reader, nil 850 } 851 852 shard := uint32(0) 853 writeTSDBFiles(t, dir, testNs1ID, shard, testStart, []testSeries{ 854 {"foo", nil, []byte{0x1}}, 855 }) 856 857 rOpts := fs.ReaderOpenOptionsMatcher{ 858 ID: fs.FileSetFileIdentifier{ 859 Namespace: testNs1ID, 860 Shard: shard, 861 BlockStart: testStart, 862 }, 863 } 864 865 reader.EXPECT(). 866 Range(). 867 Return(xtime.Range{ 868 Start: testStart, 869 End: testStart.Add(2 * time.Hour), 870 }).Times(2) 871 reader.EXPECT().Entries().Return(2).Times(2) 872 reader.EXPECT().Close().Return(nil).Times(2) 873 874 reader.EXPECT().Open(rOpts).Return(nil) 875 reader.EXPECT(). 876 Read(). 877 Return(ident.StringID("foo"), ident.EmptyTagIterator, 878 nil, digest.Checksum(nil), nil) 879 reader.EXPECT(). 880 Read(). 881 Return(ident.StringID("bar"), ident.EmptyTagIterator, 882 nil, uint32(0), errors.New("foo")) 883 884 rOpts.StreamingEnabled = true 885 reader.EXPECT().Open(rOpts).Return(nil) 886 reader.EXPECT().StreamingReadMetadata().Return( 887 fs.StreamedMetadataEntry{ID: ident.BytesID("foo")}, nil) 888 reader.EXPECT().StreamingReadMetadata().Return( 889 fs.StreamedMetadataEntry{}, errors.New("error")) 890 891 nsMD := testNsMetadata(t) 892 ranges := testShardTimeRanges() 893 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, testDefaultRunOpts, 894 ranges, testOpts.FilesystemOptions(), nsMD) 895 defer tester.Finish() 896 897 tester.TestReadWith(src) 898 tester.TestUnfulfilledForNamespace(nsMD, ranges, ranges) 899 tester.EnsureNoWrites() 900 } 901 902 func TestReadTags(t *testing.T) { 903 dir := createTempDir(t) 904 defer os.RemoveAll(dir) 905 906 id := "foo" 907 tags := map[string]string{ 908 "bar": "baz", 909 "qux": "qaz", 910 } 911 data := []byte{0x1} 912 913 writeTSDBFiles(t, dir, testNs1ID, testShard, testStart, []testSeries{ 914 {id, tags, data}, 915 }) 916 917 testOpts := newTestOptions(t, dir) 918 src, err := newFileSystemSource(testOpts) 919 require.NoError(t, err) 920 921 nsMD := testNsMetadata(t) 922 ranges := testShardTimeRanges() 923 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, testDefaultRunOpts, 924 ranges, testOpts.FilesystemOptions(), nsMD) 925 defer tester.Finish() 926 927 tester.TestReadWith(src) 928 readers := tester.EnsureDumpReadersForNamespace(nsMD) 929 require.Equal(t, 1, len(readers)) 930 readersForTime, found := readers[id] 931 require.True(t, found) 932 require.Equal(t, 1, len(readersForTime)) 933 reader := readersForTime[0] 934 require.Equal(t, tags, reader.Tags) 935 tester.EnsureNoWrites() 936 } 937 938 func TestReadRunMigrations(t *testing.T) { 939 dir := createTempDir(t) 940 defer os.RemoveAll(dir) 941 942 // Write existing data filesets with legacy encoding 943 eOpts := msgpack.LegacyEncodingOptions{ 944 EncodeLegacyIndexInfoVersion: msgpack.LegacyEncodingIndexVersionV4, // MinorVersion 0 945 EncodeLegacyIndexEntryVersion: msgpack.LegacyEncodingIndexEntryVersionV2, // No checksum data 946 } 947 writeGoodFilesWithFsOpts(t, testNs1ID, testShard, newTestFsOptions(dir).SetEncodingOptions(eOpts)) 948 949 opts := newTestOptions(t, dir) 950 sOpts, closer := newTestStorageOptions(t, opts.PersistManager(), opts.IndexClaimsManager()) 951 defer closer() 952 953 src, err := newFileSystemSource(opts. 954 SetMigrationOptions(migration.NewOptions(). 955 SetTargetMigrationVersion(migration.MigrationVersion_1_1). 956 SetConcurrency(2)). // Lower concurrency to ensure workers process more than 1 migration. 957 SetStorageOptions(sOpts)) 958 require.NoError(t, err) 959 960 validateReadResults(t, src, dir, testShardTimeRanges()) 961 } 962 963 func newTestStorageOptions( 964 t *testing.T, 965 pm persist.Manager, 966 icm fs.IndexClaimsManager, 967 ) (storage.Options, index.Closer) { 968 plCache, err := index.NewPostingsListCache(1, index.PostingsListCacheOptions{ 969 InstrumentOptions: instrument.NewOptions(), 970 }) 971 require.NoError(t, err) 972 973 md, err := namespace.NewMetadata(testNs1ID, testNamespaceOptions) 974 require.NoError(t, err) 975 976 return storage.DefaultTestOptions(). 977 SetPersistManager(pm). 978 SetIndexClaimsManager(icm). 979 SetNamespaceInitializer(namespace.NewStaticInitializer([]namespace.Metadata{md})). 980 SetRepairEnabled(false). 981 SetIndexOptions(index.NewOptions(). 982 SetPostingsListCache(plCache)). 983 SetBlockLeaseManager(block.NewLeaseManager(nil)), plCache.Start() 984 }