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