001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.backup.impl; 019 020import static org.apache.hadoop.hbase.backup.BackupInfo.withState; 021import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_BACKUP_LIST_DESC; 022import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_BANDWIDTH; 023import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_BANDWIDTH_DESC; 024import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_DEBUG; 025import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_DEBUG_DESC; 026import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_IGNORECHECKSUM; 027import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_IGNORECHECKSUM_DESC; 028import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_KEEP; 029import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_KEEP_DESC; 030import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_LIST; 031import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_PATH; 032import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_PATH_DESC; 033import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_RECORD_NUMBER; 034import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_RECORD_NUMBER_DESC; 035import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_SET; 036import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_SET_BACKUP_DESC; 037import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_SET_DESC; 038import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TABLE; 039import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TABLE_DESC; 040import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TABLE_LIST_DESC; 041import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_WORKERS; 042import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_WORKERS_DESC; 043import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_YARN_QUEUE_NAME; 044import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_YARN_QUEUE_NAME_DESC; 045import static org.apache.hadoop.hbase.backup.impl.BackupSystemTable.Order.NEW_TO_OLD; 046 047import java.io.IOException; 048import java.net.URI; 049import java.util.List; 050import org.apache.commons.lang3.StringUtils; 051import org.apache.hadoop.conf.Configuration; 052import org.apache.hadoop.conf.Configured; 053import org.apache.hadoop.fs.FileSystem; 054import org.apache.hadoop.fs.Path; 055import org.apache.hadoop.hbase.HBaseConfiguration; 056import org.apache.hadoop.hbase.TableName; 057import org.apache.hadoop.hbase.backup.BackupAdmin; 058import org.apache.hadoop.hbase.backup.BackupInfo; 059import org.apache.hadoop.hbase.backup.BackupInfo.BackupState; 060import org.apache.hadoop.hbase.backup.BackupRequest; 061import org.apache.hadoop.hbase.backup.BackupRestoreConstants; 062import org.apache.hadoop.hbase.backup.BackupRestoreConstants.BackupCommand; 063import org.apache.hadoop.hbase.backup.BackupType; 064import org.apache.hadoop.hbase.backup.HBackupFileSystem; 065import org.apache.hadoop.hbase.backup.util.BackupSet; 066import org.apache.hadoop.hbase.backup.util.BackupUtils; 067import org.apache.hadoop.hbase.client.Connection; 068import org.apache.hadoop.hbase.client.ConnectionFactory; 069import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 070import org.apache.yetus.audience.InterfaceAudience; 071 072import org.apache.hbase.thirdparty.com.google.common.base.Splitter; 073import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 074import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; 075import org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter; 076import org.apache.hbase.thirdparty.org.apache.commons.cli.Options; 077 078/** 079 * General backup commands, options and usage messages 080 */ 081@InterfaceAudience.Private 082public final class BackupCommands { 083 public final static String INCORRECT_USAGE = "Incorrect usage"; 084 085 public final static String TOP_LEVEL_NOT_ALLOWED = 086 "Top level (root) folder is not allowed to be a backup destination"; 087 088 public static final String USAGE = "Usage: hbase backup COMMAND [command-specific arguments]\n" 089 + "where COMMAND is one of:\n" + " create create a new backup image\n" 090 + " delete delete an existing backup image\n" 091 + " describe show the detailed information of a backup image\n" 092 + " history show history of all successful backups\n" 093 + " progress show the progress of the latest backup request\n" 094 + " set backup set management\n" + " repair repair backup system table\n" 095 + " merge merge backup images\n" 096 + "Run \'hbase backup COMMAND -h\' to see help message for each command\n"; 097 098 public static final String CREATE_CMD_USAGE = 099 "Usage: hbase backup create <type> <backup_path> [options]\n" 100 + " type \"full\" to create a full backup image\n" 101 + " \"incremental\" to create an incremental backup image\n" 102 + " backup_path Full path to store the backup image\n"; 103 104 public static final String PROGRESS_CMD_USAGE = "Usage: hbase backup progress <backup_id>\n" 105 + " backup_id Backup image id (optional). If no id specified, the command will show\n" 106 + " progress for currently running backup session."; 107 public static final String NO_INFO_FOUND = "No info was found for backup id: "; 108 public static final String NO_ACTIVE_SESSION_FOUND = "No active backup sessions found."; 109 110 public static final String DESCRIBE_CMD_USAGE = 111 "Usage: hbase backup describe <backup_id>\n" + " backup_id Backup image id\n"; 112 113 public static final String HISTORY_CMD_USAGE = "Usage: hbase backup history [options]"; 114 115 public static final String DELETE_CMD_USAGE = "Usage: hbase backup delete [options]"; 116 117 public static final String REPAIR_CMD_USAGE = "Usage: hbase backup repair\n"; 118 119 public static final String SET_CMD_USAGE = "Usage: hbase backup set COMMAND [name] [tables]\n" 120 + " name Backup set name\n" + " tables Comma separated list of tables.\n" 121 + "COMMAND is one of:\n" + " add add tables to a set, create a set if needed\n" 122 + " remove remove tables from a set\n" 123 + " list list all backup sets in the system\n" + " describe describe set\n" 124 + " delete delete backup set\n"; 125 public static final String MERGE_CMD_USAGE = "Usage: hbase backup merge [backup_ids]\n" 126 + " backup_ids Comma separated list of backup image ids.\n"; 127 128 public static final String USAGE_FOOTER = ""; 129 130 public static abstract class Command extends Configured { 131 CommandLine cmdline; 132 Connection conn; 133 134 Command(Configuration conf) { 135 if (conf == null) { 136 conf = HBaseConfiguration.create(); 137 } 138 setConf(conf); 139 } 140 141 public void execute() throws IOException { 142 if (cmdline.hasOption("h") || cmdline.hasOption("help")) { 143 printUsage(); 144 throw new IOException(INCORRECT_USAGE); 145 } 146 147 if (cmdline.hasOption(OPTION_YARN_QUEUE_NAME)) { 148 String queueName = cmdline.getOptionValue(OPTION_YARN_QUEUE_NAME); 149 // Set MR job queuename to configuration 150 getConf().set("mapreduce.job.queuename", queueName); 151 } 152 153 // Create connection 154 conn = ConnectionFactory.createConnection(getConf()); 155 if (requiresNoActiveSession()) { 156 // Check active session 157 try (BackupSystemTable table = new BackupSystemTable(conn)) { 158 List<BackupInfo> sessions = 159 table.getBackupHistory(NEW_TO_OLD, 1, withState(BackupState.RUNNING)); 160 161 if (sessions.size() > 0) { 162 System.err.println("Found backup session in a RUNNING state: "); 163 System.err.println(sessions.get(0)); 164 System.err.println("This may indicate that a previous session has failed abnormally."); 165 System.err.println("In this case, backup recovery is recommended."); 166 throw new IOException("Active session found, aborted command execution"); 167 } 168 } 169 } 170 if (requiresConsistentState()) { 171 // Check failed delete 172 try (BackupSystemTable table = new BackupSystemTable(conn)) { 173 String[] ids = table.getListOfBackupIdsFromDeleteOperation(); 174 175 if (ids != null && ids.length > 0) { 176 System.err.println("Found failed backup DELETE coommand. "); 177 System.err.println("Backup system recovery is required."); 178 throw new IOException("Failed backup DELETE found, aborted command execution"); 179 } 180 181 ids = table.getListOfBackupIdsFromMergeOperation(); 182 if (ids != null && ids.length > 0) { 183 System.err.println("Found failed backup MERGE coommand. "); 184 System.err.println("Backup system recovery is required."); 185 throw new IOException("Failed backup MERGE found, aborted command execution"); 186 } 187 } 188 } 189 } 190 191 public void finish() throws IOException { 192 if (conn != null) { 193 conn.close(); 194 } 195 } 196 197 protected abstract void printUsage(); 198 199 /** 200 * The command can't be run if active backup session is in progress 201 * @return true if no active sessions are in progress 202 */ 203 protected boolean requiresNoActiveSession() { 204 return false; 205 } 206 207 /** 208 * Command requires consistent state of a backup system Backup system may become inconsistent 209 * because of an abnormal termination of a backup session or delete command 210 * @return true, if yes 211 */ 212 protected boolean requiresConsistentState() { 213 return false; 214 } 215 } 216 217 private BackupCommands() { 218 throw new AssertionError("Instantiating utility class..."); 219 } 220 221 public static Command createCommand(Configuration conf, BackupCommand type, CommandLine cmdline) { 222 Command cmd; 223 switch (type) { 224 case CREATE: 225 cmd = new CreateCommand(conf, cmdline); 226 break; 227 case DESCRIBE: 228 cmd = new DescribeCommand(conf, cmdline); 229 break; 230 case PROGRESS: 231 cmd = new ProgressCommand(conf, cmdline); 232 break; 233 case DELETE: 234 cmd = new DeleteCommand(conf, cmdline); 235 break; 236 case HISTORY: 237 cmd = new HistoryCommand(conf, cmdline); 238 break; 239 case SET: 240 cmd = new BackupSetCommand(conf, cmdline); 241 break; 242 case REPAIR: 243 cmd = new RepairCommand(conf, cmdline); 244 break; 245 case MERGE: 246 cmd = new MergeCommand(conf, cmdline); 247 break; 248 case HELP: 249 default: 250 cmd = new HelpCommand(conf, cmdline); 251 break; 252 } 253 return cmd; 254 } 255 256 static int numOfArgs(String[] args) { 257 if (args == null) { 258 return 0; 259 } 260 261 return args.length; 262 } 263 264 public static class CreateCommand extends Command { 265 CreateCommand(Configuration conf, CommandLine cmdline) { 266 super(conf); 267 this.cmdline = cmdline; 268 } 269 270 @Override 271 protected boolean requiresNoActiveSession() { 272 return true; 273 } 274 275 @Override 276 protected boolean requiresConsistentState() { 277 return true; 278 } 279 280 @Override 281 public void execute() throws IOException { 282 if (cmdline == null || cmdline.getArgs() == null) { 283 printUsage(); 284 throw new IOException(INCORRECT_USAGE); 285 } 286 String[] args = cmdline.getArgs(); 287 if (args.length != 3) { 288 printUsage(); 289 throw new IOException(INCORRECT_USAGE); 290 } 291 292 if ( 293 !BackupType.FULL.toString().equalsIgnoreCase(args[1]) 294 && !BackupType.INCREMENTAL.toString().equalsIgnoreCase(args[1]) 295 ) { 296 System.out.println("ERROR: invalid backup type: " + args[1]); 297 printUsage(); 298 throw new IOException(INCORRECT_USAGE); 299 } 300 if (!verifyPath(args[2])) { 301 System.out.println("ERROR: invalid backup destination: " + args[2]); 302 printUsage(); 303 throw new IOException(INCORRECT_USAGE); 304 } 305 String targetBackupDir = args[2]; 306 // Check if backup destination is top level (root) folder - not allowed 307 if (isRootFolder(targetBackupDir)) { 308 throw new IOException(TOP_LEVEL_NOT_ALLOWED); 309 } 310 String tables; 311 312 // Check if we have both: backup set and list of tables 313 if (cmdline.hasOption(OPTION_TABLE) && cmdline.hasOption(OPTION_SET)) { 314 System.out 315 .println("ERROR: You can specify either backup set or list" + " of tables, but not both"); 316 printUsage(); 317 throw new IOException(INCORRECT_USAGE); 318 } 319 // Creates connection 320 super.execute(); 321 // Check backup set 322 String setName = null; 323 if (cmdline.hasOption(OPTION_SET)) { 324 setName = cmdline.getOptionValue(OPTION_SET); 325 tables = getTablesForSet(setName); 326 327 if (tables == null) { 328 System.out 329 .println("ERROR: Backup set '" + setName + "' is either empty or does not exist"); 330 printUsage(); 331 throw new IOException(INCORRECT_USAGE); 332 } 333 } else { 334 tables = cmdline.getOptionValue(OPTION_TABLE); 335 } 336 int bandwidth = cmdline.hasOption(OPTION_BANDWIDTH) 337 ? Integer.parseInt(cmdline.getOptionValue(OPTION_BANDWIDTH)) 338 : -1; 339 int workers = cmdline.hasOption(OPTION_WORKERS) 340 ? Integer.parseInt(cmdline.getOptionValue(OPTION_WORKERS)) 341 : -1; 342 343 boolean ignoreChecksum = cmdline.hasOption(OPTION_IGNORECHECKSUM); 344 345 try (BackupAdminImpl admin = new BackupAdminImpl(conn)) { 346 BackupRequest.Builder builder = new BackupRequest.Builder(); 347 BackupRequest request = builder.withBackupType(BackupType.valueOf(args[1].toUpperCase())) 348 .withTableList( 349 tables != null ? Lists.newArrayList(BackupUtils.parseTableNames(tables)) : null) 350 .withTargetRootDir(targetBackupDir).withTotalTasks(workers) 351 .withBandwidthPerTasks(bandwidth).withNoChecksumVerify(ignoreChecksum) 352 .withBackupSetName(setName).build(); 353 String backupId = admin.backupTables(request); 354 System.out.println("Backup session " + backupId + " finished. Status: SUCCESS"); 355 } catch (IOException e) { 356 System.out.println("Backup session finished. Status: FAILURE"); 357 throw e; 358 } 359 } 360 361 private boolean isRootFolder(String targetBackupDir) { 362 Path p = new Path(targetBackupDir); 363 return p.isRoot(); 364 } 365 366 private boolean verifyPath(String path) { 367 try { 368 Path p = new Path(path); 369 Configuration conf = getConf() != null ? getConf() : HBaseConfiguration.create(); 370 URI uri = p.toUri(); 371 372 if (uri.getScheme() == null) { 373 return false; 374 } 375 376 FileSystem.get(uri, conf); 377 return true; 378 } catch (Exception e) { 379 return false; 380 } 381 } 382 383 private String getTablesForSet(String name) throws IOException { 384 try (final BackupSystemTable table = new BackupSystemTable(conn)) { 385 List<TableName> tables = table.describeBackupSet(name); 386 387 if (tables == null) { 388 return null; 389 } 390 391 return StringUtils.join(tables, BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND); 392 } 393 } 394 395 @Override 396 protected void printUsage() { 397 System.out.println(CREATE_CMD_USAGE); 398 Options options = new Options(); 399 options.addOption(OPTION_WORKERS, true, OPTION_WORKERS_DESC); 400 options.addOption(OPTION_BANDWIDTH, true, OPTION_BANDWIDTH_DESC); 401 options.addOption(OPTION_SET, true, OPTION_SET_BACKUP_DESC); 402 options.addOption(OPTION_TABLE, true, OPTION_TABLE_LIST_DESC); 403 options.addOption(OPTION_YARN_QUEUE_NAME, true, OPTION_YARN_QUEUE_NAME_DESC); 404 options.addOption(OPTION_DEBUG, false, OPTION_DEBUG_DESC); 405 options.addOption(OPTION_IGNORECHECKSUM, false, OPTION_IGNORECHECKSUM_DESC); 406 407 HelpFormatter helpFormatter = new HelpFormatter(); 408 helpFormatter.setLeftPadding(2); 409 helpFormatter.setDescPadding(8); 410 helpFormatter.setWidth(100); 411 helpFormatter.setSyntaxPrefix("Options:"); 412 helpFormatter.printHelp(" ", null, options, USAGE_FOOTER); 413 } 414 } 415 416 public static class HelpCommand extends Command { 417 HelpCommand(Configuration conf, CommandLine cmdline) { 418 super(conf); 419 this.cmdline = cmdline; 420 } 421 422 @Override 423 public void execute() throws IOException { 424 if (cmdline == null) { 425 printUsage(); 426 throw new IOException(INCORRECT_USAGE); 427 } 428 429 String[] args = cmdline.getArgs(); 430 if (args == null || args.length == 0) { 431 printUsage(); 432 throw new IOException(INCORRECT_USAGE); 433 } 434 435 if (args.length != 2) { 436 System.out.println("ERROR: Only supports help message of a single command type"); 437 printUsage(); 438 throw new IOException(INCORRECT_USAGE); 439 } 440 441 String type = args[1]; 442 443 if (BackupCommand.CREATE.name().equalsIgnoreCase(type)) { 444 System.out.println(CREATE_CMD_USAGE); 445 } else if (BackupCommand.DESCRIBE.name().equalsIgnoreCase(type)) { 446 System.out.println(DESCRIBE_CMD_USAGE); 447 } else if (BackupCommand.HISTORY.name().equalsIgnoreCase(type)) { 448 System.out.println(HISTORY_CMD_USAGE); 449 } else if (BackupCommand.PROGRESS.name().equalsIgnoreCase(type)) { 450 System.out.println(PROGRESS_CMD_USAGE); 451 } else if (BackupCommand.DELETE.name().equalsIgnoreCase(type)) { 452 System.out.println(DELETE_CMD_USAGE); 453 } else if (BackupCommand.SET.name().equalsIgnoreCase(type)) { 454 System.out.println(SET_CMD_USAGE); 455 } else { 456 System.out.println("Unknown command : " + type); 457 printUsage(); 458 } 459 } 460 461 @Override 462 protected void printUsage() { 463 System.out.println(USAGE); 464 } 465 } 466 467 public static class DescribeCommand extends Command { 468 DescribeCommand(Configuration conf, CommandLine cmdline) { 469 super(conf); 470 this.cmdline = cmdline; 471 } 472 473 @Override 474 public void execute() throws IOException { 475 if (cmdline == null || cmdline.getArgs() == null) { 476 printUsage(); 477 throw new IOException(INCORRECT_USAGE); 478 } 479 String[] args = cmdline.getArgs(); 480 if (args.length != 2) { 481 printUsage(); 482 throw new IOException(INCORRECT_USAGE); 483 } 484 485 super.execute(); 486 487 String backupId = args[1]; 488 try (final BackupSystemTable sysTable = new BackupSystemTable(conn)) { 489 BackupInfo info = sysTable.readBackupInfo(backupId); 490 if (info == null) { 491 System.out.println("ERROR: " + backupId + " does not exist"); 492 printUsage(); 493 throw new IOException(INCORRECT_USAGE); 494 } 495 System.out.println(info.getShortDescription()); 496 } 497 } 498 499 @Override 500 protected void printUsage() { 501 System.out.println(DESCRIBE_CMD_USAGE); 502 } 503 } 504 505 public static class ProgressCommand extends Command { 506 ProgressCommand(Configuration conf, CommandLine cmdline) { 507 super(conf); 508 this.cmdline = cmdline; 509 } 510 511 @Override 512 public void execute() throws IOException { 513 514 if (cmdline == null || cmdline.getArgs() == null || cmdline.getArgs().length == 1) { 515 System.out.println( 516 "No backup id was specified, " + "will retrieve the most recent (ongoing) session"); 517 } 518 String[] args = cmdline == null ? null : cmdline.getArgs(); 519 if (args != null && args.length > 2) { 520 System.err.println("ERROR: wrong number of arguments: " + args.length); 521 printUsage(); 522 throw new IOException(INCORRECT_USAGE); 523 } 524 525 super.execute(); 526 527 String backupId = (args == null || args.length <= 1) ? null : args[1]; 528 try (final BackupSystemTable sysTable = new BackupSystemTable(conn)) { 529 BackupInfo info = null; 530 531 if (backupId != null) { 532 info = sysTable.readBackupInfo(backupId); 533 } else { 534 List<BackupInfo> infos = 535 sysTable.getBackupHistory(NEW_TO_OLD, 1, withState(BackupState.RUNNING)); 536 if (infos != null && infos.size() > 0) { 537 info = infos.get(0); 538 backupId = info.getBackupId(); 539 System.out.println("Found ongoing session with backupId=" + backupId); 540 } 541 } 542 int progress = info == null ? -1 : info.getProgress(); 543 if (progress < 0) { 544 if (backupId != null) { 545 System.out.println(NO_INFO_FOUND + backupId); 546 } else { 547 System.err.println(NO_ACTIVE_SESSION_FOUND); 548 } 549 } else { 550 System.out.println(backupId + " progress=" + progress + "%"); 551 } 552 } 553 } 554 555 @Override 556 protected void printUsage() { 557 System.out.println(PROGRESS_CMD_USAGE); 558 } 559 } 560 561 public static class DeleteCommand extends Command { 562 DeleteCommand(Configuration conf, CommandLine cmdline) { 563 super(conf); 564 this.cmdline = cmdline; 565 } 566 567 @Override 568 protected boolean requiresNoActiveSession() { 569 return true; 570 } 571 572 @Override 573 public void execute() throws IOException { 574 575 if (cmdline == null || cmdline.getArgs() == null || cmdline.getArgs().length < 1) { 576 printUsage(); 577 throw new IOException(INCORRECT_USAGE); 578 } 579 580 if (!cmdline.hasOption(OPTION_KEEP) && !cmdline.hasOption(OPTION_LIST)) { 581 printUsage(); 582 throw new IOException(INCORRECT_USAGE); 583 } 584 super.execute(); 585 if (cmdline.hasOption(OPTION_KEEP)) { 586 executeDeleteOlderThan(cmdline); 587 } else if (cmdline.hasOption(OPTION_LIST)) { 588 executeDeleteListOfBackups(cmdline); 589 } 590 } 591 592 private void executeDeleteOlderThan(CommandLine cmdline) throws IOException { 593 String value = cmdline.getOptionValue(OPTION_KEEP); 594 int days = 0; 595 try { 596 days = Integer.parseInt(value); 597 } catch (NumberFormatException e) { 598 throw new IOException(value + " is not an integer number"); 599 } 600 final long fdays = days; 601 BackupInfo.Filter dateFilter = info -> { 602 long currentTime = EnvironmentEdgeManager.currentTime(); 603 long maxTsToDelete = currentTime - fdays * 24 * 3600 * 1000; 604 return info.getCompleteTs() <= maxTsToDelete; 605 }; 606 List<BackupInfo> history = null; 607 try (final BackupSystemTable sysTable = new BackupSystemTable(conn); 608 BackupAdminImpl admin = new BackupAdminImpl(conn)) { 609 history = sysTable.getBackupHistory(dateFilter); 610 String[] backupIds = convertToBackupIds(history); 611 int deleted = admin.deleteBackups(backupIds); 612 System.out.println("Deleted " + deleted + " backups. Total older than " + days + " days: " 613 + backupIds.length); 614 } catch (IOException e) { 615 System.err.println("Delete command FAILED. Please run backup repair tool to restore backup " 616 + "system integrity"); 617 throw e; 618 } 619 } 620 621 private String[] convertToBackupIds(List<BackupInfo> history) { 622 String[] ids = new String[history.size()]; 623 for (int i = 0; i < ids.length; i++) { 624 ids[i] = history.get(i).getBackupId(); 625 } 626 return ids; 627 } 628 629 private void executeDeleteListOfBackups(CommandLine cmdline) throws IOException { 630 String value = cmdline.getOptionValue(OPTION_LIST); 631 String[] backupIds = value.split(","); 632 633 try (BackupAdminImpl admin = new BackupAdminImpl(conn)) { 634 int deleted = admin.deleteBackups(backupIds); 635 System.out.println("Deleted " + deleted + " backups. Total requested: " + backupIds.length); 636 } catch (IOException e) { 637 System.err.println("Delete command FAILED. Please run backup repair tool to restore backup " 638 + "system integrity"); 639 throw e; 640 } 641 642 } 643 644 @Override 645 protected void printUsage() { 646 System.out.println(DELETE_CMD_USAGE); 647 Options options = new Options(); 648 options.addOption(OPTION_KEEP, true, OPTION_KEEP_DESC); 649 options.addOption(OPTION_LIST, true, OPTION_BACKUP_LIST_DESC); 650 651 HelpFormatter helpFormatter = new HelpFormatter(); 652 helpFormatter.setLeftPadding(2); 653 helpFormatter.setDescPadding(8); 654 helpFormatter.setWidth(100); 655 helpFormatter.setSyntaxPrefix("Options:"); 656 helpFormatter.printHelp(" ", null, options, USAGE_FOOTER); 657 658 } 659 } 660 661 public static class RepairCommand extends Command { 662 RepairCommand(Configuration conf, CommandLine cmdline) { 663 super(conf); 664 this.cmdline = cmdline; 665 } 666 667 @Override 668 public void execute() throws IOException { 669 super.execute(); 670 671 String[] args = cmdline == null ? null : cmdline.getArgs(); 672 if (args != null && args.length > 1) { 673 System.err.println("ERROR: wrong number of arguments: " + args.length); 674 printUsage(); 675 throw new IOException(INCORRECT_USAGE); 676 } 677 678 Configuration conf = getConf() != null ? getConf() : HBaseConfiguration.create(); 679 try (final Connection conn = ConnectionFactory.createConnection(conf); 680 final BackupSystemTable sysTable = new BackupSystemTable(conn)) { 681 // Failed backup 682 BackupInfo backupInfo; 683 List<BackupInfo> list = 684 sysTable.getBackupHistory(NEW_TO_OLD, 1, withState(BackupState.RUNNING)); 685 if (list.size() == 0) { 686 // No failed sessions found 687 System.out.println("REPAIR status: no failed sessions found." 688 + " Checking failed delete backup operation ..."); 689 repairFailedBackupDeletionIfAny(conn, sysTable); 690 repairFailedBackupMergeIfAny(conn, sysTable); 691 return; 692 } 693 backupInfo = list.get(0); 694 // If this is a cancel exception, then we've already cleaned. 695 // set the failure timestamp of the overall backup 696 backupInfo.setCompleteTs(EnvironmentEdgeManager.currentTime()); 697 // set failure message 698 backupInfo.setFailedMsg("REPAIR status: repaired after failure:\n" + backupInfo); 699 // set overall backup status: failed 700 backupInfo.setState(BackupState.FAILED); 701 // compose the backup failed data 702 String backupFailedData = "BackupId=" + backupInfo.getBackupId() + ",startts=" 703 + backupInfo.getStartTs() + ",failedts=" + backupInfo.getCompleteTs() + ",failedphase=" 704 + backupInfo.getPhase() + ",failedmessage=" + backupInfo.getFailedMsg(); 705 System.out.println(backupFailedData); 706 TableBackupClient.cleanupAndRestoreBackupSystem(conn, backupInfo, conf); 707 // If backup session is updated to FAILED state - means we 708 // processed recovery already. 709 sysTable.updateBackupInfo(backupInfo); 710 sysTable.finishBackupExclusiveOperation(); 711 System.out.println("REPAIR status: finished repair failed session:\n " + backupInfo); 712 } 713 } 714 715 private void repairFailedBackupDeletionIfAny(Connection conn, BackupSystemTable sysTable) 716 throws IOException { 717 String[] backupIds = sysTable.getListOfBackupIdsFromDeleteOperation(); 718 if (backupIds == null || backupIds.length == 0) { 719 System.out.println("No failed backup DELETE operation found"); 720 // Delete backup table snapshot if exists 721 BackupSystemTable.deleteSnapshot(conn); 722 return; 723 } 724 System.out.println("Found failed DELETE operation for: " + StringUtils.join(backupIds)); 725 System.out.println("Running DELETE again ..."); 726 // Restore table from snapshot 727 BackupSystemTable.restoreFromSnapshot(conn); 728 // Finish previous failed session 729 sysTable.finishBackupExclusiveOperation(); 730 try (BackupAdmin admin = new BackupAdminImpl(conn)) { 731 admin.deleteBackups(backupIds); 732 } 733 System.out.println("DELETE operation finished OK: " + StringUtils.join(backupIds)); 734 } 735 736 public static void repairFailedBackupMergeIfAny(Connection conn, BackupSystemTable sysTable) 737 throws IOException { 738 739 String[] backupIds = sysTable.getListOfBackupIdsFromMergeOperation(); 740 if (backupIds == null || backupIds.length == 0) { 741 System.out.println("No failed backup MERGE operation found"); 742 // Delete backup table snapshot if exists 743 BackupSystemTable.deleteSnapshot(conn); 744 return; 745 } 746 System.out.println("Found failed MERGE operation for: " + StringUtils.join(backupIds)); 747 // Check if backup .tmp exists 748 BackupInfo bInfo = sysTable.readBackupInfo(backupIds[0]); 749 String backupRoot = bInfo.getBackupRootDir(); 750 FileSystem fs = FileSystem.get(new Path(backupRoot).toUri(), new Configuration()); 751 String backupId = BackupUtils.findMostRecentBackupId(backupIds); 752 Path tmpPath = HBackupFileSystem.getBackupTmpDirPathForBackupId(backupRoot, backupId); 753 if (fs.exists(tmpPath)) { 754 // Move data back 755 Path destPath = HBackupFileSystem.getBackupPath(backupRoot, backupId); 756 if (!fs.delete(destPath, true)) { 757 System.out.println("Failed to delete " + destPath); 758 } 759 boolean res = fs.rename(tmpPath, destPath); 760 if (!res) { 761 throw new IOException( 762 "MERGE repair: failed to rename from " + tmpPath + " to " + destPath); 763 } 764 System.out 765 .println("MERGE repair: renamed from " + tmpPath + " to " + destPath + " res=" + res); 766 } else { 767 checkRemoveBackupImages(fs, backupRoot, backupIds); 768 } 769 // Restore table from snapshot 770 BackupSystemTable.restoreFromSnapshot(conn); 771 // Unlock backup system 772 sysTable.finishBackupExclusiveOperation(); 773 // Finish previous failed session 774 sysTable.finishMergeOperation(); 775 776 System.out.println("MERGE repair operation finished OK: " + StringUtils.join(backupIds)); 777 } 778 779 private static void checkRemoveBackupImages(FileSystem fs, String backupRoot, 780 String[] backupIds) throws IOException { 781 String mergedBackupId = BackupUtils.findMostRecentBackupId(backupIds); 782 for (String backupId : backupIds) { 783 if (backupId.equals(mergedBackupId)) { 784 continue; 785 } 786 Path path = HBackupFileSystem.getBackupPath(backupRoot, backupId); 787 if (fs.exists(path)) { 788 if (!fs.delete(path, true)) { 789 System.out.println("MERGE repair removing: " + path + " - FAILED"); 790 } else { 791 System.out.println("MERGE repair removing: " + path + " - OK"); 792 } 793 } 794 } 795 } 796 797 @Override 798 protected void printUsage() { 799 System.out.println(REPAIR_CMD_USAGE); 800 } 801 } 802 803 public static class MergeCommand extends Command { 804 MergeCommand(Configuration conf, CommandLine cmdline) { 805 super(conf); 806 this.cmdline = cmdline; 807 } 808 809 @Override 810 protected boolean requiresNoActiveSession() { 811 return true; 812 } 813 814 @Override 815 protected boolean requiresConsistentState() { 816 return true; 817 } 818 819 @Override 820 public void execute() throws IOException { 821 super.execute(); 822 823 String[] args = cmdline == null ? null : cmdline.getArgs(); 824 if (args == null || (args.length != 2)) { 825 System.err 826 .println("ERROR: wrong number of arguments: " + (args == null ? null : args.length)); 827 printUsage(); 828 throw new IOException(INCORRECT_USAGE); 829 } 830 831 String[] backupIds = args[1].split(","); 832 if (backupIds.length < 2) { 833 String msg = "ERROR: can not merge a single backup image. " 834 + "Number of images must be greater than 1."; 835 System.err.println(msg); 836 throw new IOException(msg); 837 838 } 839 Configuration conf = getConf() != null ? getConf() : HBaseConfiguration.create(); 840 try (final Connection conn = ConnectionFactory.createConnection(conf); 841 final BackupAdminImpl admin = new BackupAdminImpl(conn)) { 842 admin.mergeBackups(backupIds); 843 } 844 } 845 846 @Override 847 protected void printUsage() { 848 System.out.println(MERGE_CMD_USAGE); 849 } 850 } 851 852 public static class HistoryCommand extends Command { 853 private final static int DEFAULT_HISTORY_LENGTH = 10; 854 855 HistoryCommand(Configuration conf, CommandLine cmdline) { 856 super(conf); 857 this.cmdline = cmdline; 858 } 859 860 @Override 861 public void execute() throws IOException { 862 int n = parseHistoryLength(); 863 final TableName tableName = getTableName(); 864 final String setName = getTableSetName(); 865 BackupInfo.Filter tableNameFilter = info -> { 866 if (tableName == null) { 867 return true; 868 } 869 870 List<TableName> names = info.getTableNames(); 871 return names.contains(tableName); 872 }; 873 BackupInfo.Filter tableSetFilter = info -> { 874 if (setName == null) { 875 return true; 876 } 877 878 String backupId = info.getBackupId(); 879 return backupId.startsWith(setName); 880 }; 881 Path backupRootPath = getBackupRootPath(); 882 List<BackupInfo> history; 883 if (backupRootPath == null) { 884 // Load from backup system table 885 super.execute(); 886 try (final BackupSystemTable sysTable = new BackupSystemTable(conn)) { 887 history = sysTable.getBackupHistory(tableNameFilter, tableSetFilter); 888 history = history.subList(0, Math.min(n, history.size())); 889 } 890 } else { 891 // load from backup FS 892 history = 893 BackupUtils.getHistory(getConf(), n, backupRootPath, tableNameFilter, tableSetFilter); 894 } 895 for (BackupInfo info : history) { 896 System.out.println(info.getShortDescription()); 897 } 898 } 899 900 private Path getBackupRootPath() throws IOException { 901 String value = null; 902 try { 903 value = cmdline.getOptionValue(OPTION_PATH); 904 905 if (value == null) { 906 return null; 907 } 908 909 return new Path(value); 910 } catch (IllegalArgumentException e) { 911 System.out.println("ERROR: Illegal argument for backup root path: " + value); 912 printUsage(); 913 throw new IOException(INCORRECT_USAGE); 914 } 915 } 916 917 private TableName getTableName() throws IOException { 918 String value = cmdline.getOptionValue(OPTION_TABLE); 919 920 if (value == null) { 921 return null; 922 } 923 924 try { 925 return TableName.valueOf(value); 926 } catch (IllegalArgumentException e) { 927 System.out.println("Illegal argument for table name: " + value); 928 printUsage(); 929 throw new IOException(INCORRECT_USAGE); 930 } 931 } 932 933 private String getTableSetName() { 934 return cmdline.getOptionValue(OPTION_SET); 935 } 936 937 private int parseHistoryLength() throws IOException { 938 String value = cmdline.getOptionValue(OPTION_RECORD_NUMBER); 939 try { 940 if (value == null) { 941 return DEFAULT_HISTORY_LENGTH; 942 } 943 944 return Integer.parseInt(value); 945 } catch (NumberFormatException e) { 946 System.out.println("Illegal argument for history length: " + value); 947 printUsage(); 948 throw new IOException(INCORRECT_USAGE); 949 } 950 } 951 952 @Override 953 protected void printUsage() { 954 System.out.println(HISTORY_CMD_USAGE); 955 Options options = new Options(); 956 options.addOption(OPTION_RECORD_NUMBER, true, OPTION_RECORD_NUMBER_DESC); 957 options.addOption(OPTION_PATH, true, OPTION_PATH_DESC); 958 options.addOption(OPTION_TABLE, true, OPTION_TABLE_DESC); 959 options.addOption(OPTION_SET, true, OPTION_SET_DESC); 960 961 HelpFormatter helpFormatter = new HelpFormatter(); 962 helpFormatter.setLeftPadding(2); 963 helpFormatter.setDescPadding(8); 964 helpFormatter.setWidth(100); 965 helpFormatter.setSyntaxPrefix("Options:"); 966 helpFormatter.printHelp(" ", null, options, USAGE_FOOTER); 967 } 968 } 969 970 public static class BackupSetCommand extends Command { 971 private final static String SET_ADD_CMD = "add"; 972 private final static String SET_REMOVE_CMD = "remove"; 973 private final static String SET_DELETE_CMD = "delete"; 974 private final static String SET_DESCRIBE_CMD = "describe"; 975 private final static String SET_LIST_CMD = "list"; 976 977 BackupSetCommand(Configuration conf, CommandLine cmdline) { 978 super(conf); 979 this.cmdline = cmdline; 980 } 981 982 @Override 983 public void execute() throws IOException { 984 // Command-line must have at least one element 985 if (cmdline == null || cmdline.getArgs() == null || cmdline.getArgs().length < 2) { 986 printUsage(); 987 throw new IOException(INCORRECT_USAGE); 988 } 989 990 String[] args = cmdline.getArgs(); 991 String cmdStr = args[1]; 992 BackupCommand cmd = getCommand(cmdStr); 993 994 switch (cmd) { 995 case SET_ADD: 996 processSetAdd(args); 997 break; 998 case SET_REMOVE: 999 processSetRemove(args); 1000 break; 1001 case SET_DELETE: 1002 processSetDelete(args); 1003 break; 1004 case SET_DESCRIBE: 1005 processSetDescribe(args); 1006 break; 1007 case SET_LIST: 1008 processSetList(); 1009 break; 1010 default: 1011 break; 1012 } 1013 } 1014 1015 private void processSetList() throws IOException { 1016 super.execute(); 1017 1018 // List all backup set names 1019 // does not expect any args 1020 try (BackupAdminImpl admin = new BackupAdminImpl(conn)) { 1021 List<BackupSet> list = admin.listBackupSets(); 1022 for (BackupSet bs : list) { 1023 System.out.println(bs); 1024 } 1025 } 1026 } 1027 1028 private void processSetDescribe(String[] args) throws IOException { 1029 if (args == null || args.length != 3) { 1030 printUsage(); 1031 throw new IOException(INCORRECT_USAGE); 1032 } 1033 super.execute(); 1034 1035 String setName = args[2]; 1036 try (final BackupSystemTable sysTable = new BackupSystemTable(conn)) { 1037 List<TableName> tables = sysTable.describeBackupSet(setName); 1038 BackupSet set = tables == null ? null : new BackupSet(setName, tables); 1039 if (set == null) { 1040 System.out.println("Set '" + setName + "' does not exist."); 1041 } else { 1042 System.out.println(set); 1043 } 1044 } 1045 } 1046 1047 private void processSetDelete(String[] args) throws IOException { 1048 if (args == null || args.length != 3) { 1049 printUsage(); 1050 throw new IOException(INCORRECT_USAGE); 1051 } 1052 super.execute(); 1053 1054 String setName = args[2]; 1055 try (final BackupAdminImpl admin = new BackupAdminImpl(conn)) { 1056 boolean result = admin.deleteBackupSet(setName); 1057 if (result) { 1058 System.out.println("Delete set " + setName + " OK."); 1059 } else { 1060 System.out.println("Set " + setName + " does not exist"); 1061 } 1062 } 1063 } 1064 1065 private void processSetRemove(String[] args) throws IOException { 1066 if (args == null || args.length != 4) { 1067 printUsage(); 1068 throw new IOException(INCORRECT_USAGE); 1069 } 1070 super.execute(); 1071 1072 String setName = args[2]; 1073 String[] tables = args[3].split(","); 1074 TableName[] tableNames = toTableNames(tables); 1075 try (final BackupAdminImpl admin = new BackupAdminImpl(conn)) { 1076 admin.removeFromBackupSet(setName, tableNames); 1077 } 1078 } 1079 1080 private TableName[] toTableNames(String[] tables) { 1081 TableName[] arr = new TableName[tables.length]; 1082 for (int i = 0; i < tables.length; i++) { 1083 arr[i] = TableName.valueOf(tables[i]); 1084 } 1085 return arr; 1086 } 1087 1088 private void processSetAdd(String[] args) throws IOException { 1089 if (args == null || args.length != 4) { 1090 printUsage(); 1091 throw new IOException(INCORRECT_USAGE); 1092 } 1093 super.execute(); 1094 String setName = args[2]; 1095 TableName[] tableNames = 1096 Splitter.on(',').splitToStream(args[3]).map(TableName::valueOf).toArray(TableName[]::new); 1097 try (final BackupAdminImpl admin = new BackupAdminImpl(conn)) { 1098 admin.addToBackupSet(setName, tableNames); 1099 } 1100 } 1101 1102 private BackupCommand getCommand(String cmdStr) throws IOException { 1103 switch (cmdStr) { 1104 case SET_ADD_CMD: 1105 return BackupCommand.SET_ADD; 1106 case SET_REMOVE_CMD: 1107 return BackupCommand.SET_REMOVE; 1108 case SET_DELETE_CMD: 1109 return BackupCommand.SET_DELETE; 1110 case SET_DESCRIBE_CMD: 1111 return BackupCommand.SET_DESCRIBE; 1112 case SET_LIST_CMD: 1113 return BackupCommand.SET_LIST; 1114 default: 1115 System.out.println("ERROR: Unknown command for 'set' :" + cmdStr); 1116 printUsage(); 1117 throw new IOException(INCORRECT_USAGE); 1118 } 1119 } 1120 1121 @Override 1122 protected void printUsage() { 1123 System.out.println(SET_CMD_USAGE); 1124 } 1125 } 1126}