github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/config/source_config.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 "bytes" 18 "context" 19 _ "embed" 20 "encoding/json" 21 "math" 22 "math/rand" 23 "os" 24 "path/filepath" 25 "strings" 26 "time" 27 28 "github.com/BurntSushi/toml" 29 "github.com/go-mysql-org/go-mysql/mysql" 30 "github.com/pingcap/tiflow/dm/config/dbconfig" 31 "github.com/pingcap/tiflow/dm/pkg/conn" 32 tcontext "github.com/pingcap/tiflow/dm/pkg/context" 33 "github.com/pingcap/tiflow/dm/pkg/gtid" 34 "github.com/pingcap/tiflow/dm/pkg/log" 35 "github.com/pingcap/tiflow/dm/pkg/terror" 36 "github.com/pingcap/tiflow/dm/pkg/utils" 37 bf "github.com/pingcap/tiflow/pkg/binlog-filter" 38 "go.uber.org/zap" 39 "gopkg.in/yaml.v2" 40 ) 41 42 const ( 43 // the default base(min) server id generated by random. 44 defaultBaseServerID = math.MaxUint32 / 10 45 defaultRelayDir = "relay-dir" 46 ) 47 48 var getAllServerIDFunc = conn.GetAllServerID 49 50 // SampleSourceConfig is sample config file of source. 51 // The embed source.yaml is a copy of dm/master/source.yaml, because embed 52 // can only match regular files in the current directory and subdirectories. 53 // 54 //go:embed source.yaml 55 var SampleSourceConfig string 56 57 // ObfuscatedPasswordForFeedback is the source encryption password that returns to the foreground. 58 // PM's requirement, we always return obfuscated password to users. 59 var ObfuscatedPasswordForFeedback string = "******" 60 61 // PurgeConfig is the configuration for Purger. 62 type PurgeConfig struct { 63 Interval int64 `yaml:"interval" toml:"interval" json:"interval"` // check whether need to purge at this @Interval (seconds) 64 Expires int64 `yaml:"expires" toml:"expires" json:"expires"` // if file's modified time is older than @Expires (hours), then it can be purged 65 RemainSpace int64 `yaml:"remain-space" toml:"remain-space" json:"remain-space"` // if remain space in @RelayBaseDir less than @RemainSpace (GB), then it can be purged 66 } 67 68 // SourceConfig is the configuration for source. 69 type SourceConfig struct { 70 Enable bool `yaml:"enable" toml:"enable" json:"enable"` 71 EnableGTID bool `yaml:"enable-gtid" toml:"enable-gtid" json:"enable-gtid"` 72 // deprecated 73 AutoFixGTID bool `yaml:"auto-fix-gtid" toml:"auto-fix-gtid" json:"auto-fix-gtid"` 74 RelayDir string `yaml:"relay-dir" toml:"relay-dir" json:"relay-dir"` 75 // deprecated 76 MetaDir string `yaml:"meta-dir" toml:"meta-dir" json:"meta-dir"` 77 Flavor string `yaml:"flavor" toml:"flavor" json:"flavor"` 78 // deprecated 79 Charset string `yaml:"charset" toml:"charset" json:"charset"` 80 81 EnableRelay bool `yaml:"enable-relay" toml:"enable-relay" json:"enable-relay"` 82 // relay synchronous starting point (if specified) 83 RelayBinLogName string `yaml:"relay-binlog-name" toml:"relay-binlog-name" json:"relay-binlog-name"` 84 RelayBinlogGTID string `yaml:"relay-binlog-gtid" toml:"relay-binlog-gtid" json:"relay-binlog-gtid"` 85 // only use when the source is bound to a worker, do not marsh it 86 UUIDSuffix int `yaml:"-" toml:"-" json:"-"` 87 88 SourceID string `yaml:"source-id" toml:"source-id" json:"source-id"` 89 From dbconfig.DBConfig `yaml:"from" toml:"from" json:"from"` 90 91 // config items for purger 92 Purge PurgeConfig `yaml:"purge" toml:"purge" json:"purge"` 93 94 // config items for task status checker 95 Checker CheckerConfig `yaml:"checker" toml:"checker" json:"checker"` 96 97 // id of the worker on which this task run 98 ServerID uint32 `yaml:"server-id" toml:"server-id" json:"server-id"` 99 100 // deprecated tracer, to keep compatibility with older version 101 Tracer map[string]interface{} `yaml:"tracer" toml:"tracer" json:"-"` 102 103 CaseSensitive bool `yaml:"case-sensitive" toml:"case-sensitive" json:"case-sensitive"` 104 Filters []*bf.BinlogEventRule `yaml:"filters" toml:"filters" json:"filters"` 105 } 106 107 // NewSourceConfig creates a new base config for upstream MySQL/MariaDB source. 108 func NewSourceConfig() *SourceConfig { 109 c := newSourceConfig() 110 c.adjust() 111 return c 112 } 113 114 // NewSourceConfig creates a new base config without adjust. 115 func newSourceConfig() *SourceConfig { 116 c := &SourceConfig{ 117 Enable: true, 118 Purge: PurgeConfig{ 119 Interval: 60 * 60, 120 Expires: 0, 121 RemainSpace: 15, 122 }, 123 Checker: CheckerConfig{ 124 CheckEnable: true, 125 BackoffRollback: Duration{DefaultBackoffRollback}, 126 BackoffMax: Duration{DefaultBackoffMax}, 127 }, 128 } 129 return c 130 } 131 132 // Clone clones a config. 133 func (c *SourceConfig) Clone() *SourceConfig { 134 clone := &SourceConfig{} 135 *clone = *c 136 return clone 137 } 138 139 // Toml returns TOML format representation of config. 140 func (c *SourceConfig) Toml() (string, error) { 141 var b bytes.Buffer 142 143 err := toml.NewEncoder(&b).Encode(c) 144 if err != nil { 145 log.L().Error("fail to marshal config to toml", log.ShortError(err)) 146 } 147 148 return b.String(), nil 149 } 150 151 // Yaml returns YAML format representation of config. 152 func (c *SourceConfig) Yaml() (string, error) { 153 b, err := yaml.Marshal(c) 154 if err != nil { 155 log.L().Error("fail to marshal config to yaml", log.ShortError(err)) 156 } 157 158 return string(b), nil 159 } 160 161 // FromToml parses flag definitions from the argument list. 162 // accept toml content for legacy use (mainly used by etcd). 163 func (c *SourceConfig) FromToml(content string) error { 164 // Parse first to get config file. 165 metaData, err := toml.Decode(content, c) 166 if err != nil { 167 return terror.ErrWorkerDecodeConfigFromFile.Delegate(err) 168 } 169 undecoded := metaData.Undecoded() 170 if len(undecoded) > 0 { 171 var undecodedItems []string 172 for _, item := range undecoded { 173 undecodedItems = append(undecodedItems, item.String()) 174 } 175 return terror.ErrWorkerUndecodedItemFromFile.Generate(strings.Join(undecodedItems, ",")) 176 } 177 c.adjust() 178 return c.Verify() 179 } 180 181 // SourceCfgFromYaml parses flag definitions from the argument list, content should be yaml format. 182 func SourceCfgFromYaml(content string) (*SourceConfig, error) { 183 c := newSourceConfig() 184 if err := yaml.UnmarshalStrict([]byte(content), c); err != nil { 185 return nil, terror.ErrConfigYamlTransform.Delegate(err, "decode source config") 186 } 187 c.adjust() 188 return c, nil 189 } 190 191 // SourceCfgFromYamlAndVerify does SourceCfgFromYaml and Verify. 192 func SourceCfgFromYamlAndVerify(content string) (*SourceConfig, error) { 193 c, err := SourceCfgFromYaml(content) 194 if err != nil { 195 return nil, err 196 } 197 if err = c.Verify(); err != nil { 198 return nil, err 199 } 200 return c, nil 201 } 202 203 // EncodeToml encodes config. 204 func (c *SourceConfig) EncodeToml() (string, error) { 205 buf := new(bytes.Buffer) 206 if err := toml.NewEncoder(buf).Encode(c); err != nil { 207 return "", err 208 } 209 return buf.String(), nil 210 } 211 212 func (c *SourceConfig) String() string { 213 cfg, err := json.Marshal(c) 214 if err != nil { 215 log.L().Error("fail to marshal config to json", log.ShortError(err)) 216 } 217 return string(cfg) 218 } 219 220 func (c *SourceConfig) adjust() { 221 c.From.Adjust() 222 c.Checker.Adjust() 223 224 if c.AutoFixGTID { 225 c.AutoFixGTID = false 226 log.L().Warn("auto-fix-gtid is deprecated, overwrite it to false") 227 } 228 } 229 230 // Verify verifies the config. 231 func (c *SourceConfig) Verify() error { 232 if len(c.SourceID) == 0 { 233 return terror.ErrWorkerNeedSourceID.Generate() 234 } 235 if len(c.SourceID) > MaxSourceIDLength { 236 return terror.ErrWorkerTooLongSourceID.Generate(c.SourceID, MaxSourceIDLength) 237 } 238 239 var err error 240 if len(c.RelayBinLogName) > 0 { 241 if !utils.VerifyFilename(c.RelayBinLogName) { 242 return terror.ErrWorkerRelayBinlogName.Generate(c.RelayBinLogName) 243 } 244 } 245 if len(c.RelayBinlogGTID) > 0 { 246 _, err = gtid.ParserGTID(c.Flavor, c.RelayBinlogGTID) 247 if err != nil { 248 return terror.WithClass(terror.Annotatef(err, "relay-binlog-gtid %s", c.RelayBinlogGTID), terror.ClassDMWorker) 249 } 250 } 251 252 _, err = bf.NewBinlogEvent(c.CaseSensitive, c.Filters) 253 if err != nil { 254 return terror.ErrConfigBinlogEventFilter.Delegate(err) 255 } 256 257 if c.Checker.BackoffMax.Duration < c.Checker.BackoffMin.Duration { 258 return terror.ErrConfigCheckerMaxTooSmall.Generate(c.Checker.BackoffMax.Duration, c.Checker.BackoffMin.Duration) 259 } 260 261 return nil 262 } 263 264 // GetDecryptedClone returns a decrypted config replica in config. 265 func (c *SourceConfig) GetDecryptedClone() *SourceConfig { 266 clone := c.Clone() 267 var pswdFrom string 268 if len(clone.From.Password) > 0 { 269 pswdFrom = utils.DecryptOrPlaintext(clone.From.Password) 270 } 271 clone.From.Password = pswdFrom 272 return clone 273 } 274 275 // GenerateDBConfig creates DBConfig for DB. 276 func (c *SourceConfig) GenerateDBConfig() *dbconfig.DBConfig { 277 // decrypt password 278 clone := c.GetDecryptedClone() 279 from := &clone.From 280 from.RawDBCfg = dbconfig.DefaultRawDBConfig().SetReadTimeout(conn.DefaultDBTimeout.String()) 281 return from 282 } 283 284 // Adjust flavor and server-id of SourceConfig. 285 func (c *SourceConfig) Adjust(ctx context.Context, db *conn.BaseDB) (err error) { 286 c.From.Adjust() 287 c.Checker.Adjust() 288 289 // use one timeout for all following DB operations. 290 ctx2, cancel := context.WithTimeout(ctx, conn.DefaultDBTimeout) 291 defer cancel() 292 if c.Flavor == "" || c.ServerID == 0 { 293 err = c.AdjustFlavor(ctx2, db) 294 if err != nil { 295 return err 296 } 297 298 err = c.AdjustServerID(ctx2, db) 299 if err != nil { 300 return err 301 } 302 } 303 304 // MariaDB automatically enabled gtid after 10.0.2, refer to https://mariadb.com/kb/en/gtid/#using-global-transaction-ids 305 if c.EnableGTID && c.Flavor != mysql.MariaDBFlavor { 306 val, err := conn.GetGTIDMode(tcontext.NewContext(ctx2, log.L()), db) 307 if err != nil { 308 return err 309 } 310 if val != "ON" { 311 return terror.ErrSourceCheckGTID.Generate(c.SourceID, val) 312 } 313 } 314 315 if len(c.RelayDir) == 0 { 316 c.RelayDir = defaultRelayDir 317 } 318 if filepath.IsAbs(c.RelayDir) { 319 log.L().Warn("using an absolute relay path, relay log can't work when starting multiple relay worker") 320 } 321 322 return nil 323 } 324 325 // AdjustCaseSensitive adjust CaseSensitive from DB. 326 func (c *SourceConfig) AdjustCaseSensitive(ctx context.Context, db *conn.BaseDB) (err error) { 327 caseSensitive, err2 := conn.GetDBCaseSensitive(ctx, db) 328 if err2 != nil { 329 return err2 330 } 331 c.CaseSensitive = caseSensitive 332 return nil 333 } 334 335 // AdjustFlavor adjust Flavor from DB. 336 func (c *SourceConfig) AdjustFlavor(ctx context.Context, db *conn.BaseDB) (err error) { 337 if c.Flavor != "" { 338 switch c.Flavor { 339 case mysql.MariaDBFlavor, mysql.MySQLFlavor: 340 return nil 341 default: 342 return terror.ErrNotSupportedFlavor.Generate(c.Flavor) 343 } 344 } 345 346 c.Flavor, err = conn.GetFlavor(ctx, db) 347 if ctx.Err() != nil { 348 err = terror.Annotatef(err, "fail to get flavor info %v", ctx.Err()) 349 } 350 return terror.WithScope(err, terror.ScopeUpstream) 351 } 352 353 // AdjustServerID adjust server id from DB. 354 func (c *SourceConfig) AdjustServerID(ctx context.Context, db *conn.BaseDB) error { 355 if c.ServerID != 0 { 356 return nil 357 } 358 359 serverIDs, err := getAllServerIDFunc(tcontext.NewContext(ctx, log.L()), db) 360 if ctx.Err() != nil { 361 err = terror.Annotatef(err, "fail to get server-id info %v", ctx.Err()) 362 } 363 if err != nil { 364 log.L().Error("failed to get server-id, will random choose a server-id for this source", 365 zap.Error(err)) 366 } 367 368 rand.Seed(time.Now().UnixNano()) 369 for i := 0; i < 5; i++ { 370 randomValue := uint32(rand.Intn(100000)) 371 randomServerID := defaultBaseServerID + randomValue 372 if _, ok := serverIDs[randomServerID]; ok { 373 continue 374 } 375 376 c.ServerID = randomServerID 377 return nil 378 } 379 380 return terror.ErrInvalidServerID.Generatef("can't find a random available server ID") 381 } 382 383 // LoadFromFile loads config from file. 384 func LoadFromFile(path string) (*SourceConfig, error) { 385 content, err := os.ReadFile(path) 386 if err != nil { 387 return nil, terror.ErrConfigReadCfgFromFile.Delegate(err, path) 388 } 389 return SourceCfgFromYaml(string(content)) 390 } 391 392 // YamlForDowngrade returns YAML format represents of config for downgrade. 393 func (c *SourceConfig) YamlForDowngrade() (string, error) { 394 s := NewSourceConfigForDowngrade(c) 395 396 // try to encrypt password 397 s.From.Password = utils.EncryptOrPlaintext(utils.DecryptOrPlaintext(c.From.Password)) 398 s.omitDefaultVals() 399 return s.Yaml() 400 } 401 402 // SourceConfigForDowngrade is the base configuration for source in v2.0. 403 // This config is used for downgrade(config export) from a higher dmctl version. 404 // When we add any new config item into SourceConfig, we should update it also. 405 type SourceConfigForDowngrade struct { 406 Enable bool `yaml:"enable,omitempty"` 407 EnableGTID bool `yaml:"enable-gtid"` 408 RelayDir string `yaml:"relay-dir"` 409 Flavor string `yaml:"flavor"` 410 Charset string `yaml:"charset"` 411 EnableRelay bool `yaml:"enable-relay"` 412 RelayBinLogName string `yaml:"relay-binlog-name"` 413 RelayBinlogGTID string `yaml:"relay-binlog-gtid"` 414 UUIDSuffix int `yaml:"-"` 415 SourceID string `yaml:"source-id"` 416 From dbconfig.DBConfig `yaml:"from"` 417 Purge PurgeConfig `yaml:"purge"` 418 Checker CheckerConfig `yaml:"checker"` 419 ServerID uint32 `yaml:"server-id"` 420 Tracer map[string]interface{} `yaml:"tracer"` 421 // any new config item, we mark it omitempty 422 CaseSensitive bool `yaml:"case-sensitive,omitempty"` 423 Filters []*bf.BinlogEventRule `yaml:"filters,omitempty"` 424 } 425 426 // NewSourceConfigForDowngrade creates a new base config for downgrade. 427 func NewSourceConfigForDowngrade(sourceCfg *SourceConfig) *SourceConfigForDowngrade { 428 return &SourceConfigForDowngrade{ 429 Enable: sourceCfg.Enable, 430 EnableGTID: sourceCfg.EnableGTID, 431 RelayDir: sourceCfg.RelayDir, 432 Flavor: sourceCfg.Flavor, 433 Charset: sourceCfg.Charset, 434 EnableRelay: sourceCfg.EnableRelay, 435 RelayBinLogName: sourceCfg.RelayBinLogName, 436 RelayBinlogGTID: sourceCfg.RelayBinlogGTID, 437 UUIDSuffix: sourceCfg.UUIDSuffix, 438 SourceID: sourceCfg.SourceID, 439 From: sourceCfg.From, 440 Purge: sourceCfg.Purge, 441 Checker: sourceCfg.Checker, 442 ServerID: sourceCfg.ServerID, 443 Tracer: sourceCfg.Tracer, 444 CaseSensitive: sourceCfg.CaseSensitive, 445 Filters: sourceCfg.Filters, 446 } 447 } 448 449 // omitDefaultVals change default value to empty value for new config item. 450 // If any default value for new config item is not empty(0 or false or nil), 451 // we should change it to empty. 452 func (c *SourceConfigForDowngrade) omitDefaultVals() { 453 c.Enable = false 454 } 455 456 // Yaml returns YAML format representation of the config. 457 func (c *SourceConfigForDowngrade) Yaml() (string, error) { 458 b, err := yaml.Marshal(c) 459 return string(b), err 460 }