github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/config/task.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 config 15 16 import ( 17 "encoding/json" 18 "flag" 19 "fmt" 20 "math" 21 "os" 22 "sort" 23 "strconv" 24 "strings" 25 "sync" 26 "time" 27 28 "github.com/coreos/go-semver/semver" 29 "github.com/docker/go-units" 30 "github.com/dustin/go-humanize" 31 "github.com/pingcap/tidb/pkg/lightning/config" 32 "github.com/pingcap/tidb/pkg/parser" 33 "github.com/pingcap/tidb/pkg/util/filter" 34 router "github.com/pingcap/tidb/pkg/util/table-router" 35 "github.com/pingcap/tiflow/dm/config/dbconfig" 36 "github.com/pingcap/tiflow/dm/pkg/log" 37 "github.com/pingcap/tiflow/dm/pkg/terror" 38 "github.com/pingcap/tiflow/dm/pkg/utils" 39 bf "github.com/pingcap/tiflow/pkg/binlog-filter" 40 "github.com/pingcap/tiflow/pkg/column-mapping" 41 "go.uber.org/zap" 42 "gopkg.in/yaml.v2" 43 ) 44 45 // Online DDL Scheme. 46 const ( 47 GHOST = "gh-ost" 48 PT = "pt" 49 ) 50 51 // shard DDL mode. 52 const ( 53 ShardPessimistic = "pessimistic" 54 ShardOptimistic = "optimistic" 55 tidbTxnMode = "tidb_txn_mode" 56 tidbTxnOptimistic = "optimistic" 57 ) 58 59 // collation_compatible. 60 const ( 61 LooseCollationCompatible = "loose" 62 StrictCollationCompatible = "strict" 63 ) 64 65 const ( 66 ValidationNone = "none" 67 ValidationFast = "fast" 68 ValidationFull = "full" 69 70 DefaultValidatorWorkerCount = 4 71 DefaultValidatorValidateInterval = 10 * time.Second 72 DefaultValidatorCheckInterval = 5 * time.Second 73 DefaultValidatorRowErrorDelay = 30 * time.Minute 74 DefaultValidatorMetaFlushInterval = 5 * time.Minute 75 DefaultValidatorBatchQuerySize = 100 76 DefaultValidatorMaxPendingRowSize = "500m" 77 78 ValidatorMaxAccumulatedRow = 100000 79 // PendingRow is substantial in this version (in sysbench test) 80 // set to MaxInt temporaly and reset in the future. 81 DefaultValidatorMaxPendingRow = math.MaxInt32 82 ) 83 84 // default config item values. 85 var ( 86 // TaskConfig. 87 defaultMetaSchema = "dm_meta" 88 defaultEnableHeartbeat = false 89 defaultIsSharding = false 90 defaultUpdateInterval = 1 91 defaultReportInterval = 10 92 defaultCollationCompatible = "loose" 93 // MydumperConfig. 94 defaultMydumperPath = "./bin/mydumper" 95 defaultThreads = 4 96 defaultChunkFilesize = "64" 97 defaultSkipTzUTC = true 98 // LoaderConfig. 99 defaultPoolSize = 16 100 defaultDir = "./dumped_data" 101 // SyncerConfig. 102 defaultWorkerCount = 16 103 defaultBatch = 100 104 defaultQueueSize = 1024 // do not give too large default value to avoid OOM 105 defaultCheckpointFlushInterval = 30 // in seconds 106 defaultSafeModeDuration = strconv.Itoa(2*defaultCheckpointFlushInterval) + "s" 107 108 // TargetDBConfig. 109 defaultSessionCfg = []struct { 110 key string 111 val string 112 minVersion *semver.Version 113 }{ 114 {tidbTxnMode, tidbTxnOptimistic, semver.New("3.0.0")}, 115 } 116 ) 117 118 // Meta represents binlog's meta pos 119 // NOTE: refine to put these config structs into pkgs 120 // NOTE: now, syncer does not support GTID mode and which is supported by relay. 121 type Meta struct { 122 BinLogName string `toml:"binlog-name" yaml:"binlog-name"` 123 BinLogPos uint32 `toml:"binlog-pos" yaml:"binlog-pos"` 124 BinLogGTID string `toml:"binlog-gtid" yaml:"binlog-gtid"` 125 } 126 127 // Verify does verification on configs 128 // NOTE: we can't decide to verify `binlog-name` or `binlog-gtid` until being bound to a source (with `enable-gtid` set). 129 func (m *Meta) Verify() error { 130 if m != nil && len(m.BinLogName) == 0 && len(m.BinLogGTID) == 0 { 131 return terror.ErrConfigMetaInvalid.Generate() 132 } 133 134 return nil 135 } 136 137 // MySQLInstance represents a sync config of a MySQL instance. 138 type MySQLInstance struct { 139 // it represents a MySQL/MariaDB instance or a replica group 140 SourceID string `yaml:"source-id"` 141 Meta *Meta `yaml:"meta"` 142 FilterRules []string `yaml:"filter-rules"` 143 // deprecated 144 ColumnMappingRules []string `yaml:"column-mapping-rules"` 145 RouteRules []string `yaml:"route-rules"` 146 ExpressionFilters []string `yaml:"expression-filters"` 147 148 // black-white-list is deprecated, use block-allow-list instead 149 BWListName string `yaml:"black-white-list"` 150 BAListName string `yaml:"block-allow-list"` 151 152 MydumperConfigName string `yaml:"mydumper-config-name"` 153 Mydumper *MydumperConfig `yaml:"mydumper"` 154 // MydumperThread is alias for Threads in MydumperConfig, and its priority is higher than Threads 155 MydumperThread int `yaml:"mydumper-thread"` 156 157 LoaderConfigName string `yaml:"loader-config-name"` 158 Loader *LoaderConfig `yaml:"loader"` 159 // LoaderThread is alias for PoolSize in LoaderConfig, and its priority is higher than PoolSize 160 LoaderThread int `yaml:"loader-thread"` 161 162 SyncerConfigName string `yaml:"syncer-config-name"` 163 Syncer *SyncerConfig `yaml:"syncer"` 164 // SyncerThread is alias for WorkerCount in SyncerConfig, and its priority is higher than WorkerCount 165 SyncerThread int `yaml:"syncer-thread"` 166 167 ContinuousValidatorConfigName string `yaml:"validator-config-name"` 168 ContinuousValidator ValidatorConfig `yaml:"-"` 169 } 170 171 // VerifyAndAdjust does verification on configs, and adjust some configs. 172 func (m *MySQLInstance) VerifyAndAdjust() error { 173 if m == nil { 174 return terror.ErrConfigMySQLInstNotFound.Generate() 175 } 176 177 if m.SourceID == "" { 178 return terror.ErrConfigEmptySourceID.Generate() 179 } 180 181 if err := m.Meta.Verify(); err != nil { 182 return terror.Annotatef(err, "source %s", m.SourceID) 183 } 184 185 if len(m.MydumperConfigName) > 0 && m.Mydumper != nil { 186 return terror.ErrConfigMydumperCfgConflict.Generate() 187 } 188 if len(m.LoaderConfigName) > 0 && m.Loader != nil { 189 return terror.ErrConfigLoaderCfgConflict.Generate() 190 } 191 if len(m.SyncerConfigName) > 0 && m.Syncer != nil { 192 return terror.ErrConfigSyncerCfgConflict.Generate() 193 } 194 195 if len(m.BAListName) == 0 && len(m.BWListName) != 0 { 196 m.BAListName = m.BWListName 197 } 198 199 return nil 200 } 201 202 // MydumperConfig represents mydumper process unit's specific config. 203 type MydumperConfig struct { 204 MydumperPath string `yaml:"mydumper-path" toml:"mydumper-path" json:"mydumper-path"` // mydumper binary path 205 Threads int `yaml:"threads" toml:"threads" json:"threads"` // -t, --threads 206 ChunkFilesize string `yaml:"chunk-filesize" toml:"chunk-filesize" json:"chunk-filesize"` // -F, --chunk-filesize 207 StatementSize uint64 `yaml:"statement-size" toml:"statement-size" json:"statement-size"` // -S, --statement-size 208 Rows uint64 `yaml:"rows" toml:"rows" json:"rows"` // -r, --rows 209 Where string `yaml:"where" toml:"where" json:"where"` // --where 210 211 SkipTzUTC bool `yaml:"skip-tz-utc" toml:"skip-tz-utc" json:"skip-tz-utc"` // --skip-tz-utc 212 ExtraArgs string `yaml:"extra-args" toml:"extra-args" json:"extra-args"` // other extra args 213 // NOTE: use LoaderConfig.Dir as --outputdir 214 // TODO zxc: combine -B -T --regex with filter rules? 215 } 216 217 // DefaultMydumperConfig return default mydumper config for task. 218 func DefaultMydumperConfig() MydumperConfig { 219 return MydumperConfig{ 220 MydumperPath: defaultMydumperPath, 221 Threads: defaultThreads, 222 ChunkFilesize: defaultChunkFilesize, 223 SkipTzUTC: defaultSkipTzUTC, 224 } 225 } 226 227 // alias to avoid infinite recursion for UnmarshalYAML. 228 type rawMydumperConfig MydumperConfig 229 230 // UnmarshalYAML implements Unmarshaler.UnmarshalYAML. 231 func (m *MydumperConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 232 raw := rawMydumperConfig(DefaultMydumperConfig()) 233 if err := unmarshal(&raw); err != nil { 234 return terror.ErrConfigYamlTransform.Delegate(err, "unmarshal mydumper config") 235 } 236 *m = MydumperConfig(raw) // raw used only internal, so no deep copy 237 return nil 238 } 239 240 // LoadMode defines different mode used in load phase. 241 type LoadMode string 242 243 const ( 244 // LoadModeSQL means write data by sql statements, uses tidb-lightning tidb backend to load data. 245 // deprecated, use LoadModeLogical instead. 246 LoadModeSQL LoadMode = "sql" 247 // LoadModeLoader is the legacy sql mode, use loader to load data. this should be replaced by LoadModeLogical mode. 248 // deprecated, use LoadModeLogical instead. 249 LoadModeLoader LoadMode = "loader" 250 // LoadModeLogical means use tidb backend of lightning to load data, which uses SQL to load data. 251 LoadModeLogical LoadMode = "logical" 252 // LoadModePhysical means use local backend of lightning to load data, which ingest SST files to load data. 253 LoadModePhysical LoadMode = "physical" 254 ) 255 256 // LogicalDuplicateResolveType defines the duplication resolution when meet duplicate rows for logical import. 257 type LogicalDuplicateResolveType string 258 259 const ( 260 // OnDuplicateReplace represents replace the old row with new data. 261 OnDuplicateReplace LogicalDuplicateResolveType = "replace" 262 // OnDuplicateError represents return an error when meet duplicate row. 263 OnDuplicateError LogicalDuplicateResolveType = "error" 264 // OnDuplicateIgnore represents ignore the new data when meet duplicate row. 265 OnDuplicateIgnore LogicalDuplicateResolveType = "ignore" 266 ) 267 268 // PhysicalDuplicateResolveType defines the duplication resolution when meet duplicate rows for physical import. 269 type PhysicalDuplicateResolveType string 270 271 const ( 272 // OnDuplicateNone represents do nothing when meet duplicate row and the task will continue. 273 OnDuplicateNone PhysicalDuplicateResolveType = "none" 274 // OnDuplicateManual represents that task should be paused when meet duplicate row to let user handle it manually. 275 OnDuplicateManual PhysicalDuplicateResolveType = "manual" 276 ) 277 278 // PhysicalPostOpLevel defines the configuration of checksum/analyze of physical import. 279 type PhysicalPostOpLevel string 280 281 const ( 282 OpLevelRequired = "required" 283 OpLevelOptional = "optional" 284 OpLevelOff = "off" 285 ) 286 287 // LoaderConfig represents loader process unit's specific config. 288 type LoaderConfig struct { 289 PoolSize int `yaml:"pool-size" toml:"pool-size" json:"pool-size"` 290 Dir string `yaml:"dir" toml:"dir" json:"dir"` 291 SortingDirPhysical string `yaml:"sorting-dir-physical" toml:"sorting-dir-physical" json:"sorting-dir-physical"` 292 SQLMode string `yaml:"-" toml:"-" json:"-"` // wrote by dump unit (DM op) or jobmaster (DM in engine) 293 ImportMode LoadMode `yaml:"import-mode" toml:"import-mode" json:"import-mode"` 294 // deprecated, use OnDuplicateLogical instead. 295 OnDuplicate LogicalDuplicateResolveType `yaml:"on-duplicate" toml:"on-duplicate" json:"on-duplicate"` 296 OnDuplicateLogical LogicalDuplicateResolveType `yaml:"on-duplicate-logical" toml:"on-duplicate-logical" json:"on-duplicate-logical"` 297 OnDuplicatePhysical PhysicalDuplicateResolveType `yaml:"on-duplicate-physical" toml:"on-duplicate-physical" json:"on-duplicate-physical"` 298 DiskQuotaPhysical config.ByteSize `yaml:"disk-quota-physical" toml:"disk-quota-physical" json:"disk-quota-physical"` 299 ChecksumPhysical PhysicalPostOpLevel `yaml:"checksum-physical" toml:"checksum-physical" json:"checksum-physical"` 300 Analyze PhysicalPostOpLevel `yaml:"analyze" toml:"analyze" json:"analyze"` 301 RangeConcurrency int `yaml:"range-concurrency" toml:"range-concurrency" json:"range-concurrency"` 302 CompressKVPairs string `yaml:"compress-kv-pairs" toml:"compress-kv-pairs" json:"compress-kv-pairs"` 303 PDAddr string `yaml:"pd-addr" toml:"pd-addr" json:"pd-addr"` 304 } 305 306 // DefaultLoaderConfig return default loader config for task. 307 func DefaultLoaderConfig() LoaderConfig { 308 return LoaderConfig{ 309 PoolSize: defaultPoolSize, 310 Dir: defaultDir, 311 ImportMode: LoadModeLogical, 312 OnDuplicateLogical: OnDuplicateReplace, 313 } 314 } 315 316 // alias to avoid infinite recursion for UnmarshalYAML. 317 type rawLoaderConfig LoaderConfig 318 319 // UnmarshalYAML implements Unmarshaler.UnmarshalYAML. 320 func (m *LoaderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 321 raw := rawLoaderConfig(DefaultLoaderConfig()) 322 if err := unmarshal(&raw); err != nil { 323 return terror.ErrConfigYamlTransform.Delegate(err, "unmarshal loader config") 324 } 325 *m = LoaderConfig(raw) // raw used only internal, so no deep copy 326 return nil 327 } 328 329 func (m *LoaderConfig) adjust() error { 330 if m.ImportMode == "" { 331 m.ImportMode = LoadModeLogical 332 } 333 if strings.EqualFold(string(m.ImportMode), string(LoadModeSQL)) || 334 strings.EqualFold(string(m.ImportMode), string(LoadModeLoader)) { 335 m.ImportMode = LoadModeLogical 336 } 337 m.ImportMode = LoadMode(strings.ToLower(string(m.ImportMode))) 338 switch m.ImportMode { 339 case LoadModeLoader, LoadModeSQL, LoadModeLogical, LoadModePhysical: 340 default: 341 return terror.ErrConfigInvalidLoadMode.Generate(m.ImportMode) 342 } 343 344 if m.PoolSize == 0 { 345 m.PoolSize = defaultPoolSize 346 } 347 348 if m.OnDuplicateLogical == "" { 349 m.OnDuplicateLogical = OnDuplicateReplace 350 } 351 m.OnDuplicateLogical = LogicalDuplicateResolveType(strings.ToLower(string(m.OnDuplicateLogical))) 352 switch m.OnDuplicateLogical { 353 case OnDuplicateReplace, OnDuplicateError, OnDuplicateIgnore: 354 default: 355 return terror.ErrConfigInvalidDuplicateResolution.Generate(m.OnDuplicateLogical) 356 } 357 358 if m.OnDuplicatePhysical == "" { 359 m.OnDuplicatePhysical = OnDuplicateNone 360 } 361 m.OnDuplicatePhysical = PhysicalDuplicateResolveType(strings.ToLower(string(m.OnDuplicatePhysical))) 362 switch m.OnDuplicatePhysical { 363 case OnDuplicateNone, OnDuplicateManual: 364 default: 365 return terror.ErrConfigInvalidPhysicalDuplicateResolution.Generate(m.OnDuplicatePhysical) 366 } 367 368 if m.ChecksumPhysical == "" { 369 m.ChecksumPhysical = OpLevelRequired 370 } 371 m.ChecksumPhysical = PhysicalPostOpLevel(strings.ToLower(string(m.ChecksumPhysical))) 372 switch m.ChecksumPhysical { 373 case OpLevelRequired, OpLevelOptional, OpLevelOff: 374 default: 375 return terror.ErrConfigInvalidPhysicalChecksum.Generate(m.ChecksumPhysical) 376 } 377 378 if m.Analyze == "" { 379 m.Analyze = OpLevelOptional 380 } 381 m.Analyze = PhysicalPostOpLevel(strings.ToLower(string(m.Analyze))) 382 switch m.Analyze { 383 case OpLevelRequired, OpLevelOptional, OpLevelOff: 384 default: 385 return terror.ErrConfigInvalidLoadAnalyze.Generate(m.Analyze) 386 } 387 388 return nil 389 } 390 391 // SyncerConfig represents syncer process unit's specific config. 392 type SyncerConfig struct { 393 MetaFile string `yaml:"meta-file" toml:"meta-file" json:"meta-file"` // meta filename, used only when load SubConfig directly 394 WorkerCount int `yaml:"worker-count" toml:"worker-count" json:"worker-count"` 395 Batch int `yaml:"batch" toml:"batch" json:"batch"` 396 QueueSize int `yaml:"queue-size" toml:"queue-size" json:"queue-size"` 397 // checkpoint flush interval in seconds. 398 CheckpointFlushInterval int `yaml:"checkpoint-flush-interval" toml:"checkpoint-flush-interval" json:"checkpoint-flush-interval"` 399 // TODO: add this two new config items for openapi. 400 Compact bool `yaml:"compact" toml:"compact" json:"compact"` 401 MultipleRows bool `yaml:"multiple-rows" toml:"multiple-rows" json:"multiple-rows"` 402 403 // deprecated 404 MaxRetry int `yaml:"max-retry" toml:"max-retry" json:"max-retry"` 405 406 // deprecated 407 AutoFixGTID bool `yaml:"auto-fix-gtid" toml:"auto-fix-gtid" json:"auto-fix-gtid"` 408 EnableGTID bool `yaml:"enable-gtid" toml:"enable-gtid" json:"enable-gtid"` 409 // deprecated 410 DisableCausality bool `yaml:"disable-detect" toml:"disable-detect" json:"disable-detect"` 411 SafeMode bool `yaml:"safe-mode" toml:"safe-mode" json:"safe-mode"` 412 SafeModeDuration string `yaml:"safe-mode-duration" toml:"safe-mode-duration" json:"safe-mode-duration"` 413 // deprecated, use `ansi-quotes` in top level config instead 414 EnableANSIQuotes bool `yaml:"enable-ansi-quotes" toml:"enable-ansi-quotes" json:"enable-ansi-quotes"` 415 } 416 417 // DefaultSyncerConfig return default syncer config for task. 418 func DefaultSyncerConfig() SyncerConfig { 419 return SyncerConfig{ 420 WorkerCount: defaultWorkerCount, 421 Batch: defaultBatch, 422 QueueSize: defaultQueueSize, 423 CheckpointFlushInterval: defaultCheckpointFlushInterval, 424 SafeModeDuration: defaultSafeModeDuration, 425 } 426 } 427 428 // alias to avoid infinite recursion for UnmarshalYAML. 429 type rawSyncerConfig SyncerConfig 430 431 // UnmarshalYAML implements Unmarshaler.UnmarshalYAML. 432 func (m *SyncerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 433 raw := rawSyncerConfig(DefaultSyncerConfig()) 434 if err := unmarshal(&raw); err != nil { 435 return terror.ErrConfigYamlTransform.Delegate(err, "unmarshal syncer config") 436 } 437 *m = SyncerConfig(raw) // raw used only internal, so no deep copy 438 return nil 439 } 440 441 type ValidatorConfig struct { 442 Mode string `yaml:"mode" toml:"mode" json:"mode"` 443 WorkerCount int `yaml:"worker-count" toml:"worker-count" json:"worker-count"` 444 ValidateInterval Duration `yaml:"validate-interval" toml:"validate-interval" json:"validate-interval"` 445 CheckInterval Duration `yaml:"check-interval" toml:"check-interval" json:"check-interval"` 446 RowErrorDelay Duration `yaml:"row-error-delay" toml:"row-error-delay" json:"row-error-delay"` 447 MetaFlushInterval Duration `yaml:"meta-flush-interval" toml:"meta-flush-interval" json:"meta-flush-interval"` 448 BatchQuerySize int `yaml:"batch-query-size" toml:"batch-query-size" json:"batch-query-size"` 449 MaxPendingRowSize string `yaml:"max-pending-row-size" toml:"max-pending-row-size" json:"max-pending-row-size"` 450 MaxPendingRowCount int `yaml:"max-pending-row-count" toml:"max-pending-row-count" json:"max-pending-row-count"` 451 StartTime string `yaml:"-" toml:"start-time" json:"-"` 452 } 453 454 func (v *ValidatorConfig) Adjust() error { 455 if v.Mode == "" { 456 v.Mode = ValidationNone 457 } 458 if v.Mode != ValidationNone && v.Mode != ValidationFast && v.Mode != ValidationFull { 459 return terror.ErrConfigValidationMode 460 } 461 if v.WorkerCount <= 0 { 462 v.WorkerCount = DefaultValidatorWorkerCount 463 } 464 if v.ValidateInterval.Duration == 0 { 465 v.ValidateInterval.Duration = DefaultValidatorValidateInterval 466 } 467 if v.CheckInterval.Duration == 0 { 468 v.CheckInterval.Duration = DefaultValidatorCheckInterval 469 } 470 if v.RowErrorDelay.Duration == 0 { 471 v.RowErrorDelay.Duration = DefaultValidatorRowErrorDelay 472 } 473 if v.MetaFlushInterval.Duration == 0 { 474 v.MetaFlushInterval.Duration = DefaultValidatorMetaFlushInterval 475 } 476 if v.BatchQuerySize == 0 { 477 v.BatchQuerySize = DefaultValidatorBatchQuerySize 478 } 479 if v.MaxPendingRowSize == "" { 480 v.MaxPendingRowSize = DefaultValidatorMaxPendingRowSize 481 } 482 483 _, err := units.RAMInBytes(v.MaxPendingRowSize) 484 if err != nil { 485 return err 486 } 487 if v.MaxPendingRowCount == 0 { 488 v.MaxPendingRowCount = DefaultValidatorMaxPendingRow 489 } 490 return nil 491 } 492 493 func defaultValidatorConfig() ValidatorConfig { 494 return ValidatorConfig{ 495 Mode: ValidationNone, 496 } 497 } 498 499 // TaskConfig is the configuration for Task. 500 type TaskConfig struct { 501 *flag.FlagSet `yaml:"-" toml:"-" json:"-"` 502 503 Name string `yaml:"name" toml:"name" json:"name"` 504 TaskMode string `yaml:"task-mode" toml:"task-mode" json:"task-mode"` 505 IsSharding bool `yaml:"is-sharding" toml:"is-sharding" json:"is-sharding"` 506 ShardMode string `yaml:"shard-mode" toml:"shard-mode" json:"shard-mode"` // when `shard-mode` set, we always enable sharding support. 507 StrictOptimisticShardMode bool `yaml:"strict-optimistic-shard-mode" toml:"strict-optimistic-shard-mode" json:"strict-optimistic-shard-mode"` 508 // treat it as hidden configuration 509 IgnoreCheckingItems []string `yaml:"ignore-checking-items" toml:"ignore-checking-items" json:"ignore-checking-items"` 510 // we store detail status in meta 511 // don't save configuration into it 512 MetaSchema string `yaml:"meta-schema" toml:"meta-schema" json:"meta-schema"` 513 // deprecated 514 EnableHeartbeat bool `yaml:"enable-heartbeat" toml:"enable-heartbeat" json:"enable-heartbeat"` 515 // deprecated 516 HeartbeatUpdateInterval int `yaml:"heartbeat-update-interval" toml:"heartbeat-update-interval" json:"heartbeat-update-interval"` 517 // deprecated 518 HeartbeatReportInterval int `yaml:"heartbeat-report-interval" toml:"heartbeat-report-interval" json:"heartbeat-report-interval"` 519 Timezone string `yaml:"timezone" toml:"timezone" json:"timezone"` 520 521 // handle schema/table name mode, and only for schema/table name 522 // if case insensitive, we would convert schema/table name to lower case 523 CaseSensitive bool `yaml:"case-sensitive" toml:"case-sensitive" json:"case-sensitive"` 524 525 // default "loose" handle create sql by original sql, will not add default collation as upstream 526 // "strict" will add default collation as upstream, and downstream will occur error when downstream don't support 527 CollationCompatible string `yaml:"collation_compatible" toml:"collation_compatible" json:"collation_compatible"` 528 529 TargetDB *dbconfig.DBConfig `yaml:"target-database" toml:"target-database" json:"target-database"` 530 531 MySQLInstances []*MySQLInstance `yaml:"mysql-instances" toml:"mysql-instances" json:"mysql-instances"` 532 533 OnlineDDL bool `yaml:"online-ddl" toml:"online-ddl" json:"online-ddl"` 534 // pt/gh-ost name rule,support regex 535 ShadowTableRules []string `yaml:"shadow-table-rules" toml:"shadow-table-rules" json:"shadow-table-rules"` 536 TrashTableRules []string `yaml:"trash-table-rules" toml:"trash-table-rules" json:"trash-table-rules"` 537 538 // deprecated 539 OnlineDDLScheme string `yaml:"online-ddl-scheme" toml:"online-ddl-scheme" json:"online-ddl-scheme"` 540 541 Routes map[string]*router.TableRule `yaml:"routes" toml:"routes" json:"routes"` 542 Filters map[string]*bf.BinlogEventRule `yaml:"filters" toml:"filters" json:"filters"` 543 // deprecated 544 ColumnMappings map[string]*column.Rule `yaml:"column-mappings" toml:"column-mappings" json:"column-mappings"` 545 ExprFilter map[string]*ExpressionFilter `yaml:"expression-filter" toml:"expression-filter" json:"expression-filter"` 546 547 // black-white-list is deprecated, use block-allow-list instead 548 BWList map[string]*filter.Rules `yaml:"black-white-list" toml:"black-white-list" json:"black-white-list"` 549 BAList map[string]*filter.Rules `yaml:"block-allow-list" toml:"block-allow-list" json:"block-allow-list"` 550 551 Mydumpers map[string]*MydumperConfig `yaml:"mydumpers" toml:"mydumpers" json:"mydumpers"` 552 Loaders map[string]*LoaderConfig `yaml:"loaders" toml:"loaders" json:"loaders"` 553 Syncers map[string]*SyncerConfig `yaml:"syncers" toml:"syncers" json:"syncers"` 554 Validators map[string]*ValidatorConfig `yaml:"validators" toml:"validators" json:"validators"` 555 556 CleanDumpFile bool `yaml:"clean-dump-file" toml:"clean-dump-file" json:"clean-dump-file"` 557 // deprecated 558 EnableANSIQuotes bool `yaml:"ansi-quotes" toml:"ansi-quotes" json:"ansi-quotes"` 559 560 // deprecated, replaced by `start-task --remove-meta` 561 RemoveMeta bool `yaml:"remove-meta"` 562 563 // task experimental configs 564 Experimental struct { 565 AsyncCheckpointFlush bool `yaml:"async-checkpoint-flush" toml:"async-checkpoint-flush" json:"async-checkpoint-flush"` 566 } `yaml:"experimental" toml:"experimental" json:"experimental"` 567 } 568 569 // NewTaskConfig creates a TaskConfig. 570 func NewTaskConfig() *TaskConfig { 571 cfg := &TaskConfig{ 572 // explicitly set default value 573 MetaSchema: defaultMetaSchema, 574 EnableHeartbeat: defaultEnableHeartbeat, 575 HeartbeatUpdateInterval: defaultUpdateInterval, 576 HeartbeatReportInterval: defaultReportInterval, 577 MySQLInstances: make([]*MySQLInstance, 0, 5), 578 IsSharding: defaultIsSharding, 579 Routes: make(map[string]*router.TableRule), 580 Filters: make(map[string]*bf.BinlogEventRule), 581 ColumnMappings: make(map[string]*column.Rule), 582 ExprFilter: make(map[string]*ExpressionFilter), 583 BWList: make(map[string]*filter.Rules), 584 BAList: make(map[string]*filter.Rules), 585 Mydumpers: make(map[string]*MydumperConfig), 586 Loaders: make(map[string]*LoaderConfig), 587 Syncers: make(map[string]*SyncerConfig), 588 Validators: make(map[string]*ValidatorConfig), 589 CleanDumpFile: true, 590 OnlineDDL: true, 591 CollationCompatible: defaultCollationCompatible, 592 } 593 cfg.FlagSet = flag.NewFlagSet("task", flag.ContinueOnError) 594 return cfg 595 } 596 597 // String returns the config's yaml string. 598 func (c *TaskConfig) String() string { 599 cfg, err := yaml.Marshal(c) 600 if err != nil { 601 log.L().Error("marshal task config to yaml", zap.String("task", c.Name), log.ShortError(err)) 602 } 603 return string(cfg) 604 } 605 606 // JSON returns the config's json string. 607 func (c *TaskConfig) JSON() string { 608 //nolint:staticcheck 609 cfg, err := json.Marshal(c) 610 if err != nil { 611 log.L().Error("marshal task config to json", zap.String("task", c.Name), log.ShortError(err)) 612 } 613 return string(cfg) 614 } 615 616 // DecodeFile loads and decodes config from file. 617 func (c *TaskConfig) DecodeFile(fpath string) error { 618 bs, err := os.ReadFile(fpath) 619 if err != nil { 620 return terror.ErrConfigReadCfgFromFile.Delegate(err, fpath) 621 } 622 623 err = yaml.UnmarshalStrict(bs, c) 624 if err != nil { 625 return terror.ErrConfigYamlTransform.Delegate(err) 626 } 627 628 return c.adjust() 629 } 630 631 // FromYaml loads config from file data. 632 func (c *TaskConfig) FromYaml(data string) error { 633 err := yaml.UnmarshalStrict([]byte(data), c) 634 if err != nil { 635 return terror.ErrConfigYamlTransform.Delegate(err, "decode task config failed") 636 } 637 638 return c.adjust() 639 } 640 641 // RawDecode loads config from file data. 642 func (c *TaskConfig) RawDecode(data string) error { 643 return terror.ErrConfigYamlTransform.Delegate(yaml.UnmarshalStrict([]byte(data), c), "decode task config failed") 644 } 645 646 // find unused items in config. 647 var configRefPrefixes = []string{"RouteRules", "FilterRules", "Mydumper", "Loader", "Syncer", "ExprFilter", "Validator"} 648 649 const ( 650 routeRulesIdx = iota 651 filterRulesIdx 652 mydumperIdx 653 loaderIdx 654 syncerIdx 655 exprFilterIdx 656 validatorIdx 657 ) 658 659 // Adjust adjusts and verifies config. 660 func (c *TaskConfig) Adjust() error { 661 if c == nil { 662 return terror.ErrConfigYamlTransform.New("task config is nil") 663 } 664 return c.adjust() 665 } 666 667 func (c *TaskConfig) adjust() error { 668 if len(c.Name) == 0 { 669 return terror.ErrConfigNeedUniqueTaskName.Generate() 670 } 671 switch c.TaskMode { 672 case ModeFull, ModeIncrement, ModeAll, ModeDump, ModeLoadSync: 673 default: 674 return terror.ErrConfigInvalidTaskMode.Generate() 675 } 676 if c.MetaSchema == "" { 677 c.MetaSchema = defaultMetaSchema 678 } 679 680 if c.ShardMode != "" && c.ShardMode != ShardPessimistic && c.ShardMode != ShardOptimistic { 681 return terror.ErrConfigShardModeNotSupport.Generate(c.ShardMode) 682 } else if c.ShardMode == "" && c.IsSharding { 683 c.ShardMode = ShardPessimistic // use the pessimistic mode as default for back compatible. 684 } 685 if c.StrictOptimisticShardMode && c.ShardMode != ShardOptimistic { 686 return terror.ErrConfigStrictOptimisticShardMode.Generate() 687 } 688 689 if len(c.ColumnMappings) > 0 { 690 return terror.ErrConfigColumnMappingDeprecated.Generate() 691 } 692 693 if c.CollationCompatible != "" && c.CollationCompatible != LooseCollationCompatible && c.CollationCompatible != StrictCollationCompatible { 694 return terror.ErrConfigCollationCompatibleNotSupport.Generate(c.CollationCompatible) 695 } else if c.CollationCompatible == "" { 696 c.CollationCompatible = LooseCollationCompatible 697 } 698 699 for _, item := range c.IgnoreCheckingItems { 700 if err := ValidateCheckingItem(item); err != nil { 701 return err 702 } 703 } 704 705 if c.OnlineDDLScheme != "" && c.OnlineDDLScheme != PT && c.OnlineDDLScheme != GHOST { 706 return terror.ErrConfigOnlineSchemeNotSupport.Generate(c.OnlineDDLScheme) 707 } else if c.OnlineDDLScheme == PT || c.OnlineDDLScheme == GHOST { 708 c.OnlineDDL = true 709 log.L().Warn("'online-ddl-scheme' will be deprecated soon. Recommend that use online-ddl instead of online-ddl-scheme.") 710 } 711 712 if c.TargetDB == nil { 713 return terror.ErrConfigNeedTargetDB.Generate() 714 } 715 716 if len(c.MySQLInstances) == 0 { 717 return terror.ErrConfigMySQLInstsAtLeastOne.Generate() 718 } 719 720 for name, exprFilter := range c.ExprFilter { 721 if exprFilter.Schema == "" { 722 return terror.ErrConfigExprFilterEmptyName.Generate(name, "schema") 723 } 724 if exprFilter.Table == "" { 725 return terror.ErrConfigExprFilterEmptyName.Generate(name, "table") 726 } 727 setFields := make([]string, 0, 1) 728 if exprFilter.InsertValueExpr != "" { 729 if err := checkValidExpr(exprFilter.InsertValueExpr); err != nil { 730 return terror.ErrConfigExprFilterWrongGrammar.Generate(name, exprFilter.InsertValueExpr, err) 731 } 732 setFields = append(setFields, "insert: ["+exprFilter.InsertValueExpr+"]") 733 } 734 if exprFilter.UpdateOldValueExpr != "" || exprFilter.UpdateNewValueExpr != "" { 735 if exprFilter.UpdateOldValueExpr != "" { 736 if err := checkValidExpr(exprFilter.UpdateOldValueExpr); err != nil { 737 return terror.ErrConfigExprFilterWrongGrammar.Generate(name, exprFilter.UpdateOldValueExpr, err) 738 } 739 } 740 if exprFilter.UpdateNewValueExpr != "" { 741 if err := checkValidExpr(exprFilter.UpdateNewValueExpr); err != nil { 742 return terror.ErrConfigExprFilterWrongGrammar.Generate(name, exprFilter.UpdateNewValueExpr, err) 743 } 744 } 745 setFields = append(setFields, "update (old value): ["+exprFilter.UpdateOldValueExpr+"] update (new value): ["+exprFilter.UpdateNewValueExpr+"]") 746 } 747 if exprFilter.DeleteValueExpr != "" { 748 if err := checkValidExpr(exprFilter.DeleteValueExpr); err != nil { 749 return terror.ErrConfigExprFilterWrongGrammar.Generate(name, exprFilter.DeleteValueExpr, err) 750 } 751 setFields = append(setFields, "delete: ["+exprFilter.DeleteValueExpr+"]") 752 } 753 if len(setFields) > 1 { 754 return terror.ErrConfigExprFilterManyExpr.Generate(name, setFields) 755 } 756 } 757 758 for _, validatorCfg := range c.Validators { 759 if err := validatorCfg.Adjust(); err != nil { 760 return err 761 } 762 } 763 764 instanceIDs := make(map[string]int) // source-id -> instance-index 765 globalConfigReferCount := map[string]int{} 766 duplicateErrorStrings := make([]string, 0) 767 for i, inst := range c.MySQLInstances { 768 if err := inst.VerifyAndAdjust(); err != nil { 769 return terror.Annotatef(err, "mysql-instance: %s", humanize.Ordinal(i)) 770 } 771 if iid, ok := instanceIDs[inst.SourceID]; ok { 772 return terror.ErrConfigMySQLInstSameSourceID.Generate(iid, i, inst.SourceID) 773 } 774 instanceIDs[inst.SourceID] = i 775 776 switch c.TaskMode { 777 case ModeFull, ModeAll, ModeDump: 778 if inst.Meta != nil { 779 log.L().Warn("metadata will not be used. for Full mode, incremental sync will never occur; for All mode, the meta dumped by MyDumper will be used", zap.Int("mysql instance", i), zap.String("task mode", c.TaskMode)) 780 } 781 case ModeIncrement: 782 if inst.Meta == nil { 783 log.L().Warn("mysql-instance doesn't set meta for incremental mode, user should specify start_time to start task.", zap.String("sourceID", inst.SourceID)) 784 } else { 785 err := inst.Meta.Verify() 786 if err != nil { 787 return terror.Annotatef(err, "mysql-instance: %d", i) 788 } 789 } 790 } 791 792 for _, name := range inst.RouteRules { 793 if _, ok := c.Routes[name]; !ok { 794 return terror.ErrConfigRouteRuleNotFound.Generate(i, name) 795 } 796 globalConfigReferCount[configRefPrefixes[routeRulesIdx]+name]++ 797 } 798 for _, name := range inst.FilterRules { 799 if _, ok := c.Filters[name]; !ok { 800 return terror.ErrConfigFilterRuleNotFound.Generate(i, name) 801 } 802 globalConfigReferCount[configRefPrefixes[filterRulesIdx]+name]++ 803 } 804 805 // only when BAList is empty use BWList 806 if len(c.BAList) == 0 && len(c.BWList) != 0 { 807 c.BAList = c.BWList 808 } 809 if _, ok := c.BAList[inst.BAListName]; len(inst.BAListName) > 0 && !ok { 810 return terror.ErrConfigBAListNotFound.Generate(i, inst.BAListName) 811 } 812 813 if len(inst.MydumperConfigName) > 0 { 814 rule, ok := c.Mydumpers[inst.MydumperConfigName] 815 if !ok { 816 return terror.ErrConfigMydumperCfgNotFound.Generate(i, inst.MydumperConfigName) 817 } 818 globalConfigReferCount[configRefPrefixes[mydumperIdx]+inst.MydumperConfigName]++ 819 if rule != nil { 820 inst.Mydumper = new(MydumperConfig) 821 *inst.Mydumper = *rule // ref mydumper config 822 } 823 } 824 if inst.Mydumper == nil { 825 if len(c.Mydumpers) != 0 { 826 log.L().Warn("mysql instance don't refer mydumper's configuration with mydumper-config-name, the default configuration will be used", zap.String("mysql instance", inst.SourceID)) 827 } 828 defaultCfg := DefaultMydumperConfig() 829 inst.Mydumper = &defaultCfg 830 } else if inst.Mydumper.ChunkFilesize == "" { 831 // avoid too big dump file that can't sent concurrently 832 inst.Mydumper.ChunkFilesize = defaultChunkFilesize 833 } 834 if inst.MydumperThread != 0 { 835 inst.Mydumper.Threads = inst.MydumperThread 836 } 837 838 if HasDump(c.TaskMode) && len(inst.Mydumper.MydumperPath) == 0 { 839 // only verify if set, whether is valid can only be verify when we run it 840 return terror.ErrConfigMydumperPathNotValid.Generate(i) 841 } 842 843 if len(inst.LoaderConfigName) > 0 { 844 rule, ok := c.Loaders[inst.LoaderConfigName] 845 if !ok { 846 return terror.ErrConfigLoaderCfgNotFound.Generate(i, inst.LoaderConfigName) 847 } 848 globalConfigReferCount[configRefPrefixes[loaderIdx]+inst.LoaderConfigName]++ 849 if rule != nil { 850 inst.Loader = new(LoaderConfig) 851 *inst.Loader = *rule // ref loader config 852 } 853 } 854 if inst.Loader == nil { 855 if len(c.Loaders) != 0 { 856 log.L().Warn("mysql instance don't refer loader's configuration with loader-config-name, the default configuration will be used", zap.String("mysql instance", inst.SourceID)) 857 } 858 defaultCfg := DefaultLoaderConfig() 859 inst.Loader = &defaultCfg 860 } 861 if inst.LoaderThread != 0 { 862 inst.Loader.PoolSize = inst.LoaderThread 863 } 864 865 if len(inst.SyncerConfigName) > 0 { 866 rule, ok := c.Syncers[inst.SyncerConfigName] 867 if !ok { 868 return terror.ErrConfigSyncerCfgNotFound.Generate(i, inst.SyncerConfigName) 869 } 870 globalConfigReferCount[configRefPrefixes[syncerIdx]+inst.SyncerConfigName]++ 871 if rule != nil { 872 inst.Syncer = new(SyncerConfig) 873 *inst.Syncer = *rule // ref syncer config 874 } 875 } 876 if inst.Syncer == nil { 877 if len(c.Syncers) != 0 { 878 log.L().Warn("mysql instance don't refer syncer's configuration with syncer-config-name, the default configuration will be used", zap.String("mysql instance", inst.SourceID)) 879 } 880 defaultCfg := DefaultSyncerConfig() 881 inst.Syncer = &defaultCfg 882 } 883 if inst.Syncer.QueueSize == 0 { 884 inst.Syncer.QueueSize = defaultQueueSize 885 } 886 if inst.Syncer.CheckpointFlushInterval == 0 { 887 inst.Syncer.CheckpointFlushInterval = defaultCheckpointFlushInterval 888 } 889 if inst.Syncer.SafeModeDuration == "" { 890 inst.Syncer.SafeModeDuration = strconv.Itoa(2*inst.Syncer.CheckpointFlushInterval) + "s" 891 } 892 if duration, err := time.ParseDuration(inst.Syncer.SafeModeDuration); err != nil { 893 return terror.ErrConfigInvalidSafeModeDuration.Generate(inst.Syncer.SafeModeDuration, err) 894 } else if inst.Syncer.SafeMode && duration == 0 { 895 return terror.ErrConfigConfictSafeModeDurationAndSafeMode.Generate() 896 } 897 if inst.SyncerThread != 0 { 898 inst.Syncer.WorkerCount = inst.SyncerThread 899 } 900 901 inst.ContinuousValidator = defaultValidatorConfig() 902 if inst.ContinuousValidatorConfigName != "" { 903 rule, ok := c.Validators[inst.ContinuousValidatorConfigName] 904 if !ok { 905 return terror.ErrContinuousValidatorCfgNotFound.Generate(i, inst.ContinuousValidatorConfigName) 906 } 907 globalConfigReferCount[configRefPrefixes[validatorIdx]+inst.ContinuousValidatorConfigName]++ 908 if rule != nil { 909 inst.ContinuousValidator = *rule 910 } 911 } 912 913 // for backward compatible, set global config `ansi-quotes: true` if any syncer is true 914 if inst.Syncer.EnableANSIQuotes { 915 log.L().Warn("DM could discover proper ANSI_QUOTES, `enable-ansi-quotes` is no longer take effect") 916 } 917 if inst.Syncer.DisableCausality { 918 log.L().Warn("`disable-causality` is no longer take effect") 919 } 920 921 for _, name := range inst.ExpressionFilters { 922 if _, ok := c.ExprFilter[name]; !ok { 923 return terror.ErrConfigExprFilterNotFound.Generate(i, name) 924 } 925 globalConfigReferCount[configRefPrefixes[exprFilterIdx]+name]++ 926 } 927 928 if dupeRules := checkDuplicateString(inst.RouteRules); len(dupeRules) > 0 { 929 duplicateErrorStrings = append(duplicateErrorStrings, fmt.Sprintf("mysql-instance(%d)'s route-rules: %s", i, strings.Join(dupeRules, ", "))) 930 } 931 if dupeRules := checkDuplicateString(inst.FilterRules); len(dupeRules) > 0 { 932 duplicateErrorStrings = append(duplicateErrorStrings, fmt.Sprintf("mysql-instance(%d)'s filter-rules: %s", i, strings.Join(dupeRules, ", "))) 933 } 934 if dupeRules := checkDuplicateString(inst.ColumnMappingRules); len(dupeRules) > 0 { 935 duplicateErrorStrings = append(duplicateErrorStrings, fmt.Sprintf("mysql-instance(%d)'s column-mapping-rules: %s", i, strings.Join(dupeRules, ", "))) 936 } 937 if dupeRules := checkDuplicateString(inst.ExpressionFilters); len(dupeRules) > 0 { 938 duplicateErrorStrings = append(duplicateErrorStrings, fmt.Sprintf("mysql-instance(%d)'s expression-filters: %s", i, strings.Join(dupeRules, ", "))) 939 } 940 } 941 if len(duplicateErrorStrings) > 0 { 942 return terror.ErrConfigDuplicateCfgItem.Generate(strings.Join(duplicateErrorStrings, "\n")) 943 } 944 945 var unusedConfigs []string 946 for route := range c.Routes { 947 if globalConfigReferCount[configRefPrefixes[routeRulesIdx]+route] == 0 { 948 unusedConfigs = append(unusedConfigs, route) 949 } 950 } 951 for filter := range c.Filters { 952 if globalConfigReferCount[configRefPrefixes[filterRulesIdx]+filter] == 0 { 953 unusedConfigs = append(unusedConfigs, filter) 954 } 955 } 956 for mydumper := range c.Mydumpers { 957 if globalConfigReferCount[configRefPrefixes[mydumperIdx]+mydumper] == 0 { 958 unusedConfigs = append(unusedConfigs, mydumper) 959 } 960 } 961 962 for loader, cfg := range c.Loaders { 963 if cfg != nil { 964 if err1 := cfg.adjust(); err1 != nil { 965 return err1 966 } 967 } 968 if globalConfigReferCount[configRefPrefixes[loaderIdx]+loader] == 0 { 969 unusedConfigs = append(unusedConfigs, loader) 970 } 971 } 972 for syncer := range c.Syncers { 973 if globalConfigReferCount[configRefPrefixes[syncerIdx]+syncer] == 0 { 974 unusedConfigs = append(unusedConfigs, syncer) 975 } 976 } 977 for exprFilter := range c.ExprFilter { 978 if globalConfigReferCount[configRefPrefixes[exprFilterIdx]+exprFilter] == 0 { 979 unusedConfigs = append(unusedConfigs, exprFilter) 980 } 981 } 982 for key := range c.Validators { 983 if globalConfigReferCount[configRefPrefixes[validatorIdx]+key] == 0 { 984 unusedConfigs = append(unusedConfigs, key) 985 } 986 } 987 988 if len(unusedConfigs) != 0 { 989 sort.Strings(unusedConfigs) 990 return terror.ErrConfigGlobalConfigsUnused.Generate(unusedConfigs) 991 } 992 993 // we postpone default time_zone init in each unit so we won't change the config value in task/sub_task config 994 if c.Timezone != "" { 995 if _, err := utils.ParseTimeZone(c.Timezone); err != nil { 996 return err 997 } 998 } 999 if c.RemoveMeta { 1000 log.L().Warn("`remove-meta` in task config is deprecated, please use `start-task ... --remove-meta` instead") 1001 } 1002 1003 if c.EnableHeartbeat || c.HeartbeatUpdateInterval != defaultUpdateInterval || 1004 c.HeartbeatReportInterval != defaultReportInterval { 1005 c.EnableHeartbeat = false 1006 log.L().Warn("heartbeat is deprecated, needn't set it anymore.") 1007 } 1008 return nil 1009 } 1010 1011 // getGenerateName generates name by rule or gets name from nameMap 1012 // if it's a new name, increase nameIdx 1013 // otherwise return current nameIdx. 1014 func getGenerateName(rule interface{}, nameIdx int, namePrefix string, nameMap map[string]string) (string, int) { 1015 // use json as key since no DeepEqual for rules now. 1016 ruleByte, err := json.Marshal(rule) 1017 if err != nil { 1018 log.L().Error(fmt.Sprintf("marshal %s rule to json", namePrefix), log.ShortError(err)) 1019 return fmt.Sprintf("%s-%02d", namePrefix, nameIdx), nameIdx + 1 1020 } else if val, ok := nameMap[string(ruleByte)]; ok { 1021 return val, nameIdx 1022 } else { 1023 ruleName := fmt.Sprintf("%s-%02d", namePrefix, nameIdx+1) 1024 nameMap[string(ruleByte)] = ruleName 1025 return ruleName, nameIdx + 1 1026 } 1027 } 1028 1029 // checkDuplicateString checks whether the given string array has duplicate string item 1030 // if there is duplicate, it will return **all** the duplicate strings. 1031 func checkDuplicateString(ruleNames []string) []string { 1032 mp := make(map[string]bool, len(ruleNames)) 1033 dupeArray := make([]string, 0) 1034 for _, name := range ruleNames { 1035 if added, ok := mp[name]; ok { 1036 if !added { 1037 dupeArray = append(dupeArray, name) 1038 mp[name] = true 1039 } 1040 } else { 1041 mp[name] = false 1042 } 1043 } 1044 return dupeArray 1045 } 1046 1047 // AdjustTargetDBSessionCfg adjust session cfg of TiDB. 1048 func AdjustTargetDBSessionCfg(dbConfig *dbconfig.DBConfig, version *semver.Version) { 1049 lowerMap := make(map[string]string, len(dbConfig.Session)) 1050 for k, v := range dbConfig.Session { 1051 lowerMap[strings.ToLower(k)] = v 1052 } 1053 // all cfg in defaultSessionCfg should be lower case 1054 for _, cfg := range defaultSessionCfg { 1055 if _, ok := lowerMap[cfg.key]; !ok && !version.LessThan(*cfg.minVersion) { 1056 lowerMap[cfg.key] = cfg.val 1057 } 1058 } 1059 dbConfig.Session = lowerMap 1060 } 1061 1062 var ( 1063 defaultParser = parser.New() 1064 parserMu sync.Mutex 1065 ) 1066 1067 func checkValidExpr(expr string) error { 1068 expr = "select " + expr 1069 parserMu.Lock() 1070 _, _, err := defaultParser.Parse(expr, "", "") 1071 parserMu.Unlock() 1072 return err 1073 } 1074 1075 // YamlForDowngrade returns YAML format represents of config for downgrade. 1076 func (c *TaskConfig) YamlForDowngrade() (string, error) { 1077 t := NewTaskConfigForDowngrade(c) 1078 1079 // try to encrypt password 1080 t.TargetDB.Password = utils.EncryptOrPlaintext(utils.DecryptOrPlaintext(t.TargetDB.Password)) 1081 1082 // omit default values, so we can ignore them for later marshal 1083 t.omitDefaultVals() 1084 1085 return t.Yaml() 1086 } 1087 1088 // MySQLInstanceForDowngrade represents a sync config of a MySQL instance for downgrade. 1089 type MySQLInstanceForDowngrade struct { 1090 SourceID string `yaml:"source-id"` 1091 Meta *Meta `yaml:"meta"` 1092 FilterRules []string `yaml:"filter-rules"` 1093 ColumnMappingRules []string `yaml:"column-mapping-rules"` 1094 RouteRules []string `yaml:"route-rules"` 1095 BWListName string `yaml:"black-white-list"` 1096 BAListName string `yaml:"block-allow-list"` 1097 MydumperConfigName string `yaml:"mydumper-config-name"` 1098 Mydumper *MydumperConfig `yaml:"mydumper"` 1099 MydumperThread int `yaml:"mydumper-thread"` 1100 LoaderConfigName string `yaml:"loader-config-name"` 1101 Loader *LoaderConfig `yaml:"loader"` 1102 LoaderThread int `yaml:"loader-thread"` 1103 SyncerConfigName string `yaml:"syncer-config-name"` 1104 Syncer *SyncerConfig `yaml:"syncer"` 1105 SyncerThread int `yaml:"syncer-thread"` 1106 // new config item 1107 ExpressionFilters []string `yaml:"expression-filters,omitempty"` 1108 } 1109 1110 // NewMySQLInstancesForDowngrade creates []* MySQLInstanceForDowngrade. 1111 func NewMySQLInstancesForDowngrade(mysqlInstances []*MySQLInstance) []*MySQLInstanceForDowngrade { 1112 mysqlInstancesForDowngrade := make([]*MySQLInstanceForDowngrade, 0, len(mysqlInstances)) 1113 for _, m := range mysqlInstances { 1114 newMySQLInstance := &MySQLInstanceForDowngrade{ 1115 SourceID: m.SourceID, 1116 Meta: m.Meta, 1117 FilterRules: m.FilterRules, 1118 ColumnMappingRules: m.ColumnMappingRules, 1119 RouteRules: m.RouteRules, 1120 BWListName: m.BWListName, 1121 BAListName: m.BAListName, 1122 MydumperConfigName: m.MydumperConfigName, 1123 Mydumper: m.Mydumper, 1124 MydumperThread: m.MydumperThread, 1125 LoaderConfigName: m.LoaderConfigName, 1126 Loader: m.Loader, 1127 LoaderThread: m.LoaderThread, 1128 SyncerConfigName: m.SyncerConfigName, 1129 Syncer: m.Syncer, 1130 SyncerThread: m.SyncerThread, 1131 ExpressionFilters: m.ExpressionFilters, 1132 } 1133 mysqlInstancesForDowngrade = append(mysqlInstancesForDowngrade, newMySQLInstance) 1134 } 1135 return mysqlInstancesForDowngrade 1136 } 1137 1138 // LoaderConfigForDowngrade is the base configuration for loader in v2.0. 1139 // This config is used for downgrade(config export) from a higher dmctl version. 1140 // When we add any new config item into LoaderConfig, we should update it also. 1141 type LoaderConfigForDowngrade struct { 1142 PoolSize int `yaml:"pool-size" toml:"pool-size" json:"pool-size"` 1143 Dir string `yaml:"dir" toml:"dir" json:"dir"` 1144 } 1145 1146 func NewLoaderConfigForDowngrade(loaderConfigs map[string]*LoaderConfig) map[string]*LoaderConfigForDowngrade { 1147 loaderConfigsForDowngrade := make(map[string]*LoaderConfigForDowngrade, len(loaderConfigs)) 1148 for k, v := range loaderConfigs { 1149 loaderConfigsForDowngrade[k] = &LoaderConfigForDowngrade{ 1150 PoolSize: v.PoolSize, 1151 Dir: v.Dir, 1152 } 1153 } 1154 return loaderConfigsForDowngrade 1155 } 1156 1157 // SyncerConfigForDowngrade is the base configuration for syncer in v2.0. 1158 // This config is used for downgrade(config export) from a higher dmctl version. 1159 // When we add any new config item into SyncerConfig, we should update it also. 1160 type SyncerConfigForDowngrade struct { 1161 MetaFile string `yaml:"meta-file"` 1162 WorkerCount int `yaml:"worker-count"` 1163 Batch int `yaml:"batch"` 1164 QueueSize int `yaml:"queue-size"` 1165 CheckpointFlushInterval int `yaml:"checkpoint-flush-interval"` 1166 MaxRetry int `yaml:"max-retry"` 1167 EnableGTID bool `yaml:"enable-gtid"` 1168 DisableCausality bool `yaml:"disable-detect"` 1169 SafeMode bool `yaml:"safe-mode"` 1170 EnableANSIQuotes bool `yaml:"enable-ansi-quotes"` 1171 1172 SafeModeDuration string `yaml:"safe-mode-duration,omitempty"` 1173 Compact bool `yaml:"compact,omitempty"` 1174 MultipleRows bool `yaml:"multipleRows,omitempty"` 1175 } 1176 1177 // NewSyncerConfigsForDowngrade converts SyncerConfig to SyncerConfigForDowngrade. 1178 func NewSyncerConfigsForDowngrade(syncerConfigs map[string]*SyncerConfig) map[string]*SyncerConfigForDowngrade { 1179 syncerConfigsForDowngrade := make(map[string]*SyncerConfigForDowngrade, len(syncerConfigs)) 1180 for configName, syncerConfig := range syncerConfigs { 1181 newSyncerConfig := &SyncerConfigForDowngrade{ 1182 MetaFile: syncerConfig.MetaFile, 1183 WorkerCount: syncerConfig.WorkerCount, 1184 Batch: syncerConfig.Batch, 1185 QueueSize: syncerConfig.QueueSize, 1186 CheckpointFlushInterval: syncerConfig.CheckpointFlushInterval, 1187 MaxRetry: syncerConfig.MaxRetry, 1188 EnableGTID: syncerConfig.EnableGTID, 1189 DisableCausality: syncerConfig.DisableCausality, 1190 SafeMode: syncerConfig.SafeMode, 1191 SafeModeDuration: syncerConfig.SafeModeDuration, 1192 EnableANSIQuotes: syncerConfig.EnableANSIQuotes, 1193 Compact: syncerConfig.Compact, 1194 MultipleRows: syncerConfig.MultipleRows, 1195 } 1196 syncerConfigsForDowngrade[configName] = newSyncerConfig 1197 } 1198 return syncerConfigsForDowngrade 1199 } 1200 1201 // omitDefaultVals change default value to empty value for new config item. 1202 // If any default value for new config item is not empty(0 or false or nil), 1203 // we should change it to empty. 1204 func (c *SyncerConfigForDowngrade) omitDefaultVals() { 1205 if c.SafeModeDuration == strconv.Itoa(2*c.CheckpointFlushInterval)+"s" { 1206 c.SafeModeDuration = "" 1207 } 1208 } 1209 1210 // TaskConfigForDowngrade is the base configuration for task in v2.0. 1211 // This config is used for downgrade(config export) from a higher dmctl version. 1212 // When we add any new config item into SourceConfig, we should update it also. 1213 type TaskConfigForDowngrade struct { 1214 Name string `yaml:"name"` 1215 TaskMode string `yaml:"task-mode"` 1216 IsSharding bool `yaml:"is-sharding"` 1217 ShardMode string `yaml:"shard-mode"` 1218 IgnoreCheckingItems []string `yaml:"ignore-checking-items"` 1219 MetaSchema string `yaml:"meta-schema"` 1220 EnableHeartbeat bool `yaml:"enable-heartbeat"` 1221 HeartbeatUpdateInterval int `yaml:"heartbeat-update-interval"` 1222 HeartbeatReportInterval int `yaml:"heartbeat-report-interval"` 1223 Timezone string `yaml:"timezone"` 1224 CaseSensitive bool `yaml:"case-sensitive"` 1225 TargetDB *dbconfig.DBConfig `yaml:"target-database"` 1226 OnlineDDLScheme string `yaml:"online-ddl-scheme"` 1227 Routes map[string]*router.TableRule `yaml:"routes"` 1228 Filters map[string]*bf.BinlogEventRule `yaml:"filters"` 1229 ColumnMappings map[string]*column.Rule `yaml:"column-mappings"` 1230 BWList map[string]*filter.Rules `yaml:"black-white-list"` 1231 BAList map[string]*filter.Rules `yaml:"block-allow-list"` 1232 Mydumpers map[string]*MydumperConfig `yaml:"mydumpers"` 1233 Loaders map[string]*LoaderConfigForDowngrade `yaml:"loaders"` 1234 Syncers map[string]*SyncerConfigForDowngrade `yaml:"syncers"` 1235 CleanDumpFile bool `yaml:"clean-dump-file"` 1236 EnableANSIQuotes bool `yaml:"ansi-quotes"` 1237 RemoveMeta bool `yaml:"remove-meta"` 1238 // new config item 1239 MySQLInstances []*MySQLInstanceForDowngrade `yaml:"mysql-instances"` 1240 ExprFilter map[string]*ExpressionFilter `yaml:"expression-filter,omitempty"` 1241 OnlineDDL bool `yaml:"online-ddl,omitempty"` 1242 ShadowTableRules []string `yaml:"shadow-table-rules,omitempty"` 1243 TrashTableRules []string `yaml:"trash-table-rules,omitempty"` 1244 StrictOptimisticShardMode bool `yaml:"strict-optimistic-shard-mode,omitempty"` 1245 } 1246 1247 // NewTaskConfigForDowngrade create new TaskConfigForDowngrade. 1248 func NewTaskConfigForDowngrade(taskConfig *TaskConfig) *TaskConfigForDowngrade { 1249 targetDB := *taskConfig.TargetDB 1250 return &TaskConfigForDowngrade{ 1251 Name: taskConfig.Name, 1252 TaskMode: taskConfig.TaskMode, 1253 IsSharding: taskConfig.IsSharding, 1254 ShardMode: taskConfig.ShardMode, 1255 StrictOptimisticShardMode: taskConfig.StrictOptimisticShardMode, 1256 IgnoreCheckingItems: taskConfig.IgnoreCheckingItems, 1257 MetaSchema: taskConfig.MetaSchema, 1258 EnableHeartbeat: taskConfig.EnableHeartbeat, 1259 HeartbeatUpdateInterval: taskConfig.HeartbeatUpdateInterval, 1260 HeartbeatReportInterval: taskConfig.HeartbeatReportInterval, 1261 Timezone: taskConfig.Timezone, 1262 CaseSensitive: taskConfig.CaseSensitive, 1263 TargetDB: &targetDB, 1264 OnlineDDLScheme: taskConfig.OnlineDDLScheme, 1265 Routes: taskConfig.Routes, 1266 Filters: taskConfig.Filters, 1267 ColumnMappings: taskConfig.ColumnMappings, 1268 BWList: taskConfig.BWList, 1269 BAList: taskConfig.BAList, 1270 Mydumpers: taskConfig.Mydumpers, 1271 Loaders: NewLoaderConfigForDowngrade(taskConfig.Loaders), 1272 Syncers: NewSyncerConfigsForDowngrade(taskConfig.Syncers), 1273 CleanDumpFile: taskConfig.CleanDumpFile, 1274 EnableANSIQuotes: taskConfig.EnableANSIQuotes, 1275 RemoveMeta: taskConfig.RemoveMeta, 1276 MySQLInstances: NewMySQLInstancesForDowngrade(taskConfig.MySQLInstances), 1277 ExprFilter: taskConfig.ExprFilter, 1278 OnlineDDL: taskConfig.OnlineDDL, 1279 ShadowTableRules: taskConfig.ShadowTableRules, 1280 TrashTableRules: taskConfig.TrashTableRules, 1281 } 1282 } 1283 1284 // omitDefaultVals change default value to empty value for new config item. 1285 // If any default value for new config item is not empty(0 or false or nil), 1286 // we should change it to empty. 1287 func (c *TaskConfigForDowngrade) omitDefaultVals() { 1288 if len(c.ShadowTableRules) == 1 && c.ShadowTableRules[0] == DefaultShadowTableRules { 1289 c.ShadowTableRules = nil 1290 } 1291 if len(c.TrashTableRules) == 1 && c.TrashTableRules[0] == DefaultTrashTableRules { 1292 c.TrashTableRules = nil 1293 } 1294 for _, s := range c.Syncers { 1295 s.omitDefaultVals() 1296 } 1297 c.OnlineDDL = false 1298 } 1299 1300 // Yaml returns YAML format representation of config. 1301 func (c *TaskConfigForDowngrade) Yaml() (string, error) { 1302 b, err := yaml.Marshal(c) 1303 return string(b), err 1304 }