github.com/m3db/m3@v1.5.0/src/dbnode/storage/cluster/database_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 cluster
    22  
    23  import (
    24  	"fmt"
    25  	"sync"
    26  	"testing"
    27  
    28  	"github.com/m3db/m3/src/cluster/shard"
    29  	"github.com/m3db/m3/src/dbnode/sharding"
    30  	"github.com/m3db/m3/src/dbnode/storage"
    31  	"github.com/m3db/m3/src/dbnode/topology"
    32  	"github.com/m3db/m3/src/dbnode/topology/testutil"
    33  
    34  	"github.com/golang/mock/gomock"
    35  	"github.com/stretchr/testify/assert"
    36  	"github.com/stretchr/testify/require"
    37  )
    38  
    39  var testOpts = storage.DefaultTestOptions()
    40  
    41  func newTestDatabase(
    42  	t *testing.T,
    43  	hostid string,
    44  	topoInit topology.Initializer,
    45  ) (Database, error) {
    46  	topo, err := topoInit.Init()
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	watch, err := topo.Watch()
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	return NewDatabase(hostid, topo, watch, testOpts)
    57  }
    58  
    59  func TestDatabaseOpenClose(t *testing.T) {
    60  	ctrl := gomock.NewController(t)
    61  	defer ctrl.Finish()
    62  
    63  	mockStorageDB, restore := mockNewStorageDatabase(ctrl)
    64  	defer restore()
    65  
    66  	viewsCh := make(chan testutil.TopologyView, 64)
    67  	defer close(viewsCh)
    68  
    69  	viewsCh <- testutil.NewTopologyView(1, map[string][]shard.Shard{
    70  		"testhost": sharding.NewShards([]uint32{0, 1, 2}, shard.Available),
    71  	})
    72  
    73  	topoInit, _ := newMockTopoInit(t, ctrl, viewsCh)
    74  
    75  	db, err := newTestDatabase(t, "testhost", topoInit)
    76  	require.NoError(t, err)
    77  
    78  	mockStorageDB.EXPECT().Open().Return(nil)
    79  	err = db.Open()
    80  	require.NoError(t, err)
    81  
    82  	mockStorageDB.EXPECT().Close().Return(nil)
    83  	err = db.Close()
    84  	require.NoError(t, err)
    85  }
    86  
    87  func TestDatabaseMarksShardAsAvailableOnReshard(t *testing.T) {
    88  	ctrl := gomock.NewController(t)
    89  	defer ctrl.Finish()
    90  
    91  	mockStorageDB, restore := mockNewStorageDatabase(ctrl)
    92  	mockStorageDB.EXPECT().Open().Return(nil)
    93  	defer restore()
    94  
    95  	viewsCh := make(chan testutil.TopologyView, 64)
    96  	defer close(viewsCh)
    97  
    98  	viewsCh <- testutil.NewTopologyView(1, map[string][]shard.Shard{
    99  		"testhost0": sharding.NewShards([]uint32{0, 1}, shard.Available),
   100  		"testhost1": sharding.NewShards([]uint32{2, 3}, shard.Available),
   101  	})
   102  
   103  	topoInit, props := newMockTopoInit(t, ctrl, viewsCh)
   104  
   105  	db, err := newTestDatabase(t, "testhost0", topoInit)
   106  	require.NoError(t, err)
   107  
   108  	// Now open the cluster database.
   109  	err = db.Open()
   110  	require.NoError(t, err)
   111  
   112  	// Reshard by taking a leaving host's shards.
   113  	updatedView := map[string][]shard.Shard{
   114  		"testhost0": append(sharding.NewShards([]uint32{0, 1}, shard.Available),
   115  			sharding.NewShards([]uint32{2, 3}, shard.Initializing)...),
   116  		"testhost1": sharding.NewShards([]uint32{2, 3}, shard.Leaving),
   117  	}
   118  
   119  	// Expect the assign shards call.
   120  	mockStorageDB.EXPECT().AssignShardSet(gomock.Any()).Do(
   121  		func(shardSet sharding.ShardSet) {
   122  			// Ensure updated shard set is as expected.
   123  			assert.Equal(t, 4, len(shardSet.AllIDs()))
   124  			values := updatedView["testhost0"]
   125  			hostShardSet, _ := sharding.NewShardSet(values, shardSet.HashFn())
   126  			assert.Equal(t, hostShardSet.AllIDs(), shardSet.AllIDs())
   127  		})
   128  
   129  	// Expect the namespaces query from report shard state background query.
   130  	mockShards := []*storage.MockShard{
   131  		storage.NewMockShard(ctrl),
   132  		storage.NewMockShard(ctrl),
   133  		storage.NewMockShard(ctrl),
   134  		storage.NewMockShard(ctrl),
   135  	}
   136  	for i, s := range mockShards {
   137  		s.EXPECT().ID().Return(uint32(i)).AnyTimes()
   138  	}
   139  	mockShards[2].EXPECT().IsBootstrapped().Return(true).AnyTimes()
   140  	mockShards[3].EXPECT().IsBootstrapped().Return(true).AnyTimes()
   141  
   142  	var expectShards []storage.Shard
   143  	for _, s := range mockShards {
   144  		expectShards = append(expectShards, s)
   145  	}
   146  
   147  	mockNamespace := storage.NewMockNamespace(ctrl)
   148  	mockNamespace.EXPECT().Shards().Return(expectShards).AnyTimes()
   149  
   150  	expectNamespaces := []storage.Namespace{mockNamespace}
   151  	mockStorageDB.EXPECT().Namespaces().Return(expectNamespaces).AnyTimes()
   152  
   153  	needsMarkAvailable := struct {
   154  		sync.Mutex
   155  		shards map[uint32]struct{}
   156  	}{
   157  		shards: map[uint32]struct{}{
   158  			2: struct{}{},
   159  			3: struct{}{},
   160  		},
   161  	}
   162  
   163  	var wg sync.WaitGroup
   164  	wg.Add(1)
   165  	onMarkShardsAvailable := func(hostID string, shardIDs ...uint32) {
   166  		needsMarkAvailable.Lock()
   167  		defer needsMarkAvailable.Unlock()
   168  
   169  		for _, shardID := range shardIDs {
   170  			delete(needsMarkAvailable.shards, shardID)
   171  		}
   172  		if len(needsMarkAvailable.shards) == 0 {
   173  			wg.Done()
   174  		}
   175  	}
   176  
   177  	// Could be batched together, or could be called one by one.
   178  	props.topology.EXPECT().
   179  		MarkShardsAvailable("testhost0", gomock.Any()).
   180  		Do(onMarkShardsAvailable).
   181  		AnyTimes()
   182  
   183  	// Simulate the case where the database isn't bootstrapped and durable
   184  	// yet (due to a snapshot not having run yet.)
   185  	mockStorageDB.EXPECT().IsBootstrappedAndDurable().Return(false)
   186  
   187  	// Allow the process to proceed by simulating the situation where the
   188  	// database has had sufficient time to make itself completely bootstrapped
   189  	// as well as durable.
   190  	mockStorageDB.EXPECT().IsBootstrappedAndDurable().Return(true)
   191  
   192  	// Enqueue the update.
   193  	viewsCh <- testutil.NewTopologyView(1, updatedView)
   194  
   195  	// Wait for the update to propagate, consume the first notification
   196  	// from the initial read and then the second that should come after
   197  	// enqueing the view just prior to this read
   198  	for i := 0; i < 2; i++ {
   199  		<-props.propogateViewsCh
   200  	}
   201  
   202  	// Wait for shards to be marked available.
   203  	wg.Wait()
   204  
   205  	mockStorageDB.EXPECT().Close().Return(nil)
   206  	err = db.Close()
   207  	require.NoError(t, err)
   208  }
   209  
   210  func TestDatabaseIsBootstrappedAndDurableNotBootstrapped(t *testing.T) {
   211  	ctrl := gomock.NewController(t)
   212  	defer ctrl.Finish()
   213  
   214  	mockStorageDB, restore := mockNewStorageDatabase(ctrl)
   215  	mockStorageDB.EXPECT().Open().Return(nil)
   216  	defer restore()
   217  
   218  	viewsCh := make(chan testutil.TopologyView, 64)
   219  	defer close(viewsCh)
   220  
   221  	viewsCh <- testutil.NewTopologyView(1, map[string][]shard.Shard{
   222  		"testhost0": sharding.NewShards([]uint32{0, 1}, shard.Available),
   223  		"testhost1": sharding.NewShards([]uint32{2, 3}, shard.Available),
   224  	})
   225  
   226  	topoInit, _ := newMockTopoInit(t, ctrl, viewsCh)
   227  
   228  	db, err := newTestDatabase(t, "testhost0", topoInit)
   229  	require.NoError(t, err)
   230  
   231  	err = db.Open()
   232  	require.NoError(t, err)
   233  
   234  	// If storage database is not bootstrapped, cluster database should
   235  	// not be bootstrapped and durable.
   236  	mockStorageDB.EXPECT().IsBootstrapped().Return(false)
   237  	require.False(t, db.IsBootstrappedAndDurable())
   238  
   239  	// Storage DB is bootstrapped and all shards are available so we should
   240  	// be bootstrapped and durable.
   241  	mockStorageDB.EXPECT().IsBootstrapped().Return(true)
   242  	require.True(t, db.IsBootstrappedAndDurable())
   243  
   244  	mockStorageDB.EXPECT().Close().Return(nil)
   245  	err = db.Close()
   246  	require.NoError(t, err)
   247  }
   248  
   249  func TestDatabaseIsBootstrappedAndDurableShardsNotAvailable(t *testing.T) {
   250  	ctrl := gomock.NewController(t)
   251  	defer ctrl.Finish()
   252  
   253  	mockStorageDB, restore := mockNewStorageDatabase(ctrl)
   254  	mockStorageDB.EXPECT().Open().Return(nil)
   255  	defer restore()
   256  
   257  	viewsCh := make(chan testutil.TopologyView, 64)
   258  	defer close(viewsCh)
   259  
   260  	viewsCh <- testutil.NewTopologyView(1, map[string][]shard.Shard{
   261  		"testhost0": sharding.NewShards([]uint32{0, 1}, shard.Initializing),
   262  		"testhost1": sharding.NewShards([]uint32{2, 3}, shard.Initializing),
   263  	})
   264  
   265  	topoInit, _ := newMockTopoInit(t, ctrl, viewsCh)
   266  
   267  	db, err := newTestDatabase(t, "testhost0", topoInit)
   268  	require.NoError(t, err)
   269  
   270  	err = db.Open()
   271  	require.NoError(t, err)
   272  
   273  	// Even though the storage database is bootstrapped, the clustered database
   274  	// should not be bootstrapped and durable because not all of its shards are
   275  	// in the AVAILABLE or LEAVING state.
   276  	mockStorageDB.EXPECT().IsBootstrapped().Return(true)
   277  	require.False(t, db.IsBootstrappedAndDurable())
   278  
   279  	// Prepare and send a new topology in which all the shards are AVAILABLE
   280  	// or LEAVING.
   281  	updatedView := map[string][]shard.Shard{
   282  		"testhost0": sharding.NewShards([]uint32{0, 1}, shard.Available),
   283  		"testhost1": sharding.NewShards([]uint32{2, 3}, shard.Leaving),
   284  	}
   285  
   286  	wg := sync.WaitGroup{}
   287  	mockStorageDB.EXPECT().AssignShardSet(gomock.Any()).Do(func(interface{}) interface{} {
   288  		wg.Done()
   289  		return nil
   290  	})
   291  
   292  	wg.Add(1)
   293  	viewsCh <- testutil.NewTopologyView(1, updatedView)
   294  
   295  	// Wait for the new shard states to be assigned.
   296  	wg.Wait()
   297  
   298  	// Cluster database should now be bootstrapped and durable because storage
   299  	// database is bootstrapped and all shards are either AVAILABLE or LEAVING.
   300  	mockStorageDB.EXPECT().IsBootstrapped().Return(true)
   301  	require.True(t, db.IsBootstrappedAndDurable())
   302  
   303  	mockStorageDB.EXPECT().Close().Return(nil)
   304  	err = db.Close()
   305  	require.NoError(t, err)
   306  }
   307  
   308  func TestDatabaseOpenUpdatesShardSetBeforeOpen(t *testing.T) {
   309  	ctrl := gomock.NewController(t)
   310  	defer ctrl.Finish()
   311  
   312  	mockStorageDB, restore := mockNewStorageDatabase(ctrl)
   313  	defer restore()
   314  
   315  	viewsCh := make(chan testutil.TopologyView, 64)
   316  	defer close(viewsCh)
   317  
   318  	viewsCh <- testutil.NewTopologyView(1, map[string][]shard.Shard{
   319  		"testhost0": append(sharding.NewShards([]uint32{0, 1}, shard.Available),
   320  			sharding.NewShards([]uint32{2, 3}, shard.Leaving)...),
   321  		"testhost1": sharding.NewShards([]uint32{2, 3}, shard.Initializing),
   322  	})
   323  
   324  	topoInit, props := newMockTopoInit(t, ctrl, viewsCh)
   325  
   326  	db, err := newTestDatabase(t, "testhost0", topoInit)
   327  	require.NoError(t, err)
   328  
   329  	updatedView := map[string][]shard.Shard{
   330  		"testhost0": sharding.NewShards([]uint32{0, 1}, shard.Available),
   331  		"testhost1": sharding.NewShards([]uint32{2, 3}, shard.Available),
   332  	}
   333  
   334  	// Expect the assign shards call before open
   335  	mockStorageDB.EXPECT().AssignShardSet(gomock.Any()).Do(
   336  		func(shardSet sharding.ShardSet) {
   337  			// Ensure updated shard set is as expected
   338  			assert.Equal(t, 2, len(shardSet.AllIDs()))
   339  			values := updatedView["testhost0"]
   340  			hostShardSet, _ := sharding.NewShardSet(values, shardSet.HashFn())
   341  			assert.Equal(t, hostShardSet.AllIDs(), shardSet.AllIDs())
   342  			// Now we can expect an open call
   343  			mockStorageDB.EXPECT().Open().Return(nil)
   344  		})
   345  
   346  	// Enqueue the update
   347  	viewsCh <- testutil.NewTopologyView(1, updatedView)
   348  
   349  	// Wait for the update to propagate, consume the first notification
   350  	// from the initial read and then the second that should come after
   351  	// enqueing the view just prior to this read
   352  	for i := 0; i < 2; i++ {
   353  		<-props.propogateViewsCh
   354  	}
   355  
   356  	// Now open the cluster database
   357  	err = db.Open()
   358  	require.NoError(t, err)
   359  
   360  	mockStorageDB.EXPECT().Close().Return(nil)
   361  	err = db.Close()
   362  	require.NoError(t, err)
   363  }
   364  
   365  func TestDatabaseEmptyShardSet(t *testing.T) {
   366  	ctrl := gomock.NewController(t)
   367  	defer ctrl.Finish()
   368  
   369  	asserted := false
   370  	defer func() {
   371  		assert.True(t, asserted)
   372  	}()
   373  	restore := setNewStorageDatabase(func(
   374  		shardSet sharding.ShardSet,
   375  		opts storage.Options,
   376  	) (storage.Database, error) {
   377  		assert.Equal(t, 0, len(shardSet.AllIDs()))
   378  		asserted = true
   379  		return nil, nil
   380  	})
   381  	defer restore()
   382  
   383  	viewsCh := make(chan testutil.TopologyView, 64)
   384  	defer close(viewsCh)
   385  
   386  	viewsCh <- testutil.NewTopologyView(1, map[string][]shard.Shard{
   387  		"testhost0": sharding.NewShards([]uint32{0}, shard.Available),
   388  		"testhost1": sharding.NewShards([]uint32{1}, shard.Available),
   389  		"testhost2": sharding.NewShards([]uint32{2}, shard.Available),
   390  	})
   391  
   392  	topoInit, _ := newMockTopoInit(t, ctrl, viewsCh)
   393  
   394  	_, err := newTestDatabase(t, "testhost_not_in_placement", topoInit)
   395  	require.NoError(t, err)
   396  }
   397  
   398  func TestDatabaseOpenTwiceError(t *testing.T) {
   399  	ctrl := gomock.NewController(t)
   400  	defer ctrl.Finish()
   401  
   402  	mockStorageDB, restore := mockNewStorageDatabase(ctrl)
   403  	defer restore()
   404  
   405  	viewsCh := make(chan testutil.TopologyView, 64)
   406  	defer close(viewsCh)
   407  
   408  	viewsCh <- testutil.NewTopologyView(1, map[string][]shard.Shard{
   409  		"testhost": sharding.NewShards([]uint32{0, 1, 2}, shard.Available),
   410  	})
   411  
   412  	topoInit, _ := newMockTopoInit(t, ctrl, viewsCh)
   413  
   414  	db, err := newTestDatabase(t, "testhost", topoInit)
   415  	require.NoError(t, err)
   416  
   417  	mockStorageDB.EXPECT().Open().Return(nil).AnyTimes()
   418  
   419  	err = db.Open()
   420  	require.NoError(t, err)
   421  
   422  	err = db.Open()
   423  	require.Error(t, err)
   424  	assert.Equal(t, errAlreadyWatchingTopology, err)
   425  
   426  	mockStorageDB.EXPECT().Close().Return(nil)
   427  	err = db.Close()
   428  	require.NoError(t, err)
   429  }
   430  
   431  func TestDatabaseCloseTwiceError(t *testing.T) {
   432  	ctrl := gomock.NewController(t)
   433  	defer ctrl.Finish()
   434  
   435  	mockStorageDB, restore := mockNewStorageDatabase(ctrl)
   436  	defer restore()
   437  
   438  	viewsCh := make(chan testutil.TopologyView, 64)
   439  	defer close(viewsCh)
   440  
   441  	viewsCh <- testutil.NewTopologyView(1, map[string][]shard.Shard{
   442  		"testhost": sharding.NewShards([]uint32{0, 1, 2}, shard.Available),
   443  	})
   444  
   445  	topoInit, _ := newMockTopoInit(t, ctrl, viewsCh)
   446  
   447  	db, err := newTestDatabase(t, "testhost", topoInit)
   448  	require.NoError(t, err)
   449  
   450  	mockStorageDB.EXPECT().Open().Return(nil)
   451  
   452  	err = db.Open()
   453  	require.NoError(t, err)
   454  
   455  	mockStorageDB.EXPECT().Close().Return(nil).AnyTimes()
   456  	err = db.Close()
   457  	require.NoError(t, err)
   458  
   459  	err = db.Close()
   460  	require.Error(t, err)
   461  	assert.Equal(t, errNotWatchingTopology, err)
   462  }
   463  
   464  func TestDatabaseOpenCanRetry(t *testing.T) {
   465  	ctrl := gomock.NewController(t)
   466  	defer ctrl.Finish()
   467  
   468  	mockStorageDB, restore := mockNewStorageDatabase(ctrl)
   469  	defer restore()
   470  
   471  	viewsCh := make(chan testutil.TopologyView, 64)
   472  	defer close(viewsCh)
   473  
   474  	viewsCh <- testutil.NewTopologyView(1, map[string][]shard.Shard{
   475  		"testhost": sharding.NewShards([]uint32{0, 1, 2}, shard.Available),
   476  	})
   477  
   478  	topoInit, _ := newMockTopoInit(t, ctrl, viewsCh)
   479  
   480  	db, err := newTestDatabase(t, "testhost", topoInit)
   481  	require.NoError(t, err)
   482  
   483  	expectedErr := fmt.Errorf("an error")
   484  	mockStorageDB.EXPECT().Open().Return(expectedErr)
   485  
   486  	err = db.Open()
   487  	require.Error(t, err)
   488  	assert.Equal(t, expectedErr, err)
   489  
   490  	mockStorageDB.EXPECT().Open().Return(nil)
   491  
   492  	err = db.Open()
   493  	require.NoError(t, err)
   494  
   495  	mockStorageDB.EXPECT().Close().Return(nil)
   496  	err = db.Close()
   497  	require.NoError(t, err)
   498  }
   499  
   500  func TestDatabaseCloseCanRetry(t *testing.T) {
   501  	ctrl := gomock.NewController(t)
   502  	defer ctrl.Finish()
   503  
   504  	mockStorageDB, restore := mockNewStorageDatabase(ctrl)
   505  	defer restore()
   506  
   507  	viewsCh := make(chan testutil.TopologyView, 64)
   508  	defer close(viewsCh)
   509  
   510  	viewsCh <- testutil.NewTopologyView(1, map[string][]shard.Shard{
   511  		"testhost": sharding.NewShards([]uint32{0, 1, 2}, shard.Available),
   512  	})
   513  
   514  	topoInit, _ := newMockTopoInit(t, ctrl, viewsCh)
   515  
   516  	db, err := newTestDatabase(t, "testhost", topoInit)
   517  	require.NoError(t, err)
   518  
   519  	mockStorageDB.EXPECT().Open().Return(nil)
   520  
   521  	err = db.Open()
   522  	require.NoError(t, err)
   523  
   524  	expectedErr := fmt.Errorf("an error")
   525  	mockStorageDB.EXPECT().Close().Return(expectedErr)
   526  	err = db.Close()
   527  	require.Error(t, err)
   528  	assert.Equal(t, expectedErr, err)
   529  
   530  	mockStorageDB.EXPECT().Close().Return(nil)
   531  	err = db.Close()
   532  	require.NoError(t, err)
   533  }