github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/worker/config.go (about)

     1  // Copyright 2019 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package worker
    15  
    16  import (
    17  	"bytes"
    18  	_ "embed"
    19  	"encoding/json"
    20  	"flag"
    21  	"fmt"
    22  	"net"
    23  	"strings"
    24  
    25  	"github.com/BurntSushi/toml"
    26  	"github.com/pingcap/failpoint"
    27  	"github.com/pingcap/tiflow/dm/config/security"
    28  	"github.com/pingcap/tiflow/dm/pkg/log"
    29  	"github.com/pingcap/tiflow/dm/pkg/terror"
    30  	"github.com/pingcap/tiflow/dm/pkg/utils"
    31  	"github.com/pingcap/tiflow/pkg/version"
    32  )
    33  
    34  // SampleConfigFile is sample config file of dm-worker.
    35  //
    36  //go:embed dm-worker.toml
    37  var SampleConfigFile string
    38  
    39  var (
    40  	defaultKeepAliveTTL      = int64(60)      // 1 minute
    41  	defaultRelayKeepAliveTTL = int64(60 * 30) // 30 minutes
    42  )
    43  
    44  func init() {
    45  	failpoint.Inject("defaultKeepAliveTTL", func(val failpoint.Value) {
    46  		i := val.(int)
    47  		defaultKeepAliveTTL = int64(i)
    48  	})
    49  	failpoint.Inject("defaultRelayKeepAliveTTL", func(val failpoint.Value) {
    50  		i := val.(int)
    51  		defaultRelayKeepAliveTTL = int64(i)
    52  	})
    53  }
    54  
    55  // NewConfig creates a new base config for worker.
    56  func NewConfig() *Config {
    57  	cfg := &Config{}
    58  	cfg.flagSet = flag.NewFlagSet("worker", flag.ContinueOnError)
    59  	fs := cfg.flagSet
    60  
    61  	fs.BoolVar(&cfg.printVersion, "V", false, "prints version and exit")
    62  	fs.BoolVar(&cfg.printSampleConfig, "print-sample-config", false, "print sample config file of dm-worker")
    63  	fs.StringVar(&cfg.ConfigFile, "config", "", "path to config file")
    64  	fs.StringVar(&cfg.WorkerAddr, "worker-addr", "", "listen address for client traffic")
    65  	fs.StringVar(&cfg.AdvertiseAddr, "advertise-addr", "", `advertise address for client traffic (default "${worker-addr}")`)
    66  	fs.StringVar(&cfg.LogLevel, "L", "info", "log level: debug, info, warn, error, fatal")
    67  	fs.StringVar(&cfg.LogFile, "log-file", "", "log file path")
    68  	fs.StringVar(&cfg.LogFormat, "log-format", "text", `the format of the log, "text" or "json"`)
    69  	// fs.StringVar(&cfg.LogRotate, "log-rotate", "day", "log file rotate type, hour/day")
    70  	// NOTE: add `advertise-addr` for dm-master if needed.
    71  	fs.StringVar(&cfg.Join, "join", "", `join to an existing cluster (usage: dm-master cluster's "${master-addr}")`)
    72  	fs.StringVar(&cfg.Name, "name", "", "human-readable name for DM-worker member")
    73  	fs.Int64Var(&cfg.KeepAliveTTL, "keepalive-ttl", defaultKeepAliveTTL, "dm-worker's TTL for keepalive with etcd (in seconds)")
    74  	fs.Int64Var(&cfg.RelayKeepAliveTTL, "relay-keepalive-ttl", defaultRelayKeepAliveTTL, "dm-worker's TTL for keepalive with etcd when handle relay enabled sources (in seconds)")
    75  
    76  	fs.StringVar(&cfg.SSLCA, "ssl-ca", "", "path of file that contains list of trusted SSL CAs for connection")
    77  	fs.StringVar(&cfg.SSLCert, "ssl-cert", "", "path of file that contains X509 certificate in PEM format for connection")
    78  	fs.StringVar(&cfg.SSLKey, "ssl-key", "", "path of file that contains X509 key in PEM format for connection")
    79  	fs.Var(&cfg.CertAllowedCN, "cert-allowed-cn", "the trusted common name that allowed to visit")
    80  
    81  	return cfg
    82  }
    83  
    84  // Config is the configuration.
    85  type Config struct {
    86  	flagSet *flag.FlagSet
    87  	Name    string `toml:"name" json:"name"`
    88  
    89  	LogLevel  string `toml:"log-level" json:"log-level"`
    90  	LogFile   string `toml:"log-file" json:"log-file"`
    91  	LogFormat string `toml:"log-format" json:"log-format"`
    92  	LogRotate string `toml:"log-rotate" json:"log-rotate"`
    93  
    94  	Join          string `toml:"join" json:"join" `
    95  	WorkerAddr    string `toml:"worker-addr" json:"worker-addr"`
    96  	AdvertiseAddr string `toml:"advertise-addr" json:"advertise-addr"`
    97  
    98  	ConfigFile string `toml:"config-file" json:"config-file"`
    99  	// TODO: in the future dm-workers should share a same ttl from dm-master
   100  	KeepAliveTTL      int64 `toml:"keepalive-ttl" json:"keepalive-ttl"`
   101  	RelayKeepAliveTTL int64 `toml:"relay-keepalive-ttl" json:"relay-keepalive-ttl"`
   102  
   103  	RelayDir string `toml:"relay-dir" json:"relay-dir"`
   104  
   105  	// tls config
   106  	security.Security
   107  
   108  	printVersion      bool
   109  	printSampleConfig bool
   110  }
   111  
   112  // Clone clones a config.
   113  func (c *Config) Clone() *Config {
   114  	clone := &Config{}
   115  	*clone = *c
   116  	return clone
   117  }
   118  
   119  func (c *Config) String() string {
   120  	cfg, err := json.Marshal(c)
   121  	if err != nil {
   122  		log.L().Error("fail to marshal config to json", log.ShortError(err))
   123  	}
   124  	return string(cfg)
   125  }
   126  
   127  // Toml returns TOML format representation of config.
   128  func (c *Config) Toml() (string, error) {
   129  	var b bytes.Buffer
   130  
   131  	err := toml.NewEncoder(&b).Encode(c)
   132  	if err != nil {
   133  		log.L().Error("fail to marshal config to toml", log.ShortError(err))
   134  	}
   135  
   136  	return b.String(), nil
   137  }
   138  
   139  // Parse parses flag definitions from the argument list.
   140  func (c *Config) Parse(arguments []string) error {
   141  	// Parse first to get config file.
   142  	err := c.flagSet.Parse(arguments)
   143  	if err != nil {
   144  		return terror.ErrWorkerParseFlagSet.Delegate(err)
   145  	}
   146  
   147  	if c.printVersion {
   148  		fmt.Println(version.GetRawInfo())
   149  		return flag.ErrHelp
   150  	}
   151  
   152  	if c.printSampleConfig {
   153  		fmt.Println(SampleConfigFile)
   154  		return flag.ErrHelp
   155  	}
   156  
   157  	// Load config file if specified.
   158  	if c.ConfigFile != "" {
   159  		err = c.configFromFile(c.ConfigFile)
   160  		if err != nil {
   161  			return err
   162  		}
   163  	}
   164  
   165  	// Parse again to replace with command line options.
   166  	err = c.flagSet.Parse(arguments)
   167  	if err != nil {
   168  		return terror.ErrWorkerParseFlagSet.Delegate(err)
   169  	}
   170  
   171  	if len(c.flagSet.Args()) != 0 {
   172  		return terror.ErrWorkerInvalidFlag.Generate(c.flagSet.Arg(0))
   173  	}
   174  
   175  	return c.adjust()
   176  }
   177  
   178  // adjust adjusts the config.
   179  func (c *Config) adjust() error {
   180  	c.WorkerAddr = utils.UnwrapScheme(c.WorkerAddr)
   181  	host, _, err := net.SplitHostPort(c.WorkerAddr)
   182  	if err != nil {
   183  		return terror.ErrWorkerHostPortNotValid.Delegate(err, c.WorkerAddr)
   184  	}
   185  
   186  	if c.AdvertiseAddr == "" {
   187  		if host == "" || host == "0.0.0.0" {
   188  			return terror.ErrWorkerHostPortNotValid.Generatef("worker-addr (%s) must include the 'host' part (should not be '0.0.0.0') when advertise-addr is not set", c.WorkerAddr)
   189  		}
   190  		c.AdvertiseAddr = c.WorkerAddr
   191  	} else {
   192  		c.AdvertiseAddr = utils.UnwrapScheme(c.AdvertiseAddr)
   193  		var port string
   194  		host, port, err = net.SplitHostPort(c.AdvertiseAddr)
   195  		if err != nil {
   196  			return terror.ErrWorkerHostPortNotValid.Delegate(err, c.AdvertiseAddr)
   197  		}
   198  		if host == "" || host == "0.0.0.0" || len(port) == 0 {
   199  			return terror.ErrWorkerHostPortNotValid.Generate("advertise-addr (%s) must include the 'host' part and should not be '0.0.0.0'", c.AdvertiseAddr)
   200  		}
   201  	}
   202  
   203  	if c.Name == "" {
   204  		fmt.Printf("worker name is not given, we will set AdvertiseAddr %s as the worker name\n", c.AdvertiseAddr)
   205  		c.Name = c.AdvertiseAddr
   206  	}
   207  
   208  	if c.Join != "" {
   209  		c.Join = utils.WrapSchemes(c.Join, c.SSLCA != "")
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  // configFromFile loads config from file.
   216  func (c *Config) configFromFile(path string) error {
   217  	metaData, err := toml.DecodeFile(path, c)
   218  	if err != nil {
   219  		return terror.ErrWorkerDecodeConfigFromFile.Delegate(err)
   220  	}
   221  	undecoded := metaData.Undecoded()
   222  	if len(undecoded) > 0 && err == nil {
   223  		var undecodedItems []string
   224  		for _, item := range undecoded {
   225  			undecodedItems = append(undecodedItems, item.String())
   226  		}
   227  		return terror.ErrWorkerUndecodedItemFromFile.Generate(strings.Join(undecodedItems, ","))
   228  	}
   229  	return nil
   230  }