github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/cmd/services/m3dbnode/config/config.go (about)

     1  // Copyright (c) 2017 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 config
    22  
    23  import (
    24  	"bytes"
    25  	"errors"
    26  	"fmt"
    27  	"net/url"
    28  	"path"
    29  	"strings"
    30  	"time"
    31  
    32  	coordinatorcfg "github.com/m3db/m3/src/cmd/services/m3query/config"
    33  	"github.com/m3db/m3/src/dbnode/client"
    34  	"github.com/m3db/m3/src/dbnode/discovery"
    35  	"github.com/m3db/m3/src/dbnode/environment"
    36  	"github.com/m3db/m3/src/dbnode/storage/repair"
    37  	"github.com/m3db/m3/src/dbnode/storage/series"
    38  	"github.com/m3db/m3/src/x/config/hostid"
    39  	"github.com/m3db/m3/src/x/debug/config"
    40  	"github.com/m3db/m3/src/x/instrument"
    41  	xlog "github.com/m3db/m3/src/x/log"
    42  	"github.com/m3db/m3/src/x/opentracing"
    43  
    44  	"github.com/m3dbx/vellum/regexp"
    45  	"go.etcd.io/etcd/client/pkg/v3/transport"
    46  	"go.etcd.io/etcd/client/pkg/v3/types"
    47  	"go.etcd.io/etcd/server/v3/embed"
    48  	"gopkg.in/yaml.v2"
    49  )
    50  
    51  const (
    52  	defaultEtcdDirSuffix  = "etcd"
    53  	defaultEtcdListenHost = "http://0.0.0.0"
    54  	// DefaultEtcdClientPort is the default port for etcd client.
    55  	DefaultEtcdClientPort = 2379
    56  	// DefaultEtcdServerPort is the default port for etcd server.
    57  	DefaultEtcdServerPort = 2380
    58  )
    59  
    60  var (
    61  	defaultLogging = xlog.Configuration{
    62  		Level: "info",
    63  	}
    64  	defaultMetricsSanitization        = instrument.PrometheusMetricSanitization
    65  	defaultMetricsExtendedMetricsType = instrument.DetailedExtendedMetrics
    66  	defaultMetrics                    = instrument.MetricsConfiguration{
    67  		PrometheusReporter: &instrument.PrometheusConfiguration{
    68  			HandlerPath: "/metrics",
    69  		},
    70  		Sanitization:    &defaultMetricsSanitization,
    71  		SamplingRate:    1.0,
    72  		ExtendedMetrics: &defaultMetricsExtendedMetricsType,
    73  	}
    74  	defaultListenAddress            = "0.0.0.0:9000"
    75  	defaultClusterListenAddress     = "0.0.0.0:9001"
    76  	defaultHTTPNodeListenAddress    = "0.0.0.0:9002"
    77  	defaultHTTPClusterListenAddress = "0.0.0.0:9003"
    78  	defaultDebugListenAddress       = "0.0.0.0:9004"
    79  	defaultHostIDValue              = "m3db_local"
    80  	defaultHostID                   = hostid.Configuration{
    81  		Resolver: hostid.ConfigResolver,
    82  		Value:    &defaultHostIDValue,
    83  	}
    84  	defaultGCPercentage                  = 100
    85  	defaultWriteNewSeriesAsync           = true
    86  	defaultWriteNewSeriesBackoffDuration = 2 * time.Millisecond
    87  	defaultCommitLogPolicy               = CommitLogPolicy{
    88  		FlushMaxBytes: 524288,
    89  		FlushEvery:    time.Second * 1,
    90  		Queue: CommitLogQueuePolicy{
    91  			Size:            2097152,
    92  			CalculationType: CalculationTypeFixed,
    93  		},
    94  	}
    95  	defaultDiscoveryType = discovery.M3DBSingleNodeType
    96  	defaultDiscovery     = discovery.Configuration{
    97  		Type: &defaultDiscoveryType,
    98  	}
    99  )
   100  
   101  // Configuration is the top level configuration that includes both a DB
   102  // node and a coordinator.
   103  type Configuration struct {
   104  	// DB is the configuration for a DB node (required).
   105  	DB *DBConfiguration `yaml:"db"`
   106  
   107  	// Coordinator is the configuration for the coordinator to run (optional).
   108  	Coordinator *coordinatorcfg.Configuration `yaml:"coordinator"`
   109  }
   110  
   111  // Validate validates the Configuration. We use this method to validate fields
   112  // where the validator package falls short.
   113  func (c *Configuration) Validate() error {
   114  	return c.DB.Validate()
   115  }
   116  
   117  // Components returns the number of components configured within the Configuration
   118  // object.
   119  func (c *Configuration) Components() int {
   120  	numComponents := 0
   121  	if c.DB != nil {
   122  		numComponents++
   123  	}
   124  	if c.Coordinator != nil {
   125  		numComponents++
   126  	}
   127  
   128  	return numComponents
   129  }
   130  
   131  // DeepCopy returns a deep copy of the current configuration object.
   132  func (c *Configuration) DeepCopy() (Configuration, error) {
   133  	rawCfg, err := yaml.Marshal(c)
   134  	if err != nil {
   135  		return Configuration{}, err
   136  	}
   137  	var dupe Configuration
   138  	if err := yaml.Unmarshal(rawCfg, &dupe); err != nil {
   139  		return Configuration{}, err
   140  	}
   141  	return dupe, nil
   142  }
   143  
   144  // DBConfiguration is the configuration for a DB node.
   145  type DBConfiguration struct {
   146  	// Index configuration.
   147  	Index IndexConfiguration `yaml:"index"`
   148  
   149  	// Transforms configuration.
   150  	Transforms TransformConfiguration `yaml:"transforms"`
   151  
   152  	// Logging configuration.
   153  	Logging *xlog.Configuration `yaml:"logging"`
   154  
   155  	// Metrics configuration.
   156  	Metrics *instrument.MetricsConfiguration `yaml:"metrics"`
   157  
   158  	// The host and port on which to listen for the node service.
   159  	ListenAddress *string `yaml:"listenAddress"`
   160  
   161  	// The host and port on which to listen for the cluster service.
   162  	ClusterListenAddress *string `yaml:"clusterListenAddress"`
   163  
   164  	// The HTTP host and port on which to listen for the node service.
   165  	HTTPNodeListenAddress *string `yaml:"httpNodeListenAddress"`
   166  
   167  	// The HTTP host and port on which to listen for the cluster service.
   168  	HTTPClusterListenAddress *string `yaml:"httpClusterListenAddress"`
   169  
   170  	// The host and port on which to listen for debug endpoints.
   171  	DebugListenAddress *string `yaml:"debugListenAddress"`
   172  
   173  	// HostID is the local host ID configuration.
   174  	HostID *hostid.Configuration `yaml:"hostID"`
   175  
   176  	// Client configuration, used for inter-node communication and when used as a coordinator.
   177  	Client client.Configuration `yaml:"client"`
   178  
   179  	// The initial garbage collection target percentage.
   180  	GCPercentage int `yaml:"gcPercentage" validate:"max=100"`
   181  
   182  	// The tick configuration, omit this to use default settings.
   183  	Tick *TickConfiguration `yaml:"tick"`
   184  
   185  	// Bootstrap configuration.
   186  	Bootstrap BootstrapConfiguration `yaml:"bootstrap"`
   187  
   188  	// The block retriever policy.
   189  	BlockRetrieve *BlockRetrievePolicy `yaml:"blockRetrieve"`
   190  
   191  	// Cache configurations.
   192  	Cache CacheConfigurations `yaml:"cache"`
   193  
   194  	// The filesystem configuration for the node.
   195  	Filesystem FilesystemConfiguration `yaml:"filesystem"`
   196  
   197  	// The commit log policy for the node.
   198  	CommitLog *CommitLogPolicy `yaml:"commitlog"`
   199  
   200  	// The repair policy for repairing data within a cluster.
   201  	Repair *RepairPolicy `yaml:"repair"`
   202  
   203  	// The replication policy for replicating data between clusters.
   204  	Replication *ReplicationPolicy `yaml:"replication"`
   205  
   206  	// The pooling policy.
   207  	PoolingPolicy *PoolingPolicy `yaml:"pooling"`
   208  
   209  	// The discovery configuration.
   210  	Discovery *discovery.Configuration `yaml:"discovery"`
   211  
   212  	// The configuration for hashing
   213  	Hashing HashingConfiguration `yaml:"hashing"`
   214  
   215  	// Write new series asynchronously for fast ingestion of new ID bursts.
   216  	WriteNewSeriesAsync *bool `yaml:"writeNewSeriesAsync"`
   217  
   218  	// Write new series backoff between batches of new series insertions.
   219  	WriteNewSeriesBackoffDuration *time.Duration `yaml:"writeNewSeriesBackoffDuration"`
   220  
   221  	// Proto contains the configuration specific to running in the ProtoDataMode.
   222  	Proto *ProtoConfiguration `yaml:"proto"`
   223  
   224  	// Tracing configures opentracing. If not provided, tracing is disabled.
   225  	Tracing *opentracing.TracingConfiguration `yaml:"tracing"`
   226  
   227  	// Limits contains configuration for limits that can be applied to M3DB for the purposes
   228  	// of applying back-pressure or protecting the db nodes.
   229  	Limits LimitsConfiguration `yaml:"limits"`
   230  
   231  	// TChannel exposes TChannel config options.
   232  	TChannel *TChannelConfiguration `yaml:"tchannel"`
   233  
   234  	// Debug configuration.
   235  	Debug config.DebugConfiguration `yaml:"debug"`
   236  
   237  	// ForceColdWritesEnabled will force enable cold writes for all namespaces
   238  	// if set.
   239  	ForceColdWritesEnabled *bool `yaml:"forceColdWritesEnabled"`
   240  }
   241  
   242  // LoggingOrDefault returns the logging configuration or defaults.
   243  func (c *DBConfiguration) LoggingOrDefault() xlog.Configuration {
   244  	if c.Logging == nil {
   245  		return defaultLogging
   246  	}
   247  
   248  	return *c.Logging
   249  }
   250  
   251  // MetricsOrDefault returns metrics configuration or defaults.
   252  func (c *DBConfiguration) MetricsOrDefault() *instrument.MetricsConfiguration {
   253  	if c.Metrics == nil {
   254  		return &defaultMetrics
   255  	}
   256  
   257  	return c.Metrics
   258  }
   259  
   260  // ListenAddressOrDefault returns the listen address or default.
   261  func (c *DBConfiguration) ListenAddressOrDefault() string {
   262  	if c.ListenAddress == nil {
   263  		return defaultListenAddress
   264  	}
   265  
   266  	return *c.ListenAddress
   267  }
   268  
   269  // ClusterListenAddressOrDefault returns the listen address or default.
   270  func (c *DBConfiguration) ClusterListenAddressOrDefault() string {
   271  	if c.ClusterListenAddress == nil {
   272  		return defaultClusterListenAddress
   273  	}
   274  
   275  	return *c.ClusterListenAddress
   276  }
   277  
   278  // HTTPNodeListenAddressOrDefault returns the listen address or default.
   279  func (c *DBConfiguration) HTTPNodeListenAddressOrDefault() string {
   280  	if c.HTTPNodeListenAddress == nil {
   281  		return defaultHTTPNodeListenAddress
   282  	}
   283  
   284  	return *c.HTTPNodeListenAddress
   285  }
   286  
   287  // HTTPClusterListenAddressOrDefault returns the listen address or default.
   288  func (c *DBConfiguration) HTTPClusterListenAddressOrDefault() string {
   289  	if c.HTTPClusterListenAddress == nil {
   290  		return defaultHTTPClusterListenAddress
   291  	}
   292  
   293  	return *c.HTTPClusterListenAddress
   294  }
   295  
   296  // DebugListenAddressOrDefault returns the listen address or default.
   297  func (c *DBConfiguration) DebugListenAddressOrDefault() string {
   298  	if c.DebugListenAddress == nil {
   299  		return defaultDebugListenAddress
   300  	}
   301  
   302  	return *c.DebugListenAddress
   303  }
   304  
   305  // HostIDOrDefault returns the host ID or default.
   306  func (c *DBConfiguration) HostIDOrDefault() hostid.Configuration {
   307  	if c.HostID == nil {
   308  		return defaultHostID
   309  	}
   310  
   311  	return *c.HostID
   312  }
   313  
   314  // CommitLogOrDefault returns the commit log policy or default.
   315  func (c *DBConfiguration) CommitLogOrDefault() CommitLogPolicy {
   316  	if c.CommitLog == nil {
   317  		return defaultCommitLogPolicy
   318  	}
   319  
   320  	return *c.CommitLog
   321  }
   322  
   323  // GCPercentageOrDefault returns the GC percentage or default.
   324  func (c *DBConfiguration) GCPercentageOrDefault() int {
   325  	if c.GCPercentage == 0 {
   326  		return defaultGCPercentage
   327  	}
   328  
   329  	return c.GCPercentage
   330  }
   331  
   332  // WriteNewSeriesAsyncOrDefault returns whether to write new series async or not.
   333  func (c *DBConfiguration) WriteNewSeriesAsyncOrDefault() bool {
   334  	if c.WriteNewSeriesAsync == nil {
   335  		return defaultWriteNewSeriesAsync
   336  	}
   337  
   338  	return *c.WriteNewSeriesAsync
   339  }
   340  
   341  // WriteNewSeriesBackoffDurationOrDefault returns the backoff duration for new series inserts.
   342  func (c *DBConfiguration) WriteNewSeriesBackoffDurationOrDefault() time.Duration {
   343  	if c.WriteNewSeriesBackoffDuration == nil {
   344  		return defaultWriteNewSeriesBackoffDuration
   345  	}
   346  
   347  	return *c.WriteNewSeriesBackoffDuration
   348  }
   349  
   350  // PoolingPolicyOrDefault returns the pooling policy or default.
   351  func (c *DBConfiguration) PoolingPolicyOrDefault() (PoolingPolicy, error) {
   352  	var policy PoolingPolicy
   353  	if c.PoolingPolicy != nil {
   354  		policy = *c.PoolingPolicy
   355  	}
   356  
   357  	if err := policy.InitDefaultsAndValidate(); err != nil {
   358  		return PoolingPolicy{}, err
   359  	}
   360  
   361  	return policy, nil
   362  }
   363  
   364  // DiscoveryOrDefault returns the discovery configuration or defaults.
   365  func (c *DBConfiguration) DiscoveryOrDefault() discovery.Configuration {
   366  	if c.Discovery == nil {
   367  		return defaultDiscovery
   368  	}
   369  
   370  	return *c.Discovery
   371  }
   372  
   373  // Validate validates the Configuration. We use this method to validate fields
   374  // where the validator package falls short.
   375  func (c *DBConfiguration) Validate() error {
   376  	if err := c.Filesystem.Validate(); err != nil {
   377  		return err
   378  	}
   379  
   380  	if _, err := c.PoolingPolicyOrDefault(); err != nil {
   381  		return err
   382  	}
   383  
   384  	if err := c.Client.Validate(); err != nil {
   385  		return err
   386  	}
   387  
   388  	if err := c.Proto.Validate(); err != nil {
   389  		return err
   390  	}
   391  
   392  	if err := c.Transforms.Validate(); err != nil {
   393  		return err
   394  	}
   395  
   396  	if c.Replication != nil {
   397  		if err := c.Replication.Validate(); err != nil {
   398  			return err
   399  		}
   400  	}
   401  
   402  	return nil
   403  }
   404  
   405  // IndexConfiguration contains index-specific configuration.
   406  type IndexConfiguration struct {
   407  	// MaxQueryIDsConcurrency controls the maximum number of outstanding QueryID
   408  	// requests that can be serviced concurrently. Limiting the concurrency is
   409  	// important to prevent index queries from overloading the database entirely
   410  	// as they are very CPU-intensive (regex and FST matching).
   411  	MaxQueryIDsConcurrency int `yaml:"maxQueryIDsConcurrency" validate:"min=0"`
   412  
   413  	// MaxWorkerTime is the maximum time a query can hold an index worker at once. If a query does not finish in this
   414  	// time it yields the worker and must wait again for another worker to resume. The number of workers available to
   415  	// all queries is defined by MaxQueryIDsConcurrency.
   416  	// Capping the maximum time per worker ensures a few large queries don't hold all the concurrent workers and lock
   417  	// out many small queries from running.
   418  	MaxWorkerTime time.Duration `yaml:"maxWorkerTime"`
   419  
   420  	// RegexpDFALimit is the limit on the max number of states used by a
   421  	// regexp deterministic finite automaton. Default is 10,000 states.
   422  	RegexpDFALimit *int `yaml:"regexpDFALimit"`
   423  
   424  	// RegexpFSALimit is the limit on the max number of bytes used by the
   425  	// finite state automaton. Default is 10mb (10 million as int).
   426  	RegexpFSALimit *uint `yaml:"regexpFSALimit"`
   427  
   428  	// ForwardIndexProbability determines the likelihood that an incoming write is
   429  	// written to the next block, when arriving close to the block boundary.
   430  	//
   431  	// NB: this is an optimization which lessens pressure on the index around
   432  	// block boundaries by eagerly writing the series to the next block
   433  	// preemptively.
   434  	ForwardIndexProbability float64 `yaml:"forwardIndexProbability" validate:"min=0.0,max=1.0"`
   435  
   436  	// ForwardIndexThreshold determines the threshold for forward writes, as a
   437  	// fraction of the given namespace's bufferFuture.
   438  	//
   439  	// NB: this is an optimization which lessens pressure on the index around
   440  	// block boundaries by eagerly writing the series to the next block
   441  	// preemptively.
   442  	ForwardIndexThreshold float64 `yaml:"forwardIndexThreshold" validate:"min=0.0,max=1.0"`
   443  }
   444  
   445  // RegexpDFALimitOrDefault returns the deterministic finite automaton states
   446  // limit or default.
   447  func (c IndexConfiguration) RegexpDFALimitOrDefault() int {
   448  	if c.RegexpDFALimit == nil {
   449  		return regexp.StateLimit()
   450  	}
   451  	return *c.RegexpDFALimit
   452  }
   453  
   454  // RegexpFSALimitOrDefault returns the finite state automaton size
   455  // limit or default.
   456  func (c IndexConfiguration) RegexpFSALimitOrDefault() uint {
   457  	if c.RegexpFSALimit == nil {
   458  		return regexp.DefaultLimit()
   459  	}
   460  	return *c.RegexpFSALimit
   461  }
   462  
   463  // TransformConfiguration contains configuration options that can transform
   464  // incoming writes.
   465  type TransformConfiguration struct {
   466  	// TruncateBy determines what type of truncatation is applied to incoming
   467  	// writes.
   468  	TruncateBy series.TruncateType `yaml:"truncateBy"`
   469  	// ForcedValue determines what to set all incoming write values to.
   470  	ForcedValue *float64 `yaml:"forceValue"`
   471  }
   472  
   473  // Validate validates the transform configuration.
   474  func (c *TransformConfiguration) Validate() error {
   475  	if c == nil {
   476  		return nil
   477  	}
   478  
   479  	return c.TruncateBy.Validate()
   480  }
   481  
   482  // TickConfiguration is the tick configuration for background processing of
   483  // series as blocks are rotated from mutable to immutable and out of order
   484  // writes are merged.
   485  type TickConfiguration struct {
   486  	// Tick series batch size is the batch size to process series together
   487  	// during a tick before yielding and sleeping the per series duration
   488  	// multiplied by the batch size.
   489  	// The higher this value is the more variable CPU utilization will be
   490  	// but the shorter ticks will ultimately be.
   491  	SeriesBatchSize int `yaml:"seriesBatchSize"`
   492  
   493  	// Tick per series sleep at the completion of a tick batch.
   494  	PerSeriesSleepDuration time.Duration `yaml:"perSeriesSleepDuration"`
   495  
   496  	// Tick minimum interval controls the minimum tick interval for the node.
   497  	MinimumInterval time.Duration `yaml:"minimumInterval"`
   498  }
   499  
   500  // BlockRetrievePolicy is the block retrieve policy.
   501  type BlockRetrievePolicy struct {
   502  	// FetchConcurrency is the concurrency to fetch blocks from disk. For
   503  	// spinning disks it is highly recommended to set this value to 1.
   504  	FetchConcurrency *int `yaml:"fetchConcurrency" validate:"min=1"`
   505  
   506  	// CacheBlocksOnRetrieve globally enables/disables callbacks used to cache blocks fetched
   507  	// from disk.
   508  	CacheBlocksOnRetrieve *bool `yaml:"cacheBlocksOnRetrieve"`
   509  }
   510  
   511  // CommitLogPolicy is the commit log policy.
   512  type CommitLogPolicy struct {
   513  	// The max size the commit log will flush a segment to disk after buffering.
   514  	FlushMaxBytes int `yaml:"flushMaxBytes" validate:"nonzero"`
   515  
   516  	// The maximum amount of time the commit log will wait to flush to disk.
   517  	FlushEvery time.Duration `yaml:"flushEvery" validate:"nonzero"`
   518  
   519  	// The queue the commit log will keep in front of the current commit log segment.
   520  	// Modifying values in this policy will control how many pending writes can be
   521  	// in the commitlog queue before M3DB will begin rejecting writes.
   522  	Queue CommitLogQueuePolicy `yaml:"queue" validate:"nonzero"`
   523  
   524  	// The actual Golang channel that implements the commit log queue. We separate this
   525  	// from the Queue field for historical / legacy reasons. Generally speaking, the
   526  	// values in this config should not need to be modified, but we leave it in for
   527  	// tuning purposes. Unlike the Queue field, values in this policy control the size
   528  	// of the channel that backs the queue. Since writes to the commitlog are batched,
   529  	// setting the size of this policy will control how many batches can be queued, and
   530  	// indrectly how many writes can be queued, but that is dependent on the batch size
   531  	// of the client. As a result, we recommend that users avoid tuning this field and
   532  	// modify the Queue size instead which maps directly to the number of writes. This
   533  	// works in most cases because the default size of the QueueChannel should be large
   534  	// enough for almost all workloads assuming a reasonable batch size is used.
   535  	QueueChannel *CommitLogQueuePolicy `yaml:"queueChannel"`
   536  }
   537  
   538  // CalculationType is a type of configuration parameter.
   539  type CalculationType string
   540  
   541  const (
   542  	// CalculationTypeFixed is a fixed parameter not to be scaled of any parameter.
   543  	CalculationTypeFixed CalculationType = "fixed"
   544  	// CalculationTypePerCPU is a parameter that needs to be scaled by number of CPUs.
   545  	CalculationTypePerCPU CalculationType = "percpu"
   546  )
   547  
   548  // CommitLogQueuePolicy is the commit log queue policy.
   549  type CommitLogQueuePolicy struct {
   550  	// The type of calculation for the size.
   551  	CalculationType CalculationType `yaml:"calculationType"`
   552  
   553  	// The size of the commit log, calculated according to the calculation type.
   554  	Size int `yaml:"size" validate:"nonzero"`
   555  }
   556  
   557  // RepairPolicyMode is the repair policy mode.
   558  type RepairPolicyMode uint
   559  
   560  // RepairPolicy is the repair policy.
   561  type RepairPolicy struct {
   562  	// Enabled or disabled.
   563  	Enabled bool `yaml:"enabled"`
   564  
   565  	// Type is the type of repair to run.
   566  	Type repair.Type `yaml:"type"`
   567  
   568  	// Strategy is the type of repair strategy to use.
   569  	Strategy repair.Strategy `yaml:"strategy"`
   570  
   571  	// Force the repair to run regardless of whether namespaces have repair enabled or not.
   572  	Force bool `yaml:"force"`
   573  
   574  	// The repair throttle.
   575  	Throttle time.Duration `yaml:"throttle"`
   576  
   577  	// The repair check interval.
   578  	CheckInterval time.Duration `yaml:"checkInterval"`
   579  
   580  	// Concurrency sets the repair shard concurrency if set.
   581  	Concurrency int `yaml:"concurrency"`
   582  
   583  	// Whether debug shadow comparisons are enabled.
   584  	DebugShadowComparisonsEnabled bool `yaml:"debugShadowComparisonsEnabled"`
   585  
   586  	// If enabled, what percentage of metadata should perform a detailed debug
   587  	// shadow comparison.
   588  	DebugShadowComparisonsPercentage float64 `yaml:"debugShadowComparisonsPercentage"`
   589  }
   590  
   591  // ReplicationPolicy is the replication policy.
   592  type ReplicationPolicy struct {
   593  	Clusters []ReplicatedCluster `yaml:"clusters"`
   594  }
   595  
   596  // Validate validates the replication policy.
   597  func (r *ReplicationPolicy) Validate() error {
   598  	names := map[string]bool{}
   599  	for _, c := range r.Clusters {
   600  		if err := c.Validate(); err != nil {
   601  			return err
   602  		}
   603  
   604  		if _, ok := names[c.Name]; ok {
   605  			return fmt.Errorf(
   606  				"replicated cluster names must be unique, but %s was repeated",
   607  				c.Name)
   608  		}
   609  		names[c.Name] = true
   610  	}
   611  
   612  	return nil
   613  }
   614  
   615  // ReplicatedCluster defines a cluster to replicate data from.
   616  type ReplicatedCluster struct {
   617  	Name          string                `yaml:"name"`
   618  	RepairEnabled bool                  `yaml:"repairEnabled"`
   619  	Client        *client.Configuration `yaml:"client"`
   620  }
   621  
   622  // Validate validates the configuration for a replicated cluster.
   623  func (r *ReplicatedCluster) Validate() error {
   624  	if r.Name == "" {
   625  		return errors.New("replicated cluster must be assigned a name")
   626  	}
   627  
   628  	if r.RepairEnabled && r.Client == nil {
   629  		return fmt.Errorf(
   630  			"replicated cluster: %s has repair enabled but not client configuration", r.Name)
   631  	}
   632  
   633  	return nil
   634  }
   635  
   636  // HashingConfiguration is the configuration for hashing.
   637  type HashingConfiguration struct {
   638  	// Murmur32 seed value.
   639  	Seed uint32 `yaml:"seed"`
   640  }
   641  
   642  // ProtoConfiguration is the configuration for running with ProtoDataMode enabled.
   643  type ProtoConfiguration struct {
   644  	// Enabled specifies whether proto is enabled.
   645  	Enabled        bool                            `yaml:"enabled"`
   646  	SchemaRegistry map[string]NamespaceProtoSchema `yaml:"schema_registry"`
   647  }
   648  
   649  // NamespaceProtoSchema is the namespace protobuf schema.
   650  type NamespaceProtoSchema struct {
   651  	// For application m3db client integration test convenience (where a local dbnode is started as a docker container),
   652  	// we allow loading user schema from local file into schema registry.
   653  	SchemaFilePath string `yaml:"schemaFilePath"`
   654  	MessageName    string `yaml:"messageName"`
   655  }
   656  
   657  // Validate validates the NamespaceProtoSchema.
   658  func (c NamespaceProtoSchema) Validate() error {
   659  	if c.SchemaFilePath == "" {
   660  		return errors.New("schemaFilePath is required for Proto data mode")
   661  	}
   662  
   663  	if c.MessageName == "" {
   664  		return errors.New("messageName is required for Proto data mode")
   665  	}
   666  
   667  	return nil
   668  }
   669  
   670  // Validate validates the ProtoConfiguration.
   671  func (c *ProtoConfiguration) Validate() error {
   672  	if c == nil || !c.Enabled {
   673  		return nil
   674  	}
   675  
   676  	for _, schema := range c.SchemaRegistry {
   677  		if err := schema.Validate(); err != nil {
   678  			return err
   679  		}
   680  	}
   681  	return nil
   682  }
   683  
   684  // NewEtcdEmbedConfig creates a new embedded etcd config from kv config.
   685  func NewEtcdEmbedConfig(cfg DBConfiguration) (*embed.Config, error) {
   686  	newKVCfg := embed.NewConfig()
   687  
   688  	hostID, err := cfg.HostIDOrDefault().Resolve()
   689  	if err != nil {
   690  		return nil, fmt.Errorf("failed resolving hostID %w", err)
   691  	}
   692  
   693  	discoveryCfg := cfg.DiscoveryOrDefault()
   694  	envCfg, err := discoveryCfg.EnvironmentConfig(hostID)
   695  	if err != nil {
   696  		return nil, fmt.Errorf("failed getting env config from discovery config %w", err)
   697  	}
   698  
   699  	kvCfg := envCfg.SeedNodes
   700  	newKVCfg.Name = hostID
   701  
   702  	dir := kvCfg.RootDir
   703  	if dir == "" {
   704  		dir = path.Join(cfg.Filesystem.FilePathPrefixOrDefault(), defaultEtcdDirSuffix)
   705  	}
   706  	newKVCfg.Dir = dir
   707  
   708  	LPUrls, err := convertToURLsWithDefault(kvCfg.ListenPeerUrls, newURL(defaultEtcdListenHost, DefaultEtcdServerPort))
   709  	if err != nil {
   710  		return nil, err
   711  	}
   712  	newKVCfg.LPUrls = LPUrls
   713  
   714  	LCUrls, err := convertToURLsWithDefault(kvCfg.ListenClientUrls, newURL(defaultEtcdListenHost, DefaultEtcdClientPort))
   715  	if err != nil {
   716  		return nil, err
   717  	}
   718  	newKVCfg.LCUrls = LCUrls
   719  
   720  	host, endpoint, err := getHostAndEndpointFromID(kvCfg.InitialCluster, hostID)
   721  	if err != nil {
   722  		return nil, err
   723  	}
   724  
   725  	if host.ClusterState != "" {
   726  		newKVCfg.ClusterState = host.ClusterState
   727  	}
   728  
   729  	APUrls, err := convertToURLsWithDefault(kvCfg.InitialAdvertisePeerUrls, newURL(endpoint, DefaultEtcdServerPort))
   730  	if err != nil {
   731  		return nil, err
   732  	}
   733  	newKVCfg.APUrls = APUrls
   734  
   735  	ACUrls, err := convertToURLsWithDefault(kvCfg.AdvertiseClientUrls, newURL(endpoint, DefaultEtcdClientPort))
   736  	if err != nil {
   737  		return nil, err
   738  	}
   739  	newKVCfg.ACUrls = ACUrls
   740  
   741  	newKVCfg.InitialCluster = initialClusterString(kvCfg.InitialCluster)
   742  
   743  	copySecurityDetails := func(tls *transport.TLSInfo, ysc *environment.SeedNodeSecurityConfig) {
   744  		tls.TrustedCAFile = ysc.CAFile
   745  		tls.CertFile = ysc.CertFile
   746  		tls.KeyFile = ysc.KeyFile
   747  		tls.ClientCertAuth = ysc.CertAuth
   748  		tls.TrustedCAFile = ysc.TrustedCAFile
   749  	}
   750  	copySecurityDetails(&newKVCfg.ClientTLSInfo, &kvCfg.ClientTransportSecurity)
   751  	copySecurityDetails(&newKVCfg.PeerTLSInfo, &kvCfg.PeerTransportSecurity)
   752  	newKVCfg.ClientAutoTLS = kvCfg.ClientTransportSecurity.AutoTLS
   753  	newKVCfg.PeerAutoTLS = kvCfg.PeerTransportSecurity.AutoTLS
   754  
   755  	return newKVCfg, nil
   756  }
   757  
   758  func newURL(host string, port int) string {
   759  	return fmt.Sprintf("%s:%d", host, port)
   760  }
   761  
   762  func convertToURLsWithDefault(urlStrs []string, def ...string) ([]url.URL, error) {
   763  	if len(urlStrs) == 0 {
   764  		urlStrs = def
   765  	}
   766  
   767  	urls, err := types.NewURLs(urlStrs)
   768  	if err != nil {
   769  		return nil, err
   770  	}
   771  
   772  	return urls, nil
   773  }
   774  
   775  func initialClusterString(initialCluster []environment.SeedNode) string {
   776  	var buffer bytes.Buffer
   777  
   778  	for i, seedNode := range initialCluster {
   779  		buffer.WriteString(seedNode.HostID)
   780  		buffer.WriteString("=")
   781  		buffer.WriteString(seedNode.Endpoint)
   782  
   783  		if i < len(initialCluster)-1 {
   784  			buffer.WriteString(",")
   785  		}
   786  	}
   787  
   788  	return buffer.String()
   789  }
   790  
   791  func getHostAndEndpointFromID(initialCluster []environment.SeedNode, hostID string) (environment.SeedNode, string, error) {
   792  	emptySeedNode := environment.SeedNode{}
   793  
   794  	if len(initialCluster) == 0 {
   795  		return emptySeedNode, "", errors.New("zero seed nodes in initialCluster")
   796  	}
   797  
   798  	for _, seedNode := range initialCluster {
   799  		if hostID == seedNode.HostID {
   800  			endpoint := seedNode.Endpoint
   801  
   802  			colonIdx := strings.LastIndex(endpoint, ":")
   803  			if colonIdx == -1 {
   804  				return emptySeedNode, "", errors.New("invalid initialCluster format")
   805  			}
   806  
   807  			return seedNode, endpoint[:colonIdx], nil
   808  		}
   809  	}
   810  
   811  	return emptySeedNode, "", errors.New("host not in initialCluster list")
   812  }
   813  
   814  // InitialClusterEndpoints returns the endpoints of the initial cluster
   815  func InitialClusterEndpoints(initialCluster []environment.SeedNode) ([]string, error) {
   816  	endpoints := make([]string, 0, len(initialCluster))
   817  
   818  	for _, seedNode := range initialCluster {
   819  		endpoint := seedNode.Endpoint
   820  
   821  		colonIdx := strings.LastIndex(endpoint, ":")
   822  		if colonIdx == -1 {
   823  			return nil, errors.New("invalid initialCluster format")
   824  		}
   825  
   826  		endpoints = append(endpoints, newURL(endpoint[:colonIdx], DefaultEtcdClientPort))
   827  	}
   828  
   829  	return endpoints, nil
   830  }
   831  
   832  // IsSeedNode returns whether the given hostID is an etcd node.
   833  func IsSeedNode(initialCluster []environment.SeedNode, hostID string) bool {
   834  	for _, seedNode := range initialCluster {
   835  		if seedNode.HostID == hostID {
   836  			return true
   837  		}
   838  	}
   839  
   840  	return false
   841  }
   842  
   843  // TChannelConfiguration holds TChannel config options.
   844  type TChannelConfiguration struct {
   845  	// MaxIdleTime is the maximum idle time.
   846  	MaxIdleTime time.Duration `yaml:"maxIdleTime"`
   847  	// IdleCheckInterval is the idle check interval.
   848  	IdleCheckInterval time.Duration `yaml:"idleCheckInterval"`
   849  }