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