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 }