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 }