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  }