github.com/m3db/m3@v1.5.0/src/dbnode/integration/integration.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 integration 22 23 import ( 24 "fmt" 25 "sync" 26 "testing" 27 "time" 28 29 "github.com/stretchr/testify/require" 30 "github.com/uber-go/tally" 31 "go.uber.org/zap" 32 33 "github.com/m3db/m3/src/cluster/shard" 34 "github.com/m3db/m3/src/dbnode/client" 35 "github.com/m3db/m3/src/dbnode/integration/generate" 36 "github.com/m3db/m3/src/dbnode/namespace" 37 "github.com/m3db/m3/src/dbnode/persist/fs" 38 persistfs "github.com/m3db/m3/src/dbnode/persist/fs" 39 "github.com/m3db/m3/src/dbnode/runtime" 40 "github.com/m3db/m3/src/dbnode/storage" 41 "github.com/m3db/m3/src/dbnode/storage/bootstrap" 42 "github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper" 43 "github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper/commitlog" 44 bfs "github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper/fs" 45 "github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper/peers" 46 "github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper/uninitialized" 47 "github.com/m3db/m3/src/dbnode/storage/bootstrap/result" 48 "github.com/m3db/m3/src/dbnode/storage/index" 49 "github.com/m3db/m3/src/dbnode/storage/index/compaction" 50 "github.com/m3db/m3/src/dbnode/storage/repair" 51 "github.com/m3db/m3/src/dbnode/topology" 52 "github.com/m3db/m3/src/dbnode/topology/testutil" 53 xmetrics "github.com/m3db/m3/src/dbnode/x/metrics" 54 "github.com/m3db/m3/src/m3ninx/doc" 55 "github.com/m3db/m3/src/m3ninx/index/segment/builder" 56 "github.com/m3db/m3/src/m3ninx/index/segment/fst" 57 idxpersist "github.com/m3db/m3/src/m3ninx/persist" 58 "github.com/m3db/m3/src/x/instrument" 59 xretry "github.com/m3db/m3/src/x/retry" 60 xtime "github.com/m3db/m3/src/x/time" 61 ) 62 63 const ( 64 multiAddrPortStart = 9000 65 multiAddrPortEach = 5 66 ) 67 68 // TODO: refactor and use m3x/clock ... 69 type conditionFn func() bool 70 71 func waitUntil(fn conditionFn, timeout time.Duration) bool { 72 deadline := time.Now().Add(timeout) 73 for time.Now().Before(deadline) { 74 if fn() { 75 return true 76 } 77 time.Sleep(100 * time.Millisecond) 78 } 79 return false 80 } 81 82 func newMultiAddrTestOptions(opts TestOptions, instance int) TestOptions { 83 bind := "127.0.0.1" 84 start := multiAddrPortStart + (instance * multiAddrPortEach) 85 return opts. 86 SetID(fmt.Sprintf("testhost%d", instance)). 87 SetTChannelNodeAddr(fmt.Sprintf("%s:%d", bind, start)). 88 SetTChannelClusterAddr(fmt.Sprintf("%s:%d", bind, start+1)). 89 SetHTTPNodeAddr(fmt.Sprintf("%s:%d", bind, start+2)). 90 SetHTTPClusterAddr(fmt.Sprintf("%s:%d", bind, start+3)). 91 SetHTTPDebugAddr(fmt.Sprintf("%s:%d", bind, start+4)) 92 } 93 94 func newMultiAddrAdminClient( 95 t *testing.T, 96 adminOpts client.AdminOptions, 97 topologyInitializer topology.Initializer, 98 origin topology.Host, 99 instrumentOpts instrument.Options, 100 customOpts ...client.CustomAdminOption, 101 ) client.AdminClient { 102 if adminOpts == nil { 103 adminOpts = client.NewAdminOptions() 104 } 105 106 adminOpts = adminOpts. 107 SetOrigin(origin). 108 SetInstrumentOptions(instrumentOpts). 109 SetClusterConnectConsistencyLevel(topology.ConnectConsistencyLevelAny). 110 SetTopologyInitializer(topologyInitializer). 111 SetClusterConnectTimeout(time.Second).(client.AdminOptions) 112 113 for _, o := range customOpts { 114 adminOpts = o(adminOpts) 115 } 116 117 adminClient, err := client.NewAdminClient(adminOpts) 118 require.NoError(t, err) 119 120 return adminClient 121 } 122 123 // BootstrappableTestSetupOptions defines options for test setups. 124 type BootstrappableTestSetupOptions struct { 125 FinalBootstrapper string 126 BootstrapBlocksBatchSize int 127 BootstrapBlocksConcurrency int 128 BootstrapConsistencyLevel topology.ReadConsistencyLevel 129 TopologyInitializer topology.Initializer 130 TestStatsReporter xmetrics.TestStatsReporter 131 DisableCommitLogBootstrapper bool 132 DisablePeersBootstrapper bool 133 UseTChannelClientForWriting bool 134 EnableRepairs bool 135 ForceRepairs bool 136 RepairType repair.Type 137 AdminClientCustomOpts []client.CustomAdminOption 138 } 139 140 type closeFn func() 141 142 func newDefaulTestResultOptions( 143 storageOpts storage.Options, 144 ) result.Options { 145 return result.NewOptions(). 146 SetClockOptions(storageOpts.ClockOptions()). 147 SetInstrumentOptions(storageOpts.InstrumentOptions()). 148 SetDatabaseBlockOptions(storageOpts.DatabaseBlockOptions()). 149 SetSeriesCachePolicy(storageOpts.SeriesCachePolicy()) 150 } 151 152 // NewDefaultBootstrappableTestSetups creates dbnode test setups. 153 func NewDefaultBootstrappableTestSetups( // nolint:gocyclo 154 t *testing.T, 155 opts TestOptions, 156 setupOpts []BootstrappableTestSetupOptions, 157 ) (testSetups, closeFn) { 158 var ( 159 replicas = len(setupOpts) 160 setups []TestSetup 161 cleanupFns []func() 162 cleanupFnsMutex sync.RWMutex 163 164 appendCleanupFn = func(fn func()) { 165 cleanupFnsMutex.Lock() 166 defer cleanupFnsMutex.Unlock() 167 cleanupFns = append(cleanupFns, fn) 168 } 169 ) 170 171 shardSet, err := newTestShardSet(opts.NumShards(), opts.ShardSetOptions()) 172 require.NoError(t, err) 173 for i := 0; i < replicas; i++ { 174 var ( 175 instance = i 176 usingCommitLogBootstrapper = !setupOpts[i].DisableCommitLogBootstrapper 177 usingPeersBootstrapper = !setupOpts[i].DisablePeersBootstrapper 178 finalBootstrapperToUse = setupOpts[i].FinalBootstrapper 179 useTChannelClientForWriting = setupOpts[i].UseTChannelClientForWriting 180 bootstrapBlocksBatchSize = setupOpts[i].BootstrapBlocksBatchSize 181 bootstrapBlocksConcurrency = setupOpts[i].BootstrapBlocksConcurrency 182 bootstrapConsistencyLevel = setupOpts[i].BootstrapConsistencyLevel 183 topologyInitializer = setupOpts[i].TopologyInitializer 184 testStatsReporter = setupOpts[i].TestStatsReporter 185 enableRepairs = setupOpts[i].EnableRepairs 186 forceRepairs = setupOpts[i].ForceRepairs 187 repairType = setupOpts[i].RepairType 188 origin topology.Host 189 instanceOpts = newMultiAddrTestOptions(opts, instance) 190 adminClientCustomOpts = setupOpts[i].AdminClientCustomOpts 191 ) 192 193 if finalBootstrapperToUse == "" { 194 finalBootstrapperToUse = bootstrapper.NoOpNoneBootstrapperName 195 } 196 197 if topologyInitializer == nil { 198 // Setup static topology initializer 199 var ( 200 start = multiAddrPortStart 201 hostShardSets []topology.HostShardSet 202 ) 203 204 for i := 0; i < replicas; i++ { 205 id := fmt.Sprintf("testhost%d", i) 206 nodeAddr := fmt.Sprintf("127.0.0.1:%d", start+(i*multiAddrPortEach)) 207 host := topology.NewHost(id, nodeAddr) 208 if i == instance { 209 origin = host 210 } 211 hostShardSet := topology.NewHostShardSet(host, shardSet) 212 hostShardSets = append(hostShardSets, hostShardSet) 213 } 214 215 staticOptions := topology.NewStaticOptions(). 216 SetShardSet(shardSet). 217 SetReplicas(replicas). 218 SetHostShardSets(hostShardSets) 219 topologyInitializer = topology.NewStaticInitializer(staticOptions) 220 } 221 222 instanceOpts = instanceOpts. 223 SetClusterDatabaseTopologyInitializer(topologyInitializer). 224 SetUseTChannelClientForWriting(useTChannelClientForWriting) 225 226 if i > 0 { 227 // NB(bodu): Need to reset the global counter of number of index 228 // claim manager instances after the initial node. 229 persistfs.ResetIndexClaimsManagersUnsafe() 230 } 231 setup, err := NewTestSetup(t, instanceOpts, nil, opts.StorageOptsFn()) 232 require.NoError(t, err) 233 topologyInitializer = setup.TopologyInitializer() 234 235 instrumentOpts := setup.StorageOpts().InstrumentOptions() 236 logger := instrumentOpts.Logger() 237 logger = logger.With(zap.Int("instance", instance)) 238 instrumentOpts = instrumentOpts.SetLogger(logger) 239 if testStatsReporter != nil { 240 scope, _ := tally.NewRootScope(tally.ScopeOptions{Reporter: testStatsReporter}, 100*time.Millisecond) 241 instrumentOpts = instrumentOpts.SetMetricsScope(scope) 242 } 243 setup.SetStorageOpts(setup.StorageOpts().SetInstrumentOptions(instrumentOpts)) 244 245 var ( 246 bsOpts = newDefaulTestResultOptions(setup.StorageOpts()) 247 finalBootstrapper bootstrap.BootstrapperProvider 248 249 adminOpts = client.NewAdminOptions(). 250 SetTopologyInitializer(topologyInitializer).(client.AdminOptions). 251 SetOrigin(origin) 252 253 // Prevent integration tests from timing out when a node is down 254 retryOpts = xretry.NewOptions(). 255 SetInitialBackoff(1 * time.Millisecond). 256 SetMaxRetries(1). 257 SetJitter(true) 258 retrier = xretry.NewRetrier(retryOpts) 259 ) 260 261 switch finalBootstrapperToUse { 262 case bootstrapper.NoOpAllBootstrapperName: 263 finalBootstrapper = bootstrapper.NewNoOpAllBootstrapperProvider() 264 case bootstrapper.NoOpNoneBootstrapperName: 265 finalBootstrapper = bootstrapper.NewNoOpNoneBootstrapperProvider() 266 case uninitialized.UninitializedTopologyBootstrapperName: 267 finalBootstrapper = uninitialized.NewUninitializedTopologyBootstrapperProvider( 268 uninitialized.NewOptions(). 269 SetInstrumentOptions(instrumentOpts), nil) 270 default: 271 panic(fmt.Sprintf( 272 "Unknown final bootstrapper to use: %v", finalBootstrapperToUse)) 273 } 274 275 if bootstrapBlocksBatchSize > 0 { 276 adminOpts = adminOpts.SetFetchSeriesBlocksBatchSize(bootstrapBlocksBatchSize) 277 } 278 if bootstrapBlocksConcurrency > 0 { 279 adminOpts = adminOpts.SetFetchSeriesBlocksBatchConcurrency(bootstrapBlocksConcurrency) 280 } 281 adminOpts = adminOpts.SetStreamBlocksRetrier(retrier) 282 283 adminClient := newMultiAddrAdminClient( 284 t, adminOpts, topologyInitializer, origin, instrumentOpts, adminClientCustomOpts...) 285 setup.SetStorageOpts(setup.StorageOpts().SetAdminClient(adminClient)) 286 287 storageIdxOpts := setup.StorageOpts().IndexOptions() 288 fsOpts := setup.StorageOpts().CommitLogOptions().FilesystemOptions() 289 if usingPeersBootstrapper { 290 var ( 291 runtimeOptsMgr = setup.StorageOpts().RuntimeOptionsManager() 292 runtimeOpts = runtimeOptsMgr.Get(). 293 SetClientBootstrapConsistencyLevel(bootstrapConsistencyLevel) 294 ) 295 runtimeOptsMgr.Update(runtimeOpts) 296 297 peersOpts := peers.NewOptions(). 298 SetResultOptions(bsOpts). 299 SetAdminClient(adminClient). 300 SetIndexOptions(storageIdxOpts). 301 SetFilesystemOptions(fsOpts). 302 // PersistManager need to be set or we will never execute 303 // the persist bootstrapping path 304 SetPersistManager(setup.StorageOpts().PersistManager()). 305 SetIndexClaimsManager(setup.StorageOpts().IndexClaimsManager()). 306 SetCompactor(newCompactor(t, storageIdxOpts)). 307 SetRuntimeOptionsManager(runtimeOptsMgr). 308 SetContextPool(setup.StorageOpts().ContextPool()) 309 310 finalBootstrapper, err = peers.NewPeersBootstrapperProvider(peersOpts, finalBootstrapper) 311 require.NoError(t, err) 312 } 313 314 if usingCommitLogBootstrapper { 315 bootstrapCommitlogOpts := commitlog.NewOptions(). 316 SetResultOptions(bsOpts). 317 SetCommitLogOptions(setup.StorageOpts().CommitLogOptions()). 318 SetRuntimeOptionsManager(runtime.NewOptionsManager()) 319 320 finalBootstrapper, err = commitlog.NewCommitLogBootstrapperProvider(bootstrapCommitlogOpts, 321 mustInspectFilesystem(fsOpts), finalBootstrapper) 322 require.NoError(t, err) 323 } 324 325 persistMgr, err := persistfs.NewPersistManager(fsOpts) 326 require.NoError(t, err) 327 328 bfsOpts := bfs.NewOptions(). 329 SetResultOptions(bsOpts). 330 SetFilesystemOptions(fsOpts). 331 SetIndexOptions(storageIdxOpts). 332 SetCompactor(newCompactor(t, storageIdxOpts)). 333 SetPersistManager(persistMgr). 334 SetIndexClaimsManager(setup.StorageOpts().IndexClaimsManager()) 335 336 fsBootstrapper, err := bfs.NewFileSystemBootstrapperProvider(bfsOpts, finalBootstrapper) 337 require.NoError(t, err) 338 339 processOpts := bootstrap.NewProcessOptions(). 340 SetTopologyMapProvider(setup). 341 SetOrigin(setup.Origin()) 342 provider, err := bootstrap.NewProcessProvider(fsBootstrapper, processOpts, bsOpts, fsOpts) 343 require.NoError(t, err) 344 345 setup.SetStorageOpts(setup.StorageOpts().SetBootstrapProcessProvider(provider)) 346 347 if enableRepairs { 348 setup.SetStorageOpts(setup.StorageOpts(). 349 SetRepairEnabled(true). 350 SetRepairOptions( 351 setup.StorageOpts().RepairOptions(). 352 SetType(repairType). 353 SetForce(forceRepairs). 354 SetRepairThrottle(time.Millisecond). 355 SetRepairCheckInterval(time.Millisecond). 356 SetAdminClients([]client.AdminClient{adminClient}). 357 SetDebugShadowComparisonsPercentage(1.0). 358 // Avoid log spam. 359 SetDebugShadowComparisonsEnabled(false))) 360 } 361 362 setups = append(setups, setup) 363 appendCleanupFn(func() { 364 setup.Close() 365 }) 366 } 367 368 return setups, func() { 369 cleanupFnsMutex.RLock() 370 defer cleanupFnsMutex.RUnlock() 371 for _, fn := range cleanupFns { 372 fn() 373 } 374 } 375 } 376 377 func writeTestDataToDiskWithIndex( 378 metadata namespace.Metadata, 379 s TestSetup, 380 seriesMaps generate.SeriesBlocksByStart, 381 ) error { 382 if err := writeTestDataToDisk(metadata, s, seriesMaps, 0); err != nil { 383 return err 384 } 385 for blockStart, series := range seriesMaps { 386 docs := generate.ToDocMetadata(series) 387 if err := writeTestIndexDataToDisk( 388 metadata, 389 s.StorageOpts(), 390 idxpersist.DefaultIndexVolumeType, 391 blockStart, 392 s.ShardSet().AllIDs(), 393 docs, 394 ); err != nil { 395 return err 396 } 397 } 398 return nil 399 } 400 401 func writeTestDataToDisk( 402 metadata namespace.Metadata, 403 setup TestSetup, 404 seriesMaps generate.SeriesBlocksByStart, 405 volume int, 406 generatorOptionsFns ...func(generate.Options) generate.Options, 407 ) error { 408 ropts := metadata.Options().RetentionOptions() 409 gOpts := setup.GeneratorOptions(ropts) 410 for _, fn := range generatorOptionsFns { 411 gOpts = fn(gOpts) 412 } 413 writer := generate.NewWriter(gOpts) 414 return writer.WriteData(namespace.NewContextFrom(metadata), setup.ShardSet(), seriesMaps, volume) 415 } 416 417 func writeTestSnapshotsToDiskWithPredicate( 418 metadata namespace.Metadata, 419 setup TestSetup, 420 seriesMaps generate.SeriesBlocksByStart, 421 volume int, 422 pred generate.WriteDatapointPredicate, 423 snapshotInterval time.Duration, 424 ) error { 425 ropts := metadata.Options().RetentionOptions() 426 writer := generate.NewWriter(setup.GeneratorOptions(ropts)) 427 return writer.WriteSnapshotWithPredicate( 428 namespace.NewContextFrom(metadata), setup.ShardSet(), seriesMaps, volume, pred, snapshotInterval) 429 } 430 431 func concatShards(a, b shard.Shards) shard.Shards { 432 all := append(a.All(), b.All()...) 433 return shard.NewShards(all) 434 } 435 436 func newClusterShardsRange(from, to uint32, s shard.State) shard.Shards { 437 return shard.NewShards(testutil.ShardsRange(from, to, s)) 438 } 439 440 func newClusterEmptyShardsRange() shard.Shards { 441 return shard.NewShards(testutil.Shards(nil, shard.Available)) 442 } 443 444 func waitUntilHasBootstrappedShardsExactly( 445 db storage.Database, 446 shards []uint32, 447 ) { 448 for { 449 if hasBootstrappedShardsExactly(db, shards) { 450 return 451 } 452 time.Sleep(time.Second) 453 } 454 } 455 456 func hasBootstrappedShardsExactly( 457 db storage.Database, 458 shards []uint32, 459 ) bool { 460 for _, namespace := range db.Namespaces() { 461 expect := make(map[uint32]struct{}) 462 pending := make(map[uint32]struct{}) 463 for _, shard := range shards { 464 expect[shard] = struct{}{} 465 pending[shard] = struct{}{} 466 } 467 468 for _, s := range namespace.Shards() { 469 if _, ok := expect[s.ID()]; !ok { 470 // Not expecting shard 471 return false 472 } 473 if s.IsBootstrapped() { 474 delete(pending, s.ID()) 475 } 476 } 477 478 if len(pending) != 0 { 479 // Not all shards bootstrapped 480 return false 481 } 482 } 483 484 return true 485 } 486 487 func newCompactor( 488 t *testing.T, 489 opts index.Options, 490 ) *compaction.Compactor { 491 compactor, err := newCompactorWithErr(opts) 492 require.NoError(t, err) 493 return compactor 494 } 495 496 func newCompactorWithErr(opts index.Options) (*compaction.Compactor, error) { 497 return compaction.NewCompactor(opts.MetadataArrayPool(), 498 index.MetadataArrayPoolCapacity, 499 opts.SegmentBuilderOptions(), 500 opts.FSTSegmentOptions(), 501 compaction.CompactorOptions{ 502 FSTWriterOptions: &fst.WriterOptions{ 503 // DisableRegistry is set to true to trade a larger FST size 504 // for a faster FST compaction since we want to reduce the end 505 // to end latency for time to first index a metric. 506 DisableRegistry: true, 507 }, 508 }) 509 } 510 511 func writeTestIndexDataToDisk( 512 md namespace.Metadata, 513 storageOpts storage.Options, 514 indexVolumeType idxpersist.IndexVolumeType, 515 blockStart xtime.UnixNano, 516 shards []uint32, 517 docs []doc.Metadata, 518 ) error { 519 blockSize := md.Options().IndexOptions().BlockSize() 520 fsOpts := storageOpts.CommitLogOptions().FilesystemOptions() 521 writer, err := fs.NewIndexWriter(fsOpts) 522 if err != nil { 523 return err 524 } 525 segmentWriter, err := idxpersist.NewMutableSegmentFileSetWriter(fst.WriterOptions{}) 526 if err != nil { 527 return err 528 } 529 530 shardsMap := make(map[uint32]struct{}) 531 for _, shard := range shards { 532 shardsMap[shard] = struct{}{} 533 } 534 volumeIndex, err := fs.NextIndexFileSetVolumeIndex( 535 fsOpts.FilePathPrefix(), 536 md.ID(), 537 blockStart, 538 ) 539 if err != nil { 540 return err 541 } 542 writerOpts := fs.IndexWriterOpenOptions{ 543 Identifier: fs.FileSetFileIdentifier{ 544 Namespace: md.ID(), 545 BlockStart: blockStart, 546 VolumeIndex: volumeIndex, 547 }, 548 BlockSize: blockSize, 549 Shards: shardsMap, 550 IndexVolumeType: indexVolumeType, 551 } 552 if err := writer.Open(writerOpts); err != nil { 553 return err 554 } 555 556 builder, err := builder.NewBuilderFromDocuments(builder.NewOptions()) 557 for _, doc := range docs { 558 _, err = builder.Insert(doc) 559 if err != nil { 560 return err 561 } 562 } 563 564 if err := segmentWriter.Reset(builder); err != nil { 565 return err 566 } 567 if err := writer.WriteSegmentFileSet(segmentWriter); err != nil { 568 return err 569 } 570 if err := builder.Close(); err != nil { 571 return err 572 } 573 return writer.Close() 574 }