github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/bootstrap_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 storage
    22  
    23  import (
    24  	"fmt"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  
    33  	"github.com/m3db/m3/src/dbnode/namespace"
    34  	"github.com/m3db/m3/src/dbnode/storage/bootstrap"
    35  	"github.com/m3db/m3/src/x/context"
    36  	"github.com/m3db/m3/src/x/ident"
    37  	xtest "github.com/m3db/m3/src/x/test"
    38  )
    39  
    40  func TestDatabaseBootstrapWithBootstrapError(t *testing.T) {
    41  	testDatabaseBootstrapWithBootstrapError(t, false)
    42  }
    43  
    44  func TestDatabaseBootstrapEnqueueWithBootstrapError(t *testing.T) {
    45  	testDatabaseBootstrapWithBootstrapError(t, true)
    46  }
    47  
    48  func testDatabaseBootstrapWithBootstrapError(t *testing.T, async bool) {
    49  	ctrl := xtest.NewController(t)
    50  	defer ctrl.Finish()
    51  
    52  	opts := DefaultTestOptions()
    53  	now := time.Now()
    54  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time {
    55  		return now
    56  	}))
    57  
    58  	id := ident.StringID("a")
    59  	meta, err := namespace.NewMetadata(id, namespace.NewOptions())
    60  	require.NoError(t, err)
    61  
    62  	ns := NewMockdatabaseNamespace(ctrl)
    63  	namespaces := []databaseNamespace{ns}
    64  
    65  	db := NewMockdatabase(ctrl)
    66  	db.EXPECT().OwnedNamespaces().Return(namespaces, nil)
    67  
    68  	m := NewMockdatabaseMediator(ctrl)
    69  	m.EXPECT().DisableFileOpsAndWait()
    70  	m.EXPECT().EnableFileOps().AnyTimes()
    71  
    72  	bsm := newBootstrapManager(db, m, opts).(*bootstrapManager)
    73  	// Don't sleep.
    74  	bsm.sleepFn = func(time.Duration) {}
    75  
    76  	gomock.InOrder(
    77  		ns.EXPECT().PrepareBootstrap(gomock.Any()).Return([]databaseShard{}, nil),
    78  		ns.EXPECT().Metadata().Return(meta),
    79  		ns.EXPECT().ID().Return(id),
    80  		ns.EXPECT().
    81  			Bootstrap(gomock.Any(), gomock.Any()).
    82  			Return(fmt.Errorf("an error")).
    83  			Do(func(ctx context.Context, bootstrapResult bootstrap.NamespaceResult) {
    84  				// After returning an error, make sure we don't re-enqueue.
    85  				require.Equal(t, Bootstrapping, bsm.state)
    86  				bsm.bootstrapFn = func() error {
    87  					require.Equal(t, Bootstrapping, bsm.state)
    88  					return nil
    89  				}
    90  			}),
    91  	)
    92  
    93  	ctx := context.NewBackground()
    94  	defer ctx.Close()
    95  
    96  	require.Equal(t, BootstrapNotStarted, bsm.state)
    97  
    98  	var result BootstrapResult
    99  	if async {
   100  		var wg sync.WaitGroup
   101  		wg.Add(1)
   102  		bsm.BootstrapEnqueue(BootstrapEnqueueOptions{
   103  			OnCompleteFn: func(r BootstrapResult) {
   104  				result = r
   105  				wg.Done()
   106  			},
   107  		})
   108  		wg.Wait()
   109  	} else {
   110  		result, err = bsm.Bootstrap()
   111  		require.NoError(t, err)
   112  	}
   113  
   114  	require.Equal(t, Bootstrapped, bsm.state)
   115  	require.Equal(t, 1, len(result.ErrorsBootstrap))
   116  	require.Equal(t, "an error", result.ErrorsBootstrap[0].Error())
   117  }
   118  
   119  func TestDatabaseBootstrapSubsequentCallsQueued(t *testing.T) {
   120  	ctrl := xtest.NewController(t)
   121  	defer ctrl.Finish()
   122  
   123  	opts := DefaultTestOptions()
   124  	now := time.Now()
   125  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time {
   126  		return now
   127  	}))
   128  
   129  	m := NewMockdatabaseMediator(ctrl)
   130  	m.EXPECT().DisableFileOpsAndWait()
   131  	m.EXPECT().EnableFileOps().AnyTimes()
   132  
   133  	db := NewMockdatabase(ctrl)
   134  	bsm := newBootstrapManager(db, m, opts).(*bootstrapManager)
   135  	ns := NewMockdatabaseNamespace(ctrl)
   136  	id := ident.StringID("testBootstrap")
   137  	meta, err := namespace.NewMetadata(id, namespace.NewOptions())
   138  	require.NoError(t, err)
   139  
   140  	var wg sync.WaitGroup
   141  	wg.Add(1)
   142  
   143  	ns.EXPECT().PrepareBootstrap(gomock.Any()).Return([]databaseShard{}, nil).Times(2)
   144  	ns.EXPECT().Metadata().Return(meta).Times(2)
   145  	ns.EXPECT().
   146  		Bootstrap(gomock.Any(), gomock.Any()).
   147  		Return(nil).
   148  		Do(func(arg0, arg1 interface{}) {
   149  			defer wg.Done()
   150  
   151  			// Enqueue the second bootstrap
   152  			_, err := bsm.Bootstrap()
   153  			assert.Error(t, err)
   154  			assert.Equal(t, errBootstrapEnqueued, err)
   155  			assert.False(t, bsm.IsBootstrapped())
   156  			bsm.RLock()
   157  			assert.Equal(t, true, bsm.hasPending)
   158  			bsm.RUnlock()
   159  
   160  			// Expect the second bootstrap call
   161  			ns.EXPECT().Bootstrap(gomock.Any(), gomock.Any()).Return(nil)
   162  		})
   163  	ns.EXPECT().
   164  		ID().
   165  		Return(id).
   166  		Times(2)
   167  	db.EXPECT().
   168  		OwnedNamespaces().
   169  		Return([]databaseNamespace{ns}, nil).
   170  		Times(2)
   171  
   172  	_, err = bsm.Bootstrap()
   173  	require.Nil(t, err)
   174  }
   175  
   176  func TestDatabaseBootstrapBootstrapHooks(t *testing.T) {
   177  	ctrl := xtest.NewController(t)
   178  	defer ctrl.Finish()
   179  
   180  	opts := DefaultTestOptions()
   181  	now := time.Now()
   182  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time {
   183  		return now
   184  	}))
   185  
   186  	m := NewMockdatabaseMediator(ctrl)
   187  	m.EXPECT().DisableFileOpsAndWait()
   188  	m.EXPECT().EnableFileOps().AnyTimes()
   189  
   190  	db := NewMockdatabase(ctrl)
   191  	bsm := newBootstrapManager(db, m, opts).(*bootstrapManager)
   192  
   193  	numNamespaces := 3
   194  	namespaces := make([]databaseNamespace, 0, 3)
   195  	for i := 0; i < numNamespaces; i++ {
   196  		ns := NewMockdatabaseNamespace(ctrl)
   197  		id := ident.StringID("testBootstrap")
   198  		meta, err := namespace.NewMetadata(id, namespace.NewOptions())
   199  		require.NoError(t, err)
   200  
   201  		var wg sync.WaitGroup
   202  		wg.Add(1)
   203  
   204  		numShards := 8
   205  		shards := make([]databaseShard, 0, numShards)
   206  		for j := 0; j < numShards; j++ {
   207  			shard := NewMockdatabaseShard(ctrl)
   208  			shard.EXPECT().IsBootstrapped().Return(false)
   209  			shard.EXPECT().IsBootstrapped().Return(true)
   210  			shard.EXPECT().UpdateFlushStates().Times(2)
   211  			shard.EXPECT().ID().Return(uint32(j)).AnyTimes()
   212  			shards = append(shards, shard)
   213  		}
   214  
   215  		ns.EXPECT().PrepareBootstrap(gomock.Any()).Return(shards, nil).AnyTimes()
   216  		ns.EXPECT().Metadata().Return(meta).AnyTimes()
   217  		ns.EXPECT().
   218  			Bootstrap(gomock.Any(), gomock.Any()).
   219  			Return(nil).
   220  			Do(func(arg0, arg1 interface{}) {
   221  				defer wg.Done()
   222  
   223  				// Enqueue the second bootstrap
   224  				_, err := bsm.Bootstrap()
   225  				assert.Error(t, err)
   226  				assert.Equal(t, errBootstrapEnqueued, err)
   227  				assert.False(t, bsm.IsBootstrapped())
   228  				bsm.RLock()
   229  				assert.Equal(t, true, bsm.hasPending)
   230  				bsm.RUnlock()
   231  
   232  				// Expect the second bootstrap call
   233  				ns.EXPECT().Bootstrap(gomock.Any(), gomock.Any()).Return(nil)
   234  			})
   235  		ns.EXPECT().
   236  			ID().
   237  			Return(id).
   238  			Times(2)
   239  		namespaces = append(namespaces, ns)
   240  	}
   241  	db.EXPECT().
   242  		OwnedNamespaces().
   243  		Return(namespaces, nil).
   244  		Times(2)
   245  
   246  	_, err := bsm.Bootstrap()
   247  	require.Nil(t, err)
   248  }