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.snapshot;
019
020import static org.apache.hadoop.util.ToolRunner.run;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertTrue;
024
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.List;
030import java.util.Map;
031import java.util.Objects;
032import java.util.Optional;
033import java.util.Set;
034import java.util.stream.Collectors;
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.fs.FileStatus;
037import org.apache.hadoop.fs.FileSystem;
038import org.apache.hadoop.fs.Path;
039import org.apache.hadoop.hbase.HBaseClassTestRule;
040import org.apache.hadoop.hbase.HBaseTestingUtil;
041import org.apache.hadoop.hbase.HConstants;
042import org.apache.hadoop.hbase.TableName;
043import org.apache.hadoop.hbase.client.Admin;
044import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
045import org.apache.hadoop.hbase.client.Put;
046import org.apache.hadoop.hbase.client.RegionInfo;
047import org.apache.hadoop.hbase.client.SnapshotType;
048import org.apache.hadoop.hbase.client.Table;
049import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
050import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
051import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
052import org.apache.hadoop.hbase.testclassification.LargeTests;
053import org.apache.hadoop.hbase.testclassification.VerySlowMapReduceTests;
054import org.apache.hadoop.hbase.tool.BulkLoadHFilesTool;
055import org.apache.hadoop.hbase.util.AbstractHBaseTool;
056import org.apache.hadoop.hbase.util.Bytes;
057import org.apache.hadoop.hbase.util.CommonFSUtils;
058import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
059import org.apache.hadoop.hbase.util.HFileTestUtil;
060import org.apache.hadoop.hbase.util.Pair;
061import org.junit.After;
062import org.junit.AfterClass;
063import org.junit.Before;
064import org.junit.BeforeClass;
065import org.junit.ClassRule;
066import org.junit.Ignore;
067import org.junit.Rule;
068import org.junit.Test;
069import org.junit.experimental.categories.Category;
070import org.junit.rules.TestName;
071import org.slf4j.Logger;
072import org.slf4j.LoggerFactory;
073
074import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
075
076import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
077import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
078
079/**
080 * Test Export Snapshot Tool
081 */
082@Ignore // HBASE-24493
083@Category({ VerySlowMapReduceTests.class, LargeTests.class })
084public class TestExportSnapshot {
085
086  @ClassRule
087  public static final HBaseClassTestRule CLASS_RULE =
088    HBaseClassTestRule.forClass(TestExportSnapshot.class);
089
090  private static final Logger LOG = LoggerFactory.getLogger(TestExportSnapshot.class);
091
092  protected final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
093
094  protected final static byte[] FAMILY = Bytes.toBytes("cf");
095
096  @Rule
097  public final TestName testName = new TestName();
098
099  protected TableName tableName;
100  private String emptySnapshotName;
101  private String snapshotName;
102  private int tableNumFiles;
103  private Admin admin;
104
105  public static void setUpBaseConf(Configuration conf) {
106    conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
107    conf.setInt("hbase.regionserver.msginterval", 100);
108    // If a single node has enough failures (default 3), resource manager will blacklist it.
109    // With only 2 nodes and tests injecting faults, we don't want that.
110    conf.setInt("mapreduce.job.maxtaskfailures.per.tracker", 100);
111  }
112
113  @BeforeClass
114  public static void setUpBeforeClass() throws Exception {
115    setUpBaseConf(TEST_UTIL.getConfiguration());
116    TEST_UTIL.startMiniCluster(1);
117    TEST_UTIL.startMiniMapReduceCluster();
118  }
119
120  @AfterClass
121  public static void tearDownAfterClass() throws Exception {
122    TEST_UTIL.shutdownMiniMapReduceCluster();
123    TEST_UTIL.shutdownMiniCluster();
124  }
125
126  /**
127   * Create a table and take a snapshot of the table used by the export test.
128   */
129  @Before
130  public void setUp() throws Exception {
131    this.admin = TEST_UTIL.getAdmin();
132
133    tableName = TableName.valueOf("testtb-" + testName.getMethodName());
134    snapshotName = "snaptb0-" + testName.getMethodName();
135    emptySnapshotName = "emptySnaptb0-" + testName.getMethodName();
136
137    // create Table
138    createTable(this.tableName);
139
140    // Take an empty snapshot
141    admin.snapshot(emptySnapshotName, tableName);
142
143    // Add some rows
144    SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 50, FAMILY);
145    tableNumFiles = admin.getRegions(tableName).size();
146
147    // take a snapshot
148    admin.snapshot(snapshotName, tableName);
149  }
150
151  protected void createTable(TableName tableName) throws Exception {
152    SnapshotTestingUtils.createPreSplitTable(TEST_UTIL, tableName, 2, FAMILY);
153  }
154
155  protected interface RegionPredicate {
156    boolean evaluate(final RegionInfo regionInfo);
157  }
158
159  protected RegionPredicate getBypassRegionPredicate() {
160    return null;
161  }
162
163  @After
164  public void tearDown() throws Exception {
165    TEST_UTIL.deleteTable(tableName);
166    SnapshotTestingUtils.deleteAllSnapshots(TEST_UTIL.getAdmin());
167    SnapshotTestingUtils.deleteArchiveDirectory(TEST_UTIL);
168  }
169
170  /**
171   * Verify if exported snapshot and copied files matches the original one.
172   */
173  @Test
174  public void testExportFileSystemState() throws Exception {
175    testExportFileSystemState(tableName, snapshotName, snapshotName, tableNumFiles);
176  }
177
178  @Test
179  public void testExportFileSystemStateWithMergeRegion() throws Exception {
180    // disable compaction
181    admin.compactionSwitch(false,
182      admin.getRegionServers().stream().map(a -> a.getServerName()).collect(Collectors.toList()));
183    // create Table
184    TableName tableName0 = TableName.valueOf("testtb-" + testName.getMethodName() + "-1");
185    String snapshotName0 = "snaptb0-" + testName.getMethodName() + "-1";
186    admin.createTable(
187      TableDescriptorBuilder.newBuilder(tableName0)
188        .setColumnFamilies(
189          Lists.newArrayList(ColumnFamilyDescriptorBuilder.newBuilder(FAMILY).build()))
190        .build(),
191      new byte[][] { Bytes.toBytes("2") });
192    // put some data
193    try (Table table = admin.getConnection().getTable(tableName0)) {
194      table.put(new Put(Bytes.toBytes("1")).addColumn(FAMILY, null, Bytes.toBytes("1")));
195      table.put(new Put(Bytes.toBytes("2")).addColumn(FAMILY, null, Bytes.toBytes("2")));
196    }
197    List<RegionInfo> regions = admin.getRegions(tableName0);
198    assertEquals(2, regions.size());
199    tableNumFiles = regions.size();
200    // merge region
201    admin.mergeRegionsAsync(new byte[][] { regions.get(0).getEncodedNameAsBytes(),
202      regions.get(1).getEncodedNameAsBytes() }, true).get();
203    // take a snapshot
204    admin.snapshot(snapshotName0, tableName0);
205    // export snapshot and verify
206    testExportFileSystemState(tableName0, snapshotName0, snapshotName0, tableNumFiles);
207    // delete table
208    TEST_UTIL.deleteTable(tableName0);
209  }
210
211  @Test
212  public void testExportFileSystemStateWithSplitRegion() throws Exception {
213    // disable compaction
214    admin.compactionSwitch(false,
215      admin.getRegionServers().stream().map(a -> a.getServerName()).collect(Collectors.toList()));
216    // create Table
217    TableName splitTableName = TableName.valueOf(testName.getMethodName());
218    String splitTableSnap = "snapshot-" + testName.getMethodName();
219    admin.createTable(TableDescriptorBuilder.newBuilder(splitTableName).setColumnFamilies(
220      Lists.newArrayList(ColumnFamilyDescriptorBuilder.newBuilder(FAMILY).build())).build());
221
222    Path output = TEST_UTIL.getDataTestDir("output/cf");
223    TEST_UTIL.getTestFileSystem().mkdirs(output);
224    // Create and load a large hfile to ensure the execution time of MR job.
225    HFileTestUtil.createHFile(TEST_UTIL.getConfiguration(), TEST_UTIL.getTestFileSystem(),
226      new Path(output, "test_file"), FAMILY, Bytes.toBytes("q"), Bytes.toBytes("1"),
227      Bytes.toBytes("9"), 9999999);
228    BulkLoadHFilesTool tool = new BulkLoadHFilesTool(TEST_UTIL.getConfiguration());
229    tool.run(new String[] { output.getParent().toString(), splitTableName.getNameAsString() });
230
231    List<RegionInfo> regions = admin.getRegions(splitTableName);
232    assertEquals(1, regions.size());
233    tableNumFiles = regions.size();
234
235    // split region
236    admin.split(splitTableName, Bytes.toBytes("5"));
237    regions = admin.getRegions(splitTableName);
238    assertEquals(2, regions.size());
239
240    // take a snapshot
241    admin.snapshot(splitTableSnap, splitTableName);
242    // export snapshot and verify
243    Configuration tmpConf = TEST_UTIL.getConfiguration();
244    // Decrease the buffer size of copier to avoid the export task finished shortly
245    tmpConf.setInt("snapshot.export.buffer.size", 1);
246    // Decrease the maximum files of each mapper to ensure the three files(1 hfile + 2 reference
247    // files)
248    // copied in different mappers concurrently.
249    tmpConf.setInt("snapshot.export.default.map.group", 1);
250    testExportFileSystemState(tmpConf, splitTableName, splitTableSnap, splitTableSnap,
251      tableNumFiles, TEST_UTIL.getDefaultRootDirPath(), getHdfsDestinationDir(), false, false,
252      getBypassRegionPredicate(), true, false);
253    // delete table
254    TEST_UTIL.deleteTable(splitTableName);
255  }
256
257  @Test
258  public void testExportFileSystemStateWithSkipTmp() throws Exception {
259    TEST_UTIL.getConfiguration().setBoolean(ExportSnapshot.CONF_SKIP_TMP, true);
260    try {
261      testExportFileSystemState(tableName, snapshotName, snapshotName, tableNumFiles);
262    } finally {
263      TEST_UTIL.getConfiguration().setBoolean(ExportSnapshot.CONF_SKIP_TMP, false);
264    }
265  }
266
267  @Test
268  public void testEmptyExportFileSystemState() throws Exception {
269    testExportFileSystemState(tableName, emptySnapshotName, emptySnapshotName, 0);
270  }
271
272  @Test
273  public void testConsecutiveExports() throws Exception {
274    Path copyDir = getLocalDestinationDir(TEST_UTIL);
275    testExportFileSystemState(tableName, snapshotName, snapshotName, tableNumFiles, copyDir, false);
276    testExportFileSystemState(tableName, snapshotName, snapshotName, tableNumFiles, copyDir, true);
277    removeExportDir(copyDir);
278  }
279
280  @Test
281  public void testExportWithChecksum() throws Exception {
282    // Test different schemes: input scheme is hdfs:// and output scheme is file://
283    // The checksum verification will fail
284    Path copyLocalDir = getLocalDestinationDir(TEST_UTIL);
285    testExportFileSystemState(TEST_UTIL.getConfiguration(), tableName, snapshotName, snapshotName,
286      tableNumFiles, TEST_UTIL.getDefaultRootDirPath(), copyLocalDir, false, false,
287      getBypassRegionPredicate(), false, true);
288
289    // Test same schemes: input scheme is hdfs:// and output scheme is hdfs://
290    // The checksum verification will success
291    Path copyHdfsDir = getHdfsDestinationDir();
292    testExportFileSystemState(TEST_UTIL.getConfiguration(), tableName, snapshotName, snapshotName,
293      tableNumFiles, TEST_UTIL.getDefaultRootDirPath(), copyHdfsDir, false, false,
294      getBypassRegionPredicate(), true, true);
295  }
296
297  @Test
298  public void testExportWithTargetName() throws Exception {
299    final String targetName = "testExportWithTargetName";
300    testExportFileSystemState(tableName, snapshotName, targetName, tableNumFiles);
301  }
302
303  @Test
304  public void testExportWithResetTtl() throws Exception {
305    String name = "testExportWithResetTtl";
306    TableName tableName = TableName.valueOf(name);
307    String snapshotName = "snaptb-" + name;
308    Long ttl = 100000L;
309
310    try {
311      // create Table
312      createTable(tableName);
313      SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 50, FAMILY);
314      int tableNumFiles = admin.getRegions(tableName).size();
315      // take a snapshot with TTL
316      Map<String, Object> props = new HashMap<>();
317      props.put("TTL", ttl);
318      admin.snapshot(snapshotName, tableName, props);
319      Optional<Long> ttlOpt =
320        admin.listSnapshots().stream().filter(s -> s.getName().equals(snapshotName))
321          .map(org.apache.hadoop.hbase.client.SnapshotDescription::getTtl).findAny();
322      assertTrue(ttlOpt.isPresent());
323      assertEquals(ttl, ttlOpt.get());
324
325      testExportFileSystemState(tableName, snapshotName, snapshotName, tableNumFiles,
326        getHdfsDestinationDir(), false, true);
327    } catch (Exception e) {
328      throw e;
329    } finally {
330      TEST_UTIL.deleteTable(tableName);
331    }
332  }
333
334  @Test
335  public void testExportExpiredSnapshot() throws Exception {
336    String name = "testExportExpiredSnapshot";
337    TableName tableName = TableName.valueOf(name);
338    String snapshotName = "snapshot-" + name;
339    createTable(tableName);
340    SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 50, FAMILY);
341    Map<String, Object> properties = new HashMap<>();
342    properties.put("TTL", 10);
343    org.apache.hadoop.hbase.client.SnapshotDescription snapshotDescription =
344      new org.apache.hadoop.hbase.client.SnapshotDescription(snapshotName, tableName,
345        SnapshotType.FLUSH, null, EnvironmentEdgeManager.currentTime(), -1, properties);
346    admin.snapshot(snapshotDescription);
347    boolean isExist =
348      admin.listSnapshots().stream().anyMatch(ele -> snapshotName.equals(ele.getName()));
349    assertTrue(isExist);
350    int retry = 6;
351    while (
352      !SnapshotDescriptionUtils.isExpiredSnapshot(snapshotDescription.getTtl(),
353        snapshotDescription.getCreationTime(), EnvironmentEdgeManager.currentTime()) && retry > 0
354    ) {
355      retry--;
356      Thread.sleep(10 * 1000);
357    }
358    boolean isExpiredSnapshot =
359      SnapshotDescriptionUtils.isExpiredSnapshot(snapshotDescription.getTtl(),
360        snapshotDescription.getCreationTime(), EnvironmentEdgeManager.currentTime());
361    assertTrue(isExpiredSnapshot);
362    int res = runExportSnapshot(TEST_UTIL.getConfiguration(), snapshotName, snapshotName,
363      TEST_UTIL.getDefaultRootDirPath(), getHdfsDestinationDir(), false, false, false, true, true);
364    assertTrue(res == AbstractHBaseTool.EXIT_FAILURE);
365  }
366
367  private void testExportFileSystemState(final TableName tableName, final String snapshotName,
368    final String targetName, int filesExpected) throws Exception {
369    testExportFileSystemState(tableName, snapshotName, targetName, filesExpected,
370      getHdfsDestinationDir(), false);
371  }
372
373  protected void testExportFileSystemState(final TableName tableName, final String snapshotName,
374    final String targetName, int filesExpected, Path copyDir, boolean overwrite) throws Exception {
375    testExportFileSystemState(tableName, snapshotName, targetName, filesExpected, copyDir,
376      overwrite, false);
377  }
378
379  protected void testExportFileSystemState(final TableName tableName, final String snapshotName,
380    final String targetName, int filesExpected, Path copyDir, boolean overwrite, boolean resetTtl)
381    throws Exception {
382    testExportFileSystemState(TEST_UTIL.getConfiguration(), tableName, snapshotName, targetName,
383      filesExpected, TEST_UTIL.getDefaultRootDirPath(), copyDir, overwrite, resetTtl,
384      getBypassRegionPredicate(), true, false);
385  }
386
387  /**
388   * Creates destination directory, runs ExportSnapshot() tool, and runs some verifications.
389   */
390  protected static void testExportFileSystemState(final Configuration conf,
391    final TableName tableName, final String snapshotName, final String targetName,
392    final int filesExpected, final Path srcDir, Path rawTgtDir, final boolean overwrite,
393    final boolean resetTtl, final RegionPredicate bypassregionPredicate, final boolean success,
394    final boolean checksumVerify) throws Exception {
395    FileSystem tgtFs = rawTgtDir.getFileSystem(conf);
396    FileSystem srcFs = srcDir.getFileSystem(conf);
397    Path tgtDir = rawTgtDir.makeQualified(tgtFs.getUri(), tgtFs.getWorkingDirectory());
398
399    // Export Snapshot
400    int res = runExportSnapshot(conf, snapshotName, targetName, srcDir, rawTgtDir, overwrite,
401      resetTtl, checksumVerify, true, true);
402    assertEquals("success " + success + ", res=" + res, success ? 0 : 1, res);
403    if (!success) {
404      final Path targetDir = new Path(HConstants.SNAPSHOT_DIR_NAME, targetName);
405      assertFalse(tgtDir.toString() + " " + targetDir.toString(),
406        tgtFs.exists(new Path(tgtDir, targetDir)));
407      return;
408    }
409    LOG.info("Exported snapshot");
410
411    // Verify File-System state
412    FileStatus[] rootFiles = tgtFs.listStatus(tgtDir);
413    assertEquals(filesExpected > 0 ? 2 : 1, rootFiles.length);
414    for (FileStatus fileStatus : rootFiles) {
415      String name = fileStatus.getPath().getName();
416      assertTrue(fileStatus.toString(), fileStatus.isDirectory());
417      assertTrue(name.toString(), name.equals(HConstants.SNAPSHOT_DIR_NAME)
418        || name.equals(HConstants.HFILE_ARCHIVE_DIRECTORY));
419    }
420    LOG.info("Verified filesystem state");
421
422    // Compare the snapshot metadata and verify the hfiles
423    final Path snapshotDir = new Path(HConstants.SNAPSHOT_DIR_NAME, snapshotName);
424    final Path targetDir = new Path(HConstants.SNAPSHOT_DIR_NAME, targetName);
425    verifySnapshotDir(srcFs, new Path(srcDir, snapshotDir), tgtFs, new Path(tgtDir, targetDir));
426    Set<String> snapshotFiles =
427      verifySnapshot(conf, tgtFs, tgtDir, tableName, targetName, resetTtl, bypassregionPredicate);
428    assertEquals(filesExpected, snapshotFiles.size());
429  }
430
431  /*
432   * verify if the snapshot folder on file-system 1 match the one on file-system 2
433   */
434  protected static void verifySnapshotDir(final FileSystem fs1, final Path root1,
435    final FileSystem fs2, final Path root2) throws IOException {
436    assertEquals(listFiles(fs1, root1, root1), listFiles(fs2, root2, root2));
437  }
438
439  /*
440   * Verify if the files exists
441   */
442  protected static Set<String> verifySnapshot(final Configuration conf, final FileSystem fs,
443    final Path rootDir, final TableName tableName, final String snapshotName,
444    final boolean resetTtl, final RegionPredicate bypassregionPredicate) throws IOException {
445    final Path exportedSnapshot =
446      new Path(rootDir, new Path(HConstants.SNAPSHOT_DIR_NAME, snapshotName));
447    final Set<String> snapshotFiles = new HashSet<>();
448    final Path exportedArchive = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
449    SnapshotReferenceUtil.visitReferencedFiles(conf, fs, exportedSnapshot,
450      new SnapshotReferenceUtil.SnapshotVisitor() {
451        @Override
452        public void storeFile(final RegionInfo regionInfo, final String family,
453          final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
454          if (bypassregionPredicate != null && bypassregionPredicate.evaluate(regionInfo)) {
455            return;
456          }
457
458          if (!storeFile.hasReference() && !StoreFileInfo.isReference(storeFile.getName())) {
459            String hfile = storeFile.getName();
460            snapshotFiles.add(hfile);
461            verifyNonEmptyFile(new Path(exportedArchive,
462              new Path(CommonFSUtils.getTableDir(new Path("./"), tableName),
463                new Path(regionInfo.getEncodedName(), new Path(family, hfile)))));
464          } else {
465            Pair<String, String> referredToRegionAndFile =
466              StoreFileInfo.getReferredToRegionAndFile(storeFile.getName());
467            String region = referredToRegionAndFile.getFirst();
468            String hfile = referredToRegionAndFile.getSecond();
469            snapshotFiles.add(hfile);
470            verifyNonEmptyFile(new Path(exportedArchive,
471              new Path(CommonFSUtils.getTableDir(new Path("./"), tableName),
472                new Path(region, new Path(family, hfile)))));
473          }
474        }
475
476        private void verifyNonEmptyFile(final Path path) throws IOException {
477          assertTrue(path + " should exists", fs.exists(path));
478          assertTrue(path + " should not be empty", fs.getFileStatus(path).getLen() > 0);
479        }
480      });
481
482    // Verify Snapshot description
483    SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, exportedSnapshot);
484    assertTrue(desc.getName().equals(snapshotName));
485    assertTrue(desc.getTable().equals(tableName.getNameAsString()));
486    if (resetTtl) {
487      assertEquals(HConstants.DEFAULT_SNAPSHOT_TTL, desc.getTtl());
488    }
489    return snapshotFiles;
490  }
491
492  private static Set<String> listFiles(final FileSystem fs, final Path root, final Path dir)
493    throws IOException {
494    Set<String> files = new HashSet<>();
495    LOG.debug("List files in {} in root {} at {}", fs, root, dir);
496    int rootPrefix = root.makeQualified(fs.getUri(), fs.getWorkingDirectory()).toString().length();
497    FileStatus[] list = CommonFSUtils.listStatus(fs, dir);
498    if (list != null) {
499      for (FileStatus fstat : list) {
500        LOG.debug(Objects.toString(fstat.getPath()));
501        if (fstat.isDirectory()) {
502          files.addAll(listFiles(fs, root, fstat.getPath()));
503        } else {
504          files.add(fstat.getPath().makeQualified(fs).toString().substring(rootPrefix));
505        }
506      }
507    }
508    return files;
509  }
510
511  private Path getHdfsDestinationDir() {
512    Path rootDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
513    Path path =
514      new Path(new Path(rootDir, "export-test"), "export-" + EnvironmentEdgeManager.currentTime());
515    LOG.info("HDFS export destination path: " + path);
516    return path;
517  }
518
519  static Path getLocalDestinationDir(HBaseTestingUtil htu) {
520    Path path = htu.getDataTestDir("local-export-" + EnvironmentEdgeManager.currentTime());
521    try {
522      FileSystem fs = FileSystem.getLocal(htu.getConfiguration());
523      LOG.info("Local export destination path: " + path);
524      return path.makeQualified(fs.getUri(), fs.getWorkingDirectory());
525    } catch (IOException ioe) {
526      throw new RuntimeException(ioe);
527    }
528  }
529
530  private static void removeExportDir(final Path path) throws IOException {
531    FileSystem fs = FileSystem.get(path.toUri(), new Configuration());
532    fs.delete(path, true);
533  }
534
535  private static int runExportSnapshot(final Configuration conf, final String sourceSnapshotName,
536    final String targetSnapshotName, final Path srcDir, Path rawTgtDir, final boolean overwrite,
537    final boolean resetTtl, final boolean checksumVerify, final boolean noSourceVerify,
538    final boolean noTargetVerify) throws Exception {
539    FileSystem tgtFs = rawTgtDir.getFileSystem(conf);
540    FileSystem srcFs = srcDir.getFileSystem(conf);
541    Path tgtDir = rawTgtDir.makeQualified(tgtFs.getUri(), tgtFs.getWorkingDirectory());
542    LOG.info("tgtFsUri={}, tgtDir={}, rawTgtDir={}, srcFsUri={}, srcDir={}", tgtFs.getUri(), tgtDir,
543      rawTgtDir, srcFs.getUri(), srcDir);
544    List<String> opts = new ArrayList<>();
545    opts.add("--snapshot");
546    opts.add(sourceSnapshotName);
547    opts.add("--copy-to");
548    opts.add(tgtDir.toString());
549    if (!targetSnapshotName.equals(sourceSnapshotName)) {
550      opts.add("--target");
551      opts.add(targetSnapshotName);
552    }
553    if (overwrite) {
554      opts.add("--overwrite");
555    }
556    if (resetTtl) {
557      opts.add("--reset-ttl");
558    }
559    if (!checksumVerify) {
560      opts.add("--no-checksum-verify");
561    }
562    if (!noSourceVerify) {
563      opts.add("--no-source-verify");
564    }
565    if (!noTargetVerify) {
566      opts.add("--no-target-verify");
567    }
568
569    // Export Snapshot
570    return run(conf, new ExportSnapshot(), opts.toArray(new String[opts.size()]));
571  }
572}