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}