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

     1  // Copyright 2021 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 config
    15  
    16  import (
    17  	"fmt"
    18  	"strings"
    19  
    20  	"github.com/pingcap/tidb/pkg/util/filter"
    21  	router "github.com/pingcap/tidb/pkg/util/table-router"
    22  	"github.com/pingcap/tiflow/dm/config/dbconfig"
    23  	"github.com/pingcap/tiflow/dm/config/security"
    24  	"github.com/pingcap/tiflow/dm/openapi"
    25  	"github.com/pingcap/tiflow/dm/pkg/log"
    26  	"github.com/pingcap/tiflow/dm/pkg/storage"
    27  	"github.com/pingcap/tiflow/dm/pkg/terror"
    28  	bf "github.com/pingcap/tiflow/pkg/binlog-filter"
    29  	"github.com/pingcap/tiflow/pkg/column-mapping"
    30  	"go.uber.org/zap"
    31  )
    32  
    33  // TaskConfigToSubTaskConfigs generates sub task configs by TaskConfig.
    34  func TaskConfigToSubTaskConfigs(c *TaskConfig, sources map[string]dbconfig.DBConfig) ([]*SubTaskConfig, error) {
    35  	cfgs := make([]*SubTaskConfig, len(c.MySQLInstances))
    36  	for i, inst := range c.MySQLInstances {
    37  		dbCfg, exist := sources[inst.SourceID]
    38  		if !exist {
    39  			return nil, terror.ErrConfigSourceIDNotFound.Generate(inst.SourceID)
    40  		}
    41  
    42  		cfg := NewSubTaskConfig()
    43  		cfg.IsSharding = c.IsSharding
    44  		cfg.ShardMode = c.ShardMode
    45  		cfg.StrictOptimisticShardMode = c.StrictOptimisticShardMode
    46  		cfg.OnlineDDL = c.OnlineDDL
    47  		cfg.TrashTableRules = c.TrashTableRules
    48  		cfg.ShadowTableRules = c.ShadowTableRules
    49  		cfg.IgnoreCheckingItems = c.IgnoreCheckingItems
    50  		cfg.Name = c.Name
    51  		cfg.Mode = c.TaskMode
    52  		cfg.CaseSensitive = c.CaseSensitive
    53  		cfg.MetaSchema = c.MetaSchema
    54  		cfg.EnableHeartbeat = false
    55  		cfg.HeartbeatUpdateInterval = c.HeartbeatUpdateInterval
    56  		cfg.HeartbeatReportInterval = c.HeartbeatReportInterval
    57  		cfg.Timezone = c.Timezone
    58  		cfg.Meta = inst.Meta
    59  		cfg.CollationCompatible = c.CollationCompatible
    60  		cfg.Experimental = c.Experimental
    61  
    62  		fromClone := dbCfg.Clone()
    63  		if fromClone == nil {
    64  			return nil, terror.ErrConfigMySQLInstNotFound
    65  		}
    66  		cfg.From = *fromClone
    67  		toClone := c.TargetDB.Clone()
    68  		if toClone == nil {
    69  			return nil, terror.ErrConfigNeedTargetDB
    70  		}
    71  		cfg.To = *toClone
    72  
    73  		cfg.SourceID = inst.SourceID
    74  
    75  		cfg.RouteRules = make([]*router.TableRule, len(inst.RouteRules))
    76  		for j, name := range inst.RouteRules {
    77  			cfg.RouteRules[j] = c.Routes[name]
    78  		}
    79  
    80  		cfg.FilterRules = make([]*bf.BinlogEventRule, len(inst.FilterRules))
    81  		for j, name := range inst.FilterRules {
    82  			cfg.FilterRules[j] = c.Filters[name]
    83  		}
    84  
    85  		cfg.ColumnMappingRules = make([]*column.Rule, len(inst.ColumnMappingRules))
    86  		for j, name := range inst.ColumnMappingRules {
    87  			cfg.ColumnMappingRules[j] = c.ColumnMappings[name]
    88  		}
    89  
    90  		cfg.ExprFilter = make([]*ExpressionFilter, len(inst.ExpressionFilters))
    91  		for j, name := range inst.ExpressionFilters {
    92  			cfg.ExprFilter[j] = c.ExprFilter[name]
    93  		}
    94  
    95  		cfg.BAList = c.BAList[inst.BAListName]
    96  
    97  		cfg.MydumperConfig = *inst.Mydumper
    98  		cfg.LoaderConfig = *inst.Loader
    99  		cfg.SyncerConfig = *inst.Syncer
   100  		cfg.ValidatorCfg = inst.ContinuousValidator
   101  
   102  		cfg.CleanDumpFile = c.CleanDumpFile
   103  
   104  		if err := cfg.Adjust(true); err != nil {
   105  			return nil, terror.Annotatef(err, "source %s", inst.SourceID)
   106  		}
   107  		cfgs[i] = cfg
   108  	}
   109  	if c.EnableHeartbeat {
   110  		log.L().Warn("DM 2.0 does not support heartbeat feature, will overwrite it to false")
   111  	}
   112  	return cfgs, nil
   113  }
   114  
   115  // OpenAPITaskToSubTaskConfigs generates sub task configs by openapi.Task.
   116  func OpenAPITaskToSubTaskConfigs(task *openapi.Task, toDBCfg *dbconfig.DBConfig, sourceCfgMap map[string]*SourceConfig) (
   117  	[]*SubTaskConfig, error,
   118  ) {
   119  	// source name -> migrate rule list
   120  	tableMigrateRuleMap := make(map[string][]openapi.TaskTableMigrateRule)
   121  	for _, rule := range task.TableMigrateRule {
   122  		tableMigrateRuleMap[rule.Source.SourceName] = append(tableMigrateRuleMap[rule.Source.SourceName], rule)
   123  	}
   124  	// rule name -> rule template
   125  	eventFilterTemplateMap := make(map[string]bf.BinlogEventRule)
   126  	if task.BinlogFilterRule != nil {
   127  		for ruleName, rule := range task.BinlogFilterRule.AdditionalProperties {
   128  			ruleT := bf.BinlogEventRule{Action: bf.Ignore}
   129  			if rule.IgnoreEvent != nil {
   130  				events := make([]bf.EventType, len(*rule.IgnoreEvent))
   131  				for i, eventStr := range *rule.IgnoreEvent {
   132  					events[i] = bf.EventType(eventStr)
   133  				}
   134  				ruleT.Events = events
   135  			}
   136  			if rule.IgnoreSql != nil {
   137  				ruleT.SQLPattern = *rule.IgnoreSql
   138  			}
   139  			eventFilterTemplateMap[ruleName] = ruleT
   140  		}
   141  	}
   142  	// start to generate sub task configs
   143  	subTaskCfgList := make([]*SubTaskConfig, len(task.SourceConfig.SourceConf))
   144  	for i, sourceCfg := range task.SourceConfig.SourceConf {
   145  		// precheck source config
   146  		_, exist := sourceCfgMap[sourceCfg.SourceName]
   147  		if !exist {
   148  			return nil, terror.ErrConfigSourceIDNotFound.Generate(sourceCfg.SourceName)
   149  		}
   150  		subTaskCfg := NewSubTaskConfig()
   151  		// set task name and mode
   152  		subTaskCfg.Name = task.Name
   153  		subTaskCfg.Mode = string(task.TaskMode)
   154  		// set task meta
   155  		subTaskCfg.MetaSchema = *task.MetaSchema
   156  		// add binlog meta
   157  		if sourceCfg.BinlogGtid != nil || sourceCfg.BinlogName != nil || sourceCfg.BinlogPos != nil {
   158  			meta := &Meta{}
   159  			if sourceCfg.BinlogGtid != nil {
   160  				meta.BinLogGTID = *sourceCfg.BinlogGtid
   161  			}
   162  			if sourceCfg.BinlogName != nil {
   163  				meta.BinLogName = *sourceCfg.BinlogName
   164  			}
   165  			if sourceCfg.BinlogPos != nil {
   166  				pos := uint32(*sourceCfg.BinlogPos)
   167  				meta.BinLogPos = pos
   168  			}
   169  			subTaskCfg.Meta = meta
   170  		}
   171  
   172  		// if there is no meta for incremental task, we print a warning log
   173  		if subTaskCfg.Meta == nil && subTaskCfg.Mode == ModeIncrement {
   174  			log.L().Warn("mysql-instance doesn't set meta for incremental mode, user should specify start_time to start task.", zap.String("sourceID", sourceCfg.SourceName))
   175  		}
   176  
   177  		// set shard config
   178  		if task.ShardMode != nil {
   179  			subTaskCfg.IsSharding = true
   180  			mode := *task.ShardMode
   181  			subTaskCfg.ShardMode = string(mode)
   182  		} else {
   183  			subTaskCfg.IsSharding = false
   184  		}
   185  		if task.StrictOptimisticShardMode != nil {
   186  			subTaskCfg.StrictOptimisticShardMode = *task.StrictOptimisticShardMode
   187  		}
   188  		// set online ddl plugin config
   189  		subTaskCfg.OnlineDDL = task.EnhanceOnlineSchemaChange
   190  		// set case sensitive from source
   191  		subTaskCfg.CaseSensitive = sourceCfgMap[sourceCfg.SourceName].CaseSensitive
   192  		// set source db config
   193  		subTaskCfg.SourceID = sourceCfg.SourceName
   194  		subTaskCfg.From = sourceCfgMap[sourceCfg.SourceName].From
   195  		// set target db config
   196  		subTaskCfg.To = *toDBCfg.Clone()
   197  		// TODO ExprFilter
   198  		// set full unit config
   199  		subTaskCfg.MydumperConfig = DefaultMydumperConfig()
   200  		subTaskCfg.LoaderConfig = DefaultLoaderConfig()
   201  		if fullCfg := task.SourceConfig.FullMigrateConf; fullCfg != nil {
   202  			if fullCfg.Analyze != nil {
   203  				subTaskCfg.LoaderConfig.Analyze = PhysicalPostOpLevel(*fullCfg.Analyze)
   204  			}
   205  			if fullCfg.Checksum != nil {
   206  				subTaskCfg.LoaderConfig.ChecksumPhysical = PhysicalPostOpLevel(*fullCfg.Checksum)
   207  			}
   208  			if fullCfg.CompressKvPairs != nil {
   209  				subTaskCfg.CompressKVPairs = *fullCfg.CompressKvPairs
   210  			}
   211  			if fullCfg.Consistency != nil {
   212  				subTaskCfg.MydumperConfig.ExtraArgs = fmt.Sprintf("--consistency %s", *fullCfg.Consistency)
   213  			}
   214  			if fullCfg.ExportThreads != nil {
   215  				subTaskCfg.MydumperConfig.Threads = *fullCfg.ExportThreads
   216  			}
   217  			if fullCfg.ImportThreads != nil {
   218  				subTaskCfg.LoaderConfig.PoolSize = *fullCfg.ImportThreads
   219  			}
   220  			if fullCfg.DataDir != nil {
   221  				subTaskCfg.LoaderConfig.Dir = *fullCfg.DataDir
   222  			}
   223  			if fullCfg.DiskQuota != nil {
   224  				if err := subTaskCfg.LoaderConfig.DiskQuotaPhysical.UnmarshalText([]byte(*fullCfg.DiskQuota)); err != nil {
   225  					return nil, err
   226  				}
   227  			}
   228  			if fullCfg.ImportMode != nil {
   229  				subTaskCfg.LoaderConfig.ImportMode = LoadMode(*fullCfg.ImportMode)
   230  			}
   231  			if fullCfg.OnDuplicateLogical != nil {
   232  				subTaskCfg.LoaderConfig.OnDuplicateLogical = LogicalDuplicateResolveType(*fullCfg.OnDuplicateLogical)
   233  			}
   234  			if fullCfg.OnDuplicatePhysical != nil {
   235  				subTaskCfg.LoaderConfig.OnDuplicatePhysical = PhysicalDuplicateResolveType(*fullCfg.OnDuplicatePhysical)
   236  			}
   237  			if fullCfg.PdAddr != nil {
   238  				subTaskCfg.LoaderConfig.PDAddr = *fullCfg.PdAddr
   239  			}
   240  			if fullCfg.RangeConcurrency != nil {
   241  				subTaskCfg.LoaderConfig.RangeConcurrency = *fullCfg.RangeConcurrency
   242  			}
   243  			if fullCfg.SortingDir != nil {
   244  				subTaskCfg.LoaderConfig.SortingDirPhysical = *fullCfg.SortingDir
   245  			}
   246  		}
   247  		// set incremental config
   248  		subTaskCfg.SyncerConfig = DefaultSyncerConfig()
   249  		if incrCfg := task.SourceConfig.IncrMigrateConf; incrCfg != nil {
   250  			if incrCfg.ReplThreads != nil {
   251  				subTaskCfg.SyncerConfig.WorkerCount = *incrCfg.ReplThreads
   252  			}
   253  			if incrCfg.ReplBatch != nil {
   254  				subTaskCfg.SyncerConfig.Batch = *incrCfg.ReplBatch
   255  			}
   256  		}
   257  		subTaskCfg.ValidatorCfg = defaultValidatorConfig()
   258  		// set route,blockAllowList,filter config
   259  		doDBs := []string{}
   260  		doTables := []*filter.Table{}
   261  		routeRules := []*router.TableRule{}
   262  		filterRules := []*bf.BinlogEventRule{}
   263  		for _, rule := range tableMigrateRuleMap[sourceCfg.SourceName] {
   264  			// route
   265  			if rule.Target != nil && (rule.Target.Schema != nil || rule.Target.Table != nil) {
   266  				tableRule := &router.TableRule{SchemaPattern: rule.Source.Schema, TablePattern: rule.Source.Table}
   267  				if rule.Target.Schema != nil {
   268  					tableRule.TargetSchema = *rule.Target.Schema
   269  				}
   270  				if rule.Target.Table != nil {
   271  					tableRule.TargetTable = *rule.Target.Table
   272  				}
   273  				routeRules = append(routeRules, tableRule)
   274  			}
   275  			// filter
   276  			if rule.BinlogFilterRule != nil {
   277  				for _, name := range *rule.BinlogFilterRule {
   278  					filterRule, ok := eventFilterTemplateMap[name] // NOTE: this return a copied value
   279  					if !ok {
   280  						return nil, terror.ErrOpenAPICommonError.Generatef("filter rule name %s not found.", name)
   281  					}
   282  					filterRule.SchemaPattern = rule.Source.Schema
   283  					if rule.Source.Table != "" {
   284  						filterRule.TablePattern = rule.Source.Table
   285  					}
   286  					filterRules = append(filterRules, &filterRule)
   287  				}
   288  			}
   289  			// BlockAllowList
   290  			if rule.Source.Table != "" {
   291  				doTables = append(doTables, &filter.Table{Schema: rule.Source.Schema, Name: rule.Source.Table})
   292  			} else {
   293  				doDBs = append(doDBs, rule.Source.Schema)
   294  			}
   295  		}
   296  		subTaskCfg.RouteRules = routeRules
   297  		subTaskCfg.FilterRules = filterRules
   298  		if len(doDBs) > 0 || len(doTables) > 0 {
   299  			bAList := &filter.Rules{}
   300  			if len(doDBs) > 0 {
   301  				bAList.DoDBs = removeDuplication(doDBs)
   302  			}
   303  			if len(doTables) > 0 {
   304  				bAList.DoTables = doTables
   305  			}
   306  			subTaskCfg.BAList = bAList
   307  		}
   308  		if task.IgnoreCheckingItems != nil && len(*task.IgnoreCheckingItems) != 0 {
   309  			subTaskCfg.IgnoreCheckingItems = *task.IgnoreCheckingItems
   310  		}
   311  		// adjust sub task config
   312  		if err := subTaskCfg.Adjust(true); err != nil {
   313  			return nil, terror.Annotatef(err, "source name %s", sourceCfg.SourceName)
   314  		}
   315  		subTaskCfgList[i] = subTaskCfg
   316  	}
   317  	return subTaskCfgList, nil
   318  }
   319  
   320  // GetTargetDBCfgFromOpenAPITask gets target db config.
   321  func GetTargetDBCfgFromOpenAPITask(task *openapi.Task) *dbconfig.DBConfig {
   322  	toDBCfg := &dbconfig.DBConfig{
   323  		Host:     task.TargetConfig.Host,
   324  		Port:     task.TargetConfig.Port,
   325  		User:     task.TargetConfig.User,
   326  		Password: task.TargetConfig.Password,
   327  	}
   328  	if task.TargetConfig.Security != nil {
   329  		var certAllowedCN []string
   330  		if task.TargetConfig.Security.CertAllowedCn != nil {
   331  			certAllowedCN = *task.TargetConfig.Security.CertAllowedCn
   332  		}
   333  		toDBCfg.Security = &security.Security{
   334  			SSLCABytes:    []byte(task.TargetConfig.Security.SslCaContent),
   335  			SSLKeyBytes:   []byte(task.TargetConfig.Security.SslKeyContent),
   336  			SSLCertBytes:  []byte(task.TargetConfig.Security.SslCertContent),
   337  			CertAllowedCN: certAllowedCN,
   338  		}
   339  	}
   340  	return toDBCfg
   341  }
   342  
   343  // SubTaskConfigsToTaskConfig constructs task configs from a list of valid subtask configs.
   344  func SubTaskConfigsToTaskConfig(stCfgs ...*SubTaskConfig) *TaskConfig {
   345  	c := &TaskConfig{}
   346  	// global configs.
   347  	stCfg0 := stCfgs[0]
   348  	c.Name = stCfg0.Name
   349  	c.TaskMode = stCfg0.Mode
   350  	c.IsSharding = stCfg0.IsSharding
   351  	c.ShardMode = stCfg0.ShardMode
   352  	c.StrictOptimisticShardMode = stCfg0.StrictOptimisticShardMode
   353  	c.IgnoreCheckingItems = stCfg0.IgnoreCheckingItems
   354  	c.MetaSchema = stCfg0.MetaSchema
   355  	c.EnableHeartbeat = stCfg0.EnableHeartbeat
   356  	c.HeartbeatUpdateInterval = stCfg0.HeartbeatUpdateInterval
   357  	c.HeartbeatReportInterval = stCfg0.HeartbeatReportInterval
   358  	c.Timezone = stCfg0.Timezone
   359  	c.CaseSensitive = stCfg0.CaseSensitive
   360  	c.TargetDB = &stCfg0.To // just ref
   361  	c.OnlineDDL = stCfg0.OnlineDDL
   362  	c.OnlineDDLScheme = stCfg0.OnlineDDLScheme
   363  	c.CleanDumpFile = stCfg0.CleanDumpFile
   364  	c.CollationCompatible = stCfg0.CollationCompatible
   365  	c.MySQLInstances = make([]*MySQLInstance, 0, len(stCfgs))
   366  	c.BAList = make(map[string]*filter.Rules)
   367  	c.Routes = make(map[string]*router.TableRule)
   368  	c.Filters = make(map[string]*bf.BinlogEventRule)
   369  	c.ColumnMappings = make(map[string]*column.Rule)
   370  	c.Mydumpers = make(map[string]*MydumperConfig)
   371  	c.Loaders = make(map[string]*LoaderConfig)
   372  	c.Syncers = make(map[string]*SyncerConfig)
   373  	c.ExprFilter = make(map[string]*ExpressionFilter)
   374  	c.Experimental = stCfg0.Experimental
   375  	c.Validators = make(map[string]*ValidatorConfig)
   376  
   377  	baListMap := make(map[string]string, len(stCfgs))
   378  	routeMap := make(map[string]string, len(stCfgs))
   379  	filterMap := make(map[string]string, len(stCfgs))
   380  	dumpMap := make(map[string]string, len(stCfgs))
   381  	loadMap := make(map[string]string, len(stCfgs))
   382  	syncMap := make(map[string]string, len(stCfgs))
   383  	cmMap := make(map[string]string, len(stCfgs))
   384  	exprFilterMap := make(map[string]string, len(stCfgs))
   385  	validatorMap := make(map[string]string, len(stCfgs))
   386  	var baListIdx, routeIdx, filterIdx, dumpIdx, loadIdx, syncIdx, validateIdx, cmIdx, efIdx int
   387  	var baListName, routeName, filterName, dumpName, loadName, syncName, validateName, cmName, efName string
   388  
   389  	// NOTE:
   390  	// - we choose to ref global configs for instances now.
   391  	for _, stCfg := range stCfgs {
   392  		baListName, baListIdx = getGenerateName(stCfg.BAList, baListIdx, "balist", baListMap)
   393  		c.BAList[baListName] = stCfg.BAList
   394  
   395  		routeNames := make([]string, 0, len(stCfg.RouteRules))
   396  		for _, rule := range stCfg.RouteRules {
   397  			routeName, routeIdx = getGenerateName(rule, routeIdx, "route", routeMap)
   398  			routeNames = append(routeNames, routeName)
   399  			c.Routes[routeName] = rule
   400  		}
   401  
   402  		filterNames := make([]string, 0, len(stCfg.FilterRules))
   403  		for _, rule := range stCfg.FilterRules {
   404  			filterName, filterIdx = getGenerateName(rule, filterIdx, "filter", filterMap)
   405  			filterNames = append(filterNames, filterName)
   406  			c.Filters[filterName] = rule
   407  		}
   408  
   409  		dumpName, dumpIdx = getGenerateName(stCfg.MydumperConfig, dumpIdx, "dump", dumpMap)
   410  		c.Mydumpers[dumpName] = &stCfg.MydumperConfig
   411  
   412  		loadName, loadIdx = getGenerateName(stCfg.LoaderConfig, loadIdx, "load", loadMap)
   413  		loaderCfg := stCfg.LoaderConfig
   414  
   415  		var dirSuffix string
   416  		var err error
   417  		if storage.IsS3Path(loaderCfg.Dir) {
   418  			// we will dump files to s3 dir's subdirectory
   419  			dirSuffix = "/" + c.Name + "." + stCfg.SourceID
   420  		} else {
   421  			// TODO we will dump local file to dir's subdirectory, but it may have risk of compatibility, we will fix in other pr
   422  			dirSuffix = "." + c.Name
   423  		}
   424  		// if ends with the task name, we remove to get user input dir.
   425  		loaderCfg.Dir, err = storage.TrimPath(loaderCfg.Dir, dirSuffix)
   426  		// because dir comes form subtask, there should not have error.
   427  		if err != nil {
   428  			log.L().Warn("parse config comes from subtask error.", zap.Error(err))
   429  		}
   430  
   431  		c.Loaders[loadName] = &loaderCfg
   432  
   433  		syncName, syncIdx = getGenerateName(stCfg.SyncerConfig, syncIdx, "sync", syncMap)
   434  		c.Syncers[syncName] = &stCfg.SyncerConfig
   435  
   436  		exprFilterNames := make([]string, 0, len(stCfg.ExprFilter))
   437  		for _, f := range stCfg.ExprFilter {
   438  			efName, efIdx = getGenerateName(f, efIdx, "expr-filter", exprFilterMap)
   439  			exprFilterNames = append(exprFilterNames, efName)
   440  			c.ExprFilter[efName] = f
   441  		}
   442  
   443  		validateName, validateIdx = getGenerateName(stCfg.ValidatorCfg, validateIdx, "validator", validatorMap)
   444  		c.Validators[validateName] = &stCfg.ValidatorCfg
   445  
   446  		cmNames := make([]string, 0, len(stCfg.ColumnMappingRules))
   447  		for _, rule := range stCfg.ColumnMappingRules {
   448  			cmName, cmIdx = getGenerateName(rule, cmIdx, "cm", cmMap)
   449  			cmNames = append(cmNames, cmName)
   450  			c.ColumnMappings[cmName] = rule
   451  		}
   452  
   453  		c.MySQLInstances = append(c.MySQLInstances, &MySQLInstance{
   454  			SourceID:                      stCfg.SourceID,
   455  			Meta:                          stCfg.Meta,
   456  			FilterRules:                   filterNames,
   457  			ColumnMappingRules:            cmNames,
   458  			RouteRules:                    routeNames,
   459  			BAListName:                    baListName,
   460  			MydumperConfigName:            dumpName,
   461  			LoaderConfigName:              loadName,
   462  			SyncerConfigName:              syncName,
   463  			ExpressionFilters:             exprFilterNames,
   464  			ContinuousValidatorConfigName: validateName,
   465  		})
   466  	}
   467  	if c.CollationCompatible == "" {
   468  		c.CollationCompatible = LooseCollationCompatible
   469  	}
   470  	return c
   471  }
   472  
   473  // SubTaskConfigsToOpenAPITaskList gets openapi task from sub task configs.
   474  // subTaskConfigMap: taskName -> sourceName -> SubTaskConfig.
   475  func SubTaskConfigsToOpenAPITaskList(subTaskConfigMap map[string]map[string]*SubTaskConfig) []*openapi.Task {
   476  	taskList := []*openapi.Task{}
   477  	for _, subTaskConfigM := range subTaskConfigMap {
   478  		subTaskConfigList := make([]*SubTaskConfig, 0, len(subTaskConfigM))
   479  		for sourceName := range subTaskConfigM {
   480  			subTaskConfigList = append(subTaskConfigList, subTaskConfigM[sourceName])
   481  		}
   482  		taskList = append(taskList, SubTaskConfigsToOpenAPITask(subTaskConfigList))
   483  	}
   484  	return taskList
   485  }
   486  
   487  // SubTaskConfigsToOpenAPITask gets openapi task from sub task configs.
   488  func SubTaskConfigsToOpenAPITask(subTaskConfigList []*SubTaskConfig) *openapi.Task {
   489  	oneSubtaskConfig := subTaskConfigList[0] // need this to get target db config
   490  	taskSourceConfig := openapi.TaskSourceConfig{}
   491  	sourceConfList := []openapi.TaskSourceConf{}
   492  	// source name -> filter rule list
   493  	filterMap := make(map[string][]*bf.BinlogEventRule)
   494  	// source name -> route rule list
   495  	routeMap := make(map[string][]*router.TableRule)
   496  
   497  	for _, cfg := range subTaskConfigList {
   498  		sourceName := cfg.SourceID
   499  		oneConf := openapi.TaskSourceConf{
   500  			SourceName: cfg.SourceID,
   501  		}
   502  		if meta := cfg.Meta; meta != nil {
   503  			oneConf.BinlogGtid = &meta.BinLogGTID
   504  			oneConf.BinlogName = &meta.BinLogName
   505  			pos := int(meta.BinLogPos)
   506  			oneConf.BinlogPos = &pos
   507  		}
   508  		sourceConfList = append(sourceConfList, oneConf)
   509  		if len(cfg.FilterRules) > 0 {
   510  			filterMap[sourceName] = cfg.FilterRules
   511  		}
   512  		if len(cfg.RouteRules) > 0 {
   513  			routeMap[sourceName] = cfg.RouteRules
   514  		}
   515  	}
   516  	taskSourceConfig.SourceConf = sourceConfList
   517  
   518  	var dirSuffix string
   519  	var err error
   520  	if storage.IsS3Path(oneSubtaskConfig.LoaderConfig.Dir) {
   521  		// we will dump files to s3 dir's subdirectory
   522  		dirSuffix = "/" + oneSubtaskConfig.Name + "." + oneSubtaskConfig.SourceID
   523  	} else {
   524  		// TODO we will dump local file to dir's subdirectory, but it may have risk of compatibility, we will fix in other pr
   525  		dirSuffix = "." + oneSubtaskConfig.Name
   526  	}
   527  	// if ends with the task name, we remove to get user input dir.
   528  	oneSubtaskConfig.LoaderConfig.Dir, err = storage.TrimPath(oneSubtaskConfig.LoaderConfig.Dir, dirSuffix)
   529  	// because dir comes form subtask, there should not have error.
   530  	if err != nil {
   531  		log.L().Warn("parse config comes from subtask error.", zap.Error(err))
   532  	}
   533  
   534  	taskSourceConfig.FullMigrateConf = &openapi.TaskFullMigrateConf{
   535  		ExportThreads: &oneSubtaskConfig.MydumperConfig.Threads,
   536  		DataDir:       &oneSubtaskConfig.LoaderConfig.Dir,
   537  		ImportThreads: &oneSubtaskConfig.LoaderConfig.PoolSize,
   538  	}
   539  	consistencyInTask := oneSubtaskConfig.MydumperConfig.ExtraArgs
   540  	consistency := strings.Replace(consistencyInTask, "--consistency ", "", 1)
   541  	if consistency != "" {
   542  		taskSourceConfig.FullMigrateConf.Consistency = &consistency
   543  	}
   544  	taskSourceConfig.IncrMigrateConf = &openapi.TaskIncrMigrateConf{
   545  		ReplBatch:   &oneSubtaskConfig.SyncerConfig.Batch,
   546  		ReplThreads: &oneSubtaskConfig.SyncerConfig.WorkerCount,
   547  	}
   548  	// set filter rules
   549  	filterRuleMap := openapi.Task_BinlogFilterRule{}
   550  	for sourceName, ruleList := range filterMap {
   551  		for idx, rule := range ruleList {
   552  			binlogFilterRule := openapi.TaskBinLogFilterRule{}
   553  			var events []string
   554  			for _, event := range rule.Events {
   555  				events = append(events, string(event))
   556  			}
   557  			if len(events) > 0 {
   558  				binlogFilterRule.IgnoreEvent = &events
   559  			}
   560  			var ignoreSQL []string
   561  			ignoreSQL = append(ignoreSQL, rule.SQLPattern...)
   562  			if len(ignoreSQL) > 0 {
   563  				binlogFilterRule.IgnoreSql = &ignoreSQL
   564  			}
   565  			filterRuleMap.Set(genFilterRuleName(sourceName, idx), binlogFilterRule)
   566  		}
   567  	}
   568  	// set table migrate rules
   569  	tableMigrateRuleList := []openapi.TaskTableMigrateRule{}
   570  	// used to remove repeated rules
   571  	ruleMap := map[string]struct{}{}
   572  	appendOneRule := func(sourceName, schemaPattern, tablePattern, targetSchema, targetTable string) {
   573  		tableMigrateRule := openapi.TaskTableMigrateRule{
   574  			Source: struct {
   575  				Schema     string `json:"schema"`
   576  				SourceName string `json:"source_name"`
   577  				Table      string `json:"table"`
   578  			}{
   579  				Schema:     schemaPattern,
   580  				SourceName: sourceName,
   581  				Table:      tablePattern,
   582  			},
   583  		}
   584  		if targetSchema != "" {
   585  			tableMigrateRule.Target = &struct {
   586  				Schema *string `json:"schema,omitempty"`
   587  				Table  *string `json:"table,omitempty"`
   588  			}{
   589  				Schema: &targetSchema,
   590  			}
   591  			if targetTable != "" {
   592  				tableMigrateRule.Target.Table = &targetTable
   593  			}
   594  		}
   595  		if filterRuleList, ok := filterMap[sourceName]; ok {
   596  			ruleNameList := make([]string, len(filterRuleList))
   597  			for idx := range filterRuleList {
   598  				ruleNameList[idx] = genFilterRuleName(sourceName, idx)
   599  			}
   600  			tableMigrateRule.BinlogFilterRule = &ruleNameList
   601  		}
   602  		ruleKey := strings.Join([]string{sourceName, schemaPattern, tablePattern}, "-")
   603  		if _, ok := ruleMap[ruleKey]; ok {
   604  			return
   605  		}
   606  		ruleMap[ruleKey] = struct{}{}
   607  		tableMigrateRuleList = append(tableMigrateRuleList, tableMigrateRule)
   608  	}
   609  	// gen migrate rules by route
   610  	for sourceName, ruleList := range routeMap {
   611  		for _, rule := range ruleList {
   612  			appendOneRule(sourceName, rule.SchemaPattern, rule.TablePattern, rule.TargetSchema, rule.TargetTable)
   613  		}
   614  	}
   615  
   616  	// gen migrate rules by BAList
   617  	for _, cfg := range subTaskConfigList {
   618  		if cfg.BAList != nil {
   619  			for idx := range cfg.BAList.DoDBs {
   620  				schemaPattern := cfg.BAList.DoDBs[idx]
   621  				appendOneRule(cfg.SourceID, schemaPattern, "", "", "")
   622  			}
   623  			for idx := range cfg.BAList.DoTables {
   624  				schemaPattern := cfg.BAList.DoTables[idx].Schema
   625  				tablePattern := cfg.BAList.DoTables[idx].Name
   626  				appendOneRule(cfg.SourceID, schemaPattern, tablePattern, "", "")
   627  			}
   628  		}
   629  	}
   630  
   631  	// set basic global config
   632  	task := openapi.Task{
   633  		Name:                      oneSubtaskConfig.Name,
   634  		TaskMode:                  openapi.TaskTaskMode(oneSubtaskConfig.Mode),
   635  		EnhanceOnlineSchemaChange: oneSubtaskConfig.OnlineDDL,
   636  		MetaSchema:                &oneSubtaskConfig.MetaSchema,
   637  		OnDuplicate:               openapi.TaskOnDuplicate(oneSubtaskConfig.LoaderConfig.OnDuplicateLogical),
   638  		SourceConfig:              taskSourceConfig,
   639  		TargetConfig: openapi.TaskTargetDataBase{
   640  			Host:     oneSubtaskConfig.To.Host,
   641  			Port:     oneSubtaskConfig.To.Port,
   642  			User:     oneSubtaskConfig.To.User,
   643  			Password: oneSubtaskConfig.To.Password,
   644  		},
   645  	}
   646  	if oneSubtaskConfig.ShardMode != "" {
   647  		taskShardMode := openapi.TaskShardMode(oneSubtaskConfig.ShardMode)
   648  		task.ShardMode = &taskShardMode
   649  	}
   650  	task.StrictOptimisticShardMode = &oneSubtaskConfig.StrictOptimisticShardMode
   651  	if len(filterMap) > 0 {
   652  		task.BinlogFilterRule = &filterRuleMap
   653  	}
   654  	task.TableMigrateRule = tableMigrateRuleList
   655  	if len(oneSubtaskConfig.IgnoreCheckingItems) != 0 {
   656  		ignoreItems := oneSubtaskConfig.IgnoreCheckingItems
   657  		task.IgnoreCheckingItems = &ignoreItems
   658  	}
   659  	return &task
   660  }
   661  
   662  // TaskConfigToOpenAPITask converts TaskConfig to an openapi task.
   663  func TaskConfigToOpenAPITask(c *TaskConfig, sourceCfgMap map[string]*SourceConfig) (*openapi.Task, error) {
   664  	cfgs := make(map[string]dbconfig.DBConfig)
   665  	for _, source := range c.MySQLInstances {
   666  		if cfg, ok := sourceCfgMap[source.SourceID]; ok {
   667  			cfgs[source.SourceID] = cfg.From
   668  		}
   669  	}
   670  
   671  	// different sources can have different configurations in TaskConfig
   672  	// but currently OpenAPI formatted tasks do not support this
   673  	// user submitted TaskConfig will only set the configuration in TaskConfig.Syncers
   674  	// but setting this will not affect the configuration in TaskConfig.MySQLInstances
   675  	// so it needs to be handled in a special way
   676  	for _, cfg := range c.MySQLInstances {
   677  		if cfg.Mydumper != nil {
   678  			cfg.Mydumper = c.Mydumpers[cfg.MydumperConfigName]
   679  		}
   680  		if cfg.Loader != nil {
   681  			cfg.Loader = c.Loaders[cfg.LoaderConfigName]
   682  		}
   683  		if cfg.Syncer != nil {
   684  			cfg.Syncer = c.Syncers[cfg.SyncerConfigName]
   685  		}
   686  	}
   687  	SubTaskConfigList, err := TaskConfigToSubTaskConfigs(c, cfgs)
   688  	if err != nil {
   689  		return nil, err
   690  	}
   691  
   692  	task := SubTaskConfigsToOpenAPITask(SubTaskConfigList)
   693  	if err := task.Adjust(); err != nil {
   694  		return nil, err
   695  	}
   696  	return task, nil
   697  }
   698  
   699  // OpenAPITaskToTaskConfig converts an openapi task to TaskConfig.
   700  func OpenAPITaskToTaskConfig(task *openapi.Task, sourceCfgMap map[string]*SourceConfig) (*TaskConfig, error) {
   701  	toDBCfg := GetTargetDBCfgFromOpenAPITask(task)
   702  	subTaskConfigList, err := OpenAPITaskToSubTaskConfigs(task, toDBCfg, sourceCfgMap)
   703  	if err != nil {
   704  		return nil, err
   705  	}
   706  	cfg := SubTaskConfigsToTaskConfig(subTaskConfigList...)
   707  	if err := cfg.Adjust(); err != nil {
   708  		return nil, err
   709  	}
   710  	return cfg, nil
   711  }
   712  
   713  func removeDuplication(in []string) []string {
   714  	m := make(map[string]struct{}, len(in))
   715  	j := 0
   716  	for _, v := range in {
   717  		_, ok := m[v]
   718  		if ok {
   719  			continue
   720  		}
   721  		m[v] = struct{}{}
   722  		in[j] = v
   723  		j++
   724  	}
   725  	return in[:j]
   726  }
   727  
   728  func genFilterRuleName(sourceName string, idx int) string {
   729  	// NOTE that we don't have user input filter rule name in sub task config, so we make one by ourself
   730  	return fmt.Sprintf("%s-filter-rule-%d", sourceName, idx)
   731  }
   732  
   733  func OpenAPIStartTaskReqToTaskCliArgs(req openapi.StartTaskRequest) (*TaskCliArgs, error) {
   734  	if req.StartTime == nil && req.SafeModeTimeDuration == nil {
   735  		return nil, nil
   736  	}
   737  	cliArgs := &TaskCliArgs{}
   738  	if req.StartTime != nil {
   739  		cliArgs.StartTime = *req.StartTime
   740  	}
   741  	if req.SafeModeTimeDuration != nil {
   742  		cliArgs.SafeModeDuration = *req.SafeModeTimeDuration
   743  	}
   744  
   745  	if err := cliArgs.Verify(); err != nil {
   746  		return nil, err
   747  	}
   748  	return cliArgs, nil
   749  }
   750  
   751  func OpenAPIStopTaskReqToTaskCliArgs(req openapi.StopTaskRequest) (*TaskCliArgs, error) {
   752  	if req.TimeoutDuration == nil {
   753  		return nil, nil
   754  	}
   755  	cliArgs := &TaskCliArgs{
   756  		WaitTimeOnStop: *req.TimeoutDuration,
   757  	}
   758  	if err := cliArgs.Verify(); err != nil {
   759  		return nil, err
   760  	}
   761  	return cliArgs, nil
   762  }