github.com/m3db/m3@v1.5.0/src/dbnode/storage/bootstrap/bootstrapper/uninitialized/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 uninitialized
    22  
    23  import (
    24  	"errors"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/cluster/shard"
    29  	"github.com/m3db/m3/src/dbnode/namespace"
    30  	"github.com/m3db/m3/src/dbnode/persist/fs"
    31  	"github.com/m3db/m3/src/dbnode/storage/bootstrap"
    32  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/result"
    33  	"github.com/m3db/m3/src/dbnode/topology"
    34  	tu "github.com/m3db/m3/src/dbnode/topology/testutil"
    35  	"github.com/m3db/m3/src/x/ident"
    36  	"github.com/m3db/m3/src/x/instrument"
    37  	xtime "github.com/m3db/m3/src/x/time"
    38  
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  var (
    43  	testNamespaceID    = ident.StringID("testnamespace")
    44  	testDefaultRunOpts = bootstrap.NewRunOptions()
    45  	notSelfID1         = "not-self-1"
    46  	notSelfID2         = "not-self-2"
    47  	notSelfID3         = "not-self-3"
    48  )
    49  
    50  func TestUnitializedTopologySourceAvailableDataAndAvailableIndex(t *testing.T) {
    51  	var (
    52  		blockSize                  = 2 * time.Hour
    53  		numShards                  = uint32(4)
    54  		blockStart                 = xtime.Now().Truncate(blockSize)
    55  		shardTimeRangesToBootstrap = result.NewShardTimeRanges()
    56  		bootstrapRanges            = xtime.NewRanges(xtime.Range{
    57  			Start: blockStart,
    58  			End:   blockStart.Add(blockSize),
    59  		})
    60  		cacheOptions = bootstrap.NewCacheOptions().
    61  				SetFilesystemOptions(fs.NewOptions()).
    62  				SetInstrumentOptions(instrument.NewOptions())
    63  	)
    64  	nsOpts := namespace.NewOptions()
    65  	nsOpts = nsOpts.SetIndexOptions(nsOpts.IndexOptions().SetEnabled(true))
    66  	nsMetadata, err := namespace.NewMetadata(testNamespaceID, nsOpts)
    67  	require.NoError(t, err)
    68  
    69  	for i := 0; i < int(numShards); i++ {
    70  		shardTimeRangesToBootstrap.Set(uint32(i), bootstrapRanges)
    71  	}
    72  
    73  	testCases := []struct {
    74  		title                             string
    75  		topoState                         *topology.StateSnapshot
    76  		shardsTimeRangesToBootstrap       result.ShardTimeRanges
    77  		expectedAvailableShardsTimeRanges result.ShardTimeRanges
    78  		expectedErr                       error
    79  	}{
    80  		// Snould return that it can bootstrap everything because
    81  		// it's a new namespace.
    82  		{
    83  			title: "Single node - Shard initializing",
    84  			topoState: tu.NewStateSnapshot(1, tu.HostShardStates{
    85  				tu.SelfID: tu.ShardsRange(0, numShards, shard.Initializing),
    86  			}),
    87  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
    88  			expectedAvailableShardsTimeRanges: shardTimeRangesToBootstrap,
    89  		},
    90  		// Snould return that it can't bootstrap anything because we don't
    91  		// know how to handle unknown shard states.
    92  		{
    93  			title: "Single node - Shard unknown",
    94  			topoState: tu.NewStateSnapshot(1, tu.HostShardStates{
    95  				tu.SelfID: tu.ShardsRange(0, numShards, shard.Unknown),
    96  			}),
    97  			shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap,
    98  			expectedErr:                 errors.New("unknown shard state: Unknown"),
    99  		},
   100  		// Snould return that it can't bootstrap anything because it's not
   101  		// a new namespace.
   102  		{
   103  			title: "Single node - Shard leaving",
   104  			topoState: tu.NewStateSnapshot(1, tu.HostShardStates{
   105  				tu.SelfID: tu.ShardsRange(0, numShards, shard.Leaving),
   106  			}),
   107  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
   108  			expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(),
   109  		},
   110  		// Snould return that it can't bootstrap anything because it's not
   111  		// a new namespace.
   112  		{
   113  			title: "Single node - Shard available",
   114  			topoState: tu.NewStateSnapshot(1, tu.HostShardStates{
   115  				tu.SelfID: tu.ShardsRange(0, numShards, shard.Available),
   116  			}),
   117  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
   118  			expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(),
   119  		},
   120  		// Snould return that it can bootstrap everything because
   121  		// it's a new namespace.
   122  		{
   123  			title: "Multi node - Brand new namespace (all nodes initializing)",
   124  			topoState: tu.NewStateSnapshot(2, tu.HostShardStates{
   125  				tu.SelfID:  tu.ShardsRange(0, numShards, shard.Initializing),
   126  				notSelfID1: tu.ShardsRange(0, numShards, shard.Initializing),
   127  				notSelfID2: tu.ShardsRange(0, numShards, shard.Initializing),
   128  			}),
   129  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
   130  			expectedAvailableShardsTimeRanges: shardTimeRangesToBootstrap,
   131  		},
   132  		// Snould return that it can bootstrap everything because
   133  		// it's a new namespace (one of the nodes hasn't completed
   134  		// initializing yet.)
   135  		{
   136  			title: "Multi node - Recently created namespace (one node still initializing)",
   137  			topoState: tu.NewStateSnapshot(2, tu.HostShardStates{
   138  				tu.SelfID:  tu.ShardsRange(0, numShards, shard.Initializing),
   139  				notSelfID1: tu.ShardsRange(0, numShards, shard.Available),
   140  				notSelfID2: tu.ShardsRange(0, numShards, shard.Available),
   141  			}),
   142  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
   143  			expectedAvailableShardsTimeRanges: shardTimeRangesToBootstrap,
   144  		},
   145  		// Snould return that it can't bootstrap anything because it's not
   146  		// a new namespace.
   147  		{
   148  			title: "Multi node - Initialized namespace (no nodes initializing)",
   149  			topoState: tu.NewStateSnapshot(2, tu.HostShardStates{
   150  				tu.SelfID:  tu.ShardsRange(0, numShards, shard.Available),
   151  				notSelfID1: tu.ShardsRange(0, numShards, shard.Available),
   152  				notSelfID2: tu.ShardsRange(0, numShards, shard.Available),
   153  			}),
   154  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
   155  			expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(),
   156  		},
   157  		// Snould return that it can't bootstrap anything because it's not
   158  		// a new namespace, we're just doing a node replace.
   159  		{
   160  			title: "Multi node - Node replace (one node leaving, one initializing)",
   161  			topoState: tu.NewStateSnapshot(2, tu.HostShardStates{
   162  				tu.SelfID:  tu.ShardsRange(0, numShards, shard.Available),
   163  				notSelfID1: tu.ShardsRange(0, numShards, shard.Leaving),
   164  				notSelfID2: tu.ShardsRange(0, numShards, shard.Available),
   165  				notSelfID3: tu.ShardsRange(0, numShards, shard.Initializing),
   166  			}),
   167  			shardsTimeRangesToBootstrap:       shardTimeRangesToBootstrap,
   168  			expectedAvailableShardsTimeRanges: result.NewShardTimeRanges(),
   169  		},
   170  		// Snould return that it can't bootstrap anything because we don't
   171  		// know how to interpret the unknown host.
   172  		{
   173  			title: "Multi node - One node unknown",
   174  			topoState: tu.NewStateSnapshot(2, tu.HostShardStates{
   175  				tu.SelfID:  tu.ShardsRange(0, numShards, shard.Available),
   176  				notSelfID1: tu.ShardsRange(0, numShards, shard.Available),
   177  				notSelfID2: tu.ShardsRange(0, numShards, shard.Unknown),
   178  			}),
   179  			shardsTimeRangesToBootstrap: shardTimeRangesToBootstrap,
   180  			expectedErr:                 errors.New("unknown shard state: Unknown"),
   181  		},
   182  	}
   183  
   184  	for _, tc := range testCases {
   185  		t.Run(tc.title, func(t *testing.T) {
   186  
   187  			var (
   188  				srcOpts = NewOptions().SetInstrumentOptions(instrument.NewOptions())
   189  				src     = newTopologyUninitializedSource(srcOpts)
   190  				runOpts = testDefaultRunOpts.SetInitialTopologyState(tc.topoState)
   191  			)
   192  			var shards []uint32
   193  			for shard := range tc.shardsTimeRangesToBootstrap.Iter() {
   194  				shards = append(shards, shard)
   195  			}
   196  			cache, sErr := bootstrap.NewCache(cacheOptions.
   197  				SetNamespaceDetails([]bootstrap.NamespaceDetails{
   198  					{
   199  						Namespace: nsMetadata,
   200  						Shards:    shards,
   201  					},
   202  				}))
   203  			require.NoError(t, sErr)
   204  
   205  			dataAvailabilityResult, dataErr := src.AvailableData(nsMetadata, tc.shardsTimeRangesToBootstrap, cache, runOpts)
   206  			indexAvailabilityResult, indexErr := src.AvailableIndex(nsMetadata, tc.shardsTimeRangesToBootstrap, cache, runOpts)
   207  
   208  			if tc.expectedErr != nil {
   209  				require.Equal(t, tc.expectedErr, dataErr)
   210  				require.Equal(t, tc.expectedErr, indexErr)
   211  			} else {
   212  				// Make sure AvailableData and AvailableIndex return the correct result
   213  				require.Equal(t, tc.expectedAvailableShardsTimeRanges, dataAvailabilityResult)
   214  				require.Equal(t, tc.expectedAvailableShardsTimeRanges, indexAvailabilityResult)
   215  
   216  				// Make sure Read marks anything that available ranges wouldn't return as unfulfilled
   217  				tester := bootstrap.BuildNamespacesTester(t, runOpts, tc.shardsTimeRangesToBootstrap, nsMetadata)
   218  				defer tester.Finish()
   219  				tester.TestReadWith(src)
   220  
   221  				expectedDataUnfulfilled := tc.shardsTimeRangesToBootstrap.Copy()
   222  				expectedDataUnfulfilled.Subtract(tc.expectedAvailableShardsTimeRanges)
   223  				expectedIndexUnfulfilled := tc.shardsTimeRangesToBootstrap.Copy()
   224  				expectedIndexUnfulfilled.Subtract(tc.expectedAvailableShardsTimeRanges)
   225  				tester.TestUnfulfilledForNamespace(
   226  					nsMetadata,
   227  					expectedDataUnfulfilled,
   228  					expectedIndexUnfulfilled,
   229  				)
   230  
   231  				tester.EnsureNoLoadedBlocks()
   232  				tester.EnsureNoWrites()
   233  			}
   234  		})
   235  	}
   236  }