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  }