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 java.io.BufferedInputStream; 021import java.io.DataInput; 022import java.io.DataOutput; 023import java.io.FileNotFoundException; 024import java.io.IOException; 025import java.io.InputStream; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.Comparator; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.concurrent.ExecutionException; 032import java.util.concurrent.ExecutorService; 033import java.util.concurrent.Executors; 034import java.util.concurrent.Future; 035import java.util.function.BiConsumer; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.fs.FSDataInputStream; 038import org.apache.hadoop.fs.FSDataOutputStream; 039import org.apache.hadoop.fs.FileChecksum; 040import org.apache.hadoop.fs.FileStatus; 041import org.apache.hadoop.fs.FileSystem; 042import org.apache.hadoop.fs.Path; 043import org.apache.hadoop.fs.permission.FsPermission; 044import org.apache.hadoop.hbase.HBaseConfiguration; 045import org.apache.hadoop.hbase.HConstants; 046import org.apache.hadoop.hbase.TableName; 047import org.apache.hadoop.hbase.client.RegionInfo; 048import org.apache.hadoop.hbase.io.FileLink; 049import org.apache.hadoop.hbase.io.HFileLink; 050import org.apache.hadoop.hbase.io.WALLink; 051import org.apache.hadoop.hbase.io.hadoopbackport.ThrottledInputStream; 052import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; 053import org.apache.hadoop.hbase.mob.MobUtils; 054import org.apache.hadoop.hbase.regionserver.StoreFileInfo; 055import org.apache.hadoop.hbase.util.AbstractHBaseTool; 056import org.apache.hadoop.hbase.util.CommonFSUtils; 057import org.apache.hadoop.hbase.util.FSUtils; 058import org.apache.hadoop.hbase.util.HFileArchiveUtil; 059import org.apache.hadoop.hbase.util.Pair; 060import org.apache.hadoop.io.BytesWritable; 061import org.apache.hadoop.io.IOUtils; 062import org.apache.hadoop.io.NullWritable; 063import org.apache.hadoop.io.Writable; 064import org.apache.hadoop.mapreduce.InputFormat; 065import org.apache.hadoop.mapreduce.InputSplit; 066import org.apache.hadoop.mapreduce.Job; 067import org.apache.hadoop.mapreduce.JobContext; 068import org.apache.hadoop.mapreduce.Mapper; 069import org.apache.hadoop.mapreduce.RecordReader; 070import org.apache.hadoop.mapreduce.TaskAttemptContext; 071import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat; 072import org.apache.hadoop.mapreduce.security.TokenCache; 073import org.apache.hadoop.util.StringUtils; 074import org.apache.hadoop.util.Tool; 075import org.apache.yetus.audience.InterfaceAudience; 076import org.slf4j.Logger; 077import org.slf4j.LoggerFactory; 078 079import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; 080import org.apache.hbase.thirdparty.org.apache.commons.cli.Option; 081 082import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; 083import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotFileInfo; 084import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest; 085 086/** 087 * Export the specified snapshot to a given FileSystem. The .snapshot/name folder is copied to the 088 * destination cluster and then all the hfiles/wals are copied using a Map-Reduce Job in the 089 * .archive/ location. When everything is done, the second cluster can restore the snapshot. 090 */ 091@InterfaceAudience.Public 092public class ExportSnapshot extends AbstractHBaseTool implements Tool { 093 public static final String NAME = "exportsnapshot"; 094 /** Configuration prefix for overrides for the source filesystem */ 095 public static final String CONF_SOURCE_PREFIX = NAME + ".from."; 096 /** Configuration prefix for overrides for the destination filesystem */ 097 public static final String CONF_DEST_PREFIX = NAME + ".to."; 098 099 private static final Logger LOG = LoggerFactory.getLogger(ExportSnapshot.class); 100 101 private static final String MR_NUM_MAPS = "mapreduce.job.maps"; 102 private static final String CONF_NUM_SPLITS = "snapshot.export.format.splits"; 103 private static final String CONF_SNAPSHOT_NAME = "snapshot.export.format.snapshot.name"; 104 private static final String CONF_SNAPSHOT_DIR = "snapshot.export.format.snapshot.dir"; 105 private static final String CONF_FILES_USER = "snapshot.export.files.attributes.user"; 106 private static final String CONF_FILES_GROUP = "snapshot.export.files.attributes.group"; 107 private static final String CONF_FILES_MODE = "snapshot.export.files.attributes.mode"; 108 private static final String CONF_CHECKSUM_VERIFY = "snapshot.export.checksum.verify"; 109 private static final String CONF_OUTPUT_ROOT = "snapshot.export.output.root"; 110 private static final String CONF_INPUT_ROOT = "snapshot.export.input.root"; 111 private static final String CONF_BUFFER_SIZE = "snapshot.export.buffer.size"; 112 private static final String CONF_MAP_GROUP = "snapshot.export.default.map.group"; 113 private static final String CONF_BANDWIDTH_MB = "snapshot.export.map.bandwidth.mb"; 114 private static final String CONF_MR_JOB_NAME = "mapreduce.job.name"; 115 protected static final String CONF_SKIP_TMP = "snapshot.export.skip.tmp"; 116 private static final String CONF_COPY_MANIFEST_THREADS = 117 "snapshot.export.copy.references.threads"; 118 private static final int DEFAULT_COPY_MANIFEST_THREADS = 119 Runtime.getRuntime().availableProcessors(); 120 121 static class Testing { 122 static final String CONF_TEST_FAILURE = "test.snapshot.export.failure"; 123 static final String CONF_TEST_FAILURE_COUNT = "test.snapshot.export.failure.count"; 124 int failuresCountToInject = 0; 125 int injectedFailureCount = 0; 126 } 127 128 // Command line options and defaults. 129 static final class Options { 130 static final Option SNAPSHOT = new Option(null, "snapshot", true, "Snapshot to restore."); 131 static final Option TARGET_NAME = 132 new Option(null, "target", true, "Target name for the snapshot."); 133 static final Option COPY_TO = 134 new Option(null, "copy-to", true, "Remote " + "destination hdfs://"); 135 static final Option COPY_FROM = 136 new Option(null, "copy-from", true, "Input folder hdfs:// (default hbase.rootdir)"); 137 static final Option NO_CHECKSUM_VERIFY = new Option(null, "no-checksum-verify", false, 138 "Do not verify checksum, use name+length only."); 139 static final Option NO_TARGET_VERIFY = new Option(null, "no-target-verify", false, 140 "Do not verify the integrity of the exported snapshot."); 141 static final Option NO_SOURCE_VERIFY = 142 new Option(null, "no-source-verify", false, "Do not verify the source of the snapshot."); 143 static final Option OVERWRITE = 144 new Option(null, "overwrite", false, "Rewrite the snapshot manifest if already exists."); 145 static final Option CHUSER = 146 new Option(null, "chuser", true, "Change the owner of the files to the specified one."); 147 static final Option CHGROUP = 148 new Option(null, "chgroup", true, "Change the group of the files to the specified one."); 149 static final Option CHMOD = 150 new Option(null, "chmod", true, "Change the permission of the files to the specified one."); 151 static final Option MAPPERS = new Option(null, "mappers", true, 152 "Number of mappers to use during the copy (mapreduce.job.maps)."); 153 static final Option BANDWIDTH = 154 new Option(null, "bandwidth", true, "Limit bandwidth to this value in MB/second."); 155 } 156 157 // Export Map-Reduce Counters, to keep track of the progress 158 public enum Counter { 159 MISSING_FILES, 160 FILES_COPIED, 161 FILES_SKIPPED, 162 COPY_FAILED, 163 BYTES_EXPECTED, 164 BYTES_SKIPPED, 165 BYTES_COPIED 166 } 167 168 private static class ExportMapper 169 extends Mapper<BytesWritable, NullWritable, NullWritable, NullWritable> { 170 private static final Logger LOG = LoggerFactory.getLogger(ExportMapper.class); 171 final static int REPORT_SIZE = 1 * 1024 * 1024; 172 final static int BUFFER_SIZE = 64 * 1024; 173 174 private boolean verifyChecksum; 175 private String filesGroup; 176 private String filesUser; 177 private short filesMode; 178 private int bufferSize; 179 180 private FileSystem outputFs; 181 private Path outputArchive; 182 private Path outputRoot; 183 184 private FileSystem inputFs; 185 private Path inputArchive; 186 private Path inputRoot; 187 188 private static Testing testing = new Testing(); 189 190 @Override 191 public void setup(Context context) throws IOException { 192 Configuration conf = context.getConfiguration(); 193 194 Configuration srcConf = HBaseConfiguration.createClusterConf(conf, null, CONF_SOURCE_PREFIX); 195 Configuration destConf = HBaseConfiguration.createClusterConf(conf, null, CONF_DEST_PREFIX); 196 197 verifyChecksum = conf.getBoolean(CONF_CHECKSUM_VERIFY, true); 198 199 filesGroup = conf.get(CONF_FILES_GROUP); 200 filesUser = conf.get(CONF_FILES_USER); 201 filesMode = (short) conf.getInt(CONF_FILES_MODE, 0); 202 outputRoot = new Path(conf.get(CONF_OUTPUT_ROOT)); 203 inputRoot = new Path(conf.get(CONF_INPUT_ROOT)); 204 205 inputArchive = new Path(inputRoot, HConstants.HFILE_ARCHIVE_DIRECTORY); 206 outputArchive = new Path(outputRoot, HConstants.HFILE_ARCHIVE_DIRECTORY); 207 208 try { 209 srcConf.setBoolean("fs." + inputRoot.toUri().getScheme() + ".impl.disable.cache", true); 210 inputFs = FileSystem.get(inputRoot.toUri(), srcConf); 211 } catch (IOException e) { 212 throw new IOException("Could not get the input FileSystem with root=" + inputRoot, e); 213 } 214 215 try { 216 destConf.setBoolean("fs." + outputRoot.toUri().getScheme() + ".impl.disable.cache", true); 217 outputFs = FileSystem.get(outputRoot.toUri(), destConf); 218 } catch (IOException e) { 219 throw new IOException("Could not get the output FileSystem with root=" + outputRoot, e); 220 } 221 222 // Use the default block size of the outputFs if bigger 223 int defaultBlockSize = Math.max((int) outputFs.getDefaultBlockSize(outputRoot), BUFFER_SIZE); 224 bufferSize = conf.getInt(CONF_BUFFER_SIZE, defaultBlockSize); 225 LOG.info("Using bufferSize=" + StringUtils.humanReadableInt(bufferSize)); 226 227 for (Counter c : Counter.values()) { 228 context.getCounter(c).increment(0); 229 } 230 if (context.getConfiguration().getBoolean(Testing.CONF_TEST_FAILURE, false)) { 231 testing.failuresCountToInject = conf.getInt(Testing.CONF_TEST_FAILURE_COUNT, 0); 232 // Get number of times we have already injected failure based on attempt number of this 233 // task. 234 testing.injectedFailureCount = context.getTaskAttemptID().getId(); 235 } 236 } 237 238 @Override 239 protected void cleanup(Context context) { 240 IOUtils.closeStream(inputFs); 241 IOUtils.closeStream(outputFs); 242 } 243 244 @Override 245 public void map(BytesWritable key, NullWritable value, Context context) 246 throws InterruptedException, IOException { 247 SnapshotFileInfo inputInfo = SnapshotFileInfo.parseFrom(key.copyBytes()); 248 Path outputPath = getOutputPath(inputInfo); 249 250 copyFile(context, inputInfo, outputPath); 251 } 252 253 /** 254 * Returns the location where the inputPath will be copied. 255 */ 256 private Path getOutputPath(final SnapshotFileInfo inputInfo) throws IOException { 257 Path path = null; 258 switch (inputInfo.getType()) { 259 case HFILE: 260 Path inputPath = new Path(inputInfo.getHfile()); 261 String family = inputPath.getParent().getName(); 262 TableName table = HFileLink.getReferencedTableName(inputPath.getName()); 263 String region = HFileLink.getReferencedRegionName(inputPath.getName()); 264 String hfile = HFileLink.getReferencedHFileName(inputPath.getName()); 265 path = new Path(CommonFSUtils.getTableDir(new Path("./"), table), 266 new Path(region, new Path(family, hfile))); 267 break; 268 case WAL: 269 LOG.warn("snapshot does not keeps WALs: " + inputInfo); 270 break; 271 default: 272 throw new IOException("Invalid File Type: " + inputInfo.getType().toString()); 273 } 274 return new Path(outputArchive, path); 275 } 276 277 @SuppressWarnings("checkstyle:linelength") 278 /** 279 * Used by TestExportSnapshot to test for retries when failures happen. Failure is injected in 280 * {@link #copyFile(Mapper.Context, org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotFileInfo, Path)}. 281 */ 282 private void injectTestFailure(final Context context, final SnapshotFileInfo inputInfo) 283 throws IOException { 284 if (!context.getConfiguration().getBoolean(Testing.CONF_TEST_FAILURE, false)) return; 285 if (testing.injectedFailureCount >= testing.failuresCountToInject) return; 286 testing.injectedFailureCount++; 287 context.getCounter(Counter.COPY_FAILED).increment(1); 288 LOG.debug("Injecting failure. Count: " + testing.injectedFailureCount); 289 throw new IOException(String.format("TEST FAILURE (%d of max %d): Unable to copy input=%s", 290 testing.injectedFailureCount, testing.failuresCountToInject, inputInfo)); 291 } 292 293 private void copyFile(final Context context, final SnapshotFileInfo inputInfo, 294 final Path outputPath) throws IOException { 295 // Get the file information 296 FileStatus inputStat = getSourceFileStatus(context, inputInfo); 297 298 // Verify if the output file exists and is the same that we want to copy 299 if (outputFs.exists(outputPath)) { 300 FileStatus outputStat = outputFs.getFileStatus(outputPath); 301 if (outputStat != null && sameFile(inputStat, outputStat)) { 302 LOG.info("Skip copy " + inputStat.getPath() + " to " + outputPath + ", same file."); 303 context.getCounter(Counter.FILES_SKIPPED).increment(1); 304 context.getCounter(Counter.BYTES_SKIPPED).increment(inputStat.getLen()); 305 return; 306 } 307 } 308 309 InputStream in = openSourceFile(context, inputInfo); 310 int bandwidthMB = context.getConfiguration().getInt(CONF_BANDWIDTH_MB, 100); 311 if (Integer.MAX_VALUE != bandwidthMB) { 312 in = new ThrottledInputStream(new BufferedInputStream(in), bandwidthMB * 1024 * 1024L); 313 } 314 315 try { 316 context.getCounter(Counter.BYTES_EXPECTED).increment(inputStat.getLen()); 317 318 // Ensure that the output folder is there and copy the file 319 createOutputPath(outputPath.getParent()); 320 FSDataOutputStream out = outputFs.create(outputPath, true); 321 try { 322 copyData(context, inputStat.getPath(), in, outputPath, out, inputStat.getLen()); 323 } finally { 324 out.close(); 325 } 326 327 // Try to Preserve attributes 328 if (!preserveAttributes(outputPath, inputStat)) { 329 LOG.warn("You may have to run manually chown on: " + outputPath); 330 } 331 } finally { 332 in.close(); 333 injectTestFailure(context, inputInfo); 334 } 335 } 336 337 /** 338 * Create the output folder and optionally set ownership. 339 */ 340 private void createOutputPath(final Path path) throws IOException { 341 if (filesUser == null && filesGroup == null) { 342 outputFs.mkdirs(path); 343 } else { 344 Path parent = path.getParent(); 345 if (!outputFs.exists(parent) && !parent.isRoot()) { 346 createOutputPath(parent); 347 } 348 outputFs.mkdirs(path); 349 if (filesUser != null || filesGroup != null) { 350 // override the owner when non-null user/group is specified 351 outputFs.setOwner(path, filesUser, filesGroup); 352 } 353 if (filesMode > 0) { 354 outputFs.setPermission(path, new FsPermission(filesMode)); 355 } 356 } 357 } 358 359 /** 360 * Try to Preserve the files attribute selected by the user copying them from the source file 361 * This is only required when you are exporting as a different user than "hbase" or on a system 362 * that doesn't have the "hbase" user. This is not considered a blocking failure since the user 363 * can force a chmod with the user that knows is available on the system. 364 */ 365 private boolean preserveAttributes(final Path path, final FileStatus refStat) { 366 FileStatus stat; 367 try { 368 stat = outputFs.getFileStatus(path); 369 } catch (IOException e) { 370 LOG.warn("Unable to get the status for file=" + path); 371 return false; 372 } 373 374 try { 375 if (filesMode > 0 && stat.getPermission().toShort() != filesMode) { 376 outputFs.setPermission(path, new FsPermission(filesMode)); 377 } else if (refStat != null && !stat.getPermission().equals(refStat.getPermission())) { 378 outputFs.setPermission(path, refStat.getPermission()); 379 } 380 } catch (IOException e) { 381 LOG.warn("Unable to set the permission for file=" + stat.getPath() + ": " + e.getMessage()); 382 return false; 383 } 384 385 boolean hasRefStat = (refStat != null); 386 String user = stringIsNotEmpty(filesUser) || !hasRefStat ? filesUser : refStat.getOwner(); 387 String group = stringIsNotEmpty(filesGroup) || !hasRefStat ? filesGroup : refStat.getGroup(); 388 if (stringIsNotEmpty(user) || stringIsNotEmpty(group)) { 389 try { 390 if (!(user.equals(stat.getOwner()) && group.equals(stat.getGroup()))) { 391 outputFs.setOwner(path, user, group); 392 } 393 } catch (IOException e) { 394 LOG.warn( 395 "Unable to set the owner/group for file=" + stat.getPath() + ": " + e.getMessage()); 396 LOG.warn("The user/group may not exist on the destination cluster: user=" + user 397 + " group=" + group); 398 return false; 399 } 400 } 401 402 return true; 403 } 404 405 private boolean stringIsNotEmpty(final String str) { 406 return str != null && str.length() > 0; 407 } 408 409 private void copyData(final Context context, final Path inputPath, final InputStream in, 410 final Path outputPath, final FSDataOutputStream out, final long inputFileSize) 411 throws IOException { 412 final String statusMessage = 413 "copied %s/" + StringUtils.humanReadableInt(inputFileSize) + " (%.1f%%)"; 414 415 try { 416 byte[] buffer = new byte[bufferSize]; 417 long totalBytesWritten = 0; 418 int reportBytes = 0; 419 int bytesRead; 420 421 long stime = System.currentTimeMillis(); 422 while ((bytesRead = in.read(buffer)) > 0) { 423 out.write(buffer, 0, bytesRead); 424 totalBytesWritten += bytesRead; 425 reportBytes += bytesRead; 426 427 if (reportBytes >= REPORT_SIZE) { 428 context.getCounter(Counter.BYTES_COPIED).increment(reportBytes); 429 context.setStatus( 430 String.format(statusMessage, StringUtils.humanReadableInt(totalBytesWritten), 431 (totalBytesWritten / (float) inputFileSize) * 100.0f) + " from " + inputPath 432 + " to " + outputPath); 433 reportBytes = 0; 434 } 435 } 436 long etime = System.currentTimeMillis(); 437 438 context.getCounter(Counter.BYTES_COPIED).increment(reportBytes); 439 context 440 .setStatus(String.format(statusMessage, StringUtils.humanReadableInt(totalBytesWritten), 441 (totalBytesWritten / (float) inputFileSize) * 100.0f) + " from " + inputPath + " to " 442 + outputPath); 443 444 // Verify that the written size match 445 if (totalBytesWritten != inputFileSize) { 446 String msg = "number of bytes copied not matching copied=" + totalBytesWritten 447 + " expected=" + inputFileSize + " for file=" + inputPath; 448 throw new IOException(msg); 449 } 450 451 LOG.info("copy completed for input=" + inputPath + " output=" + outputPath); 452 LOG 453 .info("size=" + totalBytesWritten + " (" + StringUtils.humanReadableInt(totalBytesWritten) 454 + ")" + " time=" + StringUtils.formatTimeDiff(etime, stime) + String 455 .format(" %.3fM/sec", (totalBytesWritten / ((etime - stime) / 1000.0)) / 1048576.0)); 456 context.getCounter(Counter.FILES_COPIED).increment(1); 457 } catch (IOException e) { 458 LOG.error("Error copying " + inputPath + " to " + outputPath, e); 459 context.getCounter(Counter.COPY_FAILED).increment(1); 460 throw e; 461 } 462 } 463 464 /** 465 * Try to open the "source" file. Throws an IOException if the communication with the inputFs 466 * fail or if the file is not found. 467 */ 468 private FSDataInputStream openSourceFile(Context context, final SnapshotFileInfo fileInfo) 469 throws IOException { 470 try { 471 Configuration conf = context.getConfiguration(); 472 FileLink link = null; 473 switch (fileInfo.getType()) { 474 case HFILE: 475 Path inputPath = new Path(fileInfo.getHfile()); 476 link = getFileLink(inputPath, conf); 477 break; 478 case WAL: 479 String serverName = fileInfo.getWalServer(); 480 String logName = fileInfo.getWalName(); 481 link = new WALLink(inputRoot, serverName, logName); 482 break; 483 default: 484 throw new IOException("Invalid File Type: " + fileInfo.getType().toString()); 485 } 486 return link.open(inputFs); 487 } catch (IOException e) { 488 context.getCounter(Counter.MISSING_FILES).increment(1); 489 LOG.error("Unable to open source file=" + fileInfo.toString(), e); 490 throw e; 491 } 492 } 493 494 private FileStatus getSourceFileStatus(Context context, final SnapshotFileInfo fileInfo) 495 throws IOException { 496 try { 497 Configuration conf = context.getConfiguration(); 498 FileLink link = null; 499 switch (fileInfo.getType()) { 500 case HFILE: 501 Path inputPath = new Path(fileInfo.getHfile()); 502 link = getFileLink(inputPath, conf); 503 break; 504 case WAL: 505 link = new WALLink(inputRoot, fileInfo.getWalServer(), fileInfo.getWalName()); 506 break; 507 default: 508 throw new IOException("Invalid File Type: " + fileInfo.getType().toString()); 509 } 510 return link.getFileStatus(inputFs); 511 } catch (FileNotFoundException e) { 512 context.getCounter(Counter.MISSING_FILES).increment(1); 513 LOG.error("Unable to get the status for source file=" + fileInfo.toString(), e); 514 throw e; 515 } catch (IOException e) { 516 LOG.error("Unable to get the status for source file=" + fileInfo.toString(), e); 517 throw e; 518 } 519 } 520 521 private FileLink getFileLink(Path path, Configuration conf) throws IOException { 522 String regionName = HFileLink.getReferencedRegionName(path.getName()); 523 TableName tableName = HFileLink.getReferencedTableName(path.getName()); 524 if (MobUtils.getMobRegionInfo(tableName).getEncodedName().equals(regionName)) { 525 return HFileLink.buildFromHFileLinkPattern(MobUtils.getQualifiedMobRootDir(conf), 526 HFileArchiveUtil.getArchivePath(conf), path); 527 } 528 return HFileLink.buildFromHFileLinkPattern(inputRoot, inputArchive, path); 529 } 530 531 private FileChecksum getFileChecksum(final FileSystem fs, final Path path) { 532 try { 533 return fs.getFileChecksum(path); 534 } catch (IOException e) { 535 LOG.warn("Unable to get checksum for file=" + path, e); 536 return null; 537 } 538 } 539 540 /** 541 * Check if the two files are equal by looking at the file length, and at the checksum (if user 542 * has specified the verifyChecksum flag). 543 */ 544 private boolean sameFile(final FileStatus inputStat, final FileStatus outputStat) { 545 // Not matching length 546 if (inputStat.getLen() != outputStat.getLen()) return false; 547 548 // Mark files as equals, since user asked for no checksum verification 549 if (!verifyChecksum) return true; 550 551 // If checksums are not available, files are not the same. 552 FileChecksum inChecksum = getFileChecksum(inputFs, inputStat.getPath()); 553 if (inChecksum == null) return false; 554 555 FileChecksum outChecksum = getFileChecksum(outputFs, outputStat.getPath()); 556 if (outChecksum == null) return false; 557 558 return inChecksum.equals(outChecksum); 559 } 560 } 561 562 // ========================================================================== 563 // Input Format 564 // ========================================================================== 565 566 /** 567 * Extract the list of files (HFiles/WALs) to copy using Map-Reduce. 568 * @return list of files referenced by the snapshot (pair of path and size) 569 */ 570 private static List<Pair<SnapshotFileInfo, Long>> getSnapshotFiles(final Configuration conf, 571 final FileSystem fs, final Path snapshotDir) throws IOException { 572 SnapshotDescription snapshotDesc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir); 573 574 final List<Pair<SnapshotFileInfo, Long>> files = new ArrayList<>(); 575 final TableName table = TableName.valueOf(snapshotDesc.getTable()); 576 577 // Get snapshot files 578 LOG.info("Loading Snapshot '" + snapshotDesc.getName() + "' hfile list"); 579 SnapshotReferenceUtil.visitReferencedFiles(conf, fs, snapshotDir, snapshotDesc, 580 new SnapshotReferenceUtil.SnapshotVisitor() { 581 @Override 582 public void storeFile(final RegionInfo regionInfo, final String family, 583 final SnapshotRegionManifest.StoreFile storeFile) throws IOException { 584 Pair<SnapshotFileInfo, Long> snapshotFileAndSize = null; 585 if (!storeFile.hasReference()) { 586 String region = regionInfo.getEncodedName(); 587 String hfile = storeFile.getName(); 588 snapshotFileAndSize = getSnapshotFileAndSize(fs, conf, table, region, family, hfile, 589 storeFile.hasFileSize() ? storeFile.getFileSize() : -1); 590 } else { 591 Pair<String, String> referredToRegionAndFile = 592 StoreFileInfo.getReferredToRegionAndFile(storeFile.getName()); 593 String referencedRegion = referredToRegionAndFile.getFirst(); 594 String referencedHFile = referredToRegionAndFile.getSecond(); 595 snapshotFileAndSize = getSnapshotFileAndSize(fs, conf, table, referencedRegion, family, 596 referencedHFile, storeFile.hasFileSize() ? storeFile.getFileSize() : -1); 597 } 598 files.add(snapshotFileAndSize); 599 } 600 }); 601 602 return files; 603 } 604 605 private static Pair<SnapshotFileInfo, Long> getSnapshotFileAndSize(FileSystem fs, 606 Configuration conf, TableName table, String region, String family, String hfile, long size) 607 throws IOException { 608 Path path = HFileLink.createPath(table, region, family, hfile); 609 SnapshotFileInfo fileInfo = SnapshotFileInfo.newBuilder().setType(SnapshotFileInfo.Type.HFILE) 610 .setHfile(path.toString()).build(); 611 if (size == -1) { 612 size = HFileLink.buildFromHFileLinkPattern(conf, path).getFileStatus(fs).getLen(); 613 } 614 return new Pair<>(fileInfo, size); 615 } 616 617 /** 618 * Given a list of file paths and sizes, create around ngroups in as balanced a way as possible. 619 * The groups created will have similar amounts of bytes. 620 * <p> 621 * The algorithm used is pretty straightforward; the file list is sorted by size, and then each 622 * group fetch the bigger file available, iterating through groups alternating the direction. 623 */ 624 static List<List<Pair<SnapshotFileInfo, Long>>> 625 getBalancedSplits(final List<Pair<SnapshotFileInfo, Long>> files, final int ngroups) { 626 // Sort files by size, from small to big 627 Collections.sort(files, new Comparator<Pair<SnapshotFileInfo, Long>>() { 628 public int compare(Pair<SnapshotFileInfo, Long> a, Pair<SnapshotFileInfo, Long> b) { 629 long r = a.getSecond() - b.getSecond(); 630 return (r < 0) ? -1 : ((r > 0) ? 1 : 0); 631 } 632 }); 633 634 // create balanced groups 635 List<List<Pair<SnapshotFileInfo, Long>>> fileGroups = new LinkedList<>(); 636 long[] sizeGroups = new long[ngroups]; 637 int hi = files.size() - 1; 638 int lo = 0; 639 640 List<Pair<SnapshotFileInfo, Long>> group; 641 int dir = 1; 642 int g = 0; 643 644 while (hi >= lo) { 645 if (g == fileGroups.size()) { 646 group = new LinkedList<>(); 647 fileGroups.add(group); 648 } else { 649 group = fileGroups.get(g); 650 } 651 652 Pair<SnapshotFileInfo, Long> fileInfo = files.get(hi--); 653 654 // add the hi one 655 sizeGroups[g] += fileInfo.getSecond(); 656 group.add(fileInfo); 657 658 // change direction when at the end or the beginning 659 g += dir; 660 if (g == ngroups) { 661 dir = -1; 662 g = ngroups - 1; 663 } else if (g < 0) { 664 dir = 1; 665 g = 0; 666 } 667 } 668 669 if (LOG.isDebugEnabled()) { 670 for (int i = 0; i < sizeGroups.length; ++i) { 671 LOG.debug("export split=" + i + " size=" + StringUtils.humanReadableInt(sizeGroups[i])); 672 } 673 } 674 675 return fileGroups; 676 } 677 678 private static class ExportSnapshotInputFormat extends InputFormat<BytesWritable, NullWritable> { 679 @Override 680 public RecordReader<BytesWritable, NullWritable> createRecordReader(InputSplit split, 681 TaskAttemptContext tac) throws IOException, InterruptedException { 682 return new ExportSnapshotRecordReader(((ExportSnapshotInputSplit) split).getSplitKeys()); 683 } 684 685 @Override 686 public List<InputSplit> getSplits(JobContext context) throws IOException, InterruptedException { 687 Configuration conf = context.getConfiguration(); 688 Path snapshotDir = new Path(conf.get(CONF_SNAPSHOT_DIR)); 689 FileSystem fs = FileSystem.get(snapshotDir.toUri(), conf); 690 691 List<Pair<SnapshotFileInfo, Long>> snapshotFiles = getSnapshotFiles(conf, fs, snapshotDir); 692 int mappers = conf.getInt(CONF_NUM_SPLITS, 0); 693 if (mappers == 0 && snapshotFiles.size() > 0) { 694 mappers = 1 + (snapshotFiles.size() / conf.getInt(CONF_MAP_GROUP, 10)); 695 mappers = Math.min(mappers, snapshotFiles.size()); 696 conf.setInt(CONF_NUM_SPLITS, mappers); 697 conf.setInt(MR_NUM_MAPS, mappers); 698 } 699 700 List<List<Pair<SnapshotFileInfo, Long>>> groups = getBalancedSplits(snapshotFiles, mappers); 701 List<InputSplit> splits = new ArrayList(groups.size()); 702 for (List<Pair<SnapshotFileInfo, Long>> files : groups) { 703 splits.add(new ExportSnapshotInputSplit(files)); 704 } 705 return splits; 706 } 707 708 private static class ExportSnapshotInputSplit extends InputSplit implements Writable { 709 private List<Pair<BytesWritable, Long>> files; 710 private long length; 711 712 public ExportSnapshotInputSplit() { 713 this.files = null; 714 } 715 716 public ExportSnapshotInputSplit(final List<Pair<SnapshotFileInfo, Long>> snapshotFiles) { 717 this.files = new ArrayList(snapshotFiles.size()); 718 for (Pair<SnapshotFileInfo, Long> fileInfo : snapshotFiles) { 719 this.files.add( 720 new Pair<>(new BytesWritable(fileInfo.getFirst().toByteArray()), fileInfo.getSecond())); 721 this.length += fileInfo.getSecond(); 722 } 723 } 724 725 private List<Pair<BytesWritable, Long>> getSplitKeys() { 726 return files; 727 } 728 729 @Override 730 public long getLength() throws IOException, InterruptedException { 731 return length; 732 } 733 734 @Override 735 public String[] getLocations() throws IOException, InterruptedException { 736 return new String[] {}; 737 } 738 739 @Override 740 public void readFields(DataInput in) throws IOException { 741 int count = in.readInt(); 742 files = new ArrayList<>(count); 743 length = 0; 744 for (int i = 0; i < count; ++i) { 745 BytesWritable fileInfo = new BytesWritable(); 746 fileInfo.readFields(in); 747 long size = in.readLong(); 748 files.add(new Pair<>(fileInfo, size)); 749 length += size; 750 } 751 } 752 753 @Override 754 public void write(DataOutput out) throws IOException { 755 out.writeInt(files.size()); 756 for (final Pair<BytesWritable, Long> fileInfo : files) { 757 fileInfo.getFirst().write(out); 758 out.writeLong(fileInfo.getSecond()); 759 } 760 } 761 } 762 763 private static class ExportSnapshotRecordReader 764 extends RecordReader<BytesWritable, NullWritable> { 765 private final List<Pair<BytesWritable, Long>> files; 766 private long totalSize = 0; 767 private long procSize = 0; 768 private int index = -1; 769 770 ExportSnapshotRecordReader(final List<Pair<BytesWritable, Long>> files) { 771 this.files = files; 772 for (Pair<BytesWritable, Long> fileInfo : files) { 773 totalSize += fileInfo.getSecond(); 774 } 775 } 776 777 @Override 778 public void close() { 779 } 780 781 @Override 782 public BytesWritable getCurrentKey() { 783 return files.get(index).getFirst(); 784 } 785 786 @Override 787 public NullWritable getCurrentValue() { 788 return NullWritable.get(); 789 } 790 791 @Override 792 public float getProgress() { 793 return (float) procSize / totalSize; 794 } 795 796 @Override 797 public void initialize(InputSplit split, TaskAttemptContext tac) { 798 } 799 800 @Override 801 public boolean nextKeyValue() { 802 if (index >= 0) { 803 procSize += files.get(index).getSecond(); 804 } 805 return (++index < files.size()); 806 } 807 } 808 } 809 810 // ========================================================================== 811 // Tool 812 // ========================================================================== 813 814 /** 815 * Run Map-Reduce Job to perform the files copy. 816 */ 817 private void runCopyJob(final Path inputRoot, final Path outputRoot, final String snapshotName, 818 final Path snapshotDir, final boolean verifyChecksum, final String filesUser, 819 final String filesGroup, final int filesMode, final int mappers, final int bandwidthMB) 820 throws IOException, InterruptedException, ClassNotFoundException { 821 Configuration conf = getConf(); 822 if (filesGroup != null) conf.set(CONF_FILES_GROUP, filesGroup); 823 if (filesUser != null) conf.set(CONF_FILES_USER, filesUser); 824 if (mappers > 0) { 825 conf.setInt(CONF_NUM_SPLITS, mappers); 826 conf.setInt(MR_NUM_MAPS, mappers); 827 } 828 conf.setInt(CONF_FILES_MODE, filesMode); 829 conf.setBoolean(CONF_CHECKSUM_VERIFY, verifyChecksum); 830 conf.set(CONF_OUTPUT_ROOT, outputRoot.toString()); 831 conf.set(CONF_INPUT_ROOT, inputRoot.toString()); 832 conf.setInt(CONF_BANDWIDTH_MB, bandwidthMB); 833 conf.set(CONF_SNAPSHOT_NAME, snapshotName); 834 conf.set(CONF_SNAPSHOT_DIR, snapshotDir.toString()); 835 836 String jobname = conf.get(CONF_MR_JOB_NAME, "ExportSnapshot-" + snapshotName); 837 Job job = new Job(conf); 838 job.setJobName(jobname); 839 job.setJarByClass(ExportSnapshot.class); 840 TableMapReduceUtil.addDependencyJars(job); 841 job.setMapperClass(ExportMapper.class); 842 job.setInputFormatClass(ExportSnapshotInputFormat.class); 843 job.setOutputFormatClass(NullOutputFormat.class); 844 job.setMapSpeculativeExecution(false); 845 job.setNumReduceTasks(0); 846 847 // Acquire the delegation Tokens 848 Configuration srcConf = HBaseConfiguration.createClusterConf(conf, null, CONF_SOURCE_PREFIX); 849 TokenCache.obtainTokensForNamenodes(job.getCredentials(), new Path[] { inputRoot }, srcConf); 850 Configuration destConf = HBaseConfiguration.createClusterConf(conf, null, CONF_DEST_PREFIX); 851 TokenCache.obtainTokensForNamenodes(job.getCredentials(), new Path[] { outputRoot }, destConf); 852 853 // Run the MR Job 854 if (!job.waitForCompletion(true)) { 855 throw new ExportSnapshotException(job.getStatus().getFailureInfo()); 856 } 857 } 858 859 private void verifySnapshot(final Configuration baseConf, final FileSystem fs, final Path rootDir, 860 final Path snapshotDir) throws IOException { 861 // Update the conf with the current root dir, since may be a different cluster 862 Configuration conf = new Configuration(baseConf); 863 CommonFSUtils.setRootDir(conf, rootDir); 864 CommonFSUtils.setFsDefault(conf, CommonFSUtils.getRootDir(conf)); 865 SnapshotDescription snapshotDesc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir); 866 SnapshotReferenceUtil.verifySnapshot(conf, fs, snapshotDir, snapshotDesc); 867 } 868 869 private void setConfigParallel(FileSystem outputFs, List<Path> traversedPath, 870 BiConsumer<FileSystem, Path> task, Configuration conf) throws IOException { 871 ExecutorService pool = Executors 872 .newFixedThreadPool(conf.getInt(CONF_COPY_MANIFEST_THREADS, DEFAULT_COPY_MANIFEST_THREADS)); 873 List<Future<Void>> futures = new ArrayList<>(); 874 for (Path dstPath : traversedPath) { 875 Future<Void> future = (Future<Void>) pool.submit(() -> task.accept(outputFs, dstPath)); 876 futures.add(future); 877 } 878 try { 879 for (Future<Void> future : futures) { 880 future.get(); 881 } 882 } catch (InterruptedException | ExecutionException e) { 883 throw new IOException(e); 884 } finally { 885 pool.shutdownNow(); 886 } 887 } 888 889 private void setOwnerParallel(FileSystem outputFs, String filesUser, String filesGroup, 890 Configuration conf, List<Path> traversedPath) throws IOException { 891 setConfigParallel(outputFs, traversedPath, (fs, path) -> { 892 try { 893 fs.setOwner(path, filesUser, filesGroup); 894 } catch (IOException e) { 895 throw new RuntimeException( 896 "set owner for file " + path + " to " + filesUser + ":" + filesGroup + " failed", e); 897 } 898 }, conf); 899 } 900 901 private void setPermissionParallel(final FileSystem outputFs, final short filesMode, 902 final List<Path> traversedPath, final Configuration conf) throws IOException { 903 if (filesMode <= 0) { 904 return; 905 } 906 FsPermission perm = new FsPermission(filesMode); 907 setConfigParallel(outputFs, traversedPath, (fs, path) -> { 908 try { 909 fs.setPermission(path, perm); 910 } catch (IOException e) { 911 throw new RuntimeException( 912 "set permission for file " + path + " to " + filesMode + " failed", e); 913 } 914 }, conf); 915 } 916 917 private boolean verifyTarget = true; 918 private boolean verifySource = true; 919 private boolean verifyChecksum = true; 920 private String snapshotName = null; 921 private String targetName = null; 922 private boolean overwrite = false; 923 private String filesGroup = null; 924 private String filesUser = null; 925 private Path outputRoot = null; 926 private Path inputRoot = null; 927 private int bandwidthMB = Integer.MAX_VALUE; 928 private int filesMode = 0; 929 private int mappers = 0; 930 931 @Override 932 protected void processOptions(CommandLine cmd) { 933 snapshotName = cmd.getOptionValue(Options.SNAPSHOT.getLongOpt(), snapshotName); 934 targetName = cmd.getOptionValue(Options.TARGET_NAME.getLongOpt(), targetName); 935 if (cmd.hasOption(Options.COPY_TO.getLongOpt())) { 936 outputRoot = new Path(cmd.getOptionValue(Options.COPY_TO.getLongOpt())); 937 } 938 if (cmd.hasOption(Options.COPY_FROM.getLongOpt())) { 939 inputRoot = new Path(cmd.getOptionValue(Options.COPY_FROM.getLongOpt())); 940 } 941 mappers = getOptionAsInt(cmd, Options.MAPPERS.getLongOpt(), mappers); 942 filesUser = cmd.getOptionValue(Options.CHUSER.getLongOpt(), filesUser); 943 filesGroup = cmd.getOptionValue(Options.CHGROUP.getLongOpt(), filesGroup); 944 filesMode = getOptionAsInt(cmd, Options.CHMOD.getLongOpt(), filesMode, 8); 945 bandwidthMB = getOptionAsInt(cmd, Options.BANDWIDTH.getLongOpt(), bandwidthMB); 946 overwrite = cmd.hasOption(Options.OVERWRITE.getLongOpt()); 947 // And verifyChecksum and verifyTarget with values read from old args in processOldArgs(...). 948 verifyChecksum = !cmd.hasOption(Options.NO_CHECKSUM_VERIFY.getLongOpt()); 949 verifyTarget = !cmd.hasOption(Options.NO_TARGET_VERIFY.getLongOpt()); 950 verifySource = !cmd.hasOption(Options.NO_SOURCE_VERIFY.getLongOpt()); 951 } 952 953 /** 954 * Execute the export snapshot by copying the snapshot metadata, hfiles and wals. 955 * @return 0 on success, and != 0 upon failure. 956 */ 957 @Override 958 public int doWork() throws IOException { 959 Configuration conf = getConf(); 960 961 // Check user options 962 if (snapshotName == null) { 963 System.err.println("Snapshot name not provided."); 964 LOG.error("Use -h or --help for usage instructions."); 965 return 0; 966 } 967 968 if (outputRoot == null) { 969 System.err 970 .println("Destination file-system (--" + Options.COPY_TO.getLongOpt() + ") not provided."); 971 LOG.error("Use -h or --help for usage instructions."); 972 return 0; 973 } 974 975 if (targetName == null) { 976 targetName = snapshotName; 977 } 978 if (inputRoot == null) { 979 inputRoot = CommonFSUtils.getRootDir(conf); 980 } else { 981 CommonFSUtils.setRootDir(conf, inputRoot); 982 } 983 984 Configuration srcConf = HBaseConfiguration.createClusterConf(conf, null, CONF_SOURCE_PREFIX); 985 srcConf.setBoolean("fs." + inputRoot.toUri().getScheme() + ".impl.disable.cache", true); 986 FileSystem inputFs = FileSystem.get(inputRoot.toUri(), srcConf); 987 Configuration destConf = HBaseConfiguration.createClusterConf(conf, null, CONF_DEST_PREFIX); 988 destConf.setBoolean("fs." + outputRoot.toUri().getScheme() + ".impl.disable.cache", true); 989 FileSystem outputFs = FileSystem.get(outputRoot.toUri(), destConf); 990 boolean skipTmp = conf.getBoolean(CONF_SKIP_TMP, false) 991 || conf.get(SnapshotDescriptionUtils.SNAPSHOT_WORKING_DIR) != null; 992 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, inputRoot); 993 Path snapshotTmpDir = 994 SnapshotDescriptionUtils.getWorkingSnapshotDir(targetName, outputRoot, destConf); 995 Path outputSnapshotDir = 996 SnapshotDescriptionUtils.getCompletedSnapshotDir(targetName, outputRoot); 997 Path initialOutputSnapshotDir = skipTmp ? outputSnapshotDir : snapshotTmpDir; 998 LOG.debug("inputFs={}, inputRoot={}", inputFs.getUri().toString(), inputRoot); 999 LOG.debug("outputFs={}, outputRoot={}, skipTmp={}, initialOutputSnapshotDir={}", outputFs, 1000 outputRoot.toString(), skipTmp, initialOutputSnapshotDir); 1001 1002 // Verify snapshot source before copying files 1003 if (verifySource) { 1004 LOG.info("Verify snapshot source, inputFs={}, inputRoot={}, snapshotDir={}.", 1005 inputFs.getUri(), inputRoot, snapshotDir); 1006 verifySnapshot(srcConf, inputFs, inputRoot, snapshotDir); 1007 } 1008 1009 // Find the necessary directory which need to change owner and group 1010 Path needSetOwnerDir = SnapshotDescriptionUtils.getSnapshotRootDir(outputRoot); 1011 if (outputFs.exists(needSetOwnerDir)) { 1012 if (skipTmp) { 1013 needSetOwnerDir = outputSnapshotDir; 1014 } else { 1015 needSetOwnerDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(outputRoot, destConf); 1016 if (outputFs.exists(needSetOwnerDir)) { 1017 needSetOwnerDir = snapshotTmpDir; 1018 } 1019 } 1020 } 1021 1022 // Check if the snapshot already exists 1023 if (outputFs.exists(outputSnapshotDir)) { 1024 if (overwrite) { 1025 if (!outputFs.delete(outputSnapshotDir, true)) { 1026 System.err.println("Unable to remove existing snapshot directory: " + outputSnapshotDir); 1027 return 1; 1028 } 1029 } else { 1030 System.err.println("The snapshot '" + targetName + "' already exists in the destination: " 1031 + outputSnapshotDir); 1032 return 1; 1033 } 1034 } 1035 1036 if (!skipTmp) { 1037 // Check if the snapshot already in-progress 1038 if (outputFs.exists(snapshotTmpDir)) { 1039 if (overwrite) { 1040 if (!outputFs.delete(snapshotTmpDir, true)) { 1041 System.err 1042 .println("Unable to remove existing snapshot tmp directory: " + snapshotTmpDir); 1043 return 1; 1044 } 1045 } else { 1046 System.err 1047 .println("A snapshot with the same name '" + targetName + "' may be in-progress"); 1048 System.err 1049 .println("Please check " + snapshotTmpDir + ". If the snapshot has completed, "); 1050 System.err 1051 .println("consider removing " + snapshotTmpDir + " by using the -overwrite option"); 1052 return 1; 1053 } 1054 } 1055 } 1056 1057 // Step 1 - Copy fs1:/.snapshot/<snapshot> to fs2:/.snapshot/.tmp/<snapshot> 1058 // The snapshot references must be copied before the hfiles otherwise the cleaner 1059 // will remove them because they are unreferenced. 1060 List<Path> travesedPaths = new ArrayList<>(); 1061 boolean copySucceeded = false; 1062 try { 1063 LOG.info("Copy Snapshot Manifest from " + snapshotDir + " to " + initialOutputSnapshotDir); 1064 travesedPaths = 1065 FSUtils.copyFilesParallel(inputFs, snapshotDir, outputFs, initialOutputSnapshotDir, conf, 1066 conf.getInt(CONF_COPY_MANIFEST_THREADS, DEFAULT_COPY_MANIFEST_THREADS)); 1067 copySucceeded = true; 1068 } catch (IOException e) { 1069 throw new ExportSnapshotException("Failed to copy the snapshot directory: from=" + snapshotDir 1070 + " to=" + initialOutputSnapshotDir, e); 1071 } finally { 1072 if (copySucceeded) { 1073 if (filesUser != null || filesGroup != null) { 1074 LOG.warn( 1075 (filesUser == null ? "" : "Change the owner of " + needSetOwnerDir + " to " + filesUser) 1076 + (filesGroup == null 1077 ? "" 1078 : ", Change the group of " + needSetOwnerDir + " to " + filesGroup)); 1079 setOwnerParallel(outputFs, filesUser, filesGroup, conf, travesedPaths); 1080 } 1081 if (filesMode > 0) { 1082 LOG.warn("Change the permission of " + needSetOwnerDir + " to " + filesMode); 1083 setPermissionParallel(outputFs, (short) filesMode, travesedPaths, conf); 1084 } 1085 } 1086 } 1087 1088 // Write a new .snapshotinfo if the target name is different from the source name 1089 if (!targetName.equals(snapshotName)) { 1090 SnapshotDescription snapshotDesc = SnapshotDescriptionUtils 1091 .readSnapshotInfo(inputFs, snapshotDir).toBuilder().setName(targetName).build(); 1092 SnapshotDescriptionUtils.writeSnapshotInfo(snapshotDesc, initialOutputSnapshotDir, outputFs); 1093 if (filesUser != null || filesGroup != null) { 1094 outputFs.setOwner( 1095 new Path(initialOutputSnapshotDir, SnapshotDescriptionUtils.SNAPSHOTINFO_FILE), filesUser, 1096 filesGroup); 1097 } 1098 if (filesMode > 0) { 1099 outputFs.setPermission( 1100 new Path(initialOutputSnapshotDir, SnapshotDescriptionUtils.SNAPSHOTINFO_FILE), 1101 new FsPermission((short) filesMode)); 1102 } 1103 } 1104 1105 // Step 2 - Start MR Job to copy files 1106 // The snapshot references must be copied before the files otherwise the files gets removed 1107 // by the HFileArchiver, since they have no references. 1108 try { 1109 runCopyJob(inputRoot, outputRoot, snapshotName, snapshotDir, verifyChecksum, filesUser, 1110 filesGroup, filesMode, mappers, bandwidthMB); 1111 1112 LOG.info("Finalize the Snapshot Export"); 1113 if (!skipTmp) { 1114 // Step 3 - Rename fs2:/.snapshot/.tmp/<snapshot> fs2:/.snapshot/<snapshot> 1115 if (!outputFs.rename(snapshotTmpDir, outputSnapshotDir)) { 1116 throw new ExportSnapshotException("Unable to rename snapshot directory from=" 1117 + snapshotTmpDir + " to=" + outputSnapshotDir); 1118 } 1119 } 1120 1121 // Step 4 - Verify snapshot integrity 1122 if (verifyTarget) { 1123 LOG.info("Verify snapshot integrity"); 1124 verifySnapshot(destConf, outputFs, outputRoot, outputSnapshotDir); 1125 } 1126 1127 LOG.info("Export Completed: " + targetName); 1128 return 0; 1129 } catch (Exception e) { 1130 LOG.error("Snapshot export failed", e); 1131 if (!skipTmp) { 1132 outputFs.delete(snapshotTmpDir, true); 1133 } 1134 outputFs.delete(outputSnapshotDir, true); 1135 return 1; 1136 } finally { 1137 IOUtils.closeStream(inputFs); 1138 IOUtils.closeStream(outputFs); 1139 } 1140 } 1141 1142 @Override 1143 protected void printUsage() { 1144 super.printUsage(); 1145 System.out.println("\n" + "Examples:\n" + " hbase snapshot export \\\n" 1146 + " --snapshot MySnapshot --copy-to hdfs://srv2:8082/hbase \\\n" 1147 + " --chuser MyUser --chgroup MyGroup --chmod 700 --mappers 16\n" + "\n" 1148 + " hbase snapshot export \\\n" 1149 + " --snapshot MySnapshot --copy-from hdfs://srv2:8082/hbase \\\n" 1150 + " --copy-to hdfs://srv1:50070/hbase"); 1151 } 1152 1153 @Override 1154 protected void addOptions() { 1155 addRequiredOption(Options.SNAPSHOT); 1156 addOption(Options.COPY_TO); 1157 addOption(Options.COPY_FROM); 1158 addOption(Options.TARGET_NAME); 1159 addOption(Options.NO_CHECKSUM_VERIFY); 1160 addOption(Options.NO_TARGET_VERIFY); 1161 addOption(Options.NO_SOURCE_VERIFY); 1162 addOption(Options.OVERWRITE); 1163 addOption(Options.CHUSER); 1164 addOption(Options.CHGROUP); 1165 addOption(Options.CHMOD); 1166 addOption(Options.MAPPERS); 1167 addOption(Options.BANDWIDTH); 1168 } 1169 1170 public static void main(String[] args) { 1171 new ExportSnapshot().doStaticMain(args); 1172 } 1173}