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  `