github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/task/common.go (about) 1 // Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0. 2 3 package task 4 5 import ( 6 "context" 7 "crypto/tls" 8 "fmt" 9 "net/url" 10 "path" 11 "strings" 12 "time" 13 14 gcs "cloud.google.com/go/storage" 15 "github.com/docker/go-units" 16 "github.com/gogo/protobuf/proto" 17 "github.com/pingcap/errors" 18 backuppb "github.com/pingcap/kvproto/pkg/backup" 19 "github.com/pingcap/log" 20 filter "github.com/pingcap/tidb-tools/pkg/table-filter" 21 "github.com/pingcap/tidb/sessionctx/variable" 22 "github.com/spf13/cobra" 23 "github.com/spf13/pflag" 24 pd "github.com/tikv/pd/client" 25 "go.etcd.io/etcd/pkg/transport" 26 "go.uber.org/zap" 27 "google.golang.org/grpc/keepalive" 28 29 "github.com/pingcap/br/pkg/conn" 30 berrors "github.com/pingcap/br/pkg/errors" 31 "github.com/pingcap/br/pkg/glue" 32 "github.com/pingcap/br/pkg/storage" 33 "github.com/pingcap/br/pkg/utils" 34 ) 35 36 const ( 37 // flagSendCreds specify whether to send credentials to tikv 38 flagSendCreds = "send-credentials-to-tikv" 39 // No credentials specifies that cloud credentials should not be loaded 40 flagNoCreds = "no-credentials" 41 // flagStorage is the name of storage flag. 42 flagStorage = "storage" 43 // flagPD is the name of PD url flag. 44 flagPD = "pd" 45 // flagCA is the name of TLS CA flag. 46 flagCA = "ca" 47 // flagCert is the name of TLS cert flag. 48 flagCert = "cert" 49 // flagKey is the name of TLS key flag. 50 flagKey = "key" 51 52 flagDatabase = "db" 53 flagTable = "table" 54 55 flagChecksumConcurrency = "checksum-concurrency" 56 flagRateLimit = "ratelimit" 57 flagRateLimitUnit = "ratelimit-unit" 58 flagConcurrency = "concurrency" 59 flagChecksum = "checksum" 60 flagFilter = "filter" 61 flagCaseSensitive = "case-sensitive" 62 flagRemoveTiFlash = "remove-tiflash" 63 flagCheckRequirement = "check-requirements" 64 flagSwitchModeInterval = "switch-mode-interval" 65 // flagGrpcKeepaliveTime is the interval of pinging the server. 66 flagGrpcKeepaliveTime = "grpc-keepalive-time" 67 // flagGrpcKeepaliveTimeout is the max time a grpc conn can keep idel before killed. 68 flagGrpcKeepaliveTimeout = "grpc-keepalive-timeout" 69 // flagEnableOpenTracing is whether to enable opentracing 70 flagEnableOpenTracing = "enable-opentracing" 71 flagSkipCheckPath = "skip-check-path" 72 73 defaultSwitchInterval = 5 * time.Minute 74 defaultGRPCKeepaliveTime = 10 * time.Second 75 defaultGRPCKeepaliveTimeout = 3 * time.Second 76 77 unlimited = 0 78 ) 79 80 // TLSConfig is the common configuration for TLS connection. 81 type TLSConfig struct { 82 CA string `json:"ca" toml:"ca"` 83 Cert string `json:"cert" toml:"cert"` 84 Key string `json:"key" toml:"key"` 85 } 86 87 // IsEnabled checks if TLS open or not. 88 func (tls *TLSConfig) IsEnabled() bool { 89 return tls.CA != "" 90 } 91 92 // ToTLSConfig generate tls.Config. 93 func (tls *TLSConfig) ToTLSConfig() (*tls.Config, error) { 94 tlsInfo := transport.TLSInfo{ 95 CertFile: tls.Cert, 96 KeyFile: tls.Key, 97 TrustedCAFile: tls.CA, 98 } 99 tlsConfig, err := tlsInfo.ClientConfig() 100 if err != nil { 101 return nil, errors.Trace(err) 102 } 103 return tlsConfig, nil 104 } 105 106 // Config is the common configuration for all BRIE tasks. 107 type Config struct { 108 storage.BackendOptions 109 110 Storage string `json:"storage" toml:"storage"` 111 PD []string `json:"pd" toml:"pd"` 112 TLS TLSConfig `json:"tls" toml:"tls"` 113 RateLimit uint64 `json:"rate-limit" toml:"rate-limit"` 114 ChecksumConcurrency uint `json:"checksum-concurrency" toml:"checksum-concurrency"` 115 Concurrency uint32 `json:"concurrency" toml:"concurrency"` 116 Checksum bool `json:"checksum" toml:"checksum"` 117 SendCreds bool `json:"send-credentials-to-tikv" toml:"send-credentials-to-tikv"` 118 // LogProgress is true means the progress bar is printed to the log instead of stdout. 119 LogProgress bool `json:"log-progress" toml:"log-progress"` 120 121 // CaseSensitive should not be used. 122 // 123 // Deprecated: This field is kept only to satisfy the cyclic dependency with TiDB. This field 124 // should be removed after TiDB upgrades the BR dependency. 125 CaseSensitive bool 126 127 // NoCreds means don't try to load cloud credentials 128 NoCreds bool `json:"no-credentials" toml:"no-credentials"` 129 130 CheckRequirements bool `json:"check-requirements" toml:"check-requirements"` 131 // EnableOpenTracing is whether to enable opentracing 132 EnableOpenTracing bool `json:"enable-opentracing" toml:"enable-opentracing"` 133 // SkipCheckPath skips verifying the path 134 // deprecated 135 SkipCheckPath bool `json:"skip-check-path" toml:"skip-check-path"` 136 // Filter should not be used, use TableFilter instead. 137 // 138 // Deprecated: This field is kept only to satisfy the cyclic dependency with TiDB. This field 139 // should be removed after TiDB upgrades the BR dependency. 140 Filter filter.MySQLReplicationRules 141 142 TableFilter filter.Filter `json:"-" toml:"-"` 143 SwitchModeInterval time.Duration `json:"switch-mode-interval" toml:"switch-mode-interval"` 144 // Schemas is a database name set, to check whether the restore database has been backup 145 Schemas map[string]struct{} 146 // Tables is a table name set, to check whether the restore table has been backup 147 Tables map[string]struct{} 148 149 // GrpcKeepaliveTime is the interval of pinging the server. 150 GRPCKeepaliveTime time.Duration `json:"grpc-keepalive-time" toml:"grpc-keepalive-time"` 151 // GrpcKeepaliveTimeout is the max time a grpc conn can keep idel before killed. 152 GRPCKeepaliveTimeout time.Duration `json:"grpc-keepalive-timeout" toml:"grpc-keepalive-timeout"` 153 } 154 155 // DefineCommonFlags defines the flags common to all BRIE commands. 156 func DefineCommonFlags(flags *pflag.FlagSet) { 157 flags.BoolP(flagSendCreds, "c", true, "Whether send credentials to tikv") 158 flags.StringP(flagStorage, "s", "", `specify the url where backup storage, eg, "s3://bucket/path/prefix"`) 159 flags.StringSliceP(flagPD, "u", []string{"127.0.0.1:2379"}, "PD address") 160 flags.String(flagCA, "", "CA certificate path for TLS connection") 161 flags.String(flagCert, "", "Certificate path for TLS connection") 162 flags.String(flagKey, "", "Private key path for TLS connection") 163 flags.Uint(flagChecksumConcurrency, variable.DefChecksumTableConcurrency, "The concurrency of table checksumming") 164 _ = flags.MarkHidden(flagChecksumConcurrency) 165 166 flags.Uint64(flagRateLimit, unlimited, "The rate limit of the task, MB/s per node") 167 flags.Bool(flagChecksum, true, "Run checksum at end of task") 168 flags.Bool(flagRemoveTiFlash, true, 169 "Remove TiFlash replicas before backup or restore, for unsupported versions of TiFlash") 170 171 // Default concurrency is different for backup and restore. 172 // Leave it 0 and let them adjust the value. 173 flags.Uint32(flagConcurrency, 0, "The size of thread pool on each node that executes the task") 174 // It may confuse users , so just hide it. 175 _ = flags.MarkHidden(flagConcurrency) 176 177 flags.Uint64(flagRateLimitUnit, units.MiB, "The unit of rate limit") 178 _ = flags.MarkHidden(flagRateLimitUnit) 179 _ = flags.MarkDeprecated(flagRemoveTiFlash, 180 "TiFlash is fully supported by BR now, removing TiFlash isn't needed any more. This flag would be ignored.") 181 182 flags.Bool(flagCheckRequirement, true, 183 "Whether start version check before execute command") 184 flags.Duration(flagSwitchModeInterval, defaultSwitchInterval, "maintain import mode on TiKV during restore") 185 flags.Duration(flagGrpcKeepaliveTime, defaultGRPCKeepaliveTime, 186 "the interval of pinging gRPC peer, must keep the same value with TiKV and PD") 187 flags.Duration(flagGrpcKeepaliveTimeout, defaultGRPCKeepaliveTimeout, 188 "the max time a gRPC connection can keep idle before killed, must keep the same value with TiKV and PD") 189 _ = flags.MarkHidden(flagGrpcKeepaliveTime) 190 _ = flags.MarkHidden(flagGrpcKeepaliveTimeout) 191 192 flags.Bool(flagEnableOpenTracing, false, 193 "Set whether to enable opentracing during the backup/restore process") 194 195 flags.BoolP(flagNoCreds, "", false, "Don't load credentials") 196 _ = flags.MarkHidden(flagNoCreds) 197 flags.BoolP(flagSkipCheckPath, "", false, "Skip path verification") 198 _ = flags.MarkHidden(flagSkipCheckPath) 199 200 storage.DefineFlags(flags) 201 } 202 203 // DefineDatabaseFlags defines the required --db flag for `db` subcommand. 204 func DefineDatabaseFlags(command *cobra.Command) { 205 command.Flags().String(flagDatabase, "", "database name") 206 _ = command.MarkFlagRequired(flagDatabase) 207 } 208 209 // DefineTableFlags defines the required --db and --table flags for `table` subcommand. 210 func DefineTableFlags(command *cobra.Command) { 211 DefineDatabaseFlags(command) 212 command.Flags().StringP(flagTable, "t", "", "table name") 213 _ = command.MarkFlagRequired(flagTable) 214 } 215 216 // DefineFilterFlags defines the --filter and --case-sensitive flags for `full` subcommand. 217 func DefineFilterFlags(command *cobra.Command, defaultFilter []string) { 218 flags := command.Flags() 219 flags.StringArrayP(flagFilter, "f", defaultFilter, "select tables to process") 220 flags.Bool(flagCaseSensitive, false, "whether the table names used in --filter should be case-sensitive") 221 } 222 223 // ParseFromFlags parses the TLS config from the flag set. 224 func (tls *TLSConfig) ParseFromFlags(flags *pflag.FlagSet) error { 225 var err error 226 tls.CA, tls.Cert, tls.Key, err = ParseTLSTripleFromFlags(flags) 227 return err 228 } 229 230 // ParseTLSTripleFromFlags parses the (ca, cert, key) triple from flags. 231 func ParseTLSTripleFromFlags(flags *pflag.FlagSet) (ca, cert, key string, err error) { 232 ca, err = flags.GetString(flagCA) 233 if err != nil { 234 return 235 } 236 cert, err = flags.GetString(flagCert) 237 if err != nil { 238 return 239 } 240 key, err = flags.GetString(flagKey) 241 if err != nil { 242 return 243 } 244 return 245 } 246 247 func (cfg *Config) normalizePDURLs() error { 248 for i := range cfg.PD { 249 var err error 250 cfg.PD[i], err = normalizePDURL(cfg.PD[i], cfg.TLS.IsEnabled()) 251 if err != nil { 252 return errors.Trace(err) 253 } 254 } 255 return nil 256 } 257 258 // ParseFromFlags parses the config from the flag set. 259 func (cfg *Config) ParseFromFlags(flags *pflag.FlagSet) error { 260 var err error 261 if cfg.Storage, err = flags.GetString(flagStorage); err != nil { 262 return errors.Trace(err) 263 } 264 if cfg.SendCreds, err = flags.GetBool(flagSendCreds); err != nil { 265 return errors.Trace(err) 266 } 267 if cfg.NoCreds, err = flags.GetBool(flagNoCreds); err != nil { 268 return errors.Trace(err) 269 } 270 if cfg.Concurrency, err = flags.GetUint32(flagConcurrency); err != nil { 271 return errors.Trace(err) 272 } 273 if cfg.Checksum, err = flags.GetBool(flagChecksum); err != nil { 274 return errors.Trace(err) 275 } 276 if cfg.ChecksumConcurrency, err = flags.GetUint(flagChecksumConcurrency); err != nil { 277 return errors.Trace(err) 278 } 279 280 var rateLimit, rateLimitUnit uint64 281 if rateLimit, err = flags.GetUint64(flagRateLimit); err != nil { 282 return errors.Trace(err) 283 } 284 if rateLimitUnit, err = flags.GetUint64(flagRateLimitUnit); err != nil { 285 return errors.Trace(err) 286 } 287 cfg.RateLimit = rateLimit * rateLimitUnit 288 289 cfg.Schemas = make(map[string]struct{}) 290 cfg.Tables = make(map[string]struct{}) 291 var caseSensitive bool 292 if filterFlag := flags.Lookup(flagFilter); filterFlag != nil { 293 var f filter.Filter 294 f, err = filter.Parse(filterFlag.Value.(pflag.SliceValue).GetSlice()) 295 if err != nil { 296 return errors.Trace(err) 297 } 298 cfg.TableFilter = f 299 caseSensitive, err = flags.GetBool(flagCaseSensitive) 300 if err != nil { 301 return errors.Trace(err) 302 } 303 } else if dbFlag := flags.Lookup(flagDatabase); dbFlag != nil { 304 db := dbFlag.Value.String() 305 if len(db) == 0 { 306 return errors.Annotate(berrors.ErrInvalidArgument, "empty database name is not allowed") 307 } 308 cfg.Schemas[utils.EncloseName(db)] = struct{}{} 309 if tblFlag := flags.Lookup(flagTable); tblFlag != nil { 310 tbl := tblFlag.Value.String() 311 if len(tbl) == 0 { 312 return errors.Annotate(berrors.ErrInvalidArgument, "empty table name is not allowed") 313 } 314 cfg.Tables[utils.EncloseDBAndTable(db, tbl)] = struct{}{} 315 cfg.TableFilter = filter.NewTablesFilter(filter.Table{ 316 Schema: db, 317 Name: tbl, 318 }) 319 } else { 320 cfg.TableFilter = filter.NewSchemasFilter(db) 321 } 322 } else { 323 cfg.TableFilter, _ = filter.Parse([]string{"*.*"}) 324 } 325 if !caseSensitive { 326 cfg.TableFilter = filter.CaseInsensitive(cfg.TableFilter) 327 } 328 checkRequirements, err := flags.GetBool(flagCheckRequirement) 329 if err != nil { 330 return errors.Trace(err) 331 } 332 cfg.CheckRequirements = checkRequirements 333 334 cfg.SwitchModeInterval, err = flags.GetDuration(flagSwitchModeInterval) 335 if err != nil { 336 return errors.Trace(err) 337 } 338 cfg.GRPCKeepaliveTime, err = flags.GetDuration(flagGrpcKeepaliveTime) 339 if err != nil { 340 return errors.Trace(err) 341 } 342 cfg.GRPCKeepaliveTimeout, err = flags.GetDuration(flagGrpcKeepaliveTimeout) 343 if err != nil { 344 return errors.Trace(err) 345 } 346 cfg.EnableOpenTracing, err = flags.GetBool(flagEnableOpenTracing) 347 if err != nil { 348 return errors.Trace(err) 349 } 350 351 if cfg.SwitchModeInterval <= 0 { 352 return errors.Annotatef(berrors.ErrInvalidArgument, "--switch-mode-interval must be positive, %s is not allowed", cfg.SwitchModeInterval) 353 } 354 355 if err = cfg.BackendOptions.ParseFromFlags(flags); err != nil { 356 return errors.Trace(err) 357 } 358 if err = cfg.TLS.ParseFromFlags(flags); err != nil { 359 return errors.Trace(err) 360 } 361 cfg.PD, err = flags.GetStringSlice(flagPD) 362 if err != nil { 363 return errors.Trace(err) 364 } 365 if len(cfg.PD) == 0 { 366 return errors.Annotate(berrors.ErrInvalidArgument, "must provide at least one PD server address") 367 } 368 if cfg.SkipCheckPath, err = flags.GetBool(flagSkipCheckPath); err != nil { 369 return errors.Trace(err) 370 } 371 return cfg.normalizePDURLs() 372 } 373 374 // NewMgr creates a new mgr at the given PD address. 375 func NewMgr(ctx context.Context, 376 g glue.Glue, pds []string, 377 tlsConfig TLSConfig, 378 keepalive keepalive.ClientParameters, 379 checkRequirements bool, 380 needDomain bool, 381 ) (*conn.Mgr, error) { 382 var ( 383 tlsConf *tls.Config 384 err error 385 ) 386 pdAddress := strings.Join(pds, ",") 387 if len(pdAddress) == 0 { 388 return nil, errors.Annotate(berrors.ErrInvalidArgument, "pd address can not be empty") 389 } 390 391 securityOption := pd.SecurityOption{} 392 if tlsConfig.IsEnabled() { 393 securityOption.CAPath = tlsConfig.CA 394 securityOption.CertPath = tlsConfig.Cert 395 securityOption.KeyPath = tlsConfig.Key 396 tlsConf, err = tlsConfig.ToTLSConfig() 397 if err != nil { 398 return nil, errors.Trace(err) 399 } 400 } 401 402 // Disable GC because TiDB enables GC already. 403 store, err := g.Open(fmt.Sprintf("tikv://%s?disableGC=true", pdAddress), securityOption) 404 if err != nil { 405 return nil, errors.Trace(err) 406 } 407 408 // Is it necessary to remove `StoreBehavior`? 409 return conn.NewMgr( 410 ctx, g, pdAddress, store, tlsConf, securityOption, keepalive, conn.SkipTiFlash, 411 checkRequirements, needDomain, 412 ) 413 } 414 415 // GetStorage gets the storage backend from the config. 416 func GetStorage( 417 ctx context.Context, 418 cfg *Config, 419 ) (*backuppb.StorageBackend, storage.ExternalStorage, error) { 420 u, err := storage.ParseBackend(cfg.Storage, &cfg.BackendOptions) 421 if err != nil { 422 return nil, nil, errors.Trace(err) 423 } 424 s, err := storage.New(ctx, u, storageOpts(cfg)) 425 if err != nil { 426 return nil, nil, errors.Annotate(err, "create storage failed") 427 } 428 return u, s, nil 429 } 430 431 func storageOpts(cfg *Config) *storage.ExternalStorageOptions { 432 return &storage.ExternalStorageOptions{ 433 NoCredentials: cfg.NoCreds, 434 SendCredentials: cfg.SendCreds, 435 SkipCheckPath: cfg.SkipCheckPath, 436 } 437 } 438 439 // ReadBackupMeta reads the backupmeta file from the storage. 440 func ReadBackupMeta( 441 ctx context.Context, 442 fileName string, 443 cfg *Config, 444 ) (*backuppb.StorageBackend, storage.ExternalStorage, *backuppb.BackupMeta, error) { 445 u, s, err := GetStorage(ctx, cfg) 446 if err != nil { 447 return nil, nil, nil, errors.Trace(err) 448 } 449 metaData, err := s.ReadFile(ctx, fileName) 450 if err != nil { 451 if gcsObjectNotFound(err) { 452 // change gcs://bucket/abc/def to gcs://bucket/abc and read defbackupmeta 453 oldPrefix := u.GetGcs().GetPrefix() 454 newPrefix, file := path.Split(oldPrefix) 455 newFileName := file + fileName 456 u.GetGcs().Prefix = newPrefix 457 s, err = storage.New(ctx, u, storageOpts(cfg)) 458 if err != nil { 459 return nil, nil, nil, errors.Trace(err) 460 } 461 log.Info("retry load metadata in gcs", zap.String("newPrefix", newPrefix), zap.String("newFileName", newFileName)) 462 metaData, err = s.ReadFile(ctx, newFileName) 463 if err != nil { 464 return nil, nil, nil, errors.Trace(err) 465 } 466 // reset prefix for tikv download sst file correctly. 467 u.GetGcs().Prefix = oldPrefix 468 } else { 469 return nil, nil, nil, errors.Annotate(err, "load backupmeta failed") 470 } 471 } 472 backupMeta := &backuppb.BackupMeta{} 473 if err = proto.Unmarshal(metaData, backupMeta); err != nil { 474 return nil, nil, nil, errors.Annotate(err, "parse backupmeta failed") 475 } 476 return u, s, backupMeta, nil 477 } 478 479 // flagToZapField checks whether this flag can be logged, 480 // if need to log, return its zap field. Or return a field with hidden value. 481 func flagToZapField(f *pflag.Flag) zap.Field { 482 if f.Name == flagStorage { 483 hiddenQuery, err := url.Parse(f.Value.String()) 484 if err != nil { 485 return zap.String(f.Name, "<invalid URI>") 486 } 487 // hide all query here. 488 hiddenQuery.RawQuery = "" 489 return zap.Stringer(f.Name, hiddenQuery) 490 } 491 return zap.Stringer(f.Name, f.Value) 492 } 493 494 // LogArguments prints origin command arguments. 495 func LogArguments(cmd *cobra.Command) { 496 flags := cmd.Flags() 497 fields := make([]zap.Field, 1, flags.NFlag()+1) 498 fields[0] = zap.String("__command", cmd.CommandPath()) 499 flags.Visit(func(f *pflag.Flag) { 500 fields = append(fields, flagToZapField(f)) 501 }) 502 log.Info("arguments", fields...) 503 } 504 505 // GetKeepalive get the keepalive info from the config. 506 func GetKeepalive(cfg *Config) keepalive.ClientParameters { 507 return keepalive.ClientParameters{ 508 Time: cfg.GRPCKeepaliveTime, 509 Timeout: cfg.GRPCKeepaliveTimeout, 510 } 511 } 512 513 // adjust adjusts the abnormal config value in the current config. 514 // useful when not starting BR from CLI (e.g. from BRIE in SQL). 515 func (cfg *Config) adjust() { 516 if cfg.GRPCKeepaliveTime == 0 { 517 cfg.GRPCKeepaliveTime = defaultGRPCKeepaliveTime 518 } 519 if cfg.GRPCKeepaliveTimeout == 0 { 520 cfg.GRPCKeepaliveTimeout = defaultGRPCKeepaliveTimeout 521 } 522 if cfg.ChecksumConcurrency == 0 { 523 cfg.ChecksumConcurrency = variable.DefChecksumTableConcurrency 524 } 525 } 526 527 func normalizePDURL(pd string, useTLS bool) (string, error) { 528 if strings.HasPrefix(pd, "http://") { 529 if useTLS { 530 return "", errors.Annotate(berrors.ErrInvalidArgument, "pd url starts with http while TLS enabled") 531 } 532 return strings.TrimPrefix(pd, "http://"), nil 533 } 534 if strings.HasPrefix(pd, "https://") { 535 if !useTLS { 536 return "", errors.Annotate(berrors.ErrInvalidArgument, "pd url starts with https while TLS disabled") 537 } 538 return strings.TrimPrefix(pd, "https://"), nil 539 } 540 return pd, nil 541 } 542 543 // check whether it's a bug before #647, to solve case #1 544 // If the storage is set as gcs://bucket/prefix, 545 // the SSTs are written correctly to gcs://bucket/prefix/*.sst 546 // but the backupmeta is written wrongly to gcs://bucket/prefixbackupmeta. 547 // see details https://github.com/pingcap/br/issues/675#issuecomment-753780742 548 func gcsObjectNotFound(err error) bool { 549 return errors.Cause(err) == gcs.ErrObjectNotExist // nolint:errorlint 550 }