github.com/m3db/m3@v1.5.0/src/dbnode/environment/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 environment
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"os"
    27  	"time"
    28  
    29  	clusterclient "github.com/m3db/m3/src/cluster/client"
    30  	etcdclient "github.com/m3db/m3/src/cluster/client/etcd"
    31  	"github.com/m3db/m3/src/cluster/kv"
    32  	m3clusterkvmem "github.com/m3db/m3/src/cluster/kv/mem"
    33  	"github.com/m3db/m3/src/cluster/services"
    34  	"github.com/m3db/m3/src/cluster/shard"
    35  	"github.com/m3db/m3/src/dbnode/kvconfig"
    36  	"github.com/m3db/m3/src/dbnode/namespace"
    37  	"github.com/m3db/m3/src/dbnode/sharding"
    38  	"github.com/m3db/m3/src/dbnode/topology"
    39  	"github.com/m3db/m3/src/x/instrument"
    40  )
    41  
    42  var (
    43  	errInvalidConfig    = errors.New("must supply either service or static config")
    44  	errInvalidSyncCount = errors.New("must supply exactly one synchronous cluster")
    45  )
    46  
    47  // Configuration is a configuration that can be used to create namespaces, a topology, and kv store
    48  type Configuration struct {
    49  	// Services is used when a topology initializer is not supplied.
    50  	Services DynamicConfiguration `yaml:"services"`
    51  
    52  	// StaticConfiguration is used for running M3DB with static configs
    53  	Statics StaticConfiguration `yaml:"statics"`
    54  
    55  	// Presence of a (etcd) server in this config denotes an embedded cluster
    56  	SeedNodes *SeedNodesConfig `yaml:"seedNodes"`
    57  }
    58  
    59  // SeedNodesConfig defines fields for seed node
    60  type SeedNodesConfig struct {
    61  	RootDir                  string                 `yaml:"rootDir"`
    62  	InitialAdvertisePeerUrls []string               `yaml:"initialAdvertisePeerUrls"`
    63  	AdvertiseClientUrls      []string               `yaml:"advertiseClientUrls"`
    64  	ListenPeerUrls           []string               `yaml:"listenPeerUrls"`
    65  	ListenClientUrls         []string               `yaml:"listenClientUrls"`
    66  	InitialCluster           []SeedNode             `yaml:"initialCluster"`
    67  	ClientTransportSecurity  SeedNodeSecurityConfig `yaml:"clientTransportSecurity"`
    68  	PeerTransportSecurity    SeedNodeSecurityConfig `yaml:"peerTransportSecurity"`
    69  }
    70  
    71  // SeedNode represents a seed node for the cluster
    72  type SeedNode struct {
    73  	HostID       string `yaml:"hostID"`
    74  	Endpoint     string `yaml:"endpoint"`
    75  	ClusterState string `yaml:"clusterState"`
    76  }
    77  
    78  // SeedNodeSecurityConfig contains the data used for security in seed nodes
    79  type SeedNodeSecurityConfig struct {
    80  	CAFile        string `yaml:"caFile"`
    81  	CertFile      string `yaml:"certFile"`
    82  	KeyFile       string `yaml:"keyFile"`
    83  	TrustedCAFile string `yaml:"trustedCaFile"`
    84  	CertAuth      bool   `yaml:"clientCertAuth"`
    85  	AutoTLS       bool   `yaml:"autoTls"`
    86  }
    87  
    88  // DynamicConfiguration is used for running M3DB with a dynamic config
    89  type DynamicConfiguration []*DynamicCluster
    90  
    91  // DynamicCluster is a single cluster in a dynamic configuration
    92  type DynamicCluster struct {
    93  	Async           bool                      `yaml:"async"`
    94  	ClientOverrides ClientOverrides           `yaml:"clientOverrides"`
    95  	Service         *etcdclient.Configuration `yaml:"service"`
    96  }
    97  
    98  // ClientOverrides represents M3DB client overrides for a given cluster.
    99  type ClientOverrides struct {
   100  	HostQueueFlushInterval   *time.Duration `yaml:"hostQueueFlushInterval"`
   101  	TargetHostQueueFlushSize *int           `yaml:"targetHostQueueFlushSize"`
   102  }
   103  
   104  // Validate validates the DynamicConfiguration.
   105  func (c DynamicConfiguration) Validate() error {
   106  	syncCount := 0
   107  	for _, cfg := range c {
   108  		if !cfg.Async {
   109  			syncCount++
   110  		}
   111  		if cfg.ClientOverrides.TargetHostQueueFlushSize != nil && *cfg.ClientOverrides.TargetHostQueueFlushSize <= 1 {
   112  			return fmt.Errorf("target host queue flush size must be larger than zero but was: %d", cfg.ClientOverrides.TargetHostQueueFlushSize)
   113  		}
   114  
   115  		if cfg.ClientOverrides.HostQueueFlushInterval != nil && *cfg.ClientOverrides.HostQueueFlushInterval <= 0 {
   116  			return fmt.Errorf("host queue flush interval must be larger than zero but was: %s", cfg.ClientOverrides.HostQueueFlushInterval.String())
   117  		}
   118  	}
   119  	if syncCount != 1 {
   120  		return errInvalidSyncCount
   121  	}
   122  	return nil
   123  }
   124  
   125  // SyncCluster returns the synchronous cluster in the DynamicConfiguration
   126  func (c DynamicConfiguration) SyncCluster() (*DynamicCluster, error) {
   127  	if err := c.Validate(); err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	for _, cluster := range c {
   132  		if !cluster.Async {
   133  			return cluster, nil
   134  		}
   135  	}
   136  	return nil, errInvalidSyncCount
   137  }
   138  
   139  // StaticConfiguration is used for running M3DB with a static config
   140  type StaticConfiguration []*StaticCluster
   141  
   142  // StaticCluster is a single cluster in a static configuration
   143  type StaticCluster struct {
   144  	Async           bool                              `yaml:"async"`
   145  	ClientOverrides ClientOverrides                   `yaml:"clientOverrides"`
   146  	Namespaces      []namespace.MetadataConfiguration `yaml:"namespaces"`
   147  	TopologyConfig  *topology.StaticConfiguration     `yaml:"topology"`
   148  	ListenAddress   string                            `yaml:"listenAddress"`
   149  }
   150  
   151  // Validate validates the StaticConfiguration
   152  func (c StaticConfiguration) Validate() error {
   153  	syncCount := 0
   154  	for _, cfg := range c {
   155  		if !cfg.Async {
   156  			syncCount++
   157  		}
   158  		if cfg.ClientOverrides.TargetHostQueueFlushSize != nil && *cfg.ClientOverrides.TargetHostQueueFlushSize <= 1 {
   159  			return fmt.Errorf("target host queue flush size must be larger than zero but was: %d", cfg.ClientOverrides.TargetHostQueueFlushSize)
   160  		}
   161  
   162  		if cfg.ClientOverrides.HostQueueFlushInterval != nil && *cfg.ClientOverrides.HostQueueFlushInterval <= 0 {
   163  			return fmt.Errorf("host queue flush interval must be larger than zero but was: %s", cfg.ClientOverrides.HostQueueFlushInterval.String())
   164  		}
   165  	}
   166  	if syncCount != 1 {
   167  		return errInvalidSyncCount
   168  	}
   169  	return nil
   170  }
   171  
   172  // ConfigureResult stores initializers and kv store for dynamic and static configs
   173  type ConfigureResult struct {
   174  	NamespaceInitializer namespace.Initializer
   175  	TopologyInitializer  topology.Initializer
   176  	ClusterClient        clusterclient.Client
   177  	KVStore              kv.Store
   178  	Async                bool
   179  	ClientOverrides      ClientOverrides
   180  }
   181  
   182  // ConfigureResults stores initializers and kv store for dynamic and static configs
   183  type ConfigureResults []ConfigureResult
   184  
   185  // SyncCluster returns the synchronous cluster in the ConfigureResults
   186  func (c ConfigureResults) SyncCluster() (ConfigureResult, error) {
   187  	for _, result := range c {
   188  		if !result.Async {
   189  			return result, nil
   190  		}
   191  	}
   192  	return ConfigureResult{}, errInvalidSyncCount
   193  }
   194  
   195  // ConfigurationParameters are options used to create new ConfigureResults
   196  type ConfigurationParameters struct {
   197  	InterruptedCh          <-chan struct{}
   198  	InstrumentOpts         instrument.Options
   199  	HashingSeed            uint32
   200  	HostID                 string
   201  	NewDirectoryMode       os.FileMode
   202  	ForceColdWritesEnabled bool
   203  	// AllowEmptyInitialNamespaceRegistry determines whether to allow the initial
   204  	// namespace update to be empty or to wait indefinitely until namespaces are received.
   205  	// This is used when configuring the namespaceInitializer.
   206  	AllowEmptyInitialNamespaceRegistry bool
   207  }
   208  
   209  // UnmarshalYAML normalizes the config into a list of services.
   210  func (c *Configuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
   211  	var cfg struct {
   212  		Services  DynamicConfiguration      `yaml:"services"`
   213  		Service   *etcdclient.Configuration `yaml:"service"`
   214  		Static    *StaticCluster            `yaml:"static"`
   215  		Statics   StaticConfiguration       `yaml:"statics"`
   216  		SeedNodes *SeedNodesConfig          `yaml:"seedNodes"`
   217  	}
   218  
   219  	if err := unmarshal(&cfg); err != nil {
   220  		return err
   221  	}
   222  
   223  	c.SeedNodes = cfg.SeedNodes
   224  	c.Statics = cfg.Statics
   225  	if cfg.Static != nil {
   226  		c.Statics = StaticConfiguration{cfg.Static}
   227  	}
   228  	c.Services = cfg.Services
   229  	if cfg.Service != nil {
   230  		c.Services = DynamicConfiguration{
   231  			&DynamicCluster{Service: cfg.Service},
   232  		}
   233  	}
   234  
   235  	return nil
   236  }
   237  
   238  // Validate validates the configuration.
   239  func (c *Configuration) Validate() error {
   240  	if (c.Services == nil && c.Statics == nil) ||
   241  		(len(c.Services) > 0 && len(c.Statics) > 0) {
   242  		return errInvalidConfig
   243  	}
   244  
   245  	if len(c.Services) > 0 {
   246  		if err := c.Services.Validate(); err != nil {
   247  			return err
   248  		}
   249  	}
   250  
   251  	if len(c.Statics) > 0 {
   252  		if err := c.Statics.Validate(); err != nil {
   253  			return err
   254  		}
   255  	}
   256  	return nil
   257  }
   258  
   259  // Configure creates a new ConfigureResults
   260  func (c Configuration) Configure(cfgParams ConfigurationParameters) (ConfigureResults, error) {
   261  	var emptyConfig ConfigureResults
   262  
   263  	// Validate here rather than UnmarshalYAML since we need to ensure one of
   264  	// dynamic or static configuration are provided. A blank YAML does not
   265  	// call UnmarshalYAML and therefore validation would be skipped.
   266  	if err := c.Validate(); err != nil {
   267  		return emptyConfig, err
   268  	}
   269  
   270  	if c.Services != nil {
   271  		return c.configureDynamic(cfgParams)
   272  	}
   273  
   274  	if c.Statics != nil {
   275  		return c.configureStatic(cfgParams)
   276  	}
   277  
   278  	return emptyConfig, errInvalidConfig
   279  }
   280  
   281  func (c Configuration) configureDynamic(cfgParams ConfigurationParameters) (ConfigureResults, error) {
   282  	var emptyConfig ConfigureResults
   283  	if err := c.Services.Validate(); err != nil {
   284  		return emptyConfig, err
   285  	}
   286  
   287  	cfgResults := make(ConfigureResults, 0, len(c.Services))
   288  	for _, cluster := range c.Services {
   289  		configSvcClientOpts := cluster.Service.NewOptions().
   290  			SetInstrumentOptions(cfgParams.InstrumentOpts).
   291  			// Set timeout to zero so it will wait indefinitely for the
   292  			// initial value.
   293  			SetServicesOptions(services.NewOptions().SetInitTimeout(0)).
   294  			SetNewDirectoryMode(cfgParams.NewDirectoryMode)
   295  		configSvcClient, err := etcdclient.NewConfigServiceClient(configSvcClientOpts)
   296  		if err != nil {
   297  			err = fmt.Errorf("could not create m3cluster client: %v", err)
   298  			return emptyConfig, err
   299  		}
   300  
   301  		dynamicOpts := namespace.NewDynamicOptions().
   302  			SetInstrumentOptions(cfgParams.InstrumentOpts).
   303  			SetConfigServiceClient(configSvcClient).
   304  			SetNamespaceRegistryKey(kvconfig.NamespacesKey).
   305  			SetForceColdWritesEnabled(cfgParams.ForceColdWritesEnabled).
   306  			SetAllowEmptyInitialNamespaceRegistry(cfgParams.AllowEmptyInitialNamespaceRegistry)
   307  		nsInit := namespace.NewDynamicInitializer(dynamicOpts)
   308  
   309  		serviceID := services.NewServiceID().
   310  			SetName(cluster.Service.Service).
   311  			SetEnvironment(cluster.Service.Env).
   312  			SetZone(cluster.Service.Zone)
   313  
   314  		topoOpts := topology.NewDynamicOptions().
   315  			SetConfigServiceClient(configSvcClient).
   316  			SetServiceID(serviceID).
   317  			SetQueryOptions(services.NewQueryOptions().
   318  				SetIncludeUnhealthy(true).
   319  				SetInterruptedCh(cfgParams.InterruptedCh)).
   320  			SetInstrumentOptions(cfgParams.InstrumentOpts).
   321  			SetHashGen(sharding.NewHashGenWithSeed(cfgParams.HashingSeed))
   322  		topoInit := topology.NewDynamicInitializer(topoOpts)
   323  
   324  		kv, err := configSvcClient.KV()
   325  		if err != nil {
   326  			err = fmt.Errorf("could not create KV client, %v", err)
   327  			return emptyConfig, err
   328  		}
   329  
   330  		result := ConfigureResult{
   331  			NamespaceInitializer: nsInit,
   332  			TopologyInitializer:  topoInit,
   333  			ClusterClient:        configSvcClient,
   334  			KVStore:              kv,
   335  			Async:                cluster.Async,
   336  			ClientOverrides:      cluster.ClientOverrides,
   337  		}
   338  		cfgResults = append(cfgResults, result)
   339  	}
   340  
   341  	return cfgResults, nil
   342  }
   343  
   344  func (c Configuration) configureStatic(cfgParams ConfigurationParameters) (ConfigureResults, error) {
   345  	var emptyConfig ConfigureResults
   346  
   347  	if err := c.Statics.Validate(); err != nil {
   348  		return emptyConfig, err
   349  	}
   350  
   351  	cfgResults := make(ConfigureResults, 0, len(c.Services))
   352  	for _, cluster := range c.Statics {
   353  		nsList := []namespace.Metadata{}
   354  		for _, ns := range cluster.Namespaces {
   355  			md, err := ns.Metadata()
   356  			if err != nil {
   357  				err = fmt.Errorf("unable to create metadata for static config: %v", err)
   358  				return emptyConfig, err
   359  			}
   360  			nsList = append(nsList, md)
   361  		}
   362  		// NB(bodu): Force cold writes to be enabled for all ns if specified.
   363  		if cfgParams.ForceColdWritesEnabled {
   364  			nsList = namespace.ForceColdWritesEnabledForMetadatas(nsList)
   365  		}
   366  
   367  		nsInitStatic := namespace.NewStaticInitializer(nsList)
   368  
   369  		shardSet, hostShardSets, err := newStaticShardSet(cluster.TopologyConfig.Shards, cluster.TopologyConfig.Hosts)
   370  		if err != nil {
   371  			err = fmt.Errorf("unable to create shard set for static config: %v", err)
   372  			return emptyConfig, err
   373  		}
   374  		staticOptions := topology.NewStaticOptions().
   375  			SetHostShardSets(hostShardSets).
   376  			SetShardSet(shardSet)
   377  
   378  		numHosts := len(cluster.TopologyConfig.Hosts)
   379  		numReplicas := cluster.TopologyConfig.Replicas
   380  
   381  		switch numReplicas {
   382  		case 0:
   383  			if numHosts != 1 {
   384  				err := fmt.Errorf("number of hosts (%d) must be 1 if replicas is not set", numHosts)
   385  				return emptyConfig, err
   386  			}
   387  			staticOptions = staticOptions.SetReplicas(1)
   388  		default:
   389  			if numHosts != numReplicas {
   390  				err := fmt.Errorf("number of hosts (%d) not equal to number of replicas (%d)", numHosts, numReplicas)
   391  				return emptyConfig, err
   392  			}
   393  			staticOptions = staticOptions.SetReplicas(cluster.TopologyConfig.Replicas)
   394  		}
   395  
   396  		topoInit := topology.NewStaticInitializer(staticOptions)
   397  		result := ConfigureResult{
   398  			NamespaceInitializer: nsInitStatic,
   399  			TopologyInitializer:  topoInit,
   400  			KVStore:              m3clusterkvmem.NewStore(),
   401  			Async:                cluster.Async,
   402  			ClientOverrides:      cluster.ClientOverrides,
   403  		}
   404  		cfgResults = append(cfgResults, result)
   405  	}
   406  
   407  	return cfgResults, nil
   408  }
   409  
   410  func newStaticShardSet(numShards int, hosts []topology.HostShardConfig) (sharding.ShardSet, []topology.HostShardSet, error) {
   411  	var (
   412  		shardSet      sharding.ShardSet
   413  		hostShardSets []topology.HostShardSet
   414  		shardIDs      []uint32
   415  		err           error
   416  	)
   417  
   418  	for i := uint32(0); i < uint32(numShards); i++ {
   419  		shardIDs = append(shardIDs, i)
   420  	}
   421  
   422  	shards := sharding.NewShards(shardIDs, shard.Available)
   423  	shardSet, err = sharding.NewShardSet(shards, sharding.DefaultHashFn(len(shards)))
   424  	if err != nil {
   425  		return nil, nil, err
   426  	}
   427  
   428  	for _, i := range hosts {
   429  		host := topology.NewHost(i.HostID, i.ListenAddress)
   430  		hostShardSet := topology.NewHostShardSet(host, shardSet)
   431  		hostShardSets = append(hostShardSets, hostShardSet)
   432  	}
   433  
   434  	return shardSet, hostShardSets, nil
   435  }