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