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 }