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