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