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 java.io.IOException; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import org.apache.commons.lang3.StringUtils; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.fs.FileSystem; 032import org.apache.hadoop.fs.Path; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.backup.BackupAdmin; 035import org.apache.hadoop.hbase.backup.BackupClientFactory; 036import org.apache.hadoop.hbase.backup.BackupInfo; 037import org.apache.hadoop.hbase.backup.BackupInfo.BackupState; 038import org.apache.hadoop.hbase.backup.BackupMergeJob; 039import org.apache.hadoop.hbase.backup.BackupRequest; 040import org.apache.hadoop.hbase.backup.BackupRestoreConstants; 041import org.apache.hadoop.hbase.backup.BackupRestoreFactory; 042import org.apache.hadoop.hbase.backup.BackupType; 043import org.apache.hadoop.hbase.backup.HBackupFileSystem; 044import org.apache.hadoop.hbase.backup.RestoreRequest; 045import org.apache.hadoop.hbase.backup.util.BackupSet; 046import org.apache.hadoop.hbase.backup.util.BackupUtils; 047import org.apache.hadoop.hbase.client.Admin; 048import org.apache.hadoop.hbase.client.Connection; 049import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 050import org.apache.yetus.audience.InterfaceAudience; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 055 056@InterfaceAudience.Private 057public class BackupAdminImpl implements BackupAdmin { 058 public final static String CHECK_OK = "Checking backup images: OK"; 059 public final static String CHECK_FAILED = 060 "Checking backup images: Failed. Some dependencies are missing for restore"; 061 private static final Logger LOG = LoggerFactory.getLogger(BackupAdminImpl.class); 062 063 private final Connection conn; 064 065 public BackupAdminImpl(Connection conn) { 066 this.conn = conn; 067 } 068 069 @Override 070 public void close() { 071 } 072 073 @Override 074 public BackupInfo getBackupInfo(String backupId) throws IOException { 075 BackupInfo backupInfo; 076 try (final BackupSystemTable table = new BackupSystemTable(conn)) { 077 if (backupId == null) { 078 ArrayList<BackupInfo> recentSessions = table.getBackupInfos(BackupState.RUNNING); 079 if (recentSessions.isEmpty()) { 080 LOG.warn("No ongoing sessions found."); 081 return null; 082 } 083 // else show status for ongoing session 084 // must be one maximum 085 return recentSessions.get(0); 086 } else { 087 backupInfo = table.readBackupInfo(backupId); 088 return backupInfo; 089 } 090 } 091 } 092 093 @Override 094 public int deleteBackups(String[] backupIds) throws IOException { 095 096 int totalDeleted = 0; 097 Map<String, HashSet<TableName>> allTablesMap = new HashMap<>(); 098 099 boolean deleteSessionStarted; 100 boolean snapshotDone; 101 try (final BackupSystemTable sysTable = new BackupSystemTable(conn)) { 102 // Step 1: Make sure there is no active session 103 // is running by using startBackupSession API 104 // If there is an active session in progress, exception will be thrown 105 try { 106 sysTable.startBackupExclusiveOperation(); 107 deleteSessionStarted = true; 108 } catch (IOException e) { 109 LOG.warn("You can not run delete command while active backup session is in progress. \n" 110 + "If there is no active backup session running, run backup repair utility to " 111 + "restore \nbackup system integrity."); 112 return -1; 113 } 114 115 // Step 2: Make sure there is no failed session 116 List<BackupInfo> list = sysTable.getBackupInfos(BackupState.RUNNING); 117 if (list.size() != 0) { 118 // ailed sessions found 119 LOG.warn("Failed backup session found. Run backup repair tool first."); 120 return -1; 121 } 122 123 // Step 3: Record delete session 124 sysTable.startDeleteOperation(backupIds); 125 // Step 4: Snapshot backup system table 126 if (!BackupSystemTable.snapshotExists(conn)) { 127 BackupSystemTable.snapshot(conn); 128 } else { 129 LOG.warn("Backup system table snapshot exists"); 130 } 131 snapshotDone = true; 132 try { 133 for (int i = 0; i < backupIds.length; i++) { 134 BackupInfo info = sysTable.readBackupInfo(backupIds[i]); 135 if (info != null) { 136 String rootDir = info.getBackupRootDir(); 137 HashSet<TableName> allTables = allTablesMap.get(rootDir); 138 if (allTables == null) { 139 allTables = new HashSet<>(); 140 allTablesMap.put(rootDir, allTables); 141 } 142 allTables.addAll(info.getTableNames()); 143 totalDeleted += deleteBackup(backupIds[i], sysTable); 144 } 145 } 146 finalizeDelete(allTablesMap, sysTable); 147 // Finish 148 sysTable.finishDeleteOperation(); 149 // delete snapshot 150 BackupSystemTable.deleteSnapshot(conn); 151 } catch (IOException e) { 152 // Fail delete operation 153 // Step 1 154 if (snapshotDone) { 155 if (BackupSystemTable.snapshotExists(conn)) { 156 BackupSystemTable.restoreFromSnapshot(conn); 157 // delete snapshot 158 BackupSystemTable.deleteSnapshot(conn); 159 // We still have record with unfinished delete operation 160 LOG.error("Delete operation failed, please run backup repair utility to restore " 161 + "backup system integrity", e); 162 throw e; 163 } else { 164 LOG.warn("Delete operation succeeded, there were some errors: ", e); 165 } 166 } 167 168 } finally { 169 if (deleteSessionStarted) { 170 sysTable.finishBackupExclusiveOperation(); 171 } 172 } 173 } 174 return totalDeleted; 175 } 176 177 /** 178 * Updates incremental backup set for every backupRoot 179 * @param tablesMap map [backupRoot: {@code Set<TableName>}] 180 * @param table backup system table 181 * @throws IOException if a table operation fails 182 */ 183 private void finalizeDelete(Map<String, HashSet<TableName>> tablesMap, BackupSystemTable table) 184 throws IOException { 185 for (String backupRoot : tablesMap.keySet()) { 186 Set<TableName> incrTableSet = table.getIncrementalBackupTableSet(backupRoot); 187 Map<TableName, ArrayList<BackupInfo>> tableMap = 188 table.getBackupHistoryForTableSet(incrTableSet, backupRoot); 189 for (Map.Entry<TableName, ArrayList<BackupInfo>> entry : tableMap.entrySet()) { 190 if (entry.getValue() == null) { 191 // No more backups for a table 192 incrTableSet.remove(entry.getKey()); 193 } 194 } 195 if (!incrTableSet.isEmpty()) { 196 table.addIncrementalBackupTableSet(incrTableSet, backupRoot); 197 } else { // empty 198 table.deleteIncrementalBackupTableSet(backupRoot); 199 } 200 } 201 } 202 203 /** 204 * Delete single backup and all related backups <br> 205 * Algorithm:<br> 206 * Backup type: FULL or INCREMENTAL <br> 207 * Is this last backup session for table T: YES or NO <br> 208 * For every table T from table list 'tables':<br> 209 * if(FULL, YES) deletes only physical data (PD) <br> 210 * if(FULL, NO), deletes PD, scans all newer backups and removes T from backupInfo,<br> 211 * until we either reach the most recent backup for T in the system or FULL backup<br> 212 * which includes T<br> 213 * if(INCREMENTAL, YES) deletes only physical data (PD) if(INCREMENTAL, NO) deletes physical data 214 * and for table T scans all backup images between last<br> 215 * FULL backup, which is older than the backup being deleted and the next FULL backup (if exists) 216 * <br> 217 * or last one for a particular table T and removes T from list of backup tables. 218 * @param backupId backup id 219 * @param sysTable backup system table 220 * @return total number of deleted backup images 221 * @throws IOException if deleting the backup fails 222 */ 223 private int deleteBackup(String backupId, BackupSystemTable sysTable) throws IOException { 224 BackupInfo backupInfo = sysTable.readBackupInfo(backupId); 225 226 int totalDeleted = 0; 227 if (backupInfo != null) { 228 LOG.info("Deleting backup " + backupInfo.getBackupId() + " ..."); 229 // Step 1: clean up data for backup session (idempotent) 230 BackupUtils.cleanupBackupData(backupInfo, conn.getConfiguration()); 231 // List of tables in this backup; 232 List<TableName> tables = backupInfo.getTableNames(); 233 long startTime = backupInfo.getStartTs(); 234 for (TableName tn : tables) { 235 boolean isLastBackupSession = isLastBackupSession(sysTable, tn, startTime); 236 if (isLastBackupSession) { 237 continue; 238 } 239 // else 240 List<BackupInfo> affectedBackups = getAffectedBackupSessions(backupInfo, tn, sysTable); 241 for (BackupInfo info : affectedBackups) { 242 if (info.equals(backupInfo)) { 243 continue; 244 } 245 removeTableFromBackupImage(info, tn, sysTable); 246 } 247 } 248 Map<byte[], String> map = sysTable.readBulkLoadedFiles(backupId); 249 FileSystem fs = FileSystem.get(conn.getConfiguration()); 250 boolean success = true; 251 int numDeleted = 0; 252 for (String f : map.values()) { 253 Path p = new Path(f); 254 try { 255 LOG.debug("Delete backup info " + p + " for " + backupInfo.getBackupId()); 256 if (!fs.delete(p)) { 257 if (fs.exists(p)) { 258 LOG.warn(f + " was not deleted"); 259 success = false; 260 } 261 } else { 262 numDeleted++; 263 } 264 } catch (IOException ioe) { 265 LOG.warn(f + " was not deleted", ioe); 266 success = false; 267 } 268 } 269 if (LOG.isDebugEnabled()) { 270 LOG.debug(numDeleted + " bulk loaded files out of " + map.size() + " were deleted"); 271 } 272 if (success) { 273 sysTable.deleteBulkLoadedRows(new ArrayList<>(map.keySet())); 274 } 275 276 sysTable.deleteBackupInfo(backupInfo.getBackupId()); 277 LOG.info("Delete backup " + backupInfo.getBackupId() + " completed."); 278 totalDeleted++; 279 } else { 280 LOG.warn("Delete backup failed: no information found for backupID=" + backupId); 281 } 282 return totalDeleted; 283 } 284 285 private void removeTableFromBackupImage(BackupInfo info, TableName tn, BackupSystemTable sysTable) 286 throws IOException { 287 List<TableName> tables = info.getTableNames(); 288 LOG.debug( 289 "Remove " + tn + " from " + info.getBackupId() + " tables=" + info.getTableListAsString()); 290 if (tables.contains(tn)) { 291 tables.remove(tn); 292 293 if (tables.isEmpty()) { 294 LOG.debug("Delete backup info " + info.getBackupId()); 295 296 sysTable.deleteBackupInfo(info.getBackupId()); 297 // Idempotent operation 298 BackupUtils.cleanupBackupData(info, conn.getConfiguration()); 299 } else { 300 info.setTables(tables); 301 sysTable.updateBackupInfo(info); 302 // Now, clean up directory for table (idempotent) 303 cleanupBackupDir(info, tn, conn.getConfiguration()); 304 } 305 } 306 } 307 308 private List<BackupInfo> getAffectedBackupSessions(BackupInfo backupInfo, TableName tn, 309 BackupSystemTable table) throws IOException { 310 LOG.debug("GetAffectedBackupInfos for: " + backupInfo.getBackupId() + " table=" + tn); 311 long ts = backupInfo.getStartTs(); 312 List<BackupInfo> list = new ArrayList<>(); 313 List<BackupInfo> history = table.getBackupHistory(backupInfo.getBackupRootDir()); 314 // Scan from most recent to backupInfo 315 // break when backupInfo reached 316 for (BackupInfo info : history) { 317 if (info.getStartTs() == ts) { 318 break; 319 } 320 List<TableName> tables = info.getTableNames(); 321 if (tables.contains(tn)) { 322 BackupType bt = info.getType(); 323 if (bt == BackupType.FULL) { 324 // Clear list if we encounter FULL backup 325 list.clear(); 326 } else { 327 LOG.debug("GetAffectedBackupInfos for: " + backupInfo.getBackupId() + " table=" + tn 328 + " added " + info.getBackupId() + " tables=" + info.getTableListAsString()); 329 list.add(info); 330 } 331 } 332 } 333 return list; 334 } 335 336 /** 337 * Clean up the data at target directory 338 * @throws IOException if cleaning up the backup directory fails 339 */ 340 private void cleanupBackupDir(BackupInfo backupInfo, TableName table, Configuration conf) 341 throws IOException { 342 try { 343 // clean up the data at target directory 344 String targetDir = backupInfo.getBackupRootDir(); 345 if (targetDir == null) { 346 LOG.warn("No target directory specified for " + backupInfo.getBackupId()); 347 return; 348 } 349 350 FileSystem outputFs = FileSystem.get(new Path(backupInfo.getBackupRootDir()).toUri(), conf); 351 352 Path targetDirPath = new Path(BackupUtils.getTableBackupDir(backupInfo.getBackupRootDir(), 353 backupInfo.getBackupId(), table)); 354 if (outputFs.delete(targetDirPath, true)) { 355 LOG.info("Cleaning up backup data at " + targetDirPath.toString() + " done."); 356 } else { 357 LOG.info("No data has been found in " + targetDirPath.toString() + "."); 358 } 359 } catch (IOException e1) { 360 LOG.error("Cleaning up backup data of " + backupInfo.getBackupId() + " for table " + table 361 + "at " + backupInfo.getBackupRootDir() + " failed due to " + e1.getMessage() + "."); 362 throw e1; 363 } 364 } 365 366 private boolean isLastBackupSession(BackupSystemTable table, TableName tn, long startTime) 367 throws IOException { 368 List<BackupInfo> history = table.getBackupHistory(); 369 for (BackupInfo info : history) { 370 List<TableName> tables = info.getTableNames(); 371 if (!tables.contains(tn)) { 372 continue; 373 } 374 return info.getStartTs() <= startTime; 375 } 376 return false; 377 } 378 379 @Override 380 public List<BackupInfo> getHistory(int n) throws IOException { 381 try (final BackupSystemTable table = new BackupSystemTable(conn)) { 382 List<BackupInfo> history = table.getBackupHistory(); 383 384 if (history.size() <= n) { 385 return history; 386 } 387 388 List<BackupInfo> list = new ArrayList<>(); 389 for (int i = 0; i < n; i++) { 390 list.add(history.get(i)); 391 } 392 return list; 393 } 394 } 395 396 @Override 397 public List<BackupInfo> getHistory(int n, BackupInfo.Filter... filters) throws IOException { 398 if (filters.length == 0) { 399 return getHistory(n); 400 } 401 402 try (final BackupSystemTable table = new BackupSystemTable(conn)) { 403 List<BackupInfo> history = table.getBackupHistory(); 404 List<BackupInfo> result = new ArrayList<>(); 405 for (BackupInfo bi : history) { 406 if (result.size() == n) { 407 break; 408 } 409 410 boolean passed = true; 411 for (int i = 0; i < filters.length; i++) { 412 if (!filters[i].apply(bi)) { 413 passed = false; 414 break; 415 } 416 } 417 if (passed) { 418 result.add(bi); 419 } 420 } 421 return result; 422 } 423 } 424 425 @Override 426 public List<BackupSet> listBackupSets() throws IOException { 427 try (final BackupSystemTable table = new BackupSystemTable(conn)) { 428 List<String> list = table.listBackupSets(); 429 List<BackupSet> bslist = new ArrayList<>(); 430 for (String s : list) { 431 List<TableName> tables = table.describeBackupSet(s); 432 if (tables != null) { 433 bslist.add(new BackupSet(s, tables)); 434 } 435 } 436 return bslist; 437 } 438 } 439 440 @Override 441 public BackupSet getBackupSet(String name) throws IOException { 442 try (final BackupSystemTable table = new BackupSystemTable(conn)) { 443 List<TableName> list = table.describeBackupSet(name); 444 445 if (list == null) { 446 return null; 447 } 448 449 return new BackupSet(name, list); 450 } 451 } 452 453 @Override 454 public boolean deleteBackupSet(String name) throws IOException { 455 try (final BackupSystemTable table = new BackupSystemTable(conn)) { 456 if (table.describeBackupSet(name) == null) { 457 return false; 458 } 459 table.deleteBackupSet(name); 460 return true; 461 } 462 } 463 464 @Override 465 public void addToBackupSet(String name, TableName[] tables) throws IOException { 466 String[] tableNames = new String[tables.length]; 467 try (final BackupSystemTable table = new BackupSystemTable(conn); 468 final Admin admin = conn.getAdmin()) { 469 for (int i = 0; i < tables.length; i++) { 470 tableNames[i] = tables[i].getNameAsString(); 471 if (!admin.tableExists(TableName.valueOf(tableNames[i]))) { 472 throw new IOException("Cannot add " + tableNames[i] + " because it doesn't exist"); 473 } 474 } 475 table.addToBackupSet(name, tableNames); 476 LOG.info( 477 "Added tables [" + StringUtils.join(tableNames, " ") + "] to '" + name + "' backup set"); 478 } 479 } 480 481 @Override 482 public void removeFromBackupSet(String name, TableName[] tables) throws IOException { 483 LOG.info("Removing tables [" + StringUtils.join(tables, " ") + "] from '" + name + "'"); 484 try (final BackupSystemTable table = new BackupSystemTable(conn)) { 485 table.removeFromBackupSet(name, toStringArray(tables)); 486 LOG.info( 487 "Removing tables [" + StringUtils.join(tables, " ") + "] from '" + name + "' completed."); 488 } 489 } 490 491 private String[] toStringArray(TableName[] list) { 492 String[] arr = new String[list.length]; 493 for (int i = 0; i < list.length; i++) { 494 arr[i] = list[i].toString(); 495 } 496 return arr; 497 } 498 499 @Override 500 public void restore(RestoreRequest request) throws IOException { 501 if (request.isCheck()) { 502 // check and load backup image manifest for the tables 503 Path rootPath = new Path(request.getBackupRootDir()); 504 String backupId = request.getBackupId(); 505 TableName[] sTableArray = request.getFromTables(); 506 BackupManifest manifest = 507 HBackupFileSystem.getManifest(conn.getConfiguration(), rootPath, backupId); 508 509 // Check and validate the backup image and its dependencies 510 if (BackupUtils.validate(Arrays.asList(sTableArray), manifest, conn.getConfiguration())) { 511 LOG.info(CHECK_OK); 512 } else { 513 LOG.error(CHECK_FAILED); 514 } 515 return; 516 } 517 // Execute restore request 518 new RestoreTablesClient(conn, request).execute(); 519 } 520 521 @Override 522 public String backupTables(BackupRequest request) throws IOException { 523 BackupType type = request.getBackupType(); 524 String targetRootDir = request.getTargetRootDir(); 525 List<TableName> tableList = request.getTableList(); 526 527 String backupId = BackupRestoreConstants.BACKUPID_PREFIX + EnvironmentEdgeManager.currentTime(); 528 if (type == BackupType.INCREMENTAL) { 529 Set<TableName> incrTableSet; 530 try (BackupSystemTable table = new BackupSystemTable(conn)) { 531 incrTableSet = table.getIncrementalBackupTableSet(targetRootDir); 532 } 533 534 if (incrTableSet.isEmpty()) { 535 String msg = 536 "Incremental backup table set contains no tables. " + "You need to run full backup first " 537 + (tableList != null ? "on " + StringUtils.join(tableList, ",") : ""); 538 539 throw new IOException(msg); 540 } 541 if (tableList != null) { 542 tableList.removeAll(incrTableSet); 543 if (!tableList.isEmpty()) { 544 String extraTables = StringUtils.join(tableList, ","); 545 String msg = "Some tables (" + extraTables + ") haven't gone through full backup. " 546 + "Perform full backup on " + extraTables + " first, " + "then retry the command"; 547 throw new IOException(msg); 548 } 549 } 550 tableList = Lists.newArrayList(incrTableSet); 551 } 552 if (tableList != null && !tableList.isEmpty()) { 553 for (TableName table : tableList) { 554 String targetTableBackupDir = 555 HBackupFileSystem.getTableBackupDir(targetRootDir, backupId, table); 556 Path targetTableBackupDirPath = new Path(targetTableBackupDir); 557 FileSystem outputFs = 558 FileSystem.get(targetTableBackupDirPath.toUri(), conn.getConfiguration()); 559 if (outputFs.exists(targetTableBackupDirPath)) { 560 throw new IOException( 561 "Target backup directory " + targetTableBackupDir + " exists already."); 562 } 563 outputFs.mkdirs(targetTableBackupDirPath); 564 } 565 ArrayList<TableName> nonExistingTableList = null; 566 try (Admin admin = conn.getAdmin()) { 567 for (TableName tableName : tableList) { 568 if (!admin.tableExists(tableName)) { 569 if (nonExistingTableList == null) { 570 nonExistingTableList = new ArrayList<>(); 571 } 572 nonExistingTableList.add(tableName); 573 } 574 } 575 } 576 if (nonExistingTableList != null) { 577 if (type == BackupType.INCREMENTAL) { 578 // Update incremental backup set 579 tableList = excludeNonExistingTables(tableList, nonExistingTableList); 580 } else { 581 // Throw exception only in full mode - we try to backup non-existing table 582 throw new IOException( 583 "Non-existing tables found in the table list: " + nonExistingTableList); 584 } 585 } 586 } 587 588 // update table list 589 BackupRequest.Builder builder = new BackupRequest.Builder(); 590 request = builder.withBackupType(request.getBackupType()).withTableList(tableList) 591 .withTargetRootDir(request.getTargetRootDir()).withBackupSetName(request.getBackupSetName()) 592 .withTotalTasks(request.getTotalTasks()).withBandwidthPerTasks((int) request.getBandwidth()) 593 .build(); 594 595 TableBackupClient client; 596 try { 597 client = BackupClientFactory.create(conn, backupId, request); 598 } catch (IOException e) { 599 LOG.error("There is an active session already running"); 600 throw e; 601 } 602 603 client.execute(); 604 605 return backupId; 606 } 607 608 private List<TableName> excludeNonExistingTables(List<TableName> tableList, 609 List<TableName> nonExistingTableList) { 610 for (TableName table : nonExistingTableList) { 611 tableList.remove(table); 612 } 613 return tableList; 614 } 615 616 @Override 617 public void mergeBackups(String[] backupIds) throws IOException { 618 try (final BackupSystemTable sysTable = new BackupSystemTable(conn)) { 619 checkIfValidForMerge(backupIds, sysTable); 620 // TODO run job on remote cluster 621 BackupMergeJob job = BackupRestoreFactory.getBackupMergeJob(conn.getConfiguration()); 622 job.run(backupIds); 623 } 624 } 625 626 /** 627 * Verifies that backup images are valid for merge. 628 * <ul> 629 * <li>All backups MUST be in the same destination 630 * <li>No FULL backups are allowed - only INCREMENTAL 631 * <li>All backups must be in COMPLETE state 632 * <li>No holes in backup list are allowed 633 * </ul> 634 * <p> 635 * @param backupIds list of backup ids 636 * @param table backup system table 637 * @throws IOException if the backup image is not valid for merge 638 */ 639 private void checkIfValidForMerge(String[] backupIds, BackupSystemTable table) 640 throws IOException { 641 String backupRoot = null; 642 643 final Set<TableName> allTables = new HashSet<>(); 644 final Set<String> allBackups = new HashSet<>(); 645 long minTime = Long.MAX_VALUE, maxTime = Long.MIN_VALUE; 646 for (String backupId : backupIds) { 647 BackupInfo bInfo = table.readBackupInfo(backupId); 648 if (bInfo == null) { 649 String msg = "Backup session " + backupId + " not found"; 650 throw new IOException(msg); 651 } 652 if (backupRoot == null) { 653 backupRoot = bInfo.getBackupRootDir(); 654 } else if (!bInfo.getBackupRootDir().equals(backupRoot)) { 655 throw new IOException("Found different backup destinations in a list of a backup sessions " 656 + "\n1. " + backupRoot + "\n" + "2. " + bInfo.getBackupRootDir()); 657 } 658 if (bInfo.getType() == BackupType.FULL) { 659 throw new IOException("FULL backup image can not be merged for: \n" + bInfo); 660 } 661 662 if (bInfo.getState() != BackupState.COMPLETE) { 663 throw new IOException("Backup image " + backupId 664 + " can not be merged becuase of its state: " + bInfo.getState()); 665 } 666 allBackups.add(backupId); 667 allTables.addAll(bInfo.getTableNames()); 668 long time = bInfo.getStartTs(); 669 if (time < minTime) { 670 minTime = time; 671 } 672 if (time > maxTime) { 673 maxTime = time; 674 } 675 } 676 677 final long startRangeTime = minTime; 678 final long endRangeTime = maxTime; 679 final String backupDest = backupRoot; 680 // Check we have no 'holes' in backup id list 681 // Filter 1 : backupRoot 682 // Filter 2 : time range filter 683 // Filter 3 : table filter 684 BackupInfo.Filter destinationFilter = info -> info.getBackupRootDir().equals(backupDest); 685 686 BackupInfo.Filter timeRangeFilter = info -> { 687 long time = info.getStartTs(); 688 return time >= startRangeTime && time <= endRangeTime; 689 }; 690 691 BackupInfo.Filter tableFilter = info -> { 692 List<TableName> tables = info.getTableNames(); 693 return !Collections.disjoint(allTables, tables); 694 }; 695 696 BackupInfo.Filter typeFilter = info -> info.getType() == BackupType.INCREMENTAL; 697 BackupInfo.Filter stateFilter = info -> info.getState() == BackupState.COMPLETE; 698 699 List<BackupInfo> allInfos = table.getBackupHistory(-1, destinationFilter, timeRangeFilter, 700 tableFilter, typeFilter, stateFilter); 701 if (allInfos.size() != allBackups.size()) { 702 // Yes we have at least one hole in backup image sequence 703 List<String> missingIds = new ArrayList<>(); 704 for (BackupInfo info : allInfos) { 705 if (allBackups.contains(info.getBackupId())) { 706 continue; 707 } 708 missingIds.add(info.getBackupId()); 709 } 710 String errMsg = 711 "Sequence of backup ids has 'holes'. The following backup images must be added:" 712 + org.apache.hadoop.util.StringUtils.join(",", missingIds); 713 throw new IOException(errMsg); 714 } 715 } 716}