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 }