vitess.io/vitess@v0.16.2/go/vt/vtadmin/cluster/flags.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  	"fmt"
    21  	"regexp"
    22  	"strings"
    23  
    24  	"vitess.io/vitess/go/vt/log"
    25  	"vitess.io/vitess/go/vt/vtadmin/cache"
    26  )
    27  
    28  // FlagsByImpl groups a set of flags by discovery implementation. Its mapping is
    29  // impl_name=>flag=>value.
    30  type FlagsByImpl map[string]map[string]string
    31  
    32  // Merge applies the flags in the parameter to the receiver, conflicts are
    33  // resolved in favor of the parameter and not the receiver.
    34  func (base *FlagsByImpl) Merge(override map[string]map[string]string) {
    35  	if (*base) == nil {
    36  		*base = map[string]map[string]string{}
    37  	}
    38  
    39  	for impl, flags := range override {
    40  		_, ok := (*base)[impl]
    41  		if !ok {
    42  			(*base)[impl] = map[string]string{}
    43  		}
    44  
    45  		for k, v := range flags {
    46  			(*base)[impl][k] = v
    47  		}
    48  	}
    49  }
    50  
    51  // ClustersFlag implements flag.Value allowing multiple occurrences of a flag to
    52  // be accumulated into a map.
    53  type ClustersFlag map[string]Config
    54  
    55  // String is part of the flag.Value interface.
    56  func (cf *ClustersFlag) String() string {
    57  	buf := strings.Builder{}
    58  
    59  	buf.WriteString("[")
    60  
    61  	i := 0
    62  
    63  	for _, cfg := range *cf {
    64  		buf.WriteString(cfg.String())
    65  
    66  		if i < len(*cf)-1 {
    67  			buf.WriteString(" ")
    68  		}
    69  
    70  		i++
    71  	}
    72  
    73  	buf.WriteString("]")
    74  
    75  	return buf.String()
    76  }
    77  
    78  // Type is part of the pflag.Value interface.
    79  func (cf *ClustersFlag) Type() string {
    80  	return "cluster.ClustersFlag"
    81  }
    82  
    83  // Set is part of the flag.Value interface. It merges the parsed config into the
    84  // map, allowing ClustersFlag to power a repeated flag. See (*Config).Set for
    85  // details on flag parsing.
    86  func (cf *ClustersFlag) Set(value string) error {
    87  	if (*cf) == nil {
    88  		(*cf) = map[string]Config{}
    89  	}
    90  
    91  	cfg := Config{
    92  		DiscoveryFlagsByImpl: map[string]map[string]string{},
    93  	}
    94  
    95  	if err := parseFlag(&cfg, value); err != nil {
    96  		return err
    97  	}
    98  
    99  	// Merge a potentially existing config for the same cluster ID.
   100  	c, ok := (*cf)[cfg.ID]
   101  	if !ok {
   102  		// If we don't have an existing config, create an empty one to "merge"
   103  		// into.
   104  		c = Config{}
   105  	}
   106  
   107  	(*cf)[cfg.ID] = cfg.Merge(c)
   108  
   109  	return nil
   110  }
   111  
   112  // nolint:gochecknoglobals
   113  var discoveryFlagRegexp = regexp.MustCompile(`^discovery-(?P<impl>\w+)-(?P<flag>.+)$`)
   114  
   115  func parseFlag(cfg *Config, value string) error {
   116  	args := strings.Split(value, ",")
   117  	for _, arg := range args {
   118  		var (
   119  			name string
   120  			val  string
   121  		)
   122  
   123  		if strings.Contains(arg, "=") {
   124  			parts := strings.Split(arg, "=")
   125  			name = parts[0]
   126  			val = strings.Join(parts[1:], "=")
   127  		} else {
   128  			name = arg
   129  			val = "true"
   130  		}
   131  
   132  		if err := parseOne(cfg, name, val); err != nil {
   133  			return err
   134  		}
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func parseOne(cfg *Config, name string, val string) error {
   141  	switch name {
   142  	case "id":
   143  		cfg.ID = val
   144  	case "name":
   145  		cfg.Name = val
   146  	case "discovery":
   147  		cfg.DiscoveryImpl = val
   148  	case "tablet-fqdn-tmpl":
   149  		cfg.TabletFQDNTmplStr = val
   150  	default:
   151  		switch {
   152  		case strings.HasPrefix(name, "vtsql-"):
   153  			if cfg.VtSQLFlags == nil {
   154  				cfg.VtSQLFlags = map[string]string{}
   155  			}
   156  
   157  			cfg.VtSQLFlags[strings.TrimPrefix(name, "vtsql-")] = val
   158  		case strings.HasPrefix(name, "vtctld-"):
   159  			if cfg.VtctldFlags == nil {
   160  				cfg.VtctldFlags = map[string]string{}
   161  			}
   162  
   163  			cfg.VtctldFlags[strings.TrimPrefix(name, "vtctld-")] = val
   164  		case strings.HasPrefix(name, "backup-read-pool-"):
   165  			if cfg.BackupReadPoolConfig == nil {
   166  				cfg.BackupReadPoolConfig = &RPCPoolConfig{
   167  					Size:        -1,
   168  					WaitTimeout: -1,
   169  				}
   170  			}
   171  
   172  			if err := cfg.BackupReadPoolConfig.parseFlag(strings.TrimPrefix(name, "backup-read-pool-"), val); err != nil {
   173  				return fmt.Errorf("error parsing %s: %w", name, err)
   174  			}
   175  		case strings.HasPrefix(name, "schema-read-pool-"):
   176  			if cfg.SchemaReadPoolConfig == nil {
   177  				cfg.SchemaReadPoolConfig = &RPCPoolConfig{
   178  					Size:        -1,
   179  					WaitTimeout: -1,
   180  				}
   181  			}
   182  
   183  			if err := cfg.SchemaReadPoolConfig.parseFlag(strings.TrimPrefix(name, "schema-read-pool-"), val); err != nil {
   184  				return fmt.Errorf("error parsing %s: %w", name, err)
   185  			}
   186  		case strings.HasPrefix(name, "topo-read-pool-"):
   187  			if cfg.TopoReadPoolConfig == nil {
   188  				cfg.TopoReadPoolConfig = &RPCPoolConfig{
   189  					Size:        -1,
   190  					WaitTimeout: -1,
   191  				}
   192  			}
   193  
   194  			if err := cfg.TopoReadPoolConfig.parseFlag(strings.TrimPrefix(name, "topo-read-pool-"), val); err != nil {
   195  				return fmt.Errorf("error parsing %s: %w", name, err)
   196  			}
   197  		case strings.HasPrefix(name, "topo-rw-pool-"):
   198  			if cfg.TopoRWPoolConfig == nil {
   199  				cfg.TopoRWPoolConfig = &RPCPoolConfig{
   200  					Size:        -1,
   201  					WaitTimeout: -1,
   202  				}
   203  			}
   204  
   205  			if err := cfg.TopoRWPoolConfig.parseFlag(strings.TrimPrefix(name, "topo-rw-pool-"), val); err != nil {
   206  				return fmt.Errorf("error parsing %s: %w", name, err)
   207  			}
   208  		case strings.HasPrefix(name, "workflow-read-pool-"):
   209  			if cfg.WorkflowReadPoolConfig == nil {
   210  				cfg.WorkflowReadPoolConfig = &RPCPoolConfig{
   211  					Size:        -1,
   212  					WaitTimeout: -1,
   213  				}
   214  			}
   215  
   216  			if err := cfg.WorkflowReadPoolConfig.parseFlag(strings.TrimPrefix(name, "workflow-read-pool-"), val); err != nil {
   217  				return fmt.Errorf("error parsing %s: %w", name, err)
   218  			}
   219  		case strings.HasPrefix(name, "emergency-failover-pool-"):
   220  			if cfg.EmergencyFailoverPoolConfig == nil {
   221  				cfg.EmergencyFailoverPoolConfig = &RPCPoolConfig{
   222  					Size:        -1,
   223  					WaitTimeout: -1,
   224  				}
   225  			}
   226  
   227  			if err := cfg.EmergencyFailoverPoolConfig.parseFlag(strings.TrimPrefix(name, "emergency-failover-pool-"), val); err != nil {
   228  				return fmt.Errorf("error parsing %s: %w", name, err)
   229  			}
   230  		case strings.HasPrefix(name, "failover-pool-"):
   231  			if cfg.FailoverPoolConfig == nil {
   232  				cfg.FailoverPoolConfig = &RPCPoolConfig{
   233  					Size:        -1,
   234  					WaitTimeout: -1,
   235  				}
   236  			}
   237  
   238  			if err := cfg.FailoverPoolConfig.parseFlag(strings.TrimPrefix(name, "failover-pool-"), val); err != nil {
   239  				return fmt.Errorf("error parsing %s: %w", name, err)
   240  			}
   241  		case strings.HasPrefix(name, "schema-cache-"):
   242  			if cfg.SchemaCacheConfig == nil {
   243  				cfg.SchemaCacheConfig = &cache.Config{
   244  					DefaultExpiration:                -1,
   245  					CleanupInterval:                  -1,
   246  					BackfillRequestTTL:               -1,
   247  					BackfillRequestDuplicateInterval: -1,
   248  					BackfillQueueSize:                -1,
   249  					BackfillEnqueueWaitTime:          -1,
   250  				}
   251  			}
   252  
   253  			if err := parseCacheConfigFlag(cfg.SchemaCacheConfig, strings.TrimPrefix(name, "schema-cache-"), val); err != nil {
   254  				return fmt.Errorf("error parsing %s: %w", name, err)
   255  			}
   256  		default:
   257  			match := discoveryFlagRegexp.FindStringSubmatch(name)
   258  			if match == nil {
   259  				// not a discovery flag
   260  				log.Warningf("Attempted to parse %q as a discovery flag, ignoring ...", name)
   261  				return nil
   262  			}
   263  
   264  			var impl, flag string
   265  
   266  			for i, g := range discoveryFlagRegexp.SubexpNames() {
   267  				switch g {
   268  				case "impl":
   269  					impl = match[i]
   270  				case "flag":
   271  					flag = match[i]
   272  				}
   273  			}
   274  
   275  			if cfg.DiscoveryFlagsByImpl == nil {
   276  				cfg.DiscoveryFlagsByImpl = map[string]map[string]string{}
   277  			}
   278  
   279  			if cfg.DiscoveryFlagsByImpl[impl] == nil {
   280  				cfg.DiscoveryFlagsByImpl[impl] = map[string]string{}
   281  			}
   282  
   283  			cfg.DiscoveryFlagsByImpl[impl][flag] = val
   284  		}
   285  	}
   286  
   287  	return nil
   288  }