github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/bootstrap/bootstrapper/peers/source_data_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 peers
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"sync"
    27  	"testing"
    28  
    29  	"github.com/m3db/m3/src/dbnode/client"
    30  	"github.com/m3db/m3/src/dbnode/namespace"
    31  	"github.com/m3db/m3/src/dbnode/persist"
    32  	"github.com/m3db/m3/src/dbnode/persist/fs"
    33  	m3dbruntime "github.com/m3db/m3/src/dbnode/runtime"
    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/index"
    38  	"github.com/m3db/m3/src/dbnode/storage/index/compaction"
    39  	"github.com/m3db/m3/src/dbnode/storage/series"
    40  	"github.com/m3db/m3/src/dbnode/topology"
    41  	"github.com/m3db/m3/src/dbnode/ts"
    42  	"github.com/m3db/m3/src/m3ninx/index/segment/fst"
    43  	"github.com/m3db/m3/src/x/checked"
    44  	"github.com/m3db/m3/src/x/context"
    45  	"github.com/m3db/m3/src/x/ident"
    46  	xtest "github.com/m3db/m3/src/x/test"
    47  	xtime "github.com/m3db/m3/src/x/time"
    48  
    49  	"github.com/golang/mock/gomock"
    50  	"github.com/stretchr/testify/assert"
    51  	"github.com/stretchr/testify/require"
    52  )
    53  
    54  var (
    55  	testNamespace         = ident.StringID("testnamespace")
    56  	testNamespaceMetadata = func(t *testing.T, opts ...namespaceOption) namespace.Metadata {
    57  		namespaceOpts := namespace.NewOptions()
    58  		idxOpts := namespaceOpts.IndexOptions()
    59  		namespaceOpts = namespaceOpts.SetIndexOptions(idxOpts.SetEnabled(true))
    60  		for _, opt := range opts {
    61  			namespaceOpts = opt(namespaceOpts)
    62  		}
    63  		ns, err := namespace.NewMetadata(testNamespace, namespaceOpts)
    64  		require.NoError(t, err)
    65  		return ns
    66  	}
    67  	testNamespaceMetadataNoIndex = func(t *testing.T, opts ...namespaceOption) namespace.Metadata {
    68  		newOpts := append([]namespaceOption(nil), opts...)
    69  		newOpts = append(newOpts, func(namespaceOpts namespace.Options) namespace.Options {
    70  			idxOpts := namespaceOpts.IndexOptions()
    71  			return namespaceOpts.SetIndexOptions(idxOpts.SetEnabled(false))
    72  		})
    73  		return testNamespaceMetadata(t, newOpts...)
    74  	}
    75  
    76  	testDefaultRunOpts     = bootstrap.NewRunOptions().SetPersistConfig(bootstrap.PersistConfig{Enabled: false})
    77  	testRunOptsWithPersist = bootstrap.NewRunOptions().SetPersistConfig(bootstrap.PersistConfig{Enabled: true})
    78  	testBlockOpts          = block.NewOptions()
    79  	testDefaultResultOpts  = result.NewOptions().SetSeriesCachePolicy(series.CacheAll)
    80  )
    81  
    82  type namespaceOption func(namespace.Options) namespace.Options
    83  
    84  func newTestDefaultOpts(t *testing.T, ctrl *gomock.Controller) Options {
    85  	idxOpts := index.NewOptions()
    86  	compactor, err := compaction.NewCompactor(idxOpts.MetadataArrayPool(),
    87  		index.MetadataArrayPoolCapacity,
    88  		idxOpts.SegmentBuilderOptions(),
    89  		idxOpts.FSTSegmentOptions(),
    90  		compaction.CompactorOptions{
    91  			FSTWriterOptions: &fst.WriterOptions{
    92  				// DisableRegistry is set to true to trade a larger FST size
    93  				// for a faster FST compaction since we want to reduce the end
    94  				// to end latency for time to first index a metric.
    95  				DisableRegistry: true,
    96  			},
    97  		})
    98  	require.NoError(t, err)
    99  	fsOpts := fs.NewOptions()
   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 NewOptions().
   108  		SetResultOptions(testDefaultResultOpts).
   109  		SetPersistManager(persist.NewMockManager(ctrl)).
   110  		SetIndexClaimsManager(icm).
   111  		SetAdminClient(client.NewMockAdminClient(ctrl)).
   112  		SetFilesystemOptions(fsOpts).
   113  		SetCompactor(compactor).
   114  		SetIndexOptions(idxOpts).
   115  		SetAdminClient(newValidMockClient(ctrl)).
   116  		SetRuntimeOptionsManager(newValidMockRuntimeOptionsManager(ctrl))
   117  }
   118  
   119  func newValidMockClient(ctrl *gomock.Controller) *client.MockAdminClient {
   120  	mockAdminSession := client.NewMockAdminSession(ctrl)
   121  	mockClient := client.NewMockAdminClient(ctrl)
   122  	mockClient.EXPECT().
   123  		DefaultAdminSession().
   124  		Return(mockAdminSession, nil).
   125  		AnyTimes()
   126  
   127  	return mockClient
   128  }
   129  
   130  func newValidMockRuntimeOptionsManager(ctrl *gomock.Controller) m3dbruntime.OptionsManager {
   131  	mockRuntimeOpts := m3dbruntime.NewMockOptions(ctrl)
   132  	mockRuntimeOpts.
   133  		EXPECT().
   134  		ClientBootstrapConsistencyLevel().
   135  		Return(topology.ReadConsistencyLevelAll).
   136  		AnyTimes()
   137  
   138  	mockRuntimeOptsMgr := m3dbruntime.NewMockOptionsManager(ctrl)
   139  	mockRuntimeOptsMgr.
   140  		EXPECT().
   141  		Get().
   142  		Return(mockRuntimeOpts).
   143  		AnyTimes()
   144  
   145  	return mockRuntimeOptsMgr
   146  }
   147  
   148  func TestPeersSourceEmptyShardTimeRanges(t *testing.T) {
   149  	ctrl := xtest.NewController(t)
   150  	defer ctrl.Finish()
   151  
   152  	opts := newTestDefaultOpts(t, ctrl).
   153  		SetRuntimeOptionsManager(newValidMockRuntimeOptionsManager(ctrl))
   154  
   155  	src, err := newPeersSource(opts)
   156  	require.NoError(t, err)
   157  
   158  	var (
   159  		nsMetadata = testNamespaceMetadataNoIndex(t)
   160  		target     = result.NewShardTimeRanges()
   161  		runOpts    = testDefaultRunOpts.SetInitialTopologyState(&topology.StateSnapshot{})
   162  	)
   163  	cache, err := bootstrap.NewCache(bootstrap.NewCacheOptions().
   164  		SetFilesystemOptions(opts.FilesystemOptions()).
   165  		SetInstrumentOptions(opts.FilesystemOptions().InstrumentOptions()))
   166  	require.NoError(t, err)
   167  	available, err := src.AvailableData(nsMetadata, target, cache, runOpts)
   168  	require.NoError(t, err)
   169  	require.Equal(t, target, available)
   170  
   171  	tester := bootstrap.BuildNamespacesTester(t, runOpts, target, nsMetadata)
   172  	defer tester.Finish()
   173  	tester.TestReadWith(src)
   174  	tester.TestUnfulfilledForNamespaceIsEmpty(nsMetadata)
   175  	tester.EnsureNoLoadedBlocks()
   176  	tester.EnsureNoWrites()
   177  }
   178  
   179  func TestPeersSourceReturnsErrorForAdminSession(t *testing.T) {
   180  	ctrl := xtest.NewController(t)
   181  	defer ctrl.Finish()
   182  
   183  	nsMetadata := testNamespaceMetadataNoIndex(t)
   184  	ropts := nsMetadata.Options().RetentionOptions()
   185  
   186  	expectedErr := errors.New("an error")
   187  
   188  	mockAdminClient := client.NewMockAdminClient(ctrl)
   189  	mockAdminClient.EXPECT().DefaultAdminSession().Return(nil, expectedErr)
   190  
   191  	opts := newTestDefaultOpts(t, ctrl).SetAdminClient(mockAdminClient)
   192  	src, err := newPeersSource(opts)
   193  	require.NoError(t, err)
   194  
   195  	start := xtime.Now().Add(-ropts.RetentionPeriod()).Truncate(ropts.BlockSize())
   196  	end := start.Add(ropts.BlockSize())
   197  
   198  	target := result.NewShardTimeRanges().Set(
   199  		0,
   200  		xtime.NewRanges(xtime.Range{Start: start, End: end}),
   201  	).Set(
   202  		1,
   203  		xtime.NewRanges(xtime.Range{Start: start, End: end}),
   204  	)
   205  
   206  	tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, target, nsMetadata)
   207  	defer tester.Finish()
   208  
   209  	ctx := context.NewBackground()
   210  	defer ctx.Close()
   211  
   212  	_, err = src.Read(ctx, tester.Namespaces, tester.Cache)
   213  	require.Error(t, err)
   214  	assert.Equal(t, expectedErr, err)
   215  	tester.EnsureNoLoadedBlocks()
   216  	tester.EnsureNoWrites()
   217  }
   218  
   219  func TestPeersSourceReturnsUnfulfilled(t *testing.T) {
   220  	ctrl := xtest.NewController(t)
   221  	defer ctrl.Finish()
   222  
   223  	opts := newTestDefaultOpts(t, ctrl)
   224  	nsMetadata := testNamespaceMetadataNoIndex(t)
   225  	ropts := nsMetadata.Options().RetentionOptions()
   226  
   227  	start := xtime.Now().Add(-ropts.RetentionPeriod()).Truncate(ropts.BlockSize())
   228  	end := start.Add(ropts.BlockSize())
   229  
   230  	goodResult := result.NewShardResult(opts.ResultOptions())
   231  	fooBlock := block.NewDatabaseBlock(start, ropts.BlockSize(), ts.Segment{}, testBlockOpts, namespace.Context{})
   232  	goodID := ident.StringID("foo")
   233  	goodResult.AddBlock(goodID, ident.NewTags(ident.StringTag("foo", "oof")), fooBlock)
   234  
   235  	mockAdminSession := client.NewMockAdminSession(ctrl)
   236  	mockAdminSession.EXPECT().
   237  		FetchBootstrapBlocksFromPeers(namespace.NewMetadataMatcher(nsMetadata),
   238  			uint32(0), start, end, gomock.Any()).
   239  		Return(goodResult, nil)
   240  
   241  	peerMetaIter := client.NewMockPeerBlockMetadataIter(ctrl)
   242  	peerMetaIter.EXPECT().Next().Return(false).AnyTimes()
   243  	peerMetaIter.EXPECT().Err().Return(nil).AnyTimes()
   244  	mockAdminSession.EXPECT().
   245  		FetchBootstrapBlocksMetadataFromPeers(gomock.Any(), gomock.Any(),
   246  			gomock.Any(), gomock.Any(), gomock.Any()).
   247  		Return(peerMetaIter, nil).AnyTimes()
   248  
   249  	mockAdminClient := client.NewMockAdminClient(ctrl)
   250  	mockAdminClient.EXPECT().DefaultAdminSession().Return(mockAdminSession, nil).AnyTimes()
   251  
   252  	opts = opts.SetAdminClient(mockAdminClient)
   253  
   254  	src, err := newPeersSource(opts)
   255  	require.NoError(t, err)
   256  
   257  	target := result.NewShardTimeRanges().Set(
   258  		0,
   259  		xtime.NewRanges(xtime.Range{Start: start, End: end}),
   260  	)
   261  
   262  	tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, target, nsMetadata)
   263  	defer tester.Finish()
   264  	tester.TestReadWith(src)
   265  	tester.TestUnfulfilledForNamespaceIsEmpty(nsMetadata)
   266  	vals := tester.DumpLoadedBlocks()
   267  	assert.Equal(t, 1, len(vals))
   268  	series, found := vals[nsMetadata.ID().String()]
   269  	require.True(t, found)
   270  
   271  	assert.Equal(t, 1, len(series))
   272  	points, found := series[goodID.String()]
   273  	require.True(t, found)
   274  	assert.Equal(t, 0, len(points))
   275  	tester.EnsureNoWrites()
   276  }
   277  
   278  func TestPeersSourceRunWithPersist(t *testing.T) {
   279  	for _, cachePolicy := range []series.CachePolicy{
   280  		series.CacheNone,
   281  		series.CacheRecentlyRead,
   282  		series.CacheLRU,
   283  	} {
   284  		ctrl := xtest.NewController(t)
   285  		defer ctrl.Finish()
   286  
   287  		testNsMd := testNamespaceMetadataNoIndex(t)
   288  		resultOpts := testDefaultResultOpts.SetSeriesCachePolicy(cachePolicy)
   289  		opts := newTestDefaultOpts(t, ctrl).SetResultOptions(resultOpts)
   290  		ropts := testNsMd.Options().RetentionOptions()
   291  		testNsMd.Options()
   292  		blockSize := ropts.BlockSize()
   293  
   294  		start := xtime.Now().Add(-ropts.RetentionPeriod()).Truncate(ropts.BlockSize())
   295  		end := start.Add(2 * ropts.BlockSize())
   296  
   297  		shard0ResultBlock1 := result.NewShardResult(opts.ResultOptions())
   298  		shard0ResultBlock2 := result.NewShardResult(opts.ResultOptions())
   299  		fooBlock := block.NewDatabaseBlock(start, ropts.BlockSize(),
   300  			ts.NewSegment(checked.NewBytes([]byte{1, 2, 3}, nil), nil, 1, ts.FinalizeNone),
   301  			testBlockOpts, namespace.Context{})
   302  		barBlock := block.NewDatabaseBlock(start.Add(ropts.BlockSize()), ropts.BlockSize(),
   303  			ts.NewSegment(checked.NewBytes([]byte{4, 5, 6}, nil), nil, 2, ts.FinalizeNone),
   304  			testBlockOpts, namespace.Context{})
   305  		shard0ResultBlock1.AddBlock(ident.StringID("foo"), ident.NewTags(ident.StringTag("foo", "oof")), fooBlock)
   306  		shard0ResultBlock2.AddBlock(ident.StringID("bar"), ident.NewTags(ident.StringTag("bar", "rab")), barBlock)
   307  
   308  		shard1ResultBlock1 := result.NewShardResult(opts.ResultOptions())
   309  		shard1ResultBlock2 := result.NewShardResult(opts.ResultOptions())
   310  		bazBlock := block.NewDatabaseBlock(start, ropts.BlockSize(),
   311  			ts.NewSegment(checked.NewBytes([]byte{7, 8, 9}, nil), nil, 3, ts.FinalizeNone),
   312  			testBlockOpts, namespace.Context{})
   313  		shard1ResultBlock1.AddBlock(ident.StringID("baz"), ident.NewTags(ident.StringTag("baz", "zab")), bazBlock)
   314  
   315  		mockAdminSession := client.NewMockAdminSession(ctrl)
   316  		mockAdminSession.EXPECT().
   317  			FetchBootstrapBlocksFromPeers(namespace.NewMetadataMatcher(testNsMd),
   318  				uint32(0), start, start.Add(blockSize), gomock.Any()).
   319  			Return(shard0ResultBlock1, nil)
   320  		mockAdminSession.EXPECT().
   321  			FetchBootstrapBlocksFromPeers(namespace.NewMetadataMatcher(testNsMd),
   322  				uint32(0), start.Add(blockSize), start.Add(blockSize*2), gomock.Any()).
   323  			Return(shard0ResultBlock2, nil)
   324  		mockAdminSession.EXPECT().
   325  			FetchBootstrapBlocksFromPeers(namespace.NewMetadataMatcher(testNsMd),
   326  				uint32(1), start, start.Add(blockSize), gomock.Any()).
   327  			Return(shard1ResultBlock1, nil)
   328  		mockAdminSession.EXPECT().
   329  			FetchBootstrapBlocksFromPeers(namespace.NewMetadataMatcher(testNsMd),
   330  				uint32(1), start.Add(blockSize), start.Add(blockSize*2), gomock.Any()).
   331  			Return(shard1ResultBlock2, nil)
   332  
   333  		peerMetaIter := client.NewMockPeerBlockMetadataIter(ctrl)
   334  		peerMetaIter.EXPECT().Next().Return(false).AnyTimes()
   335  		peerMetaIter.EXPECT().Err().Return(nil).AnyTimes()
   336  		mockAdminSession.EXPECT().
   337  			FetchBootstrapBlocksMetadataFromPeers(gomock.Any(), gomock.Any(),
   338  				gomock.Any(), gomock.Any(), gomock.Any()).
   339  			Return(peerMetaIter, nil).AnyTimes()
   340  
   341  		mockAdminClient := client.NewMockAdminClient(ctrl)
   342  		mockAdminClient.EXPECT().DefaultAdminSession().Return(mockAdminSession, nil).AnyTimes()
   343  
   344  		opts = opts.SetAdminClient(mockAdminClient)
   345  
   346  		flushPreparer := persist.NewMockFlushPreparer(ctrl)
   347  		flushPreparer.EXPECT().DoneFlush().Times(DefaultShardPersistenceFlushConcurrency)
   348  
   349  		var (
   350  			flushMut sync.Mutex
   351  			persists = make(map[string]int)
   352  			closes   = make(map[string]int)
   353  		)
   354  
   355  		prepareOpts := xtest.CmpMatcher(persist.DataPrepareOptions{
   356  			NamespaceMetadata: testNsMd,
   357  			Shard:             uint32(0),
   358  			BlockStart:        start,
   359  			DeleteIfExists:    true,
   360  		})
   361  		flushPreparer.EXPECT().
   362  			PrepareData(prepareOpts).
   363  			Return(persist.PreparedDataPersist{
   364  				Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   365  					flushMut.Lock()
   366  					persists["foo"]++
   367  					flushMut.Unlock()
   368  					assert.Equal(t, "foo", string(metadata.BytesID()))
   369  					assert.Equal(t, []byte{1, 2, 3}, segment.Head.Bytes())
   370  					assertBlockChecksum(t, checksum, fooBlock)
   371  					return nil
   372  				},
   373  				Close: func() error {
   374  					flushMut.Lock()
   375  					closes["foo"]++
   376  					flushMut.Unlock()
   377  					return nil
   378  				},
   379  			}, nil)
   380  		prepareOpts = xtest.CmpMatcher(persist.DataPrepareOptions{
   381  			NamespaceMetadata: testNsMd,
   382  			Shard:             uint32(0),
   383  			BlockStart:        start.Add(ropts.BlockSize()),
   384  			DeleteIfExists:    true,
   385  		})
   386  		flushPreparer.EXPECT().
   387  			PrepareData(prepareOpts).
   388  			Return(persist.PreparedDataPersist{
   389  				Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   390  					flushMut.Lock()
   391  					persists["bar"]++
   392  					flushMut.Unlock()
   393  					assert.Equal(t, "bar", string(metadata.BytesID()))
   394  					assert.Equal(t, []byte{4, 5, 6}, segment.Head.Bytes())
   395  					assertBlockChecksum(t, checksum, barBlock)
   396  					return nil
   397  				},
   398  				Close: func() error {
   399  					flushMut.Lock()
   400  					closes["bar"]++
   401  					flushMut.Unlock()
   402  					return nil
   403  				},
   404  			}, nil)
   405  		prepareOpts = xtest.CmpMatcher(persist.DataPrepareOptions{
   406  			NamespaceMetadata: testNsMd,
   407  			Shard:             uint32(1),
   408  			BlockStart:        start,
   409  			DeleteIfExists:    true,
   410  		})
   411  		flushPreparer.EXPECT().
   412  			PrepareData(prepareOpts).
   413  			Return(persist.PreparedDataPersist{
   414  				Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   415  					flushMut.Lock()
   416  					persists["baz"]++
   417  					flushMut.Unlock()
   418  					assert.Equal(t, "baz", string(metadata.BytesID()))
   419  					assert.Equal(t, []byte{7, 8, 9}, segment.Head.Bytes())
   420  					assertBlockChecksum(t, checksum, bazBlock)
   421  					return nil
   422  				},
   423  				Close: func() error {
   424  					flushMut.Lock()
   425  					closes["baz"]++
   426  					flushMut.Unlock()
   427  					return nil
   428  				},
   429  			}, nil)
   430  		prepareOpts = xtest.CmpMatcher(persist.DataPrepareOptions{
   431  			NamespaceMetadata: testNsMd,
   432  			Shard:             uint32(1),
   433  			BlockStart:        start.Add(ropts.BlockSize()),
   434  			DeleteIfExists:    true,
   435  		})
   436  		flushPreparer.EXPECT().
   437  			PrepareData(prepareOpts).
   438  			Return(persist.PreparedDataPersist{
   439  				Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   440  					assert.Fail(t, "no expected shard 1 second block")
   441  					return nil
   442  				},
   443  				Close: func() error {
   444  					flushMut.Lock()
   445  					closes["empty"]++
   446  					flushMut.Unlock()
   447  					return nil
   448  				},
   449  			}, nil)
   450  
   451  		mockPersistManager := persist.NewMockManager(ctrl)
   452  		mockPersistManager.EXPECT().StartFlushPersist().Return(flushPreparer, nil).
   453  			Times(DefaultShardPersistenceFlushConcurrency)
   454  
   455  		src, err := newPeersSource(opts)
   456  		require.NoError(t, err)
   457  
   458  		src.(*peersSource).newPersistManager = func() (persist.Manager, error) {
   459  			return mockPersistManager, nil
   460  		}
   461  
   462  		target := result.NewShardTimeRanges().Set(
   463  			0,
   464  			xtime.NewRanges(xtime.Range{Start: start, End: end}),
   465  		).Set(
   466  			1,
   467  			xtime.NewRanges(xtime.Range{Start: start, End: end}),
   468  		)
   469  
   470  		tester := bootstrap.BuildNamespacesTester(t, testRunOptsWithPersist, target, testNsMd)
   471  		defer tester.Finish()
   472  		tester.TestReadWith(src)
   473  		tester.TestUnfulfilledForNamespaceIsEmpty(testNsMd)
   474  		assert.Equal(t, map[string]int{
   475  			"foo": 1, "bar": 1, "baz": 1,
   476  		}, persists)
   477  
   478  		assert.Equal(t, map[string]int{
   479  			"foo": 1, "bar": 1, "baz": 1, "empty": 1,
   480  		}, closes)
   481  
   482  		tester.EnsureNoLoadedBlocks()
   483  		tester.EnsureNoWrites()
   484  	}
   485  }
   486  
   487  func TestPeersSourceMarksUnfulfilledOnPersistenceErrors(t *testing.T) {
   488  	ctrl := xtest.NewController(t)
   489  	defer ctrl.Finish()
   490  
   491  	opts := newTestDefaultOpts(t, ctrl).
   492  		SetResultOptions(newTestDefaultOpts(t, ctrl).
   493  			ResultOptions().
   494  			SetSeriesCachePolicy(series.CacheRecentlyRead),
   495  		)
   496  	testNsMd := testNamespaceMetadataNoIndex(t)
   497  	ropts := testNsMd.Options().RetentionOptions()
   498  
   499  	start := xtime.Now().Add(-ropts.RetentionPeriod()).Truncate(ropts.BlockSize())
   500  	midway := start.Add(ropts.BlockSize())
   501  	end := start.Add(2 * ropts.BlockSize())
   502  
   503  	type resultsKey struct {
   504  		shard       uint32
   505  		start       xtime.UnixNano
   506  		end         xtime.UnixNano
   507  		expectedErr bool
   508  	}
   509  
   510  	results := make(map[resultsKey]result.ShardResult)
   511  	addResult := func(shard uint32, id string, b block.DatabaseBlock, expectedErr bool) {
   512  		r := result.NewShardResult(opts.ResultOptions())
   513  		r.AddBlock(ident.StringID(id), ident.NewTags(ident.StringTag(id, id)), b)
   514  		start := b.StartTime()
   515  		end := start.Add(ropts.BlockSize())
   516  		results[resultsKey{shard, start, end, expectedErr}] = r
   517  	}
   518  
   519  	segmentError := errors.New("segment err")
   520  
   521  	// foo results
   522  	var fooBlocks [2]block.DatabaseBlock
   523  	fooBlocks[0] = block.NewMockDatabaseBlock(ctrl)
   524  	fooBlocks[0].(*block.MockDatabaseBlock).EXPECT().StartTime().Return(start).AnyTimes()
   525  	fooBlocks[0].(*block.MockDatabaseBlock).EXPECT().Checksum().Return(uint32(0), errors.New("stream err"))
   526  	addResult(0, "foo", fooBlocks[0], true)
   527  
   528  	fooBlocks[1] = block.NewDatabaseBlock(midway, ropts.BlockSize(),
   529  		ts.NewSegment(checked.NewBytes([]byte{1, 2, 3}, nil), nil, 1, ts.FinalizeNone),
   530  		testBlockOpts, namespace.Context{})
   531  	addResult(0, "foo", fooBlocks[1], false)
   532  
   533  	// bar results
   534  	var barBlocks [2]block.DatabaseBlock
   535  	barBlocks[0] = block.NewMockDatabaseBlock(ctrl)
   536  	barBlocks[0].(*block.MockDatabaseBlock).EXPECT().StartTime().Return(start).AnyTimes()
   537  	barBlocks[0].(*block.MockDatabaseBlock).EXPECT().Checksum().Return(uint32(0), errors.New("stream err"))
   538  	addResult(1, "bar", barBlocks[0], false)
   539  
   540  	barBlocks[1] = block.NewDatabaseBlock(midway, ropts.BlockSize(),
   541  		ts.NewSegment(checked.NewBytes([]byte{4, 5, 6}, nil), nil, 2, ts.FinalizeNone),
   542  		testBlockOpts, namespace.Context{})
   543  	addResult(1, "bar", barBlocks[1], false)
   544  
   545  	// baz results
   546  	var bazBlocks [2]block.DatabaseBlock
   547  	bazBlocks[0] = block.NewDatabaseBlock(start, ropts.BlockSize(),
   548  		ts.NewSegment(checked.NewBytes([]byte{7, 8, 9}, nil), nil, 3, ts.FinalizeNone),
   549  		testBlockOpts, namespace.Context{})
   550  	addResult(2, "baz", bazBlocks[0], false)
   551  
   552  	bazBlocks[1] = block.NewDatabaseBlock(midway, ropts.BlockSize(),
   553  		ts.NewSegment(checked.NewBytes([]byte{10, 11, 12}, nil), nil, 4, ts.FinalizeNone),
   554  		testBlockOpts, namespace.Context{})
   555  	addResult(2, "baz", bazBlocks[1], false)
   556  
   557  	// qux results
   558  	var quxBlocks [2]block.DatabaseBlock
   559  	quxBlocks[0] = block.NewDatabaseBlock(start, ropts.BlockSize(),
   560  		ts.NewSegment(checked.NewBytes([]byte{13, 14, 15}, nil), nil, 5, ts.FinalizeNone),
   561  		testBlockOpts, namespace.Context{})
   562  	addResult(3, "qux", quxBlocks[0], false)
   563  
   564  	quxBlocks[1] = block.NewDatabaseBlock(midway, ropts.BlockSize(),
   565  		ts.NewSegment(checked.NewBytes([]byte{16, 17, 18}, nil), nil, 6, ts.FinalizeNone),
   566  		testBlockOpts, namespace.Context{})
   567  	addResult(3, "qux", quxBlocks[1], false)
   568  
   569  	mockAdminSession := client.NewMockAdminSession(ctrl)
   570  
   571  	for key, result := range results {
   572  		mockAdminSession.EXPECT().
   573  			FetchBootstrapBlocksFromPeers(namespace.NewMetadataMatcher(testNsMd),
   574  				key.shard, key.start, key.end,
   575  				gomock.Any()).
   576  			Return(result, nil)
   577  
   578  		peerError := segmentError
   579  		if !key.expectedErr {
   580  			peerError = nil
   581  		}
   582  
   583  		peerMetaIter := client.NewMockPeerBlockMetadataIter(ctrl)
   584  		peerMetaIter.EXPECT().Next().Return(false).AnyTimes()
   585  		peerMetaIter.EXPECT().Err().Return(nil).AnyTimes()
   586  		mockAdminSession.EXPECT().
   587  			FetchBootstrapBlocksMetadataFromPeers(testNsMd.ID(),
   588  				key.shard, key.start, key.end, gomock.Any()).
   589  			Return(peerMetaIter, peerError).AnyTimes()
   590  	}
   591  
   592  	mockAdminClient := client.NewMockAdminClient(ctrl)
   593  	mockAdminClient.EXPECT().DefaultAdminSession().
   594  		Return(mockAdminSession, nil).AnyTimes()
   595  
   596  	opts = opts.SetAdminClient(mockAdminClient)
   597  
   598  	flushPreparer := persist.NewMockFlushPreparer(ctrl)
   599  	flushPreparer.EXPECT().DoneFlush().Times(DefaultShardPersistenceFlushConcurrency)
   600  
   601  	var (
   602  		flushMut sync.Mutex
   603  		persists = make(map[string]int)
   604  		closes   = make(map[string]int)
   605  	)
   606  
   607  	// expect foo
   608  	prepareOpts := xtest.CmpMatcher(persist.DataPrepareOptions{
   609  		NamespaceMetadata: testNsMd,
   610  		Shard:             uint32(0),
   611  		BlockStart:        start,
   612  		DeleteIfExists:    true,
   613  	})
   614  	flushPreparer.EXPECT().
   615  		PrepareData(prepareOpts).
   616  		Return(persist.PreparedDataPersist{
   617  			Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   618  				assert.Fail(t, "not expecting to flush shard 0 at start")
   619  				return nil
   620  			},
   621  			Close: func() error {
   622  				flushMut.Lock()
   623  				closes["foo"]++
   624  				flushMut.Unlock()
   625  				return nil
   626  			},
   627  		}, nil)
   628  	prepareOpts = xtest.CmpMatcher(persist.DataPrepareOptions{
   629  		NamespaceMetadata: testNsMd,
   630  		Shard:             uint32(0),
   631  		BlockStart:        midway,
   632  		DeleteIfExists:    true,
   633  	})
   634  	flushPreparer.EXPECT().
   635  		PrepareData(prepareOpts).
   636  		Return(persist.PreparedDataPersist{
   637  			Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   638  				flushMut.Lock()
   639  				persists["foo"]++
   640  				flushMut.Unlock()
   641  				return nil
   642  			},
   643  			Close: func() error {
   644  				flushMut.Lock()
   645  				closes["foo"]++
   646  				flushMut.Unlock()
   647  				return nil
   648  			},
   649  		}, nil)
   650  
   651  	// expect bar
   652  	prepareOpts = xtest.CmpMatcher(persist.DataPrepareOptions{
   653  		NamespaceMetadata: testNsMd,
   654  		Shard:             uint32(1),
   655  		BlockStart:        start,
   656  		DeleteIfExists:    true,
   657  	})
   658  	flushPreparer.EXPECT().
   659  		PrepareData(prepareOpts).
   660  		Return(persist.PreparedDataPersist{
   661  			Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   662  				assert.Fail(t, "not expecting to flush shard 0 at start + block size")
   663  				return nil
   664  			},
   665  			Close: func() error {
   666  				flushMut.Lock()
   667  				closes["bar"]++
   668  				flushMut.Unlock()
   669  				return nil
   670  			},
   671  		}, nil)
   672  	prepareOpts = xtest.CmpMatcher(persist.DataPrepareOptions{
   673  		NamespaceMetadata: testNsMd,
   674  		Shard:             uint32(1),
   675  		BlockStart:        midway,
   676  		DeleteIfExists:    true,
   677  	})
   678  	flushPreparer.EXPECT().
   679  		PrepareData(prepareOpts).
   680  		Return(persist.PreparedDataPersist{
   681  			Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   682  				flushMut.Lock()
   683  				persists["bar"]++
   684  				flushMut.Unlock()
   685  				return nil
   686  			},
   687  			Close: func() error {
   688  				flushMut.Lock()
   689  				closes["bar"]++
   690  				flushMut.Unlock()
   691  				return nil
   692  			},
   693  		}, nil)
   694  
   695  	// expect baz
   696  	prepareOpts = xtest.CmpMatcher(persist.DataPrepareOptions{
   697  		NamespaceMetadata: testNsMd,
   698  		Shard:             uint32(2),
   699  		BlockStart:        start,
   700  		DeleteIfExists:    true,
   701  	})
   702  	flushPreparer.EXPECT().
   703  		PrepareData(prepareOpts).
   704  		Return(persist.PreparedDataPersist{
   705  			Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   706  				flushMut.Lock()
   707  				persists["baz"]++
   708  				flushMut.Unlock()
   709  				return fmt.Errorf("a persist error")
   710  			},
   711  			Close: func() error {
   712  				flushMut.Lock()
   713  				closes["baz"]++
   714  				flushMut.Unlock()
   715  				return nil
   716  			},
   717  		}, nil)
   718  	prepareOpts = xtest.CmpMatcher(persist.DataPrepareOptions{
   719  		NamespaceMetadata: testNsMd,
   720  		Shard:             uint32(2),
   721  		BlockStart:        midway,
   722  		DeleteIfExists:    true,
   723  	})
   724  	flushPreparer.EXPECT().
   725  		PrepareData(prepareOpts).
   726  		Return(persist.PreparedDataPersist{
   727  			Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   728  				flushMut.Lock()
   729  				persists["baz"]++
   730  				flushMut.Unlock()
   731  				return nil
   732  			},
   733  			Close: func() error {
   734  				flushMut.Lock()
   735  				closes["baz"]++
   736  				flushMut.Unlock()
   737  				return nil
   738  			},
   739  		}, nil)
   740  
   741  	// expect qux
   742  	prepareOpts = xtest.CmpMatcher(persist.DataPrepareOptions{
   743  		NamespaceMetadata: testNsMd,
   744  		Shard:             uint32(3),
   745  		BlockStart:        start,
   746  		DeleteIfExists:    true,
   747  	})
   748  	flushPreparer.EXPECT().
   749  		PrepareData(prepareOpts).
   750  		Return(persist.PreparedDataPersist{
   751  			Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   752  				flushMut.Lock()
   753  				persists["qux"]++
   754  				flushMut.Unlock()
   755  				return nil
   756  			},
   757  			Close: func() error {
   758  				flushMut.Lock()
   759  				closes["qux"]++
   760  				flushMut.Unlock()
   761  				return fmt.Errorf("a persist close error")
   762  			},
   763  		}, nil)
   764  	prepareOpts = xtest.CmpMatcher(persist.DataPrepareOptions{
   765  		NamespaceMetadata: testNsMd,
   766  		Shard:             uint32(3),
   767  		BlockStart:        midway,
   768  		DeleteIfExists:    true,
   769  	})
   770  	flushPreparer.EXPECT().
   771  		PrepareData(prepareOpts).
   772  		Return(persist.PreparedDataPersist{
   773  			Persist: func(metadata persist.Metadata, segment ts.Segment, checksum uint32) error {
   774  				flushMut.Lock()
   775  				persists["qux"]++
   776  				flushMut.Unlock()
   777  				return nil
   778  			},
   779  			Close: func() error {
   780  				flushMut.Lock()
   781  				closes["qux"]++
   782  				flushMut.Unlock()
   783  				return nil
   784  			},
   785  		}, nil)
   786  
   787  	mockPersistManager := persist.NewMockManager(ctrl)
   788  	mockPersistManager.EXPECT().StartFlushPersist().Return(flushPreparer, nil).
   789  		Times(DefaultShardPersistenceFlushConcurrency)
   790  
   791  	src, err := newPeersSource(opts)
   792  	require.NoError(t, err)
   793  
   794  	src.(*peersSource).newPersistManager = func() (persist.Manager, error) {
   795  		return mockPersistManager, nil
   796  	}
   797  
   798  	target := result.NewShardTimeRanges().Set(
   799  		0,
   800  		xtime.NewRanges(
   801  			xtime.Range{Start: start, End: midway},
   802  			xtime.Range{Start: midway, End: end}),
   803  	).Set(
   804  		1,
   805  		xtime.NewRanges(
   806  			xtime.Range{Start: start, End: midway},
   807  			xtime.Range{Start: midway, End: end}),
   808  	).Set(
   809  		2,
   810  		xtime.NewRanges(
   811  			xtime.Range{Start: start, End: midway},
   812  			xtime.Range{Start: midway, End: end}),
   813  	).Set(
   814  		3,
   815  		xtime.NewRanges(
   816  			xtime.Range{Start: start, End: midway},
   817  			xtime.Range{Start: midway, End: end}),
   818  	)
   819  
   820  	tester := bootstrap.BuildNamespacesTester(t, testRunOptsWithPersist, target, testNsMd)
   821  	defer tester.Finish()
   822  	tester.TestReadWith(src)
   823  
   824  	expectedRanges := result.NewShardTimeRanges().Set(
   825  		0,
   826  		xtime.NewRanges(xtime.Range{Start: start, End: midway}),
   827  	).Set(
   828  		1,
   829  		xtime.NewRanges(xtime.Range{Start: start, End: midway}),
   830  	).Set(
   831  		2,
   832  		xtime.NewRanges(xtime.Range{Start: start, End: midway}),
   833  	).Set(
   834  		3,
   835  		xtime.NewRanges(xtime.Range{Start: start, End: midway}),
   836  	)
   837  
   838  	// NB(bodu): There is no time series data written to disk so all ranges fail to be fulfilled.
   839  	expectedIndexRanges := target
   840  
   841  	tester.TestUnfulfilledForNamespace(testNsMd, expectedRanges, expectedIndexRanges)
   842  	assert.Equal(t, map[string]int{
   843  		"foo": 1, "bar": 1, "baz": 2, "qux": 2,
   844  	}, persists)
   845  
   846  	assert.Equal(t, map[string]int{
   847  		"foo": 2, "bar": 2, "baz": 2, "qux": 2,
   848  	}, closes)
   849  
   850  	tester.EnsureNoLoadedBlocks()
   851  	tester.EnsureNoWrites()
   852  }
   853  
   854  func assertBlockChecksum(t *testing.T, expectedChecksum uint32, block block.DatabaseBlock) {
   855  	checksum, err := block.Checksum()
   856  	require.NoError(t, err)
   857  	require.Equal(t, expectedChecksum, checksum)
   858  }