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 }