github.com/m3db/m3@v1.5.0/src/cmd/services/m3dbnode/main/main_index_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 "context" 27 "fmt" 28 "net/http" 29 "strconv" 30 "strings" 31 "sync" 32 "testing" 33 "text/template" 34 "time" 35 36 "github.com/m3db/m3/src/cluster/integration/etcd" 37 "github.com/m3db/m3/src/cluster/placement" 38 "github.com/m3db/m3/src/cluster/services" 39 "github.com/m3db/m3/src/cmd/services/m3dbnode/config" 40 "github.com/m3db/m3/src/dbnode/client" 41 "github.com/m3db/m3/src/dbnode/kvconfig" 42 "github.com/m3db/m3/src/dbnode/server" 43 dberrors "github.com/m3db/m3/src/dbnode/storage/errors" 44 "github.com/m3db/m3/src/dbnode/storage/index" 45 m3ninxidx "github.com/m3db/m3/src/m3ninx/idx" 46 xconfig "github.com/m3db/m3/src/x/config" 47 "github.com/m3db/m3/src/x/ident" 48 "github.com/m3db/m3/src/x/instrument" 49 xtime "github.com/m3db/m3/src/x/time" 50 51 "github.com/stretchr/testify/assert" 52 "github.com/stretchr/testify/require" 53 "go.uber.org/zap" 54 ) 55 56 // TestIndexEnabledServer tests booting a server using file based configuration. 57 func TestIndexEnabledServer(t *testing.T) { 58 if testing.Short() { 59 t.SkipNow() // Just skip if we're doing a short run 60 } 61 62 // Embedded kv 63 embeddedKV, err := etcd.New(etcd.NewOptions()) 64 require.NoError(t, err) 65 defer func() { 66 require.NoError(t, embeddedKV.Close()) 67 }() 68 require.NoError(t, embeddedKV.Start()) 69 70 // Create config file 71 tmpl, err := template.New("config").Parse(indexTestConfig) 72 require.NoError(t, err) 73 74 configFd, cleanup := tempFile(t, "config.yaml") 75 defer cleanup() 76 77 logFile, cleanupLogFile := tempFileTouch(t, "m3dbnode.log") 78 defer cleanupLogFile() 79 80 configServiceCacheDir, cleanupConfigServiceCacheDir := tempDir(t, "kv") 81 defer cleanupConfigServiceCacheDir() 82 83 dataDir, cleanupDataDir := tempDir(t, "data") 84 defer cleanupDataDir() 85 86 servicePort := nextServicePort() 87 err = tmpl.Execute(configFd, struct { 88 HostID string 89 LogFile string 90 DataDir string 91 ServicePort string 92 ServiceName string 93 ServiceEnv string 94 ServiceZone string 95 ConfigServiceCacheDir string 96 EtcdEndpoints string 97 }{ 98 HostID: hostID, 99 LogFile: logFile, 100 DataDir: dataDir, 101 ServicePort: strconv.Itoa(int(servicePort)), 102 ServiceName: serviceName, 103 ServiceEnv: serviceEnv, 104 ServiceZone: serviceZone, 105 ConfigServiceCacheDir: configServiceCacheDir, 106 EtcdEndpoints: yamlArray(t, embeddedKV.Endpoints()), 107 }) 108 require.NoError(t, err) 109 110 // Setup the placement 111 var cfg config.Configuration 112 err = xconfig.LoadFile(&cfg, configFd.Name(), xconfig.Options{}) 113 require.NoError(t, err) 114 115 discoveryCfg := cfg.DB.DiscoveryOrDefault() 116 envCfg, err := discoveryCfg.EnvironmentConfig(hostID) 117 require.NoError(t, err) 118 119 syncCluster, err := envCfg.Services.SyncCluster() 120 require.NoError(t, err) 121 configSvcClient, err := syncCluster.Service.NewClient(instrument.NewOptions(). 122 SetLogger(zap.NewNop())) 123 require.NoError(t, err) 124 125 svcs, err := configSvcClient.Services(services.NewOverrideOptions()) 126 require.NoError(t, err) 127 128 serviceID := services.NewServiceID(). 129 SetName(serviceName). 130 SetEnvironment(serviceEnv). 131 SetZone(serviceZone) 132 133 metadata := services.NewMetadata(). 134 SetPort(servicePort). 135 SetLivenessInterval(time.Minute). 136 SetHeartbeatInterval(10 * time.Second) 137 138 err = svcs.SetMetadata(serviceID, metadata) 139 require.NoError(t, err) 140 141 placementOpts := placement.NewOptions(). 142 SetValidZone(serviceZone) 143 placementSvc, err := svcs.PlacementService(serviceID, placementOpts) 144 require.NoError(t, err) 145 146 var ( 147 instance = placement.NewInstance(). 148 SetID(hostID). 149 SetEndpoint(endpoint("127.0.0.1", servicePort)). 150 SetPort(servicePort). 151 SetIsolationGroup("local"). 152 SetWeight(1). 153 SetZone(serviceZone) 154 instances = []placement.Instance{instance} 155 // Keep number of shards low to avoid having to tune F.D limits. 156 shards = 4 157 replicas = 1 158 ) 159 160 _, err = placementSvc.BuildInitialPlacement(instances, shards, replicas) 161 require.NoError(t, err) 162 163 // Setup the namespace 164 ns, err := newNamespaceWithIndexProtoValue(namespaceID, true) 165 require.NoError(t, err) 166 167 kvStore, err := configSvcClient.KV() 168 require.NoError(t, err) 169 170 _, err = kvStore.Set(kvconfig.NamespacesKey, ns) 171 require.NoError(t, err) 172 173 // Run server 174 var ( 175 interruptCh = make(chan error, 1) 176 bootstrapCh = make(chan struct{}, 1) 177 serverWg sync.WaitGroup 178 ) 179 serverWg.Add(1) 180 go func() { 181 server.Run(server.RunOptions{ 182 ConfigFile: configFd.Name(), 183 BootstrapCh: bootstrapCh, 184 InterruptCh: interruptCh, 185 }) 186 serverWg.Done() 187 }() 188 defer func() { 189 // Resetting DefaultServeMux to prevent multiple assignments 190 // to /debug/dump in Server.Run() 191 http.DefaultServeMux = http.NewServeMux() 192 }() 193 194 // Wait for bootstrap 195 <-bootstrapCh 196 197 // Create client, read and write some data 198 // NB(r): Make sure client config points to the root config 199 // service since we're going to instantiate the client configuration 200 // just by itself. 201 cfg.DB.Client.EnvironmentConfig = &envCfg 202 203 cli, err := cfg.DB.Client.NewClient(client.ConfigurationParameters{}) 204 require.NoError(t, err) 205 206 adminCli := cli.(client.AdminClient) 207 adminSession, err := adminCli.DefaultAdminSession() 208 require.NoError(t, err) 209 defer adminSession.Close() 210 211 // Propagation of shard state from Initializing --> Available post-bootstrap is eventually 212 // consistent, so we must wait. 213 waitUntilAllShardsAreAvailable(t, adminSession) 214 215 // Cast to narrower-interface instead of grabbing DefaultSession to make sure 216 // we use the same topology.Map that we validated in waitUntilAllShardsAreAvailable. 217 session := adminSession.(client.Session) 218 defer session.Close() 219 220 start := xtime.Now().Add(-time.Minute) 221 values := []struct { 222 value float64 223 at xtime.UnixNano 224 unit xtime.Unit 225 }{ 226 {value: 1.0, at: start, unit: xtime.Second}, 227 {value: 2.0, at: start.Add(1 * time.Second), unit: xtime.Second}, 228 {value: 3.0, at: start.Add(2 * time.Second), unit: xtime.Second}, 229 } 230 231 // unknown ns test 232 randomNs := "random-ns" 233 for _, v := range values { 234 err := session.WriteTagged(ident.StringID(randomNs), 235 ident.StringID("foo"), 236 ident.NewTagsIterator(ident.NewTags( 237 ident.StringTag("foo", "bar"), 238 ident.StringTag("baz", "foo"), 239 )), 240 v.at, v.value, v.unit, nil) 241 require.Error(t, err) 242 require.True(t, strings.Contains(err.Error(), dberrors.NewUnknownNamespaceError(randomNs).Error())) 243 } 244 245 for _, v := range values { 246 err := session.WriteTagged(ident.StringID(namespaceID), 247 ident.StringID("foo"), 248 ident.NewTagsIterator(ident.NewTags( 249 ident.StringTag("foo", "bar"), 250 ident.StringTag("baz", "foo"), 251 )), 252 v.at, v.value, v.unit, nil) 253 require.NoError(t, err) 254 } 255 256 // Account for first value inserted at xtime.Second precision 257 fetchStart := start.Truncate(time.Second) 258 259 // Account for last value being inserted at xtime.Second and 260 // the "end" param to fetch being exclusive 261 fetchEnd := values[len(values)-1].at.Truncate(time.Second).Add(time.Nanosecond) 262 263 reQuery, err := m3ninxidx.NewRegexpQuery([]byte("foo"), []byte("b.*")) 264 assert.NoError(t, err) 265 266 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 267 defer cancel() 268 269 iters, fetchResponse, err := session.FetchTagged(ctx, 270 ident.StringID(namespaceID), 271 index.Query{reQuery}, 272 index.QueryOptions{ 273 StartInclusive: fetchStart, 274 EndExclusive: fetchEnd, 275 }) 276 assert.NoError(t, err) 277 assert.True(t, fetchResponse.Exhaustive) 278 assert.Equal(t, 1, iters.Len()) 279 iter := iters.Iters()[0] 280 assert.Equal(t, namespaceID, iter.Namespace().String()) 281 assert.Equal(t, "foo", iter.ID().String()) 282 assert.True(t, ident.NewTagIterMatcher( 283 ident.MustNewTagStringsIterator("foo", "bar", "baz", "foo")).Matches(iter.Tags())) 284 for _, v := range values { 285 require.True(t, iter.Next()) 286 dp, unit, _ := iter.Current() 287 assert.Equal(t, v.value, dp.Value) 288 // Account for xtime.Second precision on values going in 289 expectAt := v.at.Truncate(time.Second) 290 assert.Equal(t, expectAt, dp.TimestampNanos) 291 assert.Equal(t, v.unit, unit) 292 } 293 294 resultsIter, resultsFetchResponse, err := session.FetchTaggedIDs(ctx, 295 ident.StringID(namespaceID), 296 index.Query{reQuery}, 297 index.QueryOptions{ 298 StartInclusive: fetchStart, 299 EndExclusive: fetchEnd, 300 }) 301 assert.NoError(t, err) 302 assert.True(t, resultsFetchResponse.Exhaustive) 303 assert.True(t, resultsIter.Next()) 304 nsID, tsID, tags := resultsIter.Current() 305 assert.Equal(t, namespaceID, nsID.String()) 306 assert.Equal(t, "foo", tsID.String()) 307 assert.True(t, ident.NewTagIterMatcher( 308 ident.MustNewTagStringsIterator("foo", "bar", "baz", "foo")).Matches(tags)) 309 assert.False(t, resultsIter.Next()) 310 assert.NoError(t, resultsIter.Err()) 311 312 // Wait for server to stop 313 interruptCh <- fmt.Errorf("test complete") 314 serverWg.Wait() 315 } 316 317 var indexTestConfig = ` 318 db: 319 logging: 320 level: info 321 file: {{.LogFile}} 322 323 metrics: 324 prometheus: 325 handlerPath: /metrics 326 listenAddress: 0.0.0.0:10005 327 onError: none 328 sanitization: prometheus 329 samplingRate: 1.0 330 extended: detailed 331 332 listenAddress: 0.0.0.0:{{.ServicePort}} 333 clusterListenAddress: 0.0.0.0:10001 334 httpNodeListenAddress: 0.0.0.0:10002 335 httpClusterListenAddress: 0.0.0.0:10003 336 debugListenAddress: 0.0.0.0:10004 337 338 hostID: 339 resolver: config 340 value: {{.HostID}} 341 342 client: 343 writeConsistencyLevel: majority 344 readConsistencyLevel: unstrict_majority 345 connectConsistencyLevel: any 346 writeTimeout: 10s 347 fetchTimeout: 15s 348 connectTimeout: 20s 349 writeRetry: 350 initialBackoff: 500ms 351 backoffFactor: 3 352 maxRetries: 2 353 jitter: true 354 fetchRetry: 355 initialBackoff: 500ms 356 backoffFactor: 2 357 maxRetries: 3 358 jitter: true 359 backgroundHealthCheckFailLimit: 4 360 backgroundHealthCheckFailThrottleFactor: 0.5 361 362 gcPercentage: 100 363 364 writeNewSeriesAsync: false 365 writeNewSeriesBackoffDuration: 2ms 366 367 commitlog: 368 flushMaxBytes: 524288 369 flushEvery: 1s 370 queue: 371 calculationType: fixed 372 size: 2097152 373 374 filesystem: 375 filePathPrefix: {{.DataDir}} 376 writeBufferSize: 65536 377 dataReadBufferSize: 65536 378 infoReadBufferSize: 128 379 seekReadBufferSize: 4096 380 throughputLimitMbps: 100.0 381 throughputCheckEvery: 128 382 383 repair: 384 enabled: false 385 throttle: 2m 386 checkInterval: 1m 387 388 pooling: 389 blockAllocSize: 16 390 type: simple 391 seriesPool: 392 size: 128 393 lowWatermark: 0.01 394 highWatermark: 0.02 395 blockPool: 396 size: 128 397 lowWatermark: 0.01 398 highWatermark: 0.02 399 encoderPool: 400 size: 128 401 lowWatermark: 0.01 402 highWatermark: 0.02 403 closersPool: 404 size: 128 405 lowWatermark: 0.01 406 highWatermark: 0.02 407 contextPool: 408 size: 128 409 lowWatermark: 0.01 410 highWatermark: 0.02 411 segmentReaderPool: 412 size: 128 413 lowWatermark: 0.01 414 highWatermark: 0.02 415 iteratorPool: 416 size: 128 417 lowWatermark: 0.01 418 highWatermark: 0.02 419 fetchBlockMetadataResultsPool: 420 size: 128 421 capacity: 32 422 lowWatermark: 0.01 423 highWatermark: 0.02 424 fetchBlocksMetadataResultsPool: 425 size: 128 426 capacity: 128 427 lowWatermark: 0.01 428 highWatermark: 0.02 429 replicaMetadataSlicePool: 430 size: 128 431 capacity: 3 432 lowWatermark: 0.01 433 highWatermark: 0.02 434 blockMetadataPool: 435 size: 128 436 lowWatermark: 0.01 437 highWatermark: 0.02 438 blockMetadataSlicePool: 439 size: 128 440 capacity: 32 441 lowWatermark: 0.01 442 highWatermark: 0.02 443 blocksMetadataPool: 444 size: 128 445 lowWatermark: 0.01 446 highWatermark: 0.02 447 blocksMetadataSlicePool: 448 size: 128 449 capacity: 128 450 lowWatermark: 0.01 451 highWatermark: 0.02 452 identifierPool: 453 size: 128 454 lowWatermark: 0.01 455 highWatermark: 0.02 456 bytesPool: 457 buckets: 458 - capacity: 32 459 size: 128 460 - capacity: 512 461 size: 128 462 - capacity: 4096 463 size: 128 464 465 discovery: 466 config: 467 service: 468 env: {{.ServiceEnv}} 469 zone: {{.ServiceZone}} 470 service: {{.ServiceName}} 471 cacheDir: {{.ConfigServiceCacheDir}} 472 etcdClusters: 473 - zone: {{.ServiceZone}} 474 endpoints: {{.EtcdEndpoints}} 475 `