github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/cmd/tidb-lightning-ctl/main.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 main
    15  
    16  import (
    17  	"context"
    18  	"flag"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/google/uuid"
    26  	"github.com/pingcap/errors"
    27  	"github.com/pingcap/kvproto/pkg/import_sstpb"
    28  
    29  	"github.com/pingcap/br/pkg/lightning/backend"
    30  	"github.com/pingcap/br/pkg/lightning/backend/importer"
    31  	"github.com/pingcap/br/pkg/lightning/backend/local"
    32  	"github.com/pingcap/br/pkg/lightning/checkpoints"
    33  	"github.com/pingcap/br/pkg/lightning/common"
    34  	"github.com/pingcap/br/pkg/lightning/config"
    35  	"github.com/pingcap/br/pkg/lightning/restore"
    36  	"github.com/pingcap/br/pkg/lightning/tikv"
    37  )
    38  
    39  func main() {
    40  	if err := run(); err != nil {
    41  		fmt.Fprintln(os.Stderr, errors.ErrorStack(err))
    42  		exit(1)
    43  	}
    44  }
    45  
    46  // main_test.go override exit to pass unit test.
    47  var exit = os.Exit
    48  
    49  func run() error {
    50  	var (
    51  		compact, flagFetchMode                      *bool
    52  		mode, flagImportEngine, flagCleanupEngine   *string
    53  		cpRemove, cpErrIgnore, cpErrDestroy, cpDump *string
    54  		localStoringTables                          *bool
    55  
    56  		fsUsage func()
    57  	)
    58  
    59  	globalCfg := config.Must(config.LoadGlobalConfig(os.Args[1:], func(fs *flag.FlagSet) {
    60  		// change the default of `-d` from empty to 'noop://'.
    61  		// there is a check if `-d` points to a valid storage, and '' is not.
    62  		// since tidb-lightning-ctl does not need `-d` we change the default to a valid but harmless value.
    63  		dFlag := fs.Lookup("d")
    64  		_ = dFlag.Value.Set("noop://")
    65  		dFlag.DefValue = "noop://"
    66  
    67  		compact = fs.Bool("compact", false, "do manual compaction on the target cluster")
    68  		mode = fs.String("switch-mode", "", "switch tikv into import mode or normal mode, values can be ['import', 'normal']")
    69  		flagFetchMode = fs.Bool("fetch-mode", false, "obtain the current mode of every tikv in the cluster")
    70  
    71  		flagImportEngine = fs.String("import-engine", "", "manually import a closed engine (value can be '`db`.`table`:123' or a UUID")
    72  		flagCleanupEngine = fs.String("cleanup-engine", "", "manually delete a closed engine")
    73  
    74  		cpRemove = fs.String("checkpoint-remove", "", "remove the checkpoint associated with the given table (value can be 'all' or '`db`.`table`')")
    75  		cpErrIgnore = fs.String("checkpoint-error-ignore", "", "ignore errors encoutered previously on the given table (value can be 'all' or '`db`.`table`'); may corrupt this table if used incorrectly")
    76  		cpErrDestroy = fs.String("checkpoint-error-destroy", "", "deletes imported data with table which has an error before (value can be 'all' or '`db`.`table`')")
    77  		cpDump = fs.String("checkpoint-dump", "", "dump the checkpoint information as two CSV files in the given folder")
    78  
    79  		localStoringTables = fs.Bool("check-local-storage", false, "show tables that are missing local intermediate files (value can be 'all' or '`db`.`table`')")
    80  
    81  		fsUsage = fs.Usage
    82  	}))
    83  
    84  	ctx := context.Background()
    85  
    86  	cfg := config.NewConfig()
    87  	if err := cfg.LoadFromGlobal(globalCfg); err != nil {
    88  		return err
    89  	}
    90  	if err := cfg.Adjust(ctx); err != nil {
    91  		return err
    92  	}
    93  
    94  	tls, err := cfg.ToTLS()
    95  	if err != nil {
    96  		return err
    97  	}
    98  	if err = cfg.TiDB.Security.RegisterMySQL(); err != nil {
    99  		return err
   100  	}
   101  
   102  	if *compact {
   103  		return errors.Trace(compactCluster(ctx, cfg, tls))
   104  	}
   105  	if *flagFetchMode {
   106  		return errors.Trace(fetchMode(ctx, cfg, tls))
   107  	}
   108  	if len(*mode) != 0 {
   109  		return errors.Trace(switchMode(ctx, cfg, tls, *mode))
   110  	}
   111  	if len(*flagImportEngine) != 0 {
   112  		return errors.Trace(importEngine(ctx, cfg, tls, *flagImportEngine))
   113  	}
   114  	if len(*flagCleanupEngine) != 0 {
   115  		return errors.Trace(cleanupEngine(ctx, cfg, tls, *flagCleanupEngine))
   116  	}
   117  
   118  	if len(*cpRemove) != 0 {
   119  		return errors.Trace(checkpointRemove(ctx, cfg, *cpRemove))
   120  	}
   121  	if len(*cpErrIgnore) != 0 {
   122  		return errors.Trace(checkpointErrorIgnore(ctx, cfg, *cpErrIgnore))
   123  	}
   124  	if len(*cpErrDestroy) != 0 {
   125  		return errors.Trace(checkpointErrorDestroy(ctx, cfg, tls, *cpErrDestroy))
   126  	}
   127  	if len(*cpDump) != 0 {
   128  		return errors.Trace(checkpointDump(ctx, cfg, *cpDump))
   129  	}
   130  	if *localStoringTables {
   131  		return errors.Trace(getLocalStoringTables(ctx, cfg))
   132  	}
   133  
   134  	fsUsage()
   135  	return nil
   136  }
   137  
   138  func compactCluster(ctx context.Context, cfg *config.Config, tls *common.TLS) error {
   139  	return tikv.ForAllStores(
   140  		ctx,
   141  		tls.WithHost(cfg.TiDB.PdAddr),
   142  		tikv.StoreStateDisconnected,
   143  		func(c context.Context, store *tikv.Store) error {
   144  			return tikv.Compact(c, tls, store.Address, restore.FullLevelCompact)
   145  		},
   146  	)
   147  }
   148  
   149  func switchMode(ctx context.Context, cfg *config.Config, tls *common.TLS, mode string) error {
   150  	var m import_sstpb.SwitchMode
   151  	switch mode {
   152  	case config.ImportMode:
   153  		m = import_sstpb.SwitchMode_Import
   154  	case config.NormalMode:
   155  		m = import_sstpb.SwitchMode_Normal
   156  	default:
   157  		return errors.Errorf("invalid mode %s, must use %s or %s", mode, config.ImportMode, config.NormalMode)
   158  	}
   159  
   160  	return tikv.ForAllStores(
   161  		ctx,
   162  		tls.WithHost(cfg.TiDB.PdAddr),
   163  		tikv.StoreStateDisconnected,
   164  		func(c context.Context, store *tikv.Store) error {
   165  			return tikv.SwitchMode(c, tls, store.Address, m)
   166  		},
   167  	)
   168  }
   169  
   170  func fetchMode(ctx context.Context, cfg *config.Config, tls *common.TLS) error {
   171  	return tikv.ForAllStores(
   172  		ctx,
   173  		tls.WithHost(cfg.TiDB.PdAddr),
   174  		tikv.StoreStateDisconnected,
   175  		func(c context.Context, store *tikv.Store) error {
   176  			mode, err := tikv.FetchMode(c, tls, store.Address)
   177  			if err != nil {
   178  				fmt.Fprintf(os.Stderr, "%-30s | Error: %v\n", store.Address, err)
   179  			} else {
   180  				fmt.Fprintf(os.Stderr, "%-30s | %s mode\n", store.Address, mode)
   181  			}
   182  			return nil
   183  		},
   184  	)
   185  }
   186  
   187  func checkpointRemove(ctx context.Context, cfg *config.Config, tableName string) error {
   188  	cpdb, err := checkpoints.OpenCheckpointsDB(ctx, cfg)
   189  	if err != nil {
   190  		return errors.Trace(err)
   191  	}
   192  	defer cpdb.Close()
   193  
   194  	return errors.Trace(cpdb.RemoveCheckpoint(ctx, tableName))
   195  }
   196  
   197  func checkpointErrorIgnore(ctx context.Context, cfg *config.Config, tableName string) error {
   198  	cpdb, err := checkpoints.OpenCheckpointsDB(ctx, cfg)
   199  	if err != nil {
   200  		return errors.Trace(err)
   201  	}
   202  	defer cpdb.Close()
   203  
   204  	return errors.Trace(cpdb.IgnoreErrorCheckpoint(ctx, tableName))
   205  }
   206  
   207  func checkpointErrorDestroy(ctx context.Context, cfg *config.Config, tls *common.TLS, tableName string) error {
   208  	cpdb, err := checkpoints.OpenCheckpointsDB(ctx, cfg)
   209  	if err != nil {
   210  		return errors.Trace(err)
   211  	}
   212  	defer cpdb.Close()
   213  
   214  	target, err := restore.NewTiDBManager(cfg.TiDB, tls)
   215  	if err != nil {
   216  		return errors.Trace(err)
   217  	}
   218  	defer target.Close()
   219  
   220  	targetTables, err := cpdb.DestroyErrorCheckpoint(ctx, tableName)
   221  	if err != nil {
   222  		return errors.Trace(err)
   223  	}
   224  
   225  	var lastErr error
   226  
   227  	for _, table := range targetTables {
   228  		fmt.Fprintln(os.Stderr, "Dropping table:", table.TableName)
   229  		err := target.DropTable(ctx, table.TableName)
   230  		if err != nil {
   231  			fmt.Fprintln(os.Stderr, "* Encountered error while dropping table:", err)
   232  			lastErr = err
   233  		}
   234  	}
   235  
   236  	if cfg.TikvImporter.Backend == "importer" {
   237  		importer, err := importer.NewImporter(ctx, tls, cfg.TikvImporter.Addr, cfg.TiDB.PdAddr)
   238  		if err != nil {
   239  			return errors.Trace(err)
   240  		}
   241  		defer importer.Close()
   242  
   243  		for _, table := range targetTables {
   244  			for engineID := table.MinEngineID; engineID <= table.MaxEngineID; engineID++ {
   245  				fmt.Fprintln(os.Stderr, "Closing and cleaning up engine:", table.TableName, engineID)
   246  				closedEngine, err := importer.UnsafeCloseEngine(ctx, nil, table.TableName, engineID)
   247  				if err != nil {
   248  					fmt.Fprintln(os.Stderr, "* Encountered error while closing engine:", err)
   249  					lastErr = err
   250  				} else if err := closedEngine.Cleanup(ctx); err != nil {
   251  					lastErr = err
   252  				}
   253  			}
   254  		}
   255  	}
   256  	// For importer backend, engine was stored in importer's memory, we can retrieve it from alive importer process.
   257  	// But in local backend, if we want to use common API `UnsafeCloseEngine` and `Cleanup`,
   258  	// we need either lightning process alive or engine map persistent.
   259  	// both of them seems unnecessary if we only need to do is cleanup specify engine directory.
   260  	// so we didn't choose to use common API.
   261  	if cfg.TikvImporter.Backend == "local" {
   262  		for _, table := range targetTables {
   263  			for engineID := table.MinEngineID; engineID <= table.MaxEngineID; engineID++ {
   264  				fmt.Fprintln(os.Stderr, "Closing and cleaning up engine:", table.TableName, engineID)
   265  				_, eID := backend.MakeUUID(table.TableName, engineID)
   266  				file := local.File{UUID: eID}
   267  				err := file.Cleanup(cfg.TikvImporter.SortedKVDir)
   268  				if err != nil {
   269  					fmt.Fprintln(os.Stderr, "* Encountered error while cleanup engine:", err)
   270  					lastErr = err
   271  				}
   272  			}
   273  		}
   274  	}
   275  
   276  	return errors.Trace(lastErr)
   277  }
   278  
   279  func checkpointDump(ctx context.Context, cfg *config.Config, dumpFolder string) error {
   280  	cpdb, err := checkpoints.OpenCheckpointsDB(ctx, cfg)
   281  	if err != nil {
   282  		return errors.Trace(err)
   283  	}
   284  	defer cpdb.Close()
   285  
   286  	if err := os.MkdirAll(dumpFolder, 0o755); err != nil {
   287  		return errors.Trace(err)
   288  	}
   289  
   290  	tablesFileName := filepath.Join(dumpFolder, "tables.csv")
   291  	tablesFile, err := os.Create(tablesFileName)
   292  	if err != nil {
   293  		return errors.Annotatef(err, "failed to create %s", tablesFileName)
   294  	}
   295  	defer tablesFile.Close()
   296  
   297  	enginesFileName := filepath.Join(dumpFolder, "engines.csv")
   298  	enginesFile, err := os.Create(tablesFileName)
   299  	if err != nil {
   300  		return errors.Annotatef(err, "failed to create %s", enginesFileName)
   301  	}
   302  	defer enginesFile.Close()
   303  
   304  	chunksFileName := filepath.Join(dumpFolder, "chunks.csv")
   305  	chunksFile, err := os.Create(chunksFileName)
   306  	if err != nil {
   307  		return errors.Annotatef(err, "failed to create %s", chunksFileName)
   308  	}
   309  	defer chunksFile.Close()
   310  
   311  	if err := cpdb.DumpTables(ctx, tablesFile); err != nil {
   312  		return errors.Trace(err)
   313  	}
   314  	if err := cpdb.DumpEngines(ctx, enginesFile); err != nil {
   315  		return errors.Trace(err)
   316  	}
   317  	if err := cpdb.DumpChunks(ctx, chunksFile); err != nil {
   318  		return errors.Trace(err)
   319  	}
   320  	return nil
   321  }
   322  
   323  func getLocalStoringTables(ctx context.Context, cfg *config.Config) (err2 error) {
   324  	//nolint:prealloc // This is a placeholder.
   325  	var tables []string
   326  	defer func() {
   327  		if err2 == nil {
   328  			if len(tables) == 0 {
   329  				fmt.Fprintln(os.Stderr, "No table has lost intermediate files according to given config")
   330  			} else {
   331  				fmt.Fprintln(os.Stderr, "These tables are missing intermediate files:", tables)
   332  			}
   333  		}
   334  	}()
   335  
   336  	if cfg.TikvImporter.Backend != config.BackendLocal {
   337  		return nil
   338  	}
   339  	exist, err := checkpoints.IsCheckpointsDBExists(ctx, cfg)
   340  	if err != nil {
   341  		return errors.Trace(err)
   342  	}
   343  	if !exist {
   344  		return nil
   345  	}
   346  	cpdb, err := checkpoints.OpenCheckpointsDB(ctx, cfg)
   347  	if err != nil {
   348  		return errors.Trace(err)
   349  	}
   350  	defer cpdb.Close()
   351  
   352  	tableWithEngine, err := cpdb.GetLocalStoringTables(ctx)
   353  	if err != nil {
   354  		return errors.Trace(err)
   355  	}
   356  	tables = make([]string, 0, len(tableWithEngine))
   357  	for tableName := range tableWithEngine {
   358  		tables = append(tables, tableName)
   359  	}
   360  
   361  	return nil
   362  }
   363  
   364  func unsafeCloseEngine(ctx context.Context, importer backend.Backend, engine string) (*backend.ClosedEngine, error) {
   365  	if index := strings.LastIndexByte(engine, ':'); index >= 0 {
   366  		tableName := engine[:index]
   367  		engineID, err := strconv.Atoi(engine[index+1:])
   368  		if err != nil {
   369  			return nil, errors.Trace(err)
   370  		}
   371  		ce, err := importer.UnsafeCloseEngine(ctx, nil, tableName, int32(engineID))
   372  		return ce, errors.Trace(err)
   373  	}
   374  
   375  	engineUUID, err := uuid.Parse(engine)
   376  	if err != nil {
   377  		return nil, errors.Trace(err)
   378  	}
   379  
   380  	ce, err := importer.UnsafeCloseEngineWithUUID(ctx, nil, "<tidb-lightning-ctl>", engineUUID)
   381  	return ce, errors.Trace(err)
   382  }
   383  
   384  func importEngine(ctx context.Context, cfg *config.Config, tls *common.TLS, engine string) error {
   385  	importer, err := importer.NewImporter(ctx, tls, cfg.TikvImporter.Addr, cfg.TiDB.PdAddr)
   386  	if err != nil {
   387  		return errors.Trace(err)
   388  	}
   389  
   390  	ce, err := unsafeCloseEngine(ctx, importer, engine)
   391  	if err != nil {
   392  		return errors.Trace(err)
   393  	}
   394  
   395  	return errors.Trace(ce.Import(ctx))
   396  }
   397  
   398  func cleanupEngine(ctx context.Context, cfg *config.Config, tls *common.TLS, engine string) error {
   399  	importer, err := importer.NewImporter(ctx, tls, cfg.TikvImporter.Addr, cfg.TiDB.PdAddr)
   400  	if err != nil {
   401  		return errors.Trace(err)
   402  	}
   403  
   404  	ce, err := unsafeCloseEngine(ctx, importer, engine)
   405  	if err != nil {
   406  		return errors.Trace(err)
   407  	}
   408  
   409  	return errors.Trace(ce.Cleanup(ctx))
   410  }