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  )