github.com/m3db/m3@v1.5.0/src/dbnode/storage/bootstrap/bootstrapper/peers/source_test.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package peers
    22  
    23  import (
    24  	"errors"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/cluster/shard"
    30  	"github.com/m3db/m3/src/dbnode/persist/fs"
    31  	m3dbruntime "github.com/m3db/m3/src/dbnode/runtime"
    32  	"github.com/m3db/m3/src/dbnode/storage/bootstrap"
    33  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/result"
    34  	"github.com/m3db/m3/src/dbnode/topology"
    35  	tu "github.com/m3db/m3/src/dbnode/topology/testutil"
    36  	"github.com/m3db/m3/src/x/context"
    37  	"github.com/m3db/m3/src/x/instrument"
    38  	xtime "github.com/m3db/m3/src/x/time"
    39  
    40  	"github.com/golang/mock/gomock"
    41  	"github.com/stretchr/testify/require"
    42  )
    43  
    44  const (
    45  	notSelfID1 = "not-self1"
    46  	notSelfID2 = "not-self2"
    47  )
    48  
    49  func TestPeersSourceAvailableDataAndIndex(t *testing.T) {
    50  	ctrl := gomock.NewController(t)
    51  	defer ctrl.Finish()
    52  
    53  	var (
    54  		blockSize                  = 2 * time.Hour
    55  		nsMetadata                 = testNamespaceMetadata(t)
    56  		numShards                  = uint32(4)
    57  		blockStart                 = xtime.Now().Truncate(blockSize)
    58  		shardTimeRangesToBootstrap = result.NewShardTimeRanges()
    59  		bootstrapRanges            = xtime.NewRanges(xtime.Range{
    60  			Start: blockStart,
    61  			End:   blockStart.Add(blockSize),
    62  		})
    63  		cacheOptions = bootstrap.NewCacheOptions().
    64  				SetFilesystemOptions(fs.NewOptions()).
    65  				SetInstrumentOptions(instrument.NewOptions())
    66  	)
    67  
    68  	for i := 0; i < int(numShards); i++ {
    69  		shardTimeRangesToBootstrap.Set(uint32(i), bootstrapRanges)
    70  	}
    71  
    72  	shardTimeRangesToBootstrapOneExtra := shardTimeRangesToBootstrap.Copy()
    73  	shardTimeRangesToBootstrapOneExtra.Set(100, bootstrapRanges)
    74  
    75  	testCases := []struct {
    76  		title                             string
    77  		topoState                         *topology.StateSnapshot
    78  		bootstrapReadConsistency          topology.ReadConsistencyLevel
    79  		shardsTimeRangesToBootstrap       result.ShardTimeRanges
    80  		expectedAvailableShardsTimeRanges result.ShardTimeRanges
    81  		expectedErr                       error
    82  	}{
    83  		{
    84  			title: "Returns empty if only self is available",
    85  			topoState: tu.NewStateSnapshot(1, tu.HostShardStates{
    86  				tu.SelfID: tu.ShardsRange(0, numShards, shard.Available),
    87  			}),
    88  			bootstrapReadConsistency:          topology.ReadConsistencyLevelMajority,
    89  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
    90  			expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(),
    91  		},
    92  		{
    93  			title: "Returns empty if all other peers initializing/unknown",
    94  			topoState: tu.NewStateSnapshot(2, tu.HostShardStates{
    95  				tu.SelfID:  tu.ShardsRange(0, numShards, shard.Available),
    96  				notSelfID1: tu.ShardsRange(0, numShards, shard.Initializing),
    97  				notSelfID2: tu.ShardsRange(0, numShards, shard.Unknown),
    98  			}),
    99  			bootstrapReadConsistency:          topology.ReadConsistencyLevelMajority,
   100  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
   101  			expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(),
   102  			expectedErr:                       errors.New("unknown shard state: Unknown"),
   103  		},
   104  		{
   105  			title: "Returns success if consistency can be met (available/leaving)",
   106  			topoState: tu.NewStateSnapshot(2, tu.HostShardStates{
   107  				tu.SelfID:  tu.ShardsRange(0, numShards, shard.Initializing),
   108  				notSelfID1: tu.ShardsRange(0, numShards, shard.Available),
   109  				notSelfID2: tu.ShardsRange(0, numShards, shard.Leaving),
   110  			}),
   111  			bootstrapReadConsistency:          topology.ReadConsistencyLevelMajority,
   112  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
   113  			expectedAvailableShardsTimeRanges: shardTimeRangesToBootstrap,
   114  		},
   115  		{
   116  			title: "Skips shards that were not in the topology at start",
   117  			topoState: tu.NewStateSnapshot(2, tu.HostShardStates{
   118  				tu.SelfID:  tu.ShardsRange(0, numShards, shard.Initializing),
   119  				notSelfID1: tu.ShardsRange(0, numShards, shard.Available),
   120  				notSelfID2: tu.ShardsRange(0, numShards, shard.Available),
   121  			}),
   122  			bootstrapReadConsistency:          topology.ReadConsistencyLevelMajority,
   123  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrapOneExtra,
   124  			expectedAvailableShardsTimeRanges: shardTimeRangesToBootstrap,
   125  		},
   126  		{
   127  			title: "Returns empty if consistency can not be met",
   128  			topoState: tu.NewStateSnapshot(2, tu.HostShardStates{
   129  				tu.SelfID:  tu.ShardsRange(0, numShards, shard.Initializing),
   130  				notSelfID1: tu.ShardsRange(0, numShards, shard.Available),
   131  				notSelfID2: tu.ShardsRange(0, numShards, shard.Available),
   132  			}),
   133  			bootstrapReadConsistency:          topology.ReadConsistencyLevelAll,
   134  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
   135  			expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(),
   136  		},
   137  	}
   138  
   139  	for _, tc := range testCases {
   140  		t.Run(tc.title, func(t *testing.T) {
   141  			mockRuntimeOpts := m3dbruntime.NewMockOptions(ctrl)
   142  			mockRuntimeOpts.
   143  				EXPECT().
   144  				ClientBootstrapConsistencyLevel().
   145  				Return(tc.bootstrapReadConsistency).
   146  				AnyTimes()
   147  
   148  			mockRuntimeOptsMgr := m3dbruntime.NewMockOptionsManager(ctrl)
   149  			mockRuntimeOptsMgr.
   150  				EXPECT().
   151  				Get().
   152  				Return(mockRuntimeOpts).
   153  				AnyTimes()
   154  
   155  			opts := newTestDefaultOpts(t, ctrl).
   156  				SetRuntimeOptionsManager(mockRuntimeOptsMgr)
   157  
   158  			src, err := newPeersSource(opts)
   159  			require.NoError(t, err)
   160  
   161  			var shards []uint32
   162  			for shard := range tc.shardsTimeRangesToBootstrap.Iter() {
   163  				shards = append(shards, shard)
   164  			}
   165  			cache, sErr := bootstrap.NewCache(cacheOptions.
   166  				SetNamespaceDetails([]bootstrap.NamespaceDetails{
   167  					{
   168  						Namespace: nsMetadata,
   169  						Shards:    shards,
   170  					},
   171  				}))
   172  			require.NoError(t, sErr)
   173  
   174  			runOpts := testDefaultRunOpts.SetInitialTopologyState(tc.topoState)
   175  			dataRes, err := src.AvailableData(nsMetadata, tc.shardsTimeRangesToBootstrap, cache, runOpts)
   176  			if tc.expectedErr != nil {
   177  				require.Equal(t, tc.expectedErr, err)
   178  			} else {
   179  				require.NoError(t, err)
   180  				require.Equal(t, tc.expectedAvailableShardsTimeRanges, dataRes)
   181  			}
   182  
   183  			indexRes, err := src.AvailableIndex(nsMetadata, tc.shardsTimeRangesToBootstrap, cache, runOpts)
   184  			if tc.expectedErr != nil {
   185  				require.Equal(t, tc.expectedErr, err)
   186  			} else {
   187  				require.NoError(t, err)
   188  				require.Equal(t, tc.expectedAvailableShardsTimeRanges, indexRes)
   189  			}
   190  		})
   191  	}
   192  }
   193  
   194  func TestPeersSourceReturnsErrorIfUnknownPersistenceFileSetType(t *testing.T) {
   195  	ctrl := gomock.NewController(t)
   196  	defer ctrl.Finish()
   197  
   198  	var (
   199  		testNsMd = testNamespaceMetadata(t)
   200  		opts     = newTestDefaultOpts(t, ctrl)
   201  		ropts    = testNsMd.Options().RetentionOptions()
   202  
   203  		start = xtime.Now().Add(-ropts.RetentionPeriod()).Truncate(ropts.BlockSize())
   204  		end   = start.Add(2 * ropts.BlockSize())
   205  	)
   206  
   207  	src, err := newPeersSource(opts)
   208  	require.NoError(t, err)
   209  
   210  	target := result.NewShardTimeRanges().Set(
   211  		0,
   212  		xtime.NewRanges(xtime.Range{Start: start, End: end}),
   213  	).Set(
   214  		1,
   215  		xtime.NewRanges(xtime.Range{Start: start, End: end}),
   216  	)
   217  
   218  	runOpts := testRunOptsWithPersist.SetPersistConfig(bootstrap.PersistConfig{Enabled: true, FileSetType: 999})
   219  	tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, runOpts, target, opts.FilesystemOptions(), testNsMd)
   220  	defer tester.Finish()
   221  
   222  	ctx := context.NewBackground()
   223  	defer ctx.Close()
   224  
   225  	_, err = src.Read(ctx, tester.Namespaces, tester.Cache)
   226  	require.Error(t, err)
   227  	require.True(t, strings.Contains(err.Error(), "unknown persist config fileset file type"))
   228  	tester.EnsureNoLoadedBlocks()
   229  	tester.EnsureNoWrites()
   230  }