vitess.io/vitess@v0.16.2/go/vt/vtadmin/cluster/config.go (about) 1 /* 2 Copyright 2020 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cluster 18 19 import ( 20 "context" 21 "encoding/json" 22 stderrors "errors" 23 "fmt" 24 "io" 25 "strconv" 26 "time" 27 28 "github.com/spf13/viper" 29 30 "vitess.io/vitess/go/pools" 31 "vitess.io/vitess/go/vt/log" 32 "vitess.io/vitess/go/vt/vtadmin/cache" 33 "vitess.io/vitess/go/vt/vtadmin/errors" 34 "vitess.io/vitess/go/vt/vtadmin/vtctldclient" 35 "vitess.io/vitess/go/vt/vtadmin/vtsql" 36 ) 37 38 var ( 39 // DefaultRWPoolSize is the pool size used when creating read-write RPC 40 // pools if a config has no size set. 41 DefaultRWPoolSize = 50 42 // DefaultReadPoolSize is the pool size used when creating read-only RPC 43 // pools if a config has no size set. 44 DefaultReadPoolSize = 500 45 // DefaultRWPoolWaitTimeout is the pool wait timeout used when creating 46 // read-write RPC pools if a config has no wait timeout set. 47 DefaultRWPoolWaitTimeout = time.Millisecond * 100 48 // DefaultReadPoolWaitTimeout is the pool wait timeout used when creating 49 // read-only RPC pools if a config has no wait timeout set. 50 DefaultReadPoolWaitTimeout = time.Millisecond * 100 51 ) 52 53 // Config represents the options to configure a vtadmin cluster. 54 type Config struct { 55 ID string 56 Name string 57 DiscoveryImpl string 58 DiscoveryFlagsByImpl FlagsByImpl 59 TabletFQDNTmplStr string 60 VtSQLFlags map[string]string 61 VtctldFlags map[string]string 62 63 BackupReadPoolConfig *RPCPoolConfig 64 SchemaReadPoolConfig *RPCPoolConfig 65 TopoRWPoolConfig *RPCPoolConfig 66 TopoReadPoolConfig *RPCPoolConfig 67 WorkflowReadPoolConfig *RPCPoolConfig 68 69 // EmergencyFailoverPoolConfig specifies the config for a pool dedicated 70 // solely to EmergencyFailoverShard operations. It has the semantics and 71 // defaults of an RW RPCPool. 72 EmergencyFailoverPoolConfig *RPCPoolConfig 73 // FailoverPoolConfig specifies the config for a pool shared by 74 // PlannedFailoverShard operations. It has the semantics and defaults of an 75 // RW RPCPool. 76 FailoverPoolConfig *RPCPoolConfig 77 78 SchemaCacheConfig *cache.Config 79 80 vtctldConfigOpts []vtctldclient.ConfigOption 81 vtsqlConfigOpts []vtsql.ConfigOption 82 } 83 84 // Cluster returns a new cluster instance from the given config. 85 func (cfg Config) Cluster(ctx context.Context) (*Cluster, error) { 86 return New(ctx, cfg) 87 } 88 89 // String is part of the flag.Value interface. 90 func (cfg *Config) String() string { return fmt.Sprintf("%T:%+v", cfg, *cfg) } 91 92 // Type is part of the pflag.Value interface. 93 func (cfg *Config) Type() string { return "cluster.Config" } 94 95 // Set is part of the flag.Value interface. Each flag is parsed according to the 96 // following DSN: 97 // 98 // id= // ID or shortname of the cluster. 99 // name= // Name of the cluster. 100 // discovery= // Name of the discovery implementation 101 // discovery-.*= // Per-discovery-implementation flags. These are passed to 102 // // a given discovery implementation's constructor. 103 // vtsql-.*= // VtSQL-specific flags. Further parsing of these is delegated 104 // // to the vtsql package. 105 func (cfg *Config) Set(value string) error { 106 if cfg.DiscoveryFlagsByImpl == nil { 107 cfg.DiscoveryFlagsByImpl = map[string]map[string]string{} 108 } 109 110 return parseFlag(cfg, value) 111 } 112 113 // ErrNoConfigID is returned from LoadConfig when a cluster spec has a missing 114 // or empty id. 115 var ErrNoConfigID = stderrors.New("loaded config has no id") 116 117 // LoadConfig reads an io.Reader into viper and tries unmarshal a Config. 118 // 119 // The second parameter is used to instruct viper what config type it will read, 120 // so it knows what Unmarshaller to use. If no config type is given, LoadConfig 121 // defaults to "json". It is the callers responsibility to pass a configType 122 // value that viper can use, which, at the time of writing, include "json", 123 // "yaml", "toml", "ini", "hcl", "tfvars", "env", and "props". 124 // 125 // Any error that occurs during viper's initial read results in a return value 126 // of (nil, "", <the error>), and a triple of this shape should indicate to the 127 // caller complete failure. If viper is able to read the config, and get a 128 // non-empty cluster ID, then the returned id will be non-empty, but the 129 // returned Config and error values may or may not be nil, depending on if the 130 // Config can be fully unmarshalled or not. 131 // 132 // See dynamic.ClusterFromString for additional details on this three-tuple and 133 // how callers should expect to use it. 134 func LoadConfig(r io.Reader, configType string) (cfg *Config, id string, err error) { 135 v := viper.New() 136 if configType == "" { 137 log.Warning("no configType specified, defaulting to 'json'") 138 configType = "json" 139 } 140 141 v.SetConfigType(configType) 142 143 if err := v.ReadConfig(r); err != nil { 144 return nil, "", err 145 } 146 147 id = v.GetString("id") 148 if id == "" { 149 return nil, "", ErrNoConfigID 150 } 151 152 tmp := map[string]string{} 153 if err := v.Unmarshal(&tmp); err != nil { 154 return nil, id, err 155 } 156 157 cfg = &Config{ 158 ID: id, 159 DiscoveryFlagsByImpl: map[string]map[string]string{}, 160 VtSQLFlags: map[string]string{}, 161 VtctldFlags: map[string]string{}, 162 } 163 164 if err := cfg.unmarshalMap(tmp); err != nil { 165 return nil, id, err 166 } 167 168 return cfg, id, nil 169 } 170 171 func (cfg *Config) unmarshalMap(attributes map[string]string) error { 172 for k, v := range attributes { 173 if err := parseOne(cfg, k, v); err != nil { 174 return err 175 } 176 } 177 178 return nil 179 } 180 181 // MarshalJSON implements the json.Marshaler interface. 182 func (cfg *Config) MarshalJSON() ([]byte, error) { 183 defaultRWPoolConfig := &RPCPoolConfig{ 184 Size: DefaultRWPoolSize, 185 WaitTimeout: DefaultRWPoolWaitTimeout, 186 } 187 defaultReadPoolConfig := &RPCPoolConfig{ 188 Size: DefaultReadPoolSize, 189 WaitTimeout: DefaultReadPoolWaitTimeout, 190 } 191 defaultCacheConfig := &cache.Config{ 192 BackfillRequestTTL: cache.DefaultBackfillRequestTTL, 193 BackfillQueueSize: 10, 194 BackfillEnqueueWaitTime: cache.DefaultBackfillEnqueueWaitTime, 195 } 196 197 tmp := struct { 198 ID string `json:"id"` 199 Name string `json:"name"` 200 DiscoveryImpl string `json:"discovery_impl"` 201 DiscoveryFlagsByImpl FlagsByImpl `json:"discovery_flags_by_impl"` 202 TabletFQDNTmplStr string `json:"tablet_fqdn_tmpl_str"` 203 VtSQLFlags map[string]string `json:"vtsql_flags"` 204 VtctldFlags map[string]string `json:"vtctld_flags"` 205 206 BackupReadPoolConfig *RPCPoolConfig `json:"backup_read_pool_config"` 207 SchemaReadPoolConfig *RPCPoolConfig `json:"schema_read_pool_config"` 208 TopoRWPoolConfig *RPCPoolConfig `json:"topo_rw_pool_config"` 209 TopoReadPoolConfig *RPCPoolConfig `json:"topo_read_pool_config"` 210 WorkflowReadPoolConfig *RPCPoolConfig `json:"workflow_read_pool_config"` 211 212 EmergencyFailoverPoolConfig *RPCPoolConfig `json:"emergency_failover_pool_config"` 213 FailoverPoolConfig *RPCPoolConfig `json:"failover_pool_config"` 214 215 SchemaCacheConfig *cache.Config `json:"schema_cache_config"` 216 }{ 217 ID: cfg.ID, 218 Name: cfg.Name, 219 DiscoveryImpl: cfg.DiscoveryImpl, 220 DiscoveryFlagsByImpl: cfg.DiscoveryFlagsByImpl, 221 VtSQLFlags: cfg.VtSQLFlags, 222 VtctldFlags: cfg.VtctldFlags, 223 BackupReadPoolConfig: defaultReadPoolConfig.merge(cfg.BackupReadPoolConfig), 224 SchemaReadPoolConfig: defaultReadPoolConfig.merge(cfg.SchemaReadPoolConfig), 225 TopoRWPoolConfig: defaultRWPoolConfig.merge(cfg.TopoRWPoolConfig), 226 TopoReadPoolConfig: defaultReadPoolConfig.merge(cfg.TopoReadPoolConfig), 227 WorkflowReadPoolConfig: defaultReadPoolConfig.merge(cfg.WorkflowReadPoolConfig), 228 EmergencyFailoverPoolConfig: defaultRWPoolConfig.merge(cfg.EmergencyFailoverPoolConfig), 229 FailoverPoolConfig: defaultRWPoolConfig.merge(cfg.FailoverPoolConfig), 230 SchemaCacheConfig: mergeCacheConfigs(defaultCacheConfig, cfg.SchemaCacheConfig), 231 } 232 233 return json.Marshal(&tmp) 234 } 235 236 // Merge returns the result of merging the calling config into the passed 237 // config. Neither the caller or the argument are modified in any way. 238 func (cfg Config) Merge(override Config) Config { 239 merged := Config{ 240 ID: cfg.ID, 241 Name: cfg.Name, 242 DiscoveryImpl: cfg.DiscoveryImpl, 243 DiscoveryFlagsByImpl: map[string]map[string]string{}, 244 TabletFQDNTmplStr: cfg.TabletFQDNTmplStr, 245 VtSQLFlags: map[string]string{}, 246 VtctldFlags: map[string]string{}, 247 BackupReadPoolConfig: cfg.BackupReadPoolConfig.merge(override.BackupReadPoolConfig), 248 SchemaReadPoolConfig: cfg.SchemaReadPoolConfig.merge(override.SchemaReadPoolConfig), 249 TopoReadPoolConfig: cfg.TopoReadPoolConfig.merge(override.TopoReadPoolConfig), 250 TopoRWPoolConfig: cfg.TopoRWPoolConfig.merge(override.TopoRWPoolConfig), 251 WorkflowReadPoolConfig: cfg.WorkflowReadPoolConfig.merge(override.WorkflowReadPoolConfig), 252 EmergencyFailoverPoolConfig: cfg.EmergencyFailoverPoolConfig.merge(override.EmergencyFailoverPoolConfig), 253 FailoverPoolConfig: cfg.FailoverPoolConfig.merge(override.FailoverPoolConfig), 254 SchemaCacheConfig: mergeCacheConfigs(cfg.SchemaCacheConfig, override.SchemaCacheConfig), 255 } 256 257 if override.ID != "" { 258 merged.ID = override.ID 259 } 260 261 if override.Name != "" { 262 merged.Name = override.Name 263 } 264 265 if override.DiscoveryImpl != "" { 266 merged.DiscoveryImpl = override.DiscoveryImpl 267 } 268 269 if override.TabletFQDNTmplStr != "" { 270 merged.TabletFQDNTmplStr = override.TabletFQDNTmplStr 271 } 272 273 // first, the default flags 274 merged.DiscoveryFlagsByImpl.Merge(cfg.DiscoveryFlagsByImpl) 275 // then, apply any overrides 276 merged.DiscoveryFlagsByImpl.Merge(override.DiscoveryFlagsByImpl) 277 278 mergeStringMap(merged.VtSQLFlags, cfg.VtSQLFlags) 279 mergeStringMap(merged.VtSQLFlags, override.VtSQLFlags) 280 281 mergeStringMap(merged.VtctldFlags, cfg.VtctldFlags) 282 mergeStringMap(merged.VtctldFlags, override.VtctldFlags) 283 284 return merged 285 } 286 287 func mergeStringMap(base map[string]string, override map[string]string) { 288 for k, v := range override { 289 base[k] = v 290 } 291 } 292 293 func mergeCacheConfigs(base, override *cache.Config) *cache.Config { 294 if base == nil && override == nil { 295 return nil 296 } 297 298 merged := &cache.Config{ 299 DefaultExpiration: -1, 300 CleanupInterval: -1, 301 BackfillRequestTTL: -1, 302 BackfillRequestDuplicateInterval: -1, 303 BackfillQueueSize: -1, 304 BackfillEnqueueWaitTime: -1, 305 } 306 307 for _, c := range []*cache.Config{base, override} { 308 if c != nil { 309 if c.DefaultExpiration >= 0 { 310 merged.DefaultExpiration = c.DefaultExpiration 311 } 312 313 if c.CleanupInterval >= 0 { 314 merged.CleanupInterval = c.CleanupInterval 315 } 316 317 if c.BackfillRequestTTL >= 0 { 318 merged.BackfillRequestTTL = c.BackfillRequestTTL 319 } 320 321 if c.BackfillRequestDuplicateInterval >= 0 { 322 merged.BackfillRequestDuplicateInterval = c.BackfillRequestDuplicateInterval 323 } 324 325 if c.BackfillQueueSize >= 0 { 326 merged.BackfillQueueSize = c.BackfillQueueSize 327 } 328 329 if c.BackfillEnqueueWaitTime >= 0 { 330 merged.BackfillEnqueueWaitTime = c.BackfillEnqueueWaitTime 331 } 332 } 333 } 334 335 return merged 336 } 337 338 func parseCacheConfigFlag(cfg *cache.Config, name, val string) (err error) { 339 switch name { 340 case "default-expiration": 341 cfg.DefaultExpiration, err = time.ParseDuration(val) 342 case "cleanup-interval": 343 cfg.CleanupInterval, err = time.ParseDuration(val) 344 case "backfill-request-ttl": 345 cfg.BackfillRequestTTL, err = time.ParseDuration(val) 346 case "backfill-request-duplicate-interval": 347 cfg.BackfillRequestDuplicateInterval, err = time.ParseDuration(val) 348 case "backfill-queue-size": 349 size, err := strconv.ParseInt(val, 10, 64) 350 if err != nil { 351 return err 352 } 353 354 if size < 0 { 355 return fmt.Errorf("%w: backfill queue size must be non-negative; got %d", strconv.ErrRange, size) 356 } 357 358 cfg.BackfillQueueSize = int(size) 359 case "backfill-enqueue-wait-time": 360 cfg.BackfillEnqueueWaitTime, err = time.ParseDuration(val) 361 default: 362 return errors.ErrNoFlag 363 } 364 365 return err 366 } 367 368 // RPCPoolConfig holds configuration options for creating RPCPools. 369 type RPCPoolConfig struct { 370 Size int `json:"size"` 371 WaitTimeout time.Duration `json:"wait_timeout"` 372 } 373 374 // NewRWPool returns an RPCPool from the given config that should be used for 375 // performing read-write operations. If the config is nil, or has a non-positive 376 // size, DefaultRWPoolSize will be used. Similarly, if the config is nil or has 377 // a negative wait timeout, DefaultRWPoolWaitTimeout will be used. 378 func (cfg *RPCPoolConfig) NewRWPool() *pools.RPCPool { 379 size := DefaultRWPoolSize 380 waitTimeout := DefaultRWPoolWaitTimeout 381 382 if cfg != nil { 383 if cfg.Size > 0 { 384 size = cfg.Size 385 } 386 387 if cfg.WaitTimeout >= 0 { 388 waitTimeout = cfg.WaitTimeout 389 } 390 } 391 392 return pools.NewRPCPool(size, waitTimeout, nil) 393 } 394 395 // NewReadPool returns an RPCPool from the given config that should be used for 396 // performing read-only operations. If the config is nil, or has a non-positive 397 // size, DefaultReadPoolSize will be used. Similarly, if the config is nil or 398 // has a negative wait timeout, DefaultReadPoolWaitTimeout will be used. 399 func (cfg *RPCPoolConfig) NewReadPool() *pools.RPCPool { 400 size := DefaultReadPoolSize 401 waitTimeout := DefaultReadPoolWaitTimeout 402 403 if cfg != nil { 404 if cfg.Size > 0 { 405 size = cfg.Size 406 } 407 408 if cfg.WaitTimeout >= 0 { 409 waitTimeout = cfg.WaitTimeout 410 } 411 } 412 413 return pools.NewRPCPool(size, waitTimeout, nil) 414 } 415 416 // merge merges two RPCPoolConfigs, returning the merged version. neither of the 417 // original configs is modified as a result of merging, and both can be nil. 418 func (cfg *RPCPoolConfig) merge(override *RPCPoolConfig) *RPCPoolConfig { 419 if cfg == nil && override == nil { 420 return nil 421 } 422 423 merged := &RPCPoolConfig{ 424 Size: -1, 425 WaitTimeout: -1, 426 } 427 428 for _, c := range []*RPCPoolConfig{cfg, override} { // First apply the base config, then any overrides. 429 if c != nil { 430 if c.Size >= 0 { 431 merged.Size = c.Size 432 } 433 434 if c.WaitTimeout > 0 { 435 merged.WaitTimeout = c.WaitTimeout 436 } 437 } 438 } 439 440 return merged 441 } 442 443 func (cfg *RPCPoolConfig) parseFlag(name string, val string) (err error) { 444 switch name { 445 case "size": 446 size, err := strconv.ParseInt(val, 10, 64) 447 if err != nil { 448 return err 449 } 450 451 if size < 0 { 452 return fmt.Errorf("%w: pool size must be non-negative; got %d", strconv.ErrRange, size) 453 } 454 455 cfg.Size = int(size) 456 case "timeout": 457 cfg.WaitTimeout, err = time.ParseDuration(val) 458 if err != nil { 459 return err 460 } 461 default: 462 return errors.ErrNoFlag 463 } 464 465 return nil 466 } 467 468 // WithVtctldTestConfigOptions returns a new Config with the given vtctldclient 469 // ConfigOptions appended to any existing ConfigOptions in the current Config. 470 // 471 // It should be used in tests only, and is exported to for use in the 472 // vtadmin/testutil package. 473 func (cfg Config) WithVtctldTestConfigOptions(opts ...vtctldclient.ConfigOption) Config { 474 cfg.vtctldConfigOpts = append(cfg.vtctldConfigOpts, opts...) 475 return cfg 476 } 477 478 // WithVtSQLTestConfigOptions returns a new Config with the given vtsql 479 // ConfigOptions appended to any existing ConfigOptions in the current Config. 480 // 481 // It should be used in tests only, and is exported to for use in the 482 // vtadmin/testutil package. 483 func (cfg Config) WithVtSQLTestConfigOptions(opts ...vtsql.ConfigOption) Config { 484 cfg.vtsqlConfigOpts = append(cfg.vtsqlConfigOpts, opts...) 485 return cfg 486 }