github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cmd/dm-syncer/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 main
    15  
    16  import (
    17  	"flag"
    18  	"fmt"
    19  	"os"
    20  
    21  	"github.com/BurntSushi/toml"
    22  	"github.com/go-mysql-org/go-mysql/mysql"
    23  	"github.com/pingcap/errors"
    24  	"github.com/pingcap/tidb/pkg/util/filter"
    25  	router "github.com/pingcap/tidb/pkg/util/table-router"
    26  	"github.com/pingcap/tiflow/dm/config"
    27  	"github.com/pingcap/tiflow/dm/config/dbconfig"
    28  	"github.com/pingcap/tiflow/dm/pkg/log"
    29  	bf "github.com/pingcap/tiflow/pkg/binlog-filter"
    30  	"github.com/pingcap/tiflow/pkg/version"
    31  )
    32  
    33  // commonConfig collects common item for both new config and old config.
    34  type commonConfig struct {
    35  	*flag.FlagSet `json:"-"`
    36  
    37  	// task name
    38  	Name         string
    39  	printVersion bool
    40  	ConfigFile   string
    41  	ServerID     int
    42  	Flavor       string
    43  	WorkerCount  int
    44  	Batch        int
    45  	StatusAddr   string
    46  	Meta         string
    47  
    48  	LogLevel  string
    49  	LogFile   string
    50  	LogFormat string
    51  	LogRotate string
    52  
    53  	EnableGTID bool
    54  	SafeMode   bool
    55  	MaxRetry   int
    56  
    57  	// deprecated
    58  	TimezoneStr string
    59  
    60  	SyncerConfigFormat bool
    61  }
    62  
    63  func (c *commonConfig) newConfigFromSyncerConfig(args []string) (*config.SubTaskConfig, error) {
    64  	cfg := &syncerConfig{
    65  		printVersion: c.printVersion,
    66  		ConfigFile:   c.ConfigFile,
    67  		ServerID:     c.ServerID,
    68  		Flavor:       c.Flavor,
    69  		WorkerCount:  c.WorkerCount,
    70  		Batch:        c.Batch,
    71  		StatusAddr:   c.StatusAddr,
    72  		Meta:         c.Meta,
    73  		LogLevel:     c.LogLevel,
    74  		LogFile:      c.LogFile,
    75  		LogFormat:    c.LogFormat,
    76  		LogRotate:    c.LogRotate,
    77  		EnableGTID:   c.EnableGTID,
    78  		SafeMode:     c.SafeMode,
    79  		MaxRetry:     c.MaxRetry,
    80  	}
    81  
    82  	cfg.FlagSet = flag.NewFlagSet("dm-syncer", flag.ContinueOnError)
    83  	fs := cfg.FlagSet
    84  
    85  	var SyncerConfigFormat bool
    86  	var timezoneStr string
    87  
    88  	fs.BoolVar(&cfg.printVersion, "V", false, "prints version and exit")
    89  	fs.StringVar(&cfg.Name, "name", "", "the task name")
    90  	fs.StringVar(&cfg.ConfigFile, "config", "", "path to config file")
    91  	fs.IntVar(&cfg.ServerID, "server-id", 101, "MySQL slave server ID")
    92  	fs.StringVar(&cfg.Flavor, "flavor", mysql.MySQLFlavor, "use flavor for different MySQL source versions; support \"mysql\", \"mariadb\" now; if you replicate from mariadb, please set it to \"mariadb\"")
    93  	fs.IntVar(&cfg.WorkerCount, "c", 16, "parallel worker count")
    94  	fs.IntVar(&cfg.Batch, "b", 100, "batch commit count")
    95  	fs.StringVar(&cfg.StatusAddr, "status-addr", ":8271", "status addr")
    96  	fs.StringVar(&cfg.Meta, "meta", "syncer.meta", "syncer meta info")
    97  	// fs.StringVar(&cfg.PersistentTableDir, "persistent-dir", "", "syncer history table structures persistent dir; set to non-empty string will choosing history table structure according to column length when constructing DML")
    98  	fs.StringVar(&cfg.LogLevel, "L", "info", "log level: debug, info, warn, error, fatal")
    99  	fs.StringVar(&cfg.LogFile, "log-file", "", "log file path")
   100  	fs.StringVar(&cfg.LogFormat, "log-format", "text", `the format of the log, "text" or "json"`)
   101  	fs.StringVar(&cfg.LogRotate, "log-rotate", "day", "log file rotate type, hour/day")
   102  	fs.BoolVar(&cfg.EnableGTID, "enable-gtid", false, "enable gtid mode")
   103  	fs.BoolVar(&cfg.SafeMode, "safe-mode", false, "enable safe mode to make syncer reentrant")
   104  	fs.IntVar(&cfg.MaxRetry, "max-retry", 100, "maxinum retry when network interruption")
   105  	fs.StringVar(&timezoneStr, "timezone", "", "target database timezone location string")
   106  	fs.BoolVar(&SyncerConfigFormat, "syncer-config-format", false, "read syncer config format")
   107  
   108  	if err := fs.Parse(args); err != nil {
   109  		return nil, errors.Trace(err)
   110  	}
   111  
   112  	if cfg.ConfigFile != "" {
   113  		_, err := toml.DecodeFile(cfg.ConfigFile, cfg)
   114  		if err != nil {
   115  			return nil, errors.Trace(err)
   116  		}
   117  	}
   118  
   119  	if err := fs.Parse(args); err != nil {
   120  		return nil, errors.Trace(err)
   121  	}
   122  
   123  	if timezoneStr != "" {
   124  		log.L().Warn("'--timezone' is deprecated, needn't set it anymore.")
   125  	}
   126  
   127  	return cfg.convertToNewFormat()
   128  }
   129  
   130  func (c *commonConfig) parse(args []string) (*config.SubTaskConfig, error) {
   131  	if err := c.FlagSet.Parse(args); err != nil {
   132  		return nil, errors.Trace(err)
   133  	}
   134  	if c.printVersion {
   135  		fmt.Println(version.GetRawInfo())
   136  		return nil, flag.ErrHelp
   137  	}
   138  
   139  	if c.SyncerConfigFormat {
   140  		return c.newConfigFromSyncerConfig(args)
   141  	}
   142  
   143  	return c.newSubTaskConfig(args)
   144  }
   145  
   146  func (c *commonConfig) newSubTaskConfig(args []string) (*config.SubTaskConfig, error) {
   147  	cfg := &config.SubTaskConfig{}
   148  	cfg.SetFlagSet(flag.NewFlagSet("dm-syncer", flag.ContinueOnError))
   149  	fs := cfg.GetFlagSet()
   150  
   151  	var syncerConfigFormat bool
   152  	var printVersion bool
   153  	var serverID uint
   154  	var timezoneStr string
   155  
   156  	fs.BoolVar(&printVersion, "V", false, "prints version and exit")
   157  	fs.StringVar(&cfg.Name, "name", "", "the task name")
   158  	fs.StringVar(&cfg.ConfigFile, "config", "", "path to config file")
   159  	fs.UintVar(&serverID, "server-id", 101, "MySQL slave server ID")
   160  	fs.StringVar(&cfg.Flavor, "flavor", mysql.MySQLFlavor, "use flavor for different MySQL source versions; support \"mysql\", \"mariadb\" now; if you replicate from mariadb, please set it to \"mariadb\"")
   161  	fs.IntVar(&cfg.WorkerCount, "c", 16, "parallel worker count")
   162  	fs.IntVar(&cfg.Batch, "b", 100, "batch commit count")
   163  	fs.StringVar(&cfg.StatusAddr, "status-addr", ":8271", "status addr")
   164  	// fs.StringVar(&cfg.PersistentTableDir, "persistent-dir", "", "syncer history table structures persistent dir; set to non-empty string will choosing history table structure according to column length when constructing DML")
   165  	fs.StringVar(&cfg.LogLevel, "L", "info", "log level: debug, info, warn, error, fatal")
   166  	fs.StringVar(&cfg.LogFile, "log-file", "", "log file path")
   167  	fs.StringVar(&cfg.LogFormat, "log-format", "text", `the format of the log, "text" or "json"`)
   168  	fs.StringVar(&cfg.LogRotate, "log-rotate", "day", "log file rotate type, hour/day")
   169  	fs.BoolVar(&cfg.EnableGTID, "enable-gtid", false, "enable gtid mode")
   170  	fs.BoolVar(&cfg.SafeMode, "safe-mode", false, "enable safe mode to make syncer reentrant")
   171  	fs.IntVar(&cfg.MaxRetry, "max-retry", 100, "maxinum retry when network interruption")
   172  	fs.StringVar(&timezoneStr, "timezone", "", "target database timezone location string")
   173  	fs.StringVar(&cfg.Name, "cp-table-prefix", "dm-syncer", "the prefix of the checkpoint table name")
   174  	fs.BoolVar(&syncerConfigFormat, "syncer-config-format", false, "read syncer config format")
   175  
   176  	cfg.ServerID = uint32(serverID)
   177  
   178  	if err := cfg.Parse(args, false); err != nil {
   179  		return nil, errors.Trace(err)
   180  	}
   181  	if timezoneStr != "" {
   182  		log.L().Warn("'--timezone' is deprecated, needn't set it anymore.")
   183  	}
   184  
   185  	if serverID != 101 {
   186  		cfg.ServerID = uint32(serverID)
   187  	}
   188  
   189  	return cfg, nil
   190  }
   191  
   192  func newCommonConfig() *commonConfig {
   193  	cfg := &commonConfig{}
   194  	cfg.FlagSet = flag.NewFlagSet("dm-syncer", flag.ContinueOnError)
   195  	fs := cfg.FlagSet
   196  
   197  	fs.BoolVar(&cfg.printVersion, "V", false, "prints version and exit")
   198  	fs.StringVar(&cfg.Name, "name", "", "the task name")
   199  	fs.StringVar(&cfg.ConfigFile, "config", "", "path to config file")
   200  	fs.IntVar(&cfg.ServerID, "server-id", 101, "MySQL slave server ID")
   201  	fs.StringVar(&cfg.Flavor, "flavor", mysql.MySQLFlavor, "use flavor for different MySQL source versions; support \"mysql\", \"mariadb\" now; if you replicate from mariadb, please set it to \"mariadb\"")
   202  	fs.IntVar(&cfg.WorkerCount, "c", 16, "parallel worker count")
   203  	fs.IntVar(&cfg.Batch, "b", 100, "batch commit count")
   204  	fs.StringVar(&cfg.StatusAddr, "status-addr", ":8271", "status addr")
   205  	fs.StringVar(&cfg.Meta, "meta", "syncer.meta", "syncer meta info")
   206  	// fs.StringVar(&cfg.PersistentTableDir, "persistent-dir", "", "syncer history table structures persistent dir; set to non-empty string will choosing history table structure according to column length when constructing DML")
   207  	fs.StringVar(&cfg.LogLevel, "L", "info", "log level: debug, info, warn, error, fatal")
   208  	fs.StringVar(&cfg.LogFile, "log-file", "", "log file path")
   209  	fs.StringVar(&cfg.LogFormat, "log-format", "text", `the format of the log, "text" or "json"`)
   210  	fs.StringVar(&cfg.LogRotate, "log-rotate", "day", "log file rotate type, hour/day")
   211  	fs.BoolVar(&cfg.EnableGTID, "enable-gtid", false, "enable gtid mode")
   212  	fs.BoolVar(&cfg.SafeMode, "safe-mode", false, "enable safe mode to make syncer reentrant")
   213  	fs.IntVar(&cfg.MaxRetry, "max-retry", 100, "maxinum retry when network interruption")
   214  	fs.BoolVar(&cfg.SyncerConfigFormat, "syncer-config-format", false, "read syncer config format")
   215  
   216  	return cfg
   217  }
   218  
   219  // syncerConfig is the format of syncer tools, eventually it will be converted to new SubTaskConfig format.
   220  type syncerConfig struct {
   221  	*flag.FlagSet `json:"-"`
   222  
   223  	Name      string `toml:"name" json:"name"`
   224  	LogLevel  string `toml:"log-level" json:"log-level"`
   225  	LogFile   string `toml:"log-file" json:"log-file"`
   226  	LogFormat string `toml:"log-format" json:"log-format"`
   227  	LogRotate string `toml:"log-rotate" json:"log-rotate"`
   228  
   229  	StatusAddr string `toml:"status-addr" json:"status-addr"`
   230  
   231  	ServerID int    `toml:"server-id" json:"server-id"`
   232  	Meta     string `toml:"meta" json:"meta"`
   233  	// NOTE: This item is deprecated.
   234  	PersistentTableDir string `toml:"persistent-dir" json:"persistent-dir"`
   235  	Flavor             string `toml:"flavor" json:"flavor"`
   236  
   237  	WorkerCount int `toml:"worker-count" json:"worker-count"`
   238  	Batch       int `toml:"batch" json:"batch"`
   239  	MaxRetry    int `toml:"max-retry" json:"max-retry"`
   240  
   241  	// Ref: http://dev.mysql.com/doc/refman/5.7/en/replication-options-slave.html#option_mysqld_replicate-do-table
   242  	DoTables []*filter.Table `toml:"replicate-do-table" json:"replicate-do-table"`
   243  	DoDBs    []string        `toml:"replicate-do-db" json:"replicate-do-db"`
   244  
   245  	// Ref: http://dev.mysql.com/doc/refman/5.7/en/replication-options-slave.html#option_mysqld_replicate-ignore-db
   246  	IgnoreTables []*filter.Table `toml:"replicate-ignore-table" json:"replicate-ignore-table"`
   247  	IgnoreDBs    []string        `toml:"replicate-ignore-db" json:"replicate-ignore-db"`
   248  
   249  	SkipDDLs []string `toml:"skip-ddls" json:"skip-ddls"`
   250  	// NOTE: SkipSQL and SkipEvents are no longer used, leave the comments to remind others.
   251  	// SkipSQLs is deprecated, please use SkipDDLs instead.
   252  	// SkipSQLs []string `toml:"skip-sqls" json:"-"` // omit it since it's deprecated
   253  	// SkipEvents is deprecated, please use SkipDMLs instead.
   254  	// SkipEvents []string   `toml:"skip-events" json:"-"` // omit it since it's deprecated
   255  	SkipDMLs []*SkipDML `toml:"skip-dmls" json:"skip-dmls"`
   256  
   257  	RouteRules []*RouteRule `toml:"route-rules" json:"route-rules"`
   258  
   259  	From dbconfig.DBConfig `toml:"from" json:"from"`
   260  	To   dbconfig.DBConfig `toml:"to" json:"to"`
   261  
   262  	EnableGTID  bool `toml:"enable-gtid" json:"enable-gtid"`
   263  	AutoFixGTID bool `toml:"auto-fix-gtid" json:"auto-fix-gtid"`
   264  
   265  	SafeMode   bool   `toml:"safe-mode" json:"safe-mode"`
   266  	ConfigFile string `json:"config-file"`
   267  
   268  	// NOTE: These four configs are all deprecated.
   269  	// We leave this items as comments to remind others there WERE old config items.
   270  	// stopOnDDL               bool   `toml:"stop-on-ddl" json:"stop-on-ddl"`
   271  	// MaxDDLConnectionTimeout string `toml:"execute-ddl-timeout" json:"execute-ddl-timeout"`
   272  	// MaxDMLConnectionTimeout string `toml:"execute-dml-timeout" json:"execute-dml-timeout"`
   273  	// ExecutionQueueLength    int    `toml:"execute-queue-length" json:"execute-queue-length"`
   274  
   275  	TimezoneStr string `toml:"timezone" json:"timezone"`
   276  
   277  	printVersion bool
   278  }
   279  
   280  // RouteRule is route rule that syncing
   281  // schema/table to specified schema/table
   282  // This config has been replaced by `router.TableRule`.
   283  type RouteRule struct {
   284  	PatternSchema string `toml:"pattern-schema" json:"pattern-schema"`
   285  	PatternTable  string `toml:"pattern-table" json:"pattern-table"`
   286  	TargetSchema  string `toml:"target-schema" json:"target-schema"`
   287  	TargetTable   string `toml:"target-table" json:"target-table"`
   288  }
   289  
   290  // SkipDML defines config rule of skipping dml.
   291  // This config has been replaced by BinLog filter.
   292  type SkipDML struct {
   293  	Schema string `toml:"db-name" json:"db-name"`
   294  	Table  string `toml:"tbl-name" json:"tbl-name"`
   295  	Type   string `toml:"type" json:"type"`
   296  }
   297  
   298  func loadMetaFile(metaFile string) (*config.Meta, error) {
   299  	file, err := os.Open(metaFile)
   300  	if err != nil {
   301  		return nil, errors.Trace(err)
   302  	}
   303  
   304  	defer file.Close()
   305  
   306  	meta := &config.Meta{}
   307  	_, err = toml.DecodeReader(file, meta)
   308  	if err != nil {
   309  		return nil, errors.Trace(err)
   310  	}
   311  	return meta, nil
   312  }
   313  
   314  func (oc *syncerConfig) convertToNewFormat() (*config.SubTaskConfig, error) {
   315  	meta, err := loadMetaFile(oc.Meta)
   316  	if err != nil {
   317  		return nil, errors.Trace(err)
   318  	}
   319  	newTask := &config.SubTaskConfig{
   320  		Name:     oc.Name,
   321  		SourceID: fmt.Sprintf("%s_source", oc.Name),
   322  		Mode:     config.ModeIncrement,
   323  		Meta:     meta,
   324  
   325  		LogLevel:  oc.LogLevel,
   326  		LogFile:   oc.LogFile,
   327  		LogFormat: oc.LogFormat,
   328  		LogRotate: oc.LogRotate,
   329  
   330  		StatusAddr: oc.StatusAddr,
   331  		ServerID:   uint32(oc.ServerID),
   332  		Flavor:     oc.Flavor,
   333  
   334  		SyncerConfig: config.SyncerConfig{
   335  			WorkerCount: oc.WorkerCount,
   336  			Batch:       oc.Batch,
   337  			EnableGTID:  oc.EnableGTID,
   338  			SafeMode:    oc.SafeMode,
   339  		},
   340  
   341  		BAList: &filter.Rules{
   342  			DoTables:     oc.DoTables,
   343  			DoDBs:        oc.DoDBs,
   344  			IgnoreTables: oc.IgnoreTables,
   345  			IgnoreDBs:    oc.IgnoreDBs,
   346  		},
   347  
   348  		ConfigFile: oc.ConfigFile,
   349  		From:       oc.From,
   350  		To:         oc.To,
   351  	}
   352  
   353  	for _, rule := range oc.RouteRules {
   354  		newTask.RouteRules = append(newTask.RouteRules, &router.TableRule{
   355  			SchemaPattern: rule.PatternSchema,
   356  			TablePattern:  rule.PatternTable,
   357  			TargetSchema:  rule.TargetSchema,
   358  			TargetTable:   rule.TargetTable,
   359  		})
   360  	}
   361  
   362  	newTask.FilterRules, err = generateBinlogEventRule(oc.SkipDDLs, oc.SkipDMLs)
   363  	if err != nil {
   364  		return nil, errors.Trace(err)
   365  	}
   366  	err = newTask.Adjust(false)
   367  	return newTask, err
   368  }
   369  
   370  func generateBinlogEventRule(skipDDLs []string, skipDMLs []*SkipDML) ([]*bf.BinlogEventRule, error) {
   371  	result := make([]*bf.BinlogEventRule, 0, 1+len(skipDMLs))
   372  	ddlEvents := &bf.BinlogEventRule{}
   373  	for _, skipDDL := range skipDDLs {
   374  		if tp, _ := bf.ClassifyEvent(bf.EventType(skipDDL)); tp != "ddl" {
   375  			return nil, errors.NotValidf("event type %s", skipDDL)
   376  		}
   377  		ddlEvents.SQLPattern = append(ddlEvents.SQLPattern, skipDDL)
   378  	}
   379  	for _, skipDML := range skipDMLs {
   380  		if tp, _ := bf.ClassifyEvent(bf.EventType(skipDML.Type)); tp != "dml" {
   381  			return nil, errors.NotValidf("event type %s", skipDML.Type)
   382  		}
   383  		found := false
   384  		for _, evt := range result {
   385  			if evt.SchemaPattern == skipDML.Schema && evt.TablePattern == skipDML.Table {
   386  				found = true
   387  				evt.Events = append(evt.Events, bf.EventType(skipDML.Type))
   388  				break
   389  			}
   390  		}
   391  		if !found {
   392  			result = append(result, &bf.BinlogEventRule{
   393  				SchemaPattern: skipDML.Schema,
   394  				TablePattern:  skipDML.Table,
   395  				Events:        []bf.EventType{bf.EventType(skipDML.Type)},
   396  			})
   397  		}
   398  	}
   399  	return result, nil
   400  }