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.util; 019 020import static org.apache.hadoop.hbase.mapreduce.HFileOutputFormat2.MULTI_TABLE_HFILEOUTPUTFORMAT_CONF_KEY; 021 022import java.io.FileNotFoundException; 023import java.io.IOException; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.HashMap; 027import java.util.List; 028import java.util.TreeMap; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.fs.FileStatus; 031import org.apache.hadoop.fs.FileSystem; 032import org.apache.hadoop.fs.FileUtil; 033import org.apache.hadoop.fs.Path; 034import org.apache.hadoop.hbase.HConstants; 035import org.apache.hadoop.hbase.NamespaceDescriptor; 036import org.apache.hadoop.hbase.NamespaceNotFoundException; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.hadoop.hbase.backup.BackupInfo; 039import org.apache.hadoop.hbase.backup.BackupRestoreFactory; 040import org.apache.hadoop.hbase.backup.HBackupFileSystem; 041import org.apache.hadoop.hbase.backup.RestoreJob; 042import org.apache.hadoop.hbase.backup.impl.BackupAdminImpl; 043import org.apache.hadoop.hbase.client.Admin; 044import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 045import org.apache.hadoop.hbase.client.Connection; 046import org.apache.hadoop.hbase.client.TableDescriptor; 047import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 048import org.apache.hadoop.hbase.io.HFileLink; 049import org.apache.hadoop.hbase.io.hfile.HFile; 050import org.apache.hadoop.hbase.regionserver.StoreFileInfo; 051import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; 052import org.apache.hadoop.hbase.snapshot.SnapshotManifest; 053import org.apache.hadoop.hbase.snapshot.SnapshotTTLExpiredException; 054import org.apache.hadoop.hbase.tool.BulkLoadHFilesTool; 055import org.apache.hadoop.hbase.util.Bytes; 056import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 057import org.apache.hadoop.hbase.util.FSTableDescriptors; 058import org.apache.yetus.audience.InterfaceAudience; 059import org.slf4j.Logger; 060import org.slf4j.LoggerFactory; 061 062import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 063import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; 064 065/** 066 * A collection for methods used by multiple classes to restore HBase tables. 067 */ 068@InterfaceAudience.Private 069public class RestoreTool { 070 public static final Logger LOG = LoggerFactory.getLogger(RestoreTool.class); 071 private final static long TABLE_AVAILABILITY_WAIT_TIME = 180000; 072 073 private final String[] ignoreDirs = { HConstants.RECOVERED_EDITS_DIR }; 074 protected Configuration conf; 075 protected Path backupRootPath; 076 protected Path restoreRootDir; 077 protected String backupId; 078 protected FileSystem fs; 079 080 // store table name and snapshot dir mapping 081 private final HashMap<TableName, Path> snapshotMap = new HashMap<>(); 082 083 public RestoreTool(Configuration conf, final Path backupRootPath, final Path restoreRootDir, 084 final String backupId) throws IOException { 085 this.conf = conf; 086 this.backupRootPath = backupRootPath; 087 this.backupId = backupId; 088 this.fs = backupRootPath.getFileSystem(conf); 089 this.restoreRootDir = restoreRootDir; 090 } 091 092 /** 093 * return value represent path for: 094 * ".../user/biadmin/backup1/default/t1_dn/backup_1396650096738/archive/data/default/t1_dn" 095 * @param tableName table name 096 * @return path to table archive 097 * @throws IOException exception 098 */ 099 Path getTableArchivePath(TableName tableName) throws IOException { 100 Path baseDir = 101 new Path(HBackupFileSystem.getTableBackupPath(tableName, backupRootPath, backupId), 102 HConstants.HFILE_ARCHIVE_DIRECTORY); 103 Path dataDir = new Path(baseDir, HConstants.BASE_NAMESPACE_DIR); 104 Path archivePath = new Path(dataDir, tableName.getNamespaceAsString()); 105 Path tableArchivePath = new Path(archivePath, tableName.getQualifierAsString()); 106 if (!fs.exists(tableArchivePath) || !fs.getFileStatus(tableArchivePath).isDirectory()) { 107 LOG.debug("Folder tableArchivePath: " + tableArchivePath.toString() + " does not exists"); 108 tableArchivePath = null; // empty table has no archive 109 } 110 return tableArchivePath; 111 } 112 113 /** 114 * Gets region list 115 * @param tableName table name 116 * @return RegionList region list 117 * @throws IOException exception 118 */ 119 ArrayList<Path> getRegionList(TableName tableName) throws IOException { 120 Path tableArchivePath = getTableArchivePath(tableName); 121 ArrayList<Path> regionDirList = new ArrayList<>(); 122 FileStatus[] children = fs.listStatus(tableArchivePath); 123 for (FileStatus childStatus : children) { 124 // here child refer to each region(Name) 125 Path child = childStatus.getPath(); 126 regionDirList.add(child); 127 } 128 return regionDirList; 129 } 130 131 void modifyTableSync(Connection conn, TableDescriptor desc) throws IOException { 132 try (Admin admin = conn.getAdmin()) { 133 admin.modifyTable(desc); 134 int attempt = 0; 135 int maxAttempts = 600; 136 while (!admin.isTableAvailable(desc.getTableName())) { 137 Thread.sleep(100); 138 attempt++; 139 if (attempt++ > maxAttempts) { 140 throw new IOException("Timeout expired " + (maxAttempts * 100) + "ms"); 141 } 142 } 143 } catch (Exception e) { 144 throw new IOException(e); 145 } 146 } 147 148 /** 149 * During incremental backup operation. Call WalPlayer to replay WAL in backup image Currently 150 * tableNames and newTablesNames only contain single table, will be expanded to multiple tables in 151 * the future 152 * @param conn HBase connection 153 * @param tableBackupPath backup path 154 * @param logDirs : incremental backup folders, which contains WAL 155 * @param tableNames : source tableNames(table names were backuped) 156 * @param newTableNames : target tableNames(table names to be restored to) 157 * @param incrBackupId incremental backup Id 158 * @param keepOriginalSplits whether the original region splits from the full backup should be 159 * kept 160 * @throws IOException exception 161 */ 162 public void incrementalRestoreTable(Connection conn, Path tableBackupPath, Path[] logDirs, 163 TableName[] tableNames, TableName[] newTableNames, String incrBackupId, 164 boolean keepOriginalSplits) throws IOException { 165 try (Admin admin = conn.getAdmin(); BackupAdminImpl backupAdmin = new BackupAdminImpl(conn)) { 166 if (tableNames.length != newTableNames.length) { 167 throw new IOException("Number of source tables and target tables does not match!"); 168 } 169 Configuration conf = new Configuration(this.conf); 170 FileSystem fileSys = tableBackupPath.getFileSystem(conf); 171 172 BackupInfo backupInfo = backupAdmin.getBackupInfo(incrBackupId); 173 if (backupInfo.isContinuousBackupEnabled()) { 174 conf.setBoolean(MULTI_TABLE_HFILEOUTPUTFORMAT_CONF_KEY, false); 175 } 176 177 // for incremental backup image, expect the table already created either by user or previous 178 // full backup. Here, check that all new tables exists 179 for (TableName tableName : newTableNames) { 180 if (!admin.tableExists(tableName)) { 181 throw new IOException("HBase table " + tableName 182 + " does not exist. Create the table first, e.g. by restoring a full backup."); 183 } 184 } 185 // adjust table schema 186 for (int i = 0; i < tableNames.length; i++) { 187 TableName tableName = tableNames[i]; 188 TableDescriptor tableDescriptor = getTableDescriptor(fileSys, tableName, incrBackupId); 189 if (tableDescriptor == null) { 190 throw new IOException("Can't find " + tableName + "'s descriptor."); 191 } 192 LOG.debug("Found descriptor " + tableDescriptor + " through " + incrBackupId); 193 194 TableName newTableName = newTableNames[i]; 195 TableDescriptor newTableDescriptor = admin.getDescriptor(newTableName); 196 List<ColumnFamilyDescriptor> families = Arrays.asList(tableDescriptor.getColumnFamilies()); 197 List<ColumnFamilyDescriptor> existingFamilies = 198 Arrays.asList(newTableDescriptor.getColumnFamilies()); 199 TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(newTableDescriptor); 200 boolean schemaChangeNeeded = false; 201 for (ColumnFamilyDescriptor family : families) { 202 if (!existingFamilies.contains(family)) { 203 builder.setColumnFamily(family); 204 schemaChangeNeeded = true; 205 } 206 } 207 for (ColumnFamilyDescriptor family : existingFamilies) { 208 if (!families.contains(family)) { 209 builder.removeColumnFamily(family.getName()); 210 schemaChangeNeeded = true; 211 } 212 } 213 if (schemaChangeNeeded) { 214 modifyTableSync(conn, builder.build()); 215 LOG.info("Changed " + newTableDescriptor.getTableName() + " to: " + newTableDescriptor); 216 } 217 } 218 configureForRestoreJob(keepOriginalSplits); 219 RestoreJob restoreService = BackupRestoreFactory.getRestoreJob(conf); 220 221 restoreService.run(logDirs, tableNames, restoreRootDir, newTableNames, false); 222 } 223 } 224 225 public void fullRestoreTable(Connection conn, Path tableBackupPath, TableName tableName, 226 TableName newTableName, boolean truncateIfExists, boolean isKeepOriginalSplits, 227 String lastIncrBackupId) throws IOException { 228 createAndRestoreTable(conn, tableName, newTableName, tableBackupPath, truncateIfExists, 229 isKeepOriginalSplits, lastIncrBackupId); 230 } 231 232 /** 233 * Returns value represent path for path to backup table snapshot directory: 234 * "/$USER/SBACKUP_ROOT/backup_id/namespace/table/.hbase-snapshot" 235 * @param backupRootPath backup root path 236 * @param tableName table name 237 * @param backupId backup Id 238 * @return path for snapshot 239 */ 240 Path getTableSnapshotPath(Path backupRootPath, TableName tableName, String backupId) { 241 return new Path(HBackupFileSystem.getTableBackupPath(tableName, backupRootPath, backupId), 242 HConstants.SNAPSHOT_DIR_NAME); 243 } 244 245 /** 246 * Returns value represent path for: 247 * ""/$USER/SBACKUP_ROOT/backup_id/namespace/table/.hbase-snapshot/ 248 * snapshot_1396650097621_namespace_table" this path contains .snapshotinfo, .tabledesc (0.96 and 249 * 0.98) this path contains .snapshotinfo, .data.manifest (trunk) 250 * @param tableName table name 251 * @return path to table info 252 * @throws IOException exception 253 */ 254 Path getTableInfoPath(TableName tableName) throws IOException { 255 Path tableSnapShotPath = getTableSnapshotPath(backupRootPath, tableName, backupId); 256 Path tableInfoPath = null; 257 258 // can't build the path directly as the timestamp values are different 259 FileStatus[] snapshots = fs.listStatus(tableSnapShotPath, 260 new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs)); 261 for (FileStatus snapshot : snapshots) { 262 tableInfoPath = snapshot.getPath(); 263 // SnapshotManifest.DATA_MANIFEST_NAME = "data.manifest"; 264 if (tableInfoPath.getName().endsWith("data.manifest")) { 265 break; 266 } 267 } 268 return tableInfoPath; 269 } 270 271 /** 272 * Get table descriptor 273 * @param tableName is the table backed up 274 * @return {@link TableDescriptor} saved in backup image of the table 275 */ 276 TableDescriptor getTableDesc(TableName tableName) throws IOException { 277 Path tableInfoPath = this.getTableInfoPath(tableName); 278 SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, tableInfoPath); 279 SnapshotManifest manifest = SnapshotManifest.open(conf, fs, tableInfoPath, desc); 280 if ( 281 SnapshotDescriptionUtils.isExpiredSnapshot(desc.getTtl(), desc.getCreationTime(), 282 EnvironmentEdgeManager.currentTime()) 283 ) { 284 throw new SnapshotTTLExpiredException(ProtobufUtil.createSnapshotDesc(desc)); 285 } 286 TableDescriptor tableDescriptor = manifest.getTableDescriptor(); 287 if (!tableDescriptor.getTableName().equals(tableName)) { 288 LOG.error("couldn't find Table Desc for table: " + tableName + " under tableInfoPath: " 289 + tableInfoPath.toString()); 290 LOG.error( 291 "tableDescriptor.getNameAsString() = " + tableDescriptor.getTableName().getNameAsString()); 292 throw new FileNotFoundException("couldn't find Table Desc for table: " + tableName 293 + " under tableInfoPath: " + tableInfoPath.toString()); 294 } 295 return tableDescriptor; 296 } 297 298 private TableDescriptor getTableDescriptor(FileSystem fileSys, TableName tableName, 299 String lastIncrBackupId) throws IOException { 300 if (lastIncrBackupId != null) { 301 String target = 302 BackupUtils.getTableBackupDir(backupRootPath.toString(), lastIncrBackupId, tableName); 303 return FSTableDescriptors.getTableDescriptorFromFs(fileSys, new Path(target)); 304 } 305 return null; 306 } 307 308 private void createAndRestoreTable(Connection conn, TableName tableName, TableName newTableName, 309 Path tableBackupPath, boolean truncateIfExists, boolean isKeepOriginalSplits, 310 String lastIncrBackupId) throws IOException { 311 if (newTableName == null) { 312 newTableName = tableName; 313 } 314 FileSystem fileSys = tableBackupPath.getFileSystem(this.conf); 315 316 // get table descriptor first 317 TableDescriptor tableDescriptor = getTableDescriptor(fileSys, tableName, lastIncrBackupId); 318 if (tableDescriptor != null) { 319 LOG.debug("Retrieved descriptor: " + tableDescriptor + " thru " + lastIncrBackupId); 320 } 321 322 if (tableDescriptor == null) { 323 Path tableSnapshotPath = getTableSnapshotPath(backupRootPath, tableName, backupId); 324 if (fileSys.exists(tableSnapshotPath)) { 325 // snapshot path exist means the backup path is in HDFS 326 // check whether snapshot dir already recorded for target table 327 if (snapshotMap.get(tableName) != null) { 328 SnapshotDescription desc = 329 SnapshotDescriptionUtils.readSnapshotInfo(fileSys, tableSnapshotPath); 330 SnapshotManifest manifest = SnapshotManifest.open(conf, fileSys, tableSnapshotPath, desc); 331 if ( 332 SnapshotDescriptionUtils.isExpiredSnapshot(desc.getTtl(), desc.getCreationTime(), 333 EnvironmentEdgeManager.currentTime()) 334 ) { 335 throw new SnapshotTTLExpiredException(ProtobufUtil.createSnapshotDesc(desc)); 336 } 337 tableDescriptor = manifest.getTableDescriptor(); 338 } else { 339 tableDescriptor = getTableDesc(tableName); 340 snapshotMap.put(tableName, getTableInfoPath(tableName)); 341 } 342 if (tableDescriptor == null) { 343 LOG.debug("Found no table descriptor in the snapshot dir, previous schema would be lost"); 344 } 345 } else { 346 throw new IOException( 347 "Table snapshot directory: " + tableSnapshotPath + " does not exist."); 348 } 349 } 350 351 Path tableArchivePath = getTableArchivePath(tableName); 352 if (tableArchivePath == null) { 353 if (tableDescriptor != null) { 354 // find table descriptor but no archive dir means the table is empty, create table and exit 355 if (LOG.isDebugEnabled()) { 356 LOG.debug("find table descriptor but no archive dir for table " + tableName 357 + ", will only create table"); 358 } 359 tableDescriptor = TableDescriptorBuilder.copy(newTableName, tableDescriptor); 360 checkAndCreateTable(conn, newTableName, null, tableDescriptor, truncateIfExists); 361 return; 362 } else { 363 throw new IllegalStateException( 364 "Cannot restore hbase table because directory '" + " tableArchivePath is null."); 365 } 366 } 367 368 if (tableDescriptor == null) { 369 tableDescriptor = TableDescriptorBuilder.newBuilder(newTableName).build(); 370 } else { 371 tableDescriptor = TableDescriptorBuilder.copy(newTableName, tableDescriptor); 372 } 373 374 // record all region dirs: 375 // load all files in dir 376 try { 377 ArrayList<Path> regionPathList = getRegionList(tableName); 378 379 // should only try to create the table with all region informations, so we could pre-split 380 // the regions in fine grain 381 checkAndCreateTable(conn, newTableName, regionPathList, tableDescriptor, truncateIfExists); 382 configureForRestoreJob(isKeepOriginalSplits); 383 RestoreJob restoreService = BackupRestoreFactory.getRestoreJob(conf); 384 Path[] paths = new Path[regionPathList.size()]; 385 regionPathList.toArray(paths); 386 restoreService.run(paths, new TableName[] { tableName }, restoreRootDir, 387 new TableName[] { newTableName }, true); 388 389 } catch (Exception e) { 390 LOG.error(e.toString(), e); 391 throw new IllegalStateException("Cannot restore hbase table", e); 392 } 393 } 394 395 /** 396 * Gets region list 397 * @param tableArchivePath table archive path 398 * @return RegionList region list 399 * @throws IOException exception 400 */ 401 ArrayList<Path> getRegionList(Path tableArchivePath) throws IOException { 402 ArrayList<Path> regionDirList = new ArrayList<>(); 403 FileStatus[] children = fs.listStatus(tableArchivePath); 404 for (FileStatus childStatus : children) { 405 // here child refer to each region(Name) 406 Path child = childStatus.getPath(); 407 regionDirList.add(child); 408 } 409 return regionDirList; 410 } 411 412 /** 413 * Calculate region boundaries and add all the column families to the table descriptor 414 * @param regionDirList region dir list 415 * @return a set of keys to store the boundaries 416 */ 417 byte[][] generateBoundaryKeys(ArrayList<Path> regionDirList) throws IOException { 418 TreeMap<byte[], Integer> map = new TreeMap<>(Bytes.BYTES_COMPARATOR); 419 // Build a set of keys to store the boundaries 420 // calculate region boundaries and add all the column families to the table descriptor 421 for (Path regionDir : regionDirList) { 422 LOG.debug("Parsing region dir: " + regionDir); 423 Path hfofDir = regionDir; 424 425 if (!fs.exists(hfofDir)) { 426 LOG.warn("HFileOutputFormat dir " + hfofDir + " not found"); 427 } 428 429 FileStatus[] familyDirStatuses = fs.listStatus(hfofDir); 430 if (familyDirStatuses == null) { 431 throw new IOException("No families found in " + hfofDir); 432 } 433 434 for (FileStatus stat : familyDirStatuses) { 435 if (!stat.isDirectory()) { 436 LOG.warn("Skipping non-directory " + stat.getPath()); 437 continue; 438 } 439 boolean isIgnore = false; 440 String pathName = stat.getPath().getName(); 441 for (String ignore : ignoreDirs) { 442 if (pathName.contains(ignore)) { 443 LOG.warn("Skipping non-family directory" + pathName); 444 isIgnore = true; 445 break; 446 } 447 } 448 if (isIgnore) { 449 continue; 450 } 451 Path familyDir = stat.getPath(); 452 LOG.debug("Parsing family dir [" + familyDir.toString() + " in region [" + regionDir + "]"); 453 // Skip _logs, etc 454 if (familyDir.getName().startsWith("_") || familyDir.getName().startsWith(".")) { 455 continue; 456 } 457 458 // start to parse hfile inside one family dir 459 Path[] hfiles = FileUtil.stat2Paths(fs.listStatus(familyDir)); 460 for (Path hfile : hfiles) { 461 if ( 462 hfile.getName().startsWith("_") || hfile.getName().startsWith(".") 463 || StoreFileInfo.isReference(hfile.getName()) 464 || HFileLink.isHFileLink(hfile.getName()) 465 ) { 466 continue; 467 } 468 HFile.Reader reader = HFile.createReader(fs, hfile, conf); 469 final byte[] first, last; 470 try { 471 if (reader.getEntries() == 0) { 472 LOG.debug("Skipping hfile with 0 entries: " + hfile); 473 continue; 474 } 475 first = reader.getFirstRowKey().get(); 476 last = reader.getLastRowKey().get(); 477 LOG.debug("Trying to figure out region boundaries hfile=" + hfile + " first=" 478 + Bytes.toStringBinary(first) + " last=" + Bytes.toStringBinary(last)); 479 480 // To eventually infer start key-end key boundaries 481 Integer value = map.containsKey(first) ? (Integer) map.get(first) : 0; 482 map.put(first, value + 1); 483 value = map.containsKey(last) ? (Integer) map.get(last) : 0; 484 map.put(last, value - 1); 485 } finally { 486 reader.close(); 487 } 488 } 489 } 490 } 491 return BulkLoadHFilesTool.inferBoundaries(map); 492 } 493 494 /** 495 * Prepare the table for bulkload, most codes copied from {@code createTable} method in 496 * {@code BulkLoadHFilesTool}. 497 * @param conn connection 498 * @param targetTableName target table name 499 * @param regionDirList region directory list 500 * @param htd table descriptor 501 * @param truncateIfExists truncates table if exists 502 * @throws IOException exception 503 */ 504 private void checkAndCreateTable(Connection conn, TableName targetTableName, 505 ArrayList<Path> regionDirList, TableDescriptor htd, boolean truncateIfExists) 506 throws IOException { 507 try (Admin admin = conn.getAdmin()) { 508 boolean createNew = false; 509 if (admin.tableExists(targetTableName)) { 510 if (truncateIfExists) { 511 LOG.info( 512 "Truncating exising target table '" + targetTableName + "', preserving region splits"); 513 admin.disableTable(targetTableName); 514 admin.truncateTable(targetTableName, true); 515 } else { 516 LOG.info("Using exising target table '" + targetTableName + "'"); 517 } 518 } else { 519 createNew = true; 520 } 521 if (createNew) { 522 LOG.info("Creating target table '" + targetTableName + "'"); 523 byte[][] keys = null; 524 try { 525 if (regionDirList == null || regionDirList.size() == 0) { 526 admin.createTable(htd); 527 } else { 528 keys = generateBoundaryKeys(regionDirList); 529 if (keys.length > 0) { 530 // create table using table descriptor and region boundaries 531 admin.createTable(htd, keys); 532 } else { 533 admin.createTable(htd); 534 } 535 } 536 } catch (NamespaceNotFoundException e) { 537 LOG.warn("There was no namespace and the same will be created"); 538 String namespaceAsString = targetTableName.getNamespaceAsString(); 539 LOG.info("Creating target namespace '" + namespaceAsString + "'"); 540 admin.createNamespace(NamespaceDescriptor.create(namespaceAsString).build()); 541 if (null == keys) { 542 admin.createTable(htd); 543 } else { 544 admin.createTable(htd, keys); 545 } 546 } 547 548 } 549 long startTime = EnvironmentEdgeManager.currentTime(); 550 while (!admin.isTableAvailable(targetTableName)) { 551 try { 552 Thread.sleep(100); 553 } catch (InterruptedException ie) { 554 Thread.currentThread().interrupt(); 555 } 556 if (EnvironmentEdgeManager.currentTime() - startTime > TABLE_AVAILABILITY_WAIT_TIME) { 557 throw new IOException("Time out " + TABLE_AVAILABILITY_WAIT_TIME + "ms expired, table " 558 + targetTableName + " is still not available"); 559 } 560 } 561 } 562 } 563 564 private void configureForRestoreJob(boolean keepOriginalSplits) { 565 conf.setBoolean(RestoreJob.KEEP_ORIGINAL_SPLITS_KEY, keepOriginalSplits); 566 conf.set(RestoreJob.BACKUP_ROOT_PATH_KEY, backupRootPath.toString()); 567 } 568}