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 org.apache.hadoop.conf.Configuration; 035import org.apache.hadoop.fs.FileStatus; 036import org.apache.hadoop.fs.FileSystem; 037import org.apache.hadoop.fs.Path; 038import org.apache.hadoop.hbase.HBaseClassTestRule; 039import org.apache.hadoop.hbase.HBaseTestingUtility; 040import org.apache.hadoop.hbase.HConstants; 041import org.apache.hadoop.hbase.TableName; 042import org.apache.hadoop.hbase.client.Admin; 043import org.apache.hadoop.hbase.client.RegionInfo; 044import org.apache.hadoop.hbase.master.snapshot.SnapshotManager; 045import org.apache.hadoop.hbase.testclassification.LargeTests; 046import org.apache.hadoop.hbase.testclassification.VerySlowMapReduceTests; 047import org.apache.hadoop.hbase.util.Bytes; 048import org.apache.hadoop.hbase.util.CommonFSUtils; 049import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 050import org.junit.After; 051import org.junit.AfterClass; 052import org.junit.Before; 053import org.junit.BeforeClass; 054import org.junit.ClassRule; 055import org.junit.Ignore; 056import org.junit.Rule; 057import org.junit.Test; 058import org.junit.experimental.categories.Category; 059import org.junit.rules.TestName; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; 064import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest; 065 066/** 067 * Test Export Snapshot Tool 068 */ 069@Ignore // HBASE-24493 070@Category({ VerySlowMapReduceTests.class, LargeTests.class }) 071public class TestExportSnapshot { 072 073 @ClassRule 074 public static final HBaseClassTestRule CLASS_RULE = 075 HBaseClassTestRule.forClass(TestExportSnapshot.class); 076 077 private static final Logger LOG = LoggerFactory.getLogger(TestExportSnapshot.class); 078 079 protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 080 081 protected final static byte[] FAMILY = Bytes.toBytes("cf"); 082 083 @Rule 084 public final TestName testName = new TestName(); 085 086 protected TableName tableName; 087 private byte[] emptySnapshotName; 088 private byte[] snapshotName; 089 private int tableNumFiles; 090 private Admin admin; 091 092 public static void setUpBaseConf(Configuration conf) { 093 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true); 094 conf.setInt("hbase.regionserver.msginterval", 100); 095 // If a single node has enough failures (default 3), resource manager will blacklist it. 096 // With only 2 nodes and tests injecting faults, we don't want that. 097 conf.setInt("mapreduce.job.maxtaskfailures.per.tracker", 100); 098 } 099 100 @BeforeClass 101 public static void setUpBeforeClass() throws Exception { 102 setUpBaseConf(TEST_UTIL.getConfiguration()); 103 TEST_UTIL.startMiniCluster(1); 104 TEST_UTIL.startMiniMapReduceCluster(); 105 } 106 107 @AfterClass 108 public static void tearDownAfterClass() throws Exception { 109 TEST_UTIL.shutdownMiniMapReduceCluster(); 110 TEST_UTIL.shutdownMiniCluster(); 111 } 112 113 /** 114 * Create a table and take a snapshot of the table used by the export test. 115 */ 116 @Before 117 public void setUp() throws Exception { 118 this.admin = TEST_UTIL.getAdmin(); 119 120 tableName = TableName.valueOf("testtb-" + testName.getMethodName()); 121 snapshotName = Bytes.toBytes("snaptb0-" + testName.getMethodName()); 122 emptySnapshotName = Bytes.toBytes("emptySnaptb0-" + testName.getMethodName()); 123 124 // create Table 125 createTable(this.tableName); 126 127 // Take an empty snapshot 128 admin.snapshot(emptySnapshotName, tableName); 129 130 // Add some rows 131 SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 50, FAMILY); 132 tableNumFiles = admin.getTableRegions(tableName).size(); 133 134 // take a snapshot 135 admin.snapshot(snapshotName, tableName); 136 } 137 138 protected void createTable(TableName tableName) throws Exception { 139 SnapshotTestingUtils.createPreSplitTable(TEST_UTIL, tableName, 2, FAMILY); 140 } 141 142 protected interface RegionPredicate { 143 boolean evaluate(final RegionInfo regionInfo); 144 } 145 146 protected RegionPredicate getBypassRegionPredicate() { 147 return null; 148 } 149 150 @After 151 public void tearDown() throws Exception { 152 TEST_UTIL.deleteTable(tableName); 153 SnapshotTestingUtils.deleteAllSnapshots(TEST_UTIL.getAdmin()); 154 SnapshotTestingUtils.deleteArchiveDirectory(TEST_UTIL); 155 } 156 157 /** 158 * Verify if exported snapshot and copied files matches the original one. 159 */ 160 @Test 161 public void testExportFileSystemState() throws Exception { 162 testExportFileSystemState(tableName, snapshotName, snapshotName, tableNumFiles); 163 } 164 165 @Test 166 public void testExportFileSystemStateWithSkipTmp() throws Exception { 167 TEST_UTIL.getConfiguration().setBoolean(ExportSnapshot.CONF_SKIP_TMP, true); 168 try { 169 testExportFileSystemState(tableName, snapshotName, snapshotName, tableNumFiles); 170 } finally { 171 TEST_UTIL.getConfiguration().setBoolean(ExportSnapshot.CONF_SKIP_TMP, false); 172 } 173 } 174 175 @Test 176 public void testEmptyExportFileSystemState() throws Exception { 177 testExportFileSystemState(tableName, emptySnapshotName, emptySnapshotName, 0); 178 } 179 180 @Test 181 public void testConsecutiveExports() throws Exception { 182 Path copyDir = getLocalDestinationDir(TEST_UTIL); 183 testExportFileSystemState(tableName, snapshotName, snapshotName, tableNumFiles, copyDir, false); 184 testExportFileSystemState(tableName, snapshotName, snapshotName, tableNumFiles, copyDir, true); 185 removeExportDir(copyDir); 186 } 187 188 @Test 189 public void testExportWithTargetName() throws Exception { 190 final byte[] targetName = Bytes.toBytes("testExportWithTargetName"); 191 testExportFileSystemState(tableName, snapshotName, targetName, tableNumFiles); 192 } 193 194 @Test 195 public void testExportWithResetTtl() throws Exception { 196 String name = "testExportWithResetTtl"; 197 TableName tableName = TableName.valueOf(name); 198 String snapshotNameStr = "snaptb-" + name; 199 byte[] snapshotName = Bytes.toBytes(snapshotNameStr); 200 Long ttl = 100000L; 201 202 try { 203 // create Table 204 createTable(tableName); 205 SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 50, FAMILY); 206 int tableNumFiles = admin.getRegions(tableName).size(); 207 // take a snapshot with TTL 208 Map<String, Object> props = new HashMap<>(); 209 props.put("TTL", ttl); 210 admin.snapshot(snapshotNameStr, tableName, props); 211 Optional<Long> ttlOpt = 212 admin.listSnapshots().stream().filter(s -> s.getName().equals(snapshotNameStr)) 213 .map(org.apache.hadoop.hbase.client.SnapshotDescription::getTtl).findAny(); 214 assertTrue(ttlOpt.isPresent()); 215 assertEquals(ttl, ttlOpt.get()); 216 217 testExportFileSystemState(tableName, snapshotName, snapshotName, tableNumFiles, 218 getHdfsDestinationDir(), false, true); 219 } catch (Exception e) { 220 throw e; 221 } finally { 222 TEST_UTIL.deleteTable(tableName); 223 } 224 } 225 226 private void testExportFileSystemState(final TableName tableName, final byte[] snapshotName, 227 final byte[] targetName, int filesExpected) throws Exception { 228 testExportFileSystemState(tableName, snapshotName, targetName, filesExpected, 229 getHdfsDestinationDir(), false); 230 } 231 232 protected void testExportFileSystemState(final TableName tableName, final byte[] snapshotName, 233 final byte[] targetName, int filesExpected, Path copyDir, boolean overwrite) throws Exception { 234 testExportFileSystemState(tableName, snapshotName, targetName, filesExpected, copyDir, 235 overwrite, false); 236 } 237 238 protected void testExportFileSystemState(final TableName tableName, final byte[] snapshotName, 239 final byte[] targetName, int filesExpected, Path copyDir, boolean overwrite, boolean resetTtl) 240 throws Exception { 241 testExportFileSystemState(TEST_UTIL.getConfiguration(), tableName, snapshotName, targetName, 242 filesExpected, TEST_UTIL.getDefaultRootDirPath(), copyDir, overwrite, resetTtl, 243 getBypassRegionPredicate(), true); 244 } 245 246 /** 247 * Creates destination directory, runs ExportSnapshot() tool, and runs some verifications. 248 */ 249 protected static void testExportFileSystemState(final Configuration conf, 250 final TableName tableName, final byte[] snapshotName, final byte[] targetName, 251 final int filesExpected, final Path srcDir, Path rawTgtDir, final boolean overwrite, 252 final boolean resetTtl, final RegionPredicate bypassregionPredicate, boolean success) 253 throws Exception { 254 FileSystem tgtFs = rawTgtDir.getFileSystem(conf); 255 FileSystem srcFs = srcDir.getFileSystem(conf); 256 Path tgtDir = rawTgtDir.makeQualified(tgtFs.getUri(), tgtFs.getWorkingDirectory()); 257 LOG.info("tgtFsUri={}, tgtDir={}, rawTgtDir={}, srcFsUri={}, srcDir={}", tgtFs.getUri(), tgtDir, 258 rawTgtDir, srcFs.getUri(), srcDir); 259 List<String> opts = new ArrayList<>(); 260 opts.add("--snapshot"); 261 opts.add(Bytes.toString(snapshotName)); 262 opts.add("--copy-to"); 263 opts.add(tgtDir.toString()); 264 if (targetName != snapshotName) { 265 opts.add("--target"); 266 opts.add(Bytes.toString(targetName)); 267 } 268 if (overwrite) { 269 opts.add("--overwrite"); 270 } 271 if (resetTtl) { 272 opts.add("--reset-ttl"); 273 } 274 275 // Export Snapshot 276 int res = run(conf, new ExportSnapshot(), opts.toArray(new String[opts.size()])); 277 assertEquals("success " + success + ", res=" + res, success ? 0 : 1, res); 278 if (!success) { 279 final Path targetDir = new Path(HConstants.SNAPSHOT_DIR_NAME, Bytes.toString(targetName)); 280 assertFalse(tgtDir.toString() + " " + targetDir.toString(), 281 tgtFs.exists(new Path(tgtDir, targetDir))); 282 return; 283 } 284 LOG.info("Exported snapshot"); 285 286 // Verify File-System state 287 FileStatus[] rootFiles = tgtFs.listStatus(tgtDir); 288 assertEquals(filesExpected > 0 ? 2 : 1, rootFiles.length); 289 for (FileStatus fileStatus : rootFiles) { 290 String name = fileStatus.getPath().getName(); 291 assertTrue(fileStatus.toString(), fileStatus.isDirectory()); 292 assertTrue(name.toString(), name.equals(HConstants.SNAPSHOT_DIR_NAME) 293 || name.equals(HConstants.HFILE_ARCHIVE_DIRECTORY)); 294 } 295 LOG.info("Verified filesystem state"); 296 297 // Compare the snapshot metadata and verify the hfiles 298 final Path snapshotDir = new Path(HConstants.SNAPSHOT_DIR_NAME, Bytes.toString(snapshotName)); 299 final Path targetDir = new Path(HConstants.SNAPSHOT_DIR_NAME, Bytes.toString(targetName)); 300 verifySnapshotDir(srcFs, new Path(srcDir, snapshotDir), tgtFs, new Path(tgtDir, targetDir)); 301 Set<String> snapshotFiles = verifySnapshot(conf, tgtFs, tgtDir, tableName, 302 Bytes.toString(targetName), resetTtl, bypassregionPredicate); 303 assertEquals(filesExpected, snapshotFiles.size()); 304 } 305 306 /* 307 * verify if the snapshot folder on file-system 1 match the one on file-system 2 308 */ 309 protected static void verifySnapshotDir(final FileSystem fs1, final Path root1, 310 final FileSystem fs2, final Path root2) throws IOException { 311 assertEquals(listFiles(fs1, root1, root1), listFiles(fs2, root2, root2)); 312 } 313 314 /* 315 * Verify if the files exists 316 */ 317 protected static Set<String> verifySnapshot(final Configuration conf, final FileSystem fs, 318 final Path rootDir, final TableName tableName, final String snapshotName, 319 final boolean resetTtl, final RegionPredicate bypassregionPredicate) throws IOException { 320 final Path exportedSnapshot = 321 new Path(rootDir, new Path(HConstants.SNAPSHOT_DIR_NAME, snapshotName)); 322 final Set<String> snapshotFiles = new HashSet<>(); 323 final Path exportedArchive = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY); 324 SnapshotReferenceUtil.visitReferencedFiles(conf, fs, exportedSnapshot, 325 new SnapshotReferenceUtil.SnapshotVisitor() { 326 @Override 327 public void storeFile(final RegionInfo regionInfo, final String family, 328 final SnapshotRegionManifest.StoreFile storeFile) throws IOException { 329 if (bypassregionPredicate != null && bypassregionPredicate.evaluate(regionInfo)) { 330 return; 331 } 332 333 String hfile = storeFile.getName(); 334 snapshotFiles.add(hfile); 335 if (!storeFile.hasReference()) { 336 verifyNonEmptyFile(new Path(exportedArchive, 337 new Path(CommonFSUtils.getTableDir(new Path("./"), tableName), 338 new Path(regionInfo.getEncodedName(), new Path(family, hfile))))); 339 } 340 } 341 342 private void verifyNonEmptyFile(final Path path) throws IOException { 343 assertTrue(path + " should exists", fs.exists(path)); 344 assertTrue(path + " should not be empty", fs.getFileStatus(path).getLen() > 0); 345 } 346 }); 347 348 // Verify Snapshot description 349 SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, exportedSnapshot); 350 assertTrue(desc.getName().equals(snapshotName)); 351 assertTrue(desc.getTable().equals(tableName.getNameAsString())); 352 if (resetTtl) { 353 assertEquals(HConstants.DEFAULT_SNAPSHOT_TTL, desc.getTtl()); 354 } 355 return snapshotFiles; 356 } 357 358 private static Set<String> listFiles(final FileSystem fs, final Path root, final Path dir) 359 throws IOException { 360 Set<String> files = new HashSet<>(); 361 LOG.debug("List files in {} in root {} at {}", fs, root, dir); 362 int rootPrefix = root.makeQualified(fs.getUri(), fs.getWorkingDirectory()).toString().length(); 363 FileStatus[] list = CommonFSUtils.listStatus(fs, dir); 364 if (list != null) { 365 for (FileStatus fstat : list) { 366 LOG.debug(Objects.toString(fstat.getPath())); 367 if (fstat.isDirectory()) { 368 files.addAll(listFiles(fs, root, fstat.getPath())); 369 } else { 370 files.add(fstat.getPath().makeQualified(fs).toString().substring(rootPrefix)); 371 } 372 } 373 } 374 return files; 375 } 376 377 private Path getHdfsDestinationDir() { 378 Path rootDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir(); 379 Path path = 380 new Path(new Path(rootDir, "export-test"), "export-" + EnvironmentEdgeManager.currentTime()); 381 LOG.info("HDFS export destination path: " + path); 382 return path; 383 } 384 385 static Path getLocalDestinationDir(HBaseTestingUtility htu) { 386 Path path = htu.getDataTestDir("local-export-" + EnvironmentEdgeManager.currentTime()); 387 try { 388 FileSystem fs = FileSystem.getLocal(htu.getConfiguration()); 389 LOG.info("Local export destination path: " + path); 390 return path.makeQualified(fs.getUri(), fs.getWorkingDirectory()); 391 } catch (IOException ioe) { 392 throw new RuntimeException(ioe); 393 } 394 } 395 396 private static void removeExportDir(final Path path) throws IOException { 397 FileSystem fs = FileSystem.get(path.toUri(), new Configuration()); 398 fs.delete(path, true); 399 } 400}