github.com/m3db/m3@v1.5.0/src/cmd/services/m3dbnode/main/main_test.go (about) 1 // +build big 2 // 3 // Copyright (c) 2017 Uber Technologies, Inc. 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 // 12 // The above copyright notice and this permission notice shall be included in 13 // all copies or substantial portions of the Software. 14 // 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 // THE SOFTWARE. 22 23 package main_test 24 25 import ( 26 "fmt" 27 "net/http" 28 "strconv" 29 "sync" 30 "testing" 31 "text/template" 32 "time" 33 34 "github.com/m3db/m3/src/cluster/integration/etcd" 35 "github.com/m3db/m3/src/cluster/placement" 36 "github.com/m3db/m3/src/cluster/services" 37 "github.com/m3db/m3/src/cmd/services/m3dbnode/config" 38 "github.com/m3db/m3/src/dbnode/client" 39 "github.com/m3db/m3/src/dbnode/kvconfig" 40 "github.com/m3db/m3/src/dbnode/server" 41 xconfig "github.com/m3db/m3/src/x/config" 42 "github.com/m3db/m3/src/x/ident" 43 "github.com/m3db/m3/src/x/instrument" 44 xtime "github.com/m3db/m3/src/x/time" 45 46 "github.com/stretchr/testify/assert" 47 "github.com/stretchr/testify/require" 48 "go.uber.org/zap" 49 ) 50 51 // TestConfig tests booting a server using file based configuration. 52 func TestConfig(t *testing.T) { 53 // Embedded kv 54 embeddedKV, err := etcd.New(etcd.NewOptions()) 55 require.NoError(t, err) 56 defer func() { 57 require.NoError(t, embeddedKV.Close()) 58 }() 59 require.NoError(t, embeddedKV.Start()) 60 61 // Create config file 62 tmpl, err := template.New("config").Parse(testConfig + kvConfigPortion) 63 require.NoError(t, err) 64 65 configFd, cleanup := tempFile(t, "config.yaml") 66 defer cleanup() 67 68 logFile, cleanupLogFile := tempFileTouch(t, "m3dbnode.log") 69 defer cleanupLogFile() 70 71 configServiceCacheDir, cleanupConfigServiceCacheDir := tempDir(t, "kv") 72 defer cleanupConfigServiceCacheDir() 73 74 dataDir, cleanupDataDir := tempDir(t, "data") 75 defer cleanupDataDir() 76 77 servicePort := nextServicePort() 78 err = tmpl.Execute(configFd, struct { 79 HostID string 80 LogFile string 81 DataDir string 82 ServicePort string 83 ServiceName string 84 ServiceEnv string 85 ServiceZone string 86 ConfigServiceCacheDir string 87 EtcdEndpoints string 88 }{ 89 HostID: hostID, 90 LogFile: logFile, 91 DataDir: dataDir, 92 ServicePort: strconv.Itoa(int(servicePort)), 93 ServiceName: serviceName, 94 ServiceEnv: serviceEnv, 95 ServiceZone: serviceZone, 96 ConfigServiceCacheDir: configServiceCacheDir, 97 EtcdEndpoints: yamlArray(t, embeddedKV.Endpoints()), 98 }) 99 require.NoError(t, err) 100 101 // Setup the placement 102 var cfg config.Configuration 103 err = xconfig.LoadFile(&cfg, configFd.Name(), xconfig.Options{}) 104 require.NoError(t, err) 105 106 discoveryCfg := cfg.DB.DiscoveryOrDefault() 107 envCfg, err := discoveryCfg.EnvironmentConfig(hostID) 108 require.NoError(t, err) 109 110 syncCluster, err := envCfg.Services.SyncCluster() 111 require.NoError(t, err) 112 configSvcClient, err := syncCluster.Service.NewClient(instrument.NewOptions(). 113 SetLogger(zap.NewNop())) 114 require.NoError(t, err) 115 116 svcs, err := configSvcClient.Services(services.NewOverrideOptions()) 117 require.NoError(t, err) 118 119 serviceID := services.NewServiceID(). 120 SetName(serviceName). 121 SetEnvironment(serviceEnv). 122 SetZone(serviceZone) 123 124 metadata := services.NewMetadata(). 125 SetPort(servicePort). 126 SetLivenessInterval(time.Minute). 127 SetHeartbeatInterval(10 * time.Second) 128 129 err = svcs.SetMetadata(serviceID, metadata) 130 require.NoError(t, err) 131 132 placementOpts := placement.NewOptions(). 133 SetValidZone(serviceZone) 134 placementSvc, err := svcs.PlacementService(serviceID, placementOpts) 135 require.NoError(t, err) 136 137 var ( 138 instance = placement.NewInstance(). 139 SetID(hostID). 140 SetEndpoint(endpoint("127.0.0.1", servicePort)). 141 SetPort(servicePort). 142 SetIsolationGroup("local"). 143 SetWeight(1). 144 SetZone(serviceZone) 145 instances = []placement.Instance{instance} 146 // Reduce number of shards to avoid having to tune F.D limits. 147 shards = 4 148 replicas = 1 149 ) 150 151 _, err = placementSvc.BuildInitialPlacement(instances, shards, replicas) 152 require.NoError(t, err) 153 154 // Setup the namespace 155 ns, err := newNamespaceProtoValue(namespaceID) 156 require.NoError(t, err) 157 158 kvStore, err := configSvcClient.KV() 159 require.NoError(t, err) 160 161 _, err = kvStore.Set(kvconfig.NamespacesKey, ns) 162 require.NoError(t, err) 163 164 // Run server 165 var ( 166 interruptCh = make(chan error, 1) 167 bootstrapCh = make(chan struct{}, 1) 168 serverWg sync.WaitGroup 169 ) 170 serverWg.Add(1) 171 go func() { 172 server.Run(server.RunOptions{ 173 ConfigFile: configFd.Name(), 174 BootstrapCh: bootstrapCh, 175 InterruptCh: interruptCh, 176 }) 177 serverWg.Done() 178 }() 179 defer func() { 180 // Resetting DefaultServeMux to prevent multiple assignments 181 // to /debug/dump in Server.Run() 182 http.DefaultServeMux = http.NewServeMux() 183 }() 184 185 // Wait for bootstrap 186 <-bootstrapCh 187 188 // Create client, read and write some data 189 // NB(r): Make sure client config points to the root config 190 // service since we're going to instantiate the client configuration 191 // just by itself. 192 cfg.DB.Client.EnvironmentConfig = &envCfg 193 194 cli, err := cfg.DB.Client.NewClient(client.ConfigurationParameters{}) 195 require.NoError(t, err) 196 197 adminCli := cli.(client.AdminClient) 198 adminSession, err := adminCli.DefaultAdminSession() 199 require.NoError(t, err) 200 defer adminSession.Close() 201 202 // Propagation of shard state from Initializing --> Available post-bootstrap is eventually 203 // consistent, so we must wait. 204 waitUntilAllShardsAreAvailable(t, adminSession) 205 206 // Cast to narrower-interface instead of grabbing DefaultSession to make sure 207 // we use the same topology.Map that we validated in waitUntilAllShardsAreAvailable. 208 session := adminSession.(client.Session) 209 210 start := xtime.Now().Add(-time.Minute) 211 values := []struct { 212 value float64 213 at xtime.UnixNano 214 unit xtime.Unit 215 }{ 216 {value: 1.0, at: start, unit: xtime.Second}, 217 {value: 2.0, at: start.Add(1 * time.Second), unit: xtime.Second}, 218 {value: 3.0, at: start.Add(2 * time.Second), unit: xtime.Second}, 219 } 220 221 for _, v := range values { 222 err := session.Write(ident.StringID(namespaceID), ident.StringID("foo"), v.at, v.value, v.unit, nil) 223 require.NoError(t, err) 224 } 225 226 // Account for first value inserted at xtime.Second precision 227 fetchStart := start.Truncate(time.Second) 228 229 // Account for last value being inserted at xtime.Second and 230 // the "end" param to fetch being exclusive 231 fetchEnd := values[len(values)-1].at.Truncate(time.Second).Add(time.Nanosecond) 232 233 iter, err := session.Fetch(ident.StringID(namespaceID), ident.StringID("foo"), fetchStart, fetchEnd) 234 require.NoError(t, err) 235 236 for _, v := range values { 237 require.True(t, iter.Next()) 238 dp, unit, _ := iter.Current() 239 assert.Equal(t, v.value, dp.Value) 240 // Account for xtime.Second precision on values going in 241 expectAt := v.at.Truncate(time.Second) 242 assert.Equal(t, expectAt, dp.TimestampNanos) 243 assert.Equal(t, v.unit, unit) 244 } 245 246 // Wait for server to stop 247 interruptCh <- fmt.Errorf("test complete") 248 serverWg.Wait() 249 } 250 251 // TestEmbeddedConfig tests booting a server using an embedded KV. 252 func TestEmbeddedConfig(t *testing.T) { 253 // Create config file 254 tmpl, err := template.New("config").Parse(testConfig + embeddedKVConfigPortion) 255 require.NoError(t, err) 256 257 configFd, cleanup := tempFile(t, "config.yaml") 258 defer cleanup() 259 260 logFile, cleanupLogFile := tempFileTouch(t, "m3dbnode.log") 261 defer cleanupLogFile() 262 263 configServiceCacheDir, cleanupConfigServiceCacheDir := tempDir(t, "kv") 264 defer cleanupConfigServiceCacheDir() 265 266 embeddedKVDir, cleanupEmbeddedKVDir := tempDir(t, "embedded") 267 defer cleanupEmbeddedKVDir() 268 269 dataDir, cleanupDataDir := tempDir(t, "data") 270 defer cleanupDataDir() 271 272 servicePort := nextServicePort() 273 err = tmpl.Execute(configFd, struct { 274 HostID string 275 LogFile string 276 DataDir string 277 ServicePort string 278 ServiceName string 279 ServiceEnv string 280 ServiceZone string 281 ConfigServiceCacheDir string 282 EmbeddedKVDir string 283 LPURL string 284 LCURL string 285 APURL string 286 ACURL string 287 EtcdEndpoint string 288 InitialClusterHostID string 289 InitialClusterEndpoint string 290 }{ 291 HostID: hostID, 292 LogFile: logFile, 293 DataDir: dataDir, 294 ServicePort: strconv.Itoa(int(servicePort)), 295 ServiceName: serviceName, 296 ServiceEnv: serviceEnv, 297 ServiceZone: serviceZone, 298 ConfigServiceCacheDir: configServiceCacheDir, 299 EmbeddedKVDir: embeddedKVDir, 300 LPURL: lpURL, 301 LCURL: lcURL, 302 APURL: apURL, 303 ACURL: acURL, 304 EtcdEndpoint: etcdEndpoint, 305 InitialClusterHostID: initialClusterHostID, 306 InitialClusterEndpoint: initialClusterEndpoint, 307 }) 308 require.NoError(t, err) 309 310 // Run server 311 var ( 312 interruptCh = make(chan error, 1) 313 bootstrapCh = make(chan struct{}, 1) 314 embeddedKVCh = make(chan struct{}, 1) 315 serverWg sync.WaitGroup 316 ) 317 serverWg.Add(1) 318 go func() { 319 server.Run(server.RunOptions{ 320 ConfigFile: configFd.Name(), 321 BootstrapCh: bootstrapCh, 322 EmbeddedKVCh: embeddedKVCh, 323 InterruptCh: interruptCh, 324 }) 325 serverWg.Done() 326 }() 327 defer func() { 328 // Resetting DefaultServeMux to prevent multiple assignments 329 // to /debug/dump in Server.Run() 330 http.DefaultServeMux = http.NewServeMux() 331 }() 332 333 // Wait for embedded KV to be up. 334 <-embeddedKVCh 335 336 // Setup the placement 337 var cfg config.Configuration 338 err = xconfig.LoadFile(&cfg, configFd.Name(), xconfig.Options{}) 339 require.NoError(t, err) 340 341 discoveryCfg := cfg.DB.DiscoveryOrDefault() 342 envCfg, err := discoveryCfg.EnvironmentConfig(hostID) 343 require.NoError(t, err) 344 345 syncCluster, err := envCfg.Services.SyncCluster() 346 require.NoError(t, err) 347 configSvcClient, err := syncCluster.Service.NewClient(instrument.NewOptions(). 348 SetLogger(zap.NewNop())) 349 require.NoError(t, err) 350 351 svcs, err := configSvcClient.Services(services.NewOverrideOptions()) 352 require.NoError(t, err) 353 354 serviceID := services.NewServiceID(). 355 SetName(serviceName). 356 SetEnvironment(serviceEnv). 357 SetZone(serviceZone) 358 359 metadata := services.NewMetadata(). 360 SetPort(servicePort). 361 SetLivenessInterval(time.Minute). 362 SetHeartbeatInterval(10 * time.Second) 363 364 err = svcs.SetMetadata(serviceID, metadata) 365 require.NoError(t, err) 366 367 placementOpts := placement.NewOptions(). 368 SetValidZone(serviceZone) 369 placementSvc, err := svcs.PlacementService(serviceID, placementOpts) 370 require.NoError(t, err) 371 372 var ( 373 instance = placement.NewInstance(). 374 SetID(hostID). 375 SetEndpoint(endpoint("127.0.0.1", servicePort)). 376 SetPort(servicePort). 377 SetIsolationGroup("local"). 378 SetWeight(1). 379 SetZone(serviceZone) 380 instances = []placement.Instance{instance} 381 // Use a low number of shards to avoid having to tune F.D limits. 382 shards = 4 383 replicas = 1 384 ) 385 386 _, err = placementSvc.BuildInitialPlacement(instances, shards, replicas) 387 require.NoError(t, err) 388 389 // Setup the namespace 390 ns, err := newNamespaceProtoValue(namespaceID) 391 require.NoError(t, err) 392 393 kvStore, err := configSvcClient.KV() 394 require.NoError(t, err) 395 396 _, err = kvStore.Set(kvconfig.NamespacesKey, ns) 397 require.NoError(t, err) 398 399 // Wait for bootstrap 400 <-bootstrapCh 401 402 // Create client, read and write some data 403 // NB(r): Make sure client config points to the root config 404 // service since we're going to instantiate the client configuration 405 // just by itself. 406 cfg.DB.Client.EnvironmentConfig = &envCfg 407 408 cli, err := cfg.DB.Client.NewClient(client.ConfigurationParameters{}) 409 require.NoError(t, err) 410 411 adminCli := cli.(client.AdminClient) 412 adminSession, err := adminCli.DefaultAdminSession() 413 require.NoError(t, err) 414 defer adminSession.Close() 415 416 // Propagation of shard state from Initializing --> Available post-bootstrap is eventually 417 // consistent, so we must wait. 418 waitUntilAllShardsAreAvailable(t, adminSession) 419 420 // Cast to narrower-interface instead of grabbing DefaultSession to make sure 421 // we use the same topology.Map that we validated in waitUntilAllShardsAreAvailable. 422 session := adminSession.(client.Session) 423 424 start := xtime.Now().Add(-time.Minute) 425 values := []struct { 426 value float64 427 at xtime.UnixNano 428 unit xtime.Unit 429 }{ 430 {value: 1.0, at: start, unit: xtime.Second}, 431 {value: 2.0, at: start.Add(1 * time.Second), unit: xtime.Second}, 432 {value: 3.0, at: start.Add(2 * time.Second), unit: xtime.Second}, 433 } 434 435 for _, v := range values { 436 err := session.Write(ident.StringID(namespaceID), ident.StringID("foo"), v.at, v.value, v.unit, nil) 437 require.NoError(t, err) 438 } 439 440 // Account for first value inserted at xtime.Second precision 441 fetchStart := start.Truncate(time.Second) 442 443 // Account for last value being inserted at xtime.Second and 444 // the "end" param to fetch being exclusive 445 fetchEnd := values[len(values)-1].at.Truncate(time.Second).Add(time.Nanosecond) 446 447 iter, err := session.Fetch(ident.StringID(namespaceID), ident.StringID("foo"), fetchStart, fetchEnd) 448 require.NoError(t, err) 449 450 for _, v := range values { 451 require.True(t, iter.Next()) 452 dp, unit, _ := iter.Current() 453 assert.Equal(t, v.value, dp.Value) 454 // Account for xtime.Second precision on values going in 455 expectAt := v.at.Truncate(time.Second) 456 assert.Equal(t, expectAt, dp.TimestampNanos) 457 assert.Equal(t, v.unit, unit) 458 } 459 460 // Wait for server to stop 461 interruptCh <- fmt.Errorf("test complete") 462 serverWg.Wait() 463 } 464 465 var ( 466 testConfig = ` 467 db: 468 logging: 469 level: info 470 file: {{.LogFile}} 471 472 metrics: 473 prometheus: 474 handlerPath: /metrics 475 listenAddress: 0.0.0.0:9005 476 onError: none 477 sanitization: prometheus 478 samplingRate: 1.0 479 extended: detailed 480 481 listenAddress: 0.0.0.0:{{.ServicePort}} 482 clusterListenAddress: 0.0.0.0:9001 483 httpNodeListenAddress: 0.0.0.0:9002 484 httpClusterListenAddress: 0.0.0.0:9003 485 debugListenAddress: 0.0.0.0:9004 486 487 hostID: 488 resolver: config 489 value: {{.HostID}} 490 491 client: 492 writeConsistencyLevel: majority 493 readConsistencyLevel: unstrict_majority 494 connectConsistencyLevel: any 495 writeTimeout: 10s 496 fetchTimeout: 15s 497 connectTimeout: 20s 498 writeRetry: 499 initialBackoff: 500ms 500 backoffFactor: 3 501 maxRetries: 2 502 jitter: true 503 fetchRetry: 504 initialBackoff: 500ms 505 backoffFactor: 2 506 maxRetries: 3 507 jitter: true 508 backgroundHealthCheckFailLimit: 4 509 backgroundHealthCheckFailThrottleFactor: 0.5 510 511 gcPercentage: 100 512 513 writeNewSeriesAsync: true 514 writeNewSeriesBackoffDuration: 2ms 515 516 commitlog: 517 flushMaxBytes: 524288 518 flushEvery: 1s 519 queue: 520 calculationType: fixed 521 size: 2097152 522 523 filesystem: 524 filePathPrefix: {{.DataDir}} 525 writeBufferSize: 65536 526 dataReadBufferSize: 65536 527 infoReadBufferSize: 128 528 seekReadBufferSize: 4096 529 throughputLimitMbps: 100.0 530 throughputCheckEvery: 128 531 532 repair: 533 enabled: false 534 throttle: 2m 535 checkInterval: 1m 536 537 pooling: 538 blockAllocSize: 16 539 type: simple 540 seriesPool: 541 size: 128 542 lowWatermark: 0.01 543 highWatermark: 0.02 544 blockPool: 545 size: 128 546 lowWatermark: 0.01 547 highWatermark: 0.02 548 encoderPool: 549 size: 128 550 lowWatermark: 0.01 551 highWatermark: 0.02 552 closersPool: 553 size: 128 554 lowWatermark: 0.01 555 highWatermark: 0.02 556 contextPool: 557 size: 128 558 lowWatermark: 0.01 559 highWatermark: 0.02 560 segmentReaderPool: 561 size: 128 562 lowWatermark: 0.01 563 highWatermark: 0.02 564 iteratorPool: 565 size: 128 566 lowWatermark: 0.01 567 highWatermark: 0.02 568 fetchBlockMetadataResultsPool: 569 size: 128 570 capacity: 32 571 lowWatermark: 0.01 572 highWatermark: 0.02 573 fetchBlocksMetadataResultsPool: 574 size: 128 575 capacity: 128 576 lowWatermark: 0.01 577 highWatermark: 0.02 578 replicaMetadataSlicePool: 579 size: 128 580 capacity: 3 581 lowWatermark: 0.01 582 highWatermark: 0.02 583 blockMetadataPool: 584 size: 128 585 lowWatermark: 0.01 586 highWatermark: 0.02 587 blockMetadataSlicePool: 588 size: 128 589 capacity: 32 590 lowWatermark: 0.01 591 highWatermark: 0.02 592 blocksMetadataPool: 593 size: 128 594 lowWatermark: 0.01 595 highWatermark: 0.02 596 blocksMetadataSlicePool: 597 size: 128 598 capacity: 128 599 lowWatermark: 0.01 600 highWatermark: 0.02 601 identifierPool: 602 size: 128 603 lowWatermark: 0.01 604 highWatermark: 0.02 605 bufferBucketPool: 606 size: 128 607 lowWatermark: 0.01 608 highWatermark: 0.02 609 bufferBucketVersionsPool: 610 size: 128 611 lowWatermark: 0.01 612 highWatermark: 0.02 613 bytesPool: 614 buckets: 615 - capacity: 32 616 size: 128 617 - capacity: 512 618 size: 128 619 - capacity: 4096 620 size: 128 621 ` 622 623 kvConfigPortion = ` 624 discovery: 625 config: 626 service: 627 env: {{.ServiceEnv}} 628 zone: {{.ServiceZone}} 629 service: {{.ServiceName}} 630 cacheDir: {{.ConfigServiceCacheDir}} 631 etcdClusters: 632 - zone: {{.ServiceZone}} 633 endpoints: {{.EtcdEndpoints}} 634 ` 635 636 embeddedKVConfigPortion = ` 637 discovery: 638 config: 639 service: 640 env: {{.ServiceEnv}} 641 zone: {{.ServiceZone}} 642 service: {{.ServiceName}} 643 cacheDir: {{.ConfigServiceCacheDir}} 644 etcdClusters: 645 - zone: {{.ServiceZone}} 646 endpoints: 647 - {{.EtcdEndpoint}} 648 seedNodes: 649 rootDir: {{.EmbeddedKVDir}} 650 listenPeerUrls: 651 - {{.LPURL}} 652 listenClientUrls: 653 - {{.LCURL}} 654 initialAdvertisePeerUrls: 655 - {{.APURL}} 656 advertiseClientUrls: 657 - {{.ACURL}} 658 initialCluster: 659 - hostID: {{.InitialClusterHostID}} 660 endpoint: {{.InitialClusterEndpoint}} 661 ` 662 )