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 }